[
  {
    "path": ".clang-format",
    "content": "BasedOnStyle: WebKit\nAllowShortIfStatementsOnASingleLine: WithoutElse\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_white_space = true\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Force LF\n*.c text eol=lf\n*.h text eol=lf\n*.md text eol=lf\n*.json text eol=lf\n*.yml text eol=lf\n.clang-format text eol=lf\n.editorconfig text eol=lf\n.gitattributes text eol=lf\n.gitignore text eol=lf\nCMakeLists.txt text eol=lf\nLICENSE text eol=lf\n"
  },
  {
    "path": ".github/workflows/ci-build.yml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    types:\n      - opened\n      - synchronize\n\ndefaults:\n  run:\n    shell: bash\n\njobs:\n  build:\n    runs-on: windows-2019\n    steps:\n      - name: Clone\n        uses: actions/checkout@v3\n\n      - name: Cache cmake build\n        uses: actions/cache@v3\n        with:\n          path: build\n          key: windows-x86-cmake-v1\n\n      - name: Configure\n        run: |\n          cmake \\\n            -B build \\\n            -G \"Visual Studio 16 2019\" \\\n            -A Win32 \\\n            # EOL\n\n      - name: Build\n        run: |\n          cmake \\\n            --build build \\\n            --config RelWithDebInfo \\\n            # EOL\n\n      - name: Upload\n        uses: actions/upload-artifact@v3\n        with:\n          name: fallout2-re\n          path:  build/RelWithDebInfo/fallout2-re.exe\n          retention-days: 7\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Dd]ebug-Remote/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.tlog\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Nuget personal access tokens and Credentials\nnuget.config\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd\n\n# VS Code files for those working on multiple tools\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# Local History for Visual Studio Code\n.history/\n\n# Windows Installer files from build outputs\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# JetBrains Rider\n.idea/\n*.sln.iml\n\n# CMake\n/out\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.13)\n\nset(EXECUTABLE_NAME fallout2-re)\n\nproject(${EXECUTABLE_NAME})\n\nadd_executable(${EXECUTABLE_NAME} WIN32\n    \"src/game/ability.c\"\n    \"src/game/ability.h\"\n    \"src/game/actions.c\"\n    \"src/game/actions.h\"\n    \"src/game/amutex.c\"\n    \"src/game/amutex.h\"\n    \"src/game/anim.c\"\n    \"src/game/anim.h\"\n    \"src/game/art.c\"\n    \"src/game/art.h\"\n    \"src/game/artload.c\"\n    \"src/game/artload.h\"\n    \"src/game/automap.c\"\n    \"src/game/automap.h\"\n    \"src/game/bmpdlog.c\"\n    \"src/game/bmpdlog.h\"\n    \"src/game/cache.c\"\n    \"src/game/cache.h\"\n    \"src/game/cd.c\"\n    \"src/game/cd.h\"\n    \"src/game/combat_defs.h\"\n    \"src/game/combat.c\"\n    \"src/game/combat.h\"\n    \"src/game/combatai_defs.h\"\n    \"src/game/combatai.c\"\n    \"src/game/combatai.h\"\n    \"src/game/config.c\"\n    \"src/game/config.h\"\n    \"src/game/counter.c\"\n    \"src/game/counter.h\"\n    \"src/game/credits.c\"\n    \"src/game/credits.h\"\n    \"src/game/critter.c\"\n    \"src/game/critter.h\"\n    \"src/game/cycle.c\"\n    \"src/game/cycle.h\"\n    \"src/game/diskspce.c\"\n    \"src/game/diskspce.h\"\n    \"src/game/display.c\"\n    \"src/game/display.h\"\n    \"src/game/editor.c\"\n    \"src/game/editor.h\"\n    \"src/game/elevator.c\"\n    \"src/game/elevator.h\"\n    \"src/game/endgame.c\"\n    \"src/game/endgame.h\"\n    \"src/game/ereg.c\"\n    \"src/game/ereg.h\"\n    \"src/game/fontmgr.c\"\n    \"src/game/fontmgr.h\"\n    \"src/game/game_vars.h\"\n    \"src/game/game.c\"\n    \"src/game/game.h\"\n    \"src/game/gconfig.c\"\n    \"src/game/gconfig.h\"\n    \"src/game/gdebug.c\"\n    \"src/game/gdebug.h\"\n    \"src/game/gdialog.c\"\n    \"src/game/gdialog.h\"\n    \"src/game/gmemory.c\"\n    \"src/game/gmemory.h\"\n    \"src/game/gmouse.c\"\n    \"src/game/gmouse.h\"\n    \"src/game/gmovie.c\"\n    \"src/game/gmovie.h\"\n    \"src/game/graphlib.c\"\n    \"src/game/graphlib.h\"\n    \"src/game/gsound.c\"\n    \"src/game/gsound.h\"\n    \"src/game/gz.c\"\n    \"src/game/gz.h\"\n    \"src/game/heap.c\"\n    \"src/game/heap.h\"\n    \"src/game/intface.c\"\n    \"src/game/intface.h\"\n    \"src/game/inventry.c\"\n    \"src/game/inventry.h\"\n    \"src/game/item.c\"\n    \"src/game/item.h\"\n    \"src/game/light.c\"\n    \"src/game/light.h\"\n    \"src/game/lip_sync.c\"\n    \"src/game/lip_sync.h\"\n    \"src/game/loadsave.c\"\n    \"src/game/loadsave.h\"\n    \"src/game/main.c\"\n    \"src/game/main.h\"\n    \"src/game/mainmenu.c\"\n    \"src/game/mainmenu.h\"\n    \"src/game/map_defs.h\"\n    \"src/game/map.c\"\n    \"src/game/map.h\"\n    \"src/game/message.c\"\n    \"src/game/message.h\"\n    \"src/game/moviefx.c\"\n    \"src/game/moviefx.h\"\n    \"src/game/object_types.h\"\n    \"src/game/object.c\"\n    \"src/game/object.h\"\n    \"src/game/options.c\"\n    \"src/game/options.h\"\n    \"src/game/palette.c\"\n    \"src/game/palette.h\"\n    \"src/game/party.c\"\n    \"src/game/party.h\"\n    \"src/game/perk_defs.h\"\n    \"src/game/perk.c\"\n    \"src/game/perk.h\"\n    \"src/game/pipboy.c\"\n    \"src/game/pipboy.h\"\n    \"src/game/protinst.c\"\n    \"src/game/protinst.h\"\n    \"src/game/proto_types.h\"\n    \"src/game/proto.c\"\n    \"src/game/proto.h\"\n    \"src/game/queue.c\"\n    \"src/game/queue.h\"\n    \"src/game/reaction.c\"\n    \"src/game/reaction.h\"\n    \"src/game/roll.c\"\n    \"src/game/roll.h\"\n    \"src/game/scripts.c\"\n    \"src/game/scripts.h\"\n    \"src/game/select.c\"\n    \"src/game/select.h\"\n    \"src/game/selfrun.c\"\n    \"src/game/selfrun.h\"\n    \"src/game/sfxcache.c\"\n    \"src/game/sfxcache.h\"\n    \"src/game/sfxlist.c\"\n    \"src/game/sfxlist.h\"\n    \"src/game/skill_defs.h\"\n    \"src/game/skill.c\"\n    \"src/game/skill.h\"\n    \"src/game/skilldex.c\"\n    \"src/game/skilldex.h\"\n    \"src/game/stat_defs.h\"\n    \"src/game/stat.c\"\n    \"src/game/stat.h\"\n    \"src/game/strparse.c\"\n    \"src/game/strparse.h\"\n    \"src/game/textobj.c\"\n    \"src/game/textobj.h\"\n    \"src/game/tile.c\"\n    \"src/game/tile.h\"\n    \"src/game/trait_defs.h\"\n    \"src/game/trait.c\"\n    \"src/game/trait.h\"\n    \"src/game/trap.c\"\n    \"src/game/trap.h\"\n    \"src/game/version.c\"\n    \"src/game/version.h\"\n    \"src/game/wordwrap.c\"\n    \"src/game/wordwrap.h\"\n    \"src/game/worldmap.c\"\n    \"src/game/worldmap.h\"\n    \"src/int/audio.c\"\n    \"src/int/audio.h\"\n    \"src/int/audiof.c\"\n    \"src/int/audiof.h\"\n    \"src/int/datafile.c\"\n    \"src/int/datafile.h\"\n    \"src/int/dialog.c\"\n    \"src/int/dialog.h\"\n    \"src/int/export.c\"\n    \"src/int/export.h\"\n    \"src/int/intlib.c\"\n    \"src/int/intlib.h\"\n    \"src/int/intrpret.c\"\n    \"src/int/intrpret.h\"\n    \"src/int/memdbg.c\"\n    \"src/int/memdbg.h\"\n    \"src/int/mousemgr.c\"\n    \"src/int/mousemgr.h\"\n    \"src/int/movie.c\"\n    \"src/int/movie.h\"\n    \"src/int/nevs.c\"\n    \"src/int/nevs.h\"\n    \"src/int/pcx.c\"\n    \"src/int/pcx.h\"\n    \"src/int/region.c\"\n    \"src/int/region.h\"\n    \"src/int/share1.c\"\n    \"src/int/share1.h\"\n    \"src/int/sound.c\"\n    \"src/int/sound.h\"\n    \"src/int/support/intextra.c\"\n    \"src/int/support/intextra.h\"\n    \"src/int/widget.c\"\n    \"src/int/widget.h\"\n    \"src/int/window.c\"\n    \"src/int/window.h\"\n    \"src/plib/assoc/assoc.c\"\n    \"src/plib/assoc/assoc.h\"\n    \"src/plib/color/color.c\"\n    \"src/plib/color/color.h\"\n    \"src/plib/db/db.c\"\n    \"src/plib/db/db.h\"\n    \"src/plib/gnw/button.c\"\n    \"src/plib/gnw/button.h\"\n    \"src/plib/gnw/debug.c\"\n    \"src/plib/gnw/debug.h\"\n    \"src/plib/gnw/doscmdln.c\"\n    \"src/plib/gnw/doscmdln.h\"\n    \"src/plib/gnw/dxinput.c\"\n    \"src/plib/gnw/dxinput.h\"\n    \"src/plib/gnw/gnw95dx.c\"\n    \"src/plib/gnw/gnw95dx.h\"\n    \"src/plib/gnw/grbuf.c\"\n    \"src/plib/gnw/grbuf.h\"\n    \"src/plib/gnw/input.c\"\n    \"src/plib/gnw/input.h\"\n    \"src/plib/gnw/gnw_types.h\"\n    \"src/plib/gnw/gnw.c\"\n    \"src/plib/gnw/gnw.h\"\n    \"src/plib/gnw/intrface.c\"\n    \"src/plib/gnw/intrface.h\"\n    \"src/plib/gnw/kb.c\"\n    \"src/plib/gnw/kb.h\"\n    \"src/plib/gnw/memory.c\"\n    \"src/plib/gnw/memory.h\"\n    \"src/plib/gnw/mouse.c\"\n    \"src/plib/gnw/mouse.h\"\n    \"src/plib/gnw/rect.c\"\n    \"src/plib/gnw/rect.h\"\n    \"src/plib/gnw/svga_types.h\"\n    \"src/plib/gnw/svga.c\"\n    \"src/plib/gnw/svga.h\"\n    \"src/plib/gnw/text.c\"\n    \"src/plib/gnw/text.h\"\n    \"src/plib/gnw/vcr.c\"\n    \"src/plib/gnw/vcr.h\"\n    \"src/plib/gnw/winmain.c\"\n    \"src/plib/gnw/winmain.h\"\n    \"src/plib/xfile/dfile.c\"\n    \"src/plib/xfile/dfile.h\"\n    \"src/plib/xfile/xfile.c\"\n    \"src/plib/xfile/xfile.h\"\n    \"src/plib/xfile/xsys_find.c\"\n    \"src/plib/xfile/xsys_find.h\"\n    \"src/memory_defs.h\"\n    \"src/mmx.c\"\n    \"src/mmx.h\"\n    \"src/movie_lib.c\"\n    \"src/movie_lib.h\"\n    \"src/sound_decoder.c\"\n    \"src/sound_decoder.h\"\n)\n\ntarget_include_directories(${EXECUTABLE_NAME} PUBLIC src)\n\ntarget_compile_definitions(${EXECUTABLE_NAME} PUBLIC\n    _CRT_SECURE_NO_WARNINGS\n    _CRT_NONSTDC_NO_WARNINGS\n)\n\ntarget_link_libraries(${EXECUTABLE_NAME}\n    winmm\n)\n\nadd_subdirectory(\"third_party/fpattern\")\ntarget_link_libraries(${EXECUTABLE_NAME} ${FPATTERN_LIBRARY})\ntarget_include_directories(${EXECUTABLE_NAME} PRIVATE ${FPATTERN_INCLUDE_DIR})\n\nadd_subdirectory(\"third_party/zlib\")\ntarget_link_libraries(${EXECUTABLE_NAME} ${ZLIB_LIBRARIES})\ntarget_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS})\n"
  },
  {
    "path": "CMakeSettings.json",
    "content": "{\n  \"configurations\": [\n    {\n      \"name\": \"x86-Debug\",\n      \"generator\": \"Visual Studio 16 2019\",\n      \"configurationType\": \"Debug\",\n      \"inheritEnvironments\": [ \"msvc_x86\" ],\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"\",\n      \"ctestCommandArgs\": \"\"\n    },\n    {\n      \"name\": \"x86-Release\",\n      \"generator\": \"Visual Studio 16 2019\",\n      \"configurationType\": \"Release\",\n      \"inheritEnvironments\": [ \"msvc_x86\" ],\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"\",\n      \"ctestCommandArgs\": \"\"\n    }\n  ]\n}"
  },
  {
    "path": "LICENSE.md",
    "content": "# Sustainable Use License\n\nVersion 1.0\n\n## Acceptance\n\nBy using the software, you agree to all of the terms and conditions below.\n\n## Copyright License\n\nThe licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below.\n\n## Limitations\n\nYou may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law.\n\n## Patents\n\nThe licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.\n\n## Notices\n\nYou must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software.\n\n## No Other Rights\n\nThese terms do not imply any licenses other than those expressly granted in these terms.\n\n## Termination\n\nIf you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently.\n\n## No Liability\n\nAs far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.\n\n## Definitions\n\nThe \"licensor\" is the entity offering these terms.\n\nThe \"software\" is the software the licensor makes available under these terms, including any portion of it.\n\n\"You\" refers to the individual or entity agreeing to these terms.\n\n\"Your company\" is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.\n\n\"Your license\" is the license granted to you for the software under these terms.\n\n\"Use\" means anything you do with the software requiring your license.\n\n\"Trademark\" means trademarks, service marks, and similar rights.\n"
  },
  {
    "path": "README.md",
    "content": "# Fallout 2 Reference Edition\n\nIn this repository you'll find reverse engineered source code for Fallout 2.\n\nAs a player/gamer you're most likely interested in [Fallout 2 Community Edition](https://github.com/alexbatalov/fallout2-ce) which is based on this project.\n\nIf you're a developer you might also want to check [Fallout Reference Edition](https://github.com/alexbatalov/fallout1-re) to see evolution of the engine.\n\n## Goal\n\nThe goal of this project is to restore original source code as close possible with all it's imperfections. In very many respects this goal can be considered achieved.\n\n## Status\n\nThere is a small number of functions which are not yet decompiled. These functions are not essential to gameplay, most of them are leftovers from Fallout 1, others are a part of larger APIs that were not fully utilized. Aside from these missing functions there is ongoing effort to update the codebase to C89 to make sure the game can be compiled with Watcom C compiler (which might be handy to achieve binary identical results). This tasks are low priority and probably will never be completed.\n\n## Installation\n\nYou must own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2) or [Steam](https://store.steampowered.com/app/38410). Download latest build or build from source. The `fallout2-re.exe` serves as a drop-in replacement for `fallout2.exe`. Copy it to your Fallout 2 directory and run.\n\n## Legal\n\nThe source code in this repository is produced by reverse engineering the original binary. There are couple of exceptions for reverse engineering under DMCA - documentation, interoperability, fair use. Documentation is needed to achieve interoperability. Running your legally purchased copy on modern Mac M1 for example (interoperability in action) constitutes fair use. Publishing this stuff to wide audience is questionable. Eventually it's up to Bethesda/Microsoft to takedown the project or leave it be. See [#29](https://github.com/alexbatalov/fallout2-re/issues/29) for discussion.\n\n## License\n\nThe source code is this repository is available under the [Sustainable Use License](LICENSE.md).\n"
  },
  {
    "path": "src/game/ability.c",
    "content": "#include \"game/ability.h\"\n\n#include <string.h>\n\n#include \"plib/gnw/memory.h\"\n\n// 0x410010\nint abil_init(Ability* ability, int initialCapacity)\n{\n    ability->length = 0;\n    ability->capacity = 0;\n    ability->entries = NULL;\n\n    if (initialCapacity != 0) {\n        ability->entries = (AbilityData*)mem_malloc(sizeof(*ability->entries) * initialCapacity);\n        if (ability->entries != NULL) {\n            ability->length = 0;\n            ability->capacity = initialCapacity;\n            return 0;\n        }\n    }\n\n    return -1;\n}\n\n// 0x410058\nint abil_resize(Ability* ability, int capacity)\n{\n    AbilityData* entries;\n\n    if (capacity >= ability->length) {\n        entries = (AbilityData*)mem_realloc(ability->entries, sizeof(*ability->entries) * capacity);\n        if (entries != NULL) {\n            ability->entries = entries;\n            ability->capacity = capacity;\n            return 0;\n        }\n    }\n\n    return -1;\n}\n\n// 0x410094\nint abil_free(Ability* ability)\n{\n    if (ability->entries != NULL) {\n        mem_free(ability->entries);\n    }\n    return 0;\n}\n\n// 0x4100A8\nint abil_find(Ability* ability, int a2, int* indexPtr)\n{\n    int right;\n    int left;\n    int mid;\n    int cmp;\n    int rc;\n\n    if (ability->length == 0) {\n        rc = -1;\n        *indexPtr = 0;\n    } else {\n        right = ability->length - 1;\n        left = 0;\n\n        while (right >= left) {\n            mid = (right + left) >> 1;\n\n            if (a2 == ability->entries[mid].field_0) {\n                cmp = 0;\n                break;\n            }\n\n            if (a2 < ability->entries[mid].field_0) {\n                right = mid - 1;\n                cmp = -1;\n            } else {\n                left = mid + 1;\n                cmp = 1;\n            }\n        }\n\n        if (cmp == 0) {\n            *indexPtr = mid;\n            rc = 0;\n        } else {\n            if (cmp < 0) {\n                *indexPtr = mid;\n            } else {\n                *indexPtr = mid + 1;\n            }\n            rc = -1;\n        }\n    }\n\n    return rc;\n}\n\n// 0x410130\nint abil_search(Ability* ability, int a2)\n{\n    int index;\n\n    if (abil_find(ability, a2, &index) == 0) {\n        return index;\n    }\n\n    return -1;\n}\n\n// 0x410154\nint abil_insert(Ability* ability, AbilityData* entry)\n{\n    int index;\n\n    if (abil_find(ability, entry->field_0, &index) == 0) {\n        return -1;\n    }\n\n    if (ability->capacity == ability->length && abil_resize(ability, ability->capacity * 2) == -1) {\n        return -1;\n    }\n\n    if (sizeof(*ability->entries) * (ability->length - index) != 0) {\n        memmove((unsigned char*)ability->entries + sizeof(*ability->entries) * index + sizeof(*ability->entries),\n            (unsigned char*)ability->entries + sizeof(*ability->entries) * index,\n            sizeof(*ability->entries) * (ability->length - index));\n    }\n\n    ability->entries[index].field_0 = entry->field_0;\n    ability->entries[index].field_4 = entry->field_4;\n    ability->entries[index].field_8 = entry->field_8;\n    ability->length++;\n\n    return 0;\n}\n\n// 0x41021C\nint abil_delete(Ability* ability, int a2)\n{\n    int index;\n\n    if (abil_find(ability, a2, &index) == -1) {\n        return -1;\n    }\n\n    ability->length--;\n\n    if (sizeof(*ability->entries) * (ability->length - index) != 0) {\n        memmove((unsigned char*)ability->entries + sizeof(*ability->entries) * index,\n            (unsigned char*)ability->entries + sizeof(*ability->entries) * index + sizeof(*ability->entries),\n            sizeof(*ability->entries) * (ability->length - index));\n    }\n\n    return 0;\n}\n\n// 0x41026C\nint abil_copy(Ability* dest, Ability* src)\n{\n    int index;\n\n    if (abil_init(dest, src->capacity) == -1) {\n        return -1;\n    }\n\n    for (index = 0; index < src->length; index++) {\n        if (abil_insert(dest, &(src->entries[index])) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4102B0\nint abil_load(File* stream, Ability* ability)\n{\n    if (db_freadInt(stream, &(ability->length)) == -1\n        || db_freadInt(stream, &(ability->capacity)) == -1\n        || abil_read_ability_data(ability, stream) == -1) return -1;\n    return 0;\n}\n\n// 0x4102EC\nint abil_read_ability_data(Ability* ability, File* stream)\n{\n    int index;\n\n    ability->entries = (AbilityData*)mem_malloc(sizeof(*ability->entries) * ability->capacity);\n    if (ability->entries == NULL) {\n        return -1;\n    }\n\n    for (index = 0; index < ability->length; index++) {\n        if (db_freadInt(stream, &(ability->entries[index].field_0)) == -1\n            || db_freadInt(stream, &(ability->entries[index].field_4)) == -1\n            || db_freadInt(stream, &(ability->entries[index].field_8)) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x410378\nint abil_save(File* stream, Ability* ability)\n{\n    if (db_fwriteInt(stream, ability->length) == -1\n        || db_fwriteInt(stream, ability->capacity) == -1\n        || abil_write_ability_data(ability->length, ability->entries, stream) == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4103B8\nint abil_write_ability_data(int length, AbilityData* entries, File* stream)\n{\n    int index;\n\n    for (index = 0; index < length; index++) {\n        if (db_fwriteInt(stream, entries[index].field_0) == -1\n            || db_fwriteInt(stream, entries[index].field_4) == -1\n            || db_fwriteInt(stream, entries[index].field_8) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/ability.h",
    "content": "#ifndef FALLOUT_GAME_ABILITY_H_\n#define FALLOUT_GAME_ABILITY_H_\n\n#include \"plib/db/db.h\"\n\ntypedef struct AbilityData {\n    int field_0;\n    int field_4;\n    int field_8;\n} AbilityData;\n\ntypedef struct Ability {\n    int length;\n    int capacity;\n    AbilityData* entries;\n} Ability;\n\nint abil_init(Ability* ability, int initialCapacity);\nint abil_resize(Ability* ability, int capacity);\nint abil_free(Ability* ability);\nint abil_find(Ability* ability, int a2, int* indexPtr);\nint abil_search(Ability* ability, int a2);\nint abil_insert(Ability* ability, AbilityData* entry);\nint abil_delete(Ability* ability, int a2);\nint abil_copy(Ability* dest, Ability* src);\nint abil_load(File* stream, Ability* ability);\nint abil_read_ability_data(Ability* ability, File* stream);\nint abil_save(File* stream, Ability* ability);\nint abil_write_ability_data(int length, AbilityData* entries, File* stream);\n\n#endif /* FALLOUT_GAME_ABILITY_H_ */\n"
  },
  {
    "path": "src/game/actions.c",
    "content": "#include \"game/actions.h\"\n\n#include <limits.h>\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"game/config.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n#include \"plib/gnw/svga.h\"\n\nstatic int pick_death(Object* attacker, Object* defender, Object* weapon, int damage, int anim, bool isFallingBack);\nstatic int check_death(Object* obj, int anim, int minViolenceLevel, bool isFallingBack);\nstatic int internal_destroy(Object* a1, Object* a2);\nstatic int show_death(Object* obj, int anim);\nstatic int action_melee(Attack* attack, int a2);\nstatic int action_ranged(Attack* attack, int a2);\nstatic int is_next_to(Object* a1, Object* a2);\nstatic int action_climb_ladder(Object* a1, Object* a2);\nstatic int report_explosion(Attack* attack, Object* a2);\nstatic int finished_explosion(Object* a1, Object* a2);\nstatic int compute_explosion_damage(int min, int max, Object* a3, int* a4);\nstatic int can_talk_to(Object* a1, Object* a2);\nstatic int talk_to(Object* a1, Object* a2);\nstatic int report_dmg(Attack* attack, Object* a2);\nstatic int compute_dmg_damage(int min, int max, Object* obj, int* a4, int damage_type);\n\n// 0x5106D0\nstatic bool action_in_explode = false;\n\n// 0x5106D4\nunsigned int rotation = 0;\n\n// 0x5106D8\nint obj_fid = -1;\n\n// 0x5106DC\nint obj_pid_old = -1;\n\n// TODO: Strange location in debug build, check if it's static in |pick_death|.\n//\n// 0x5106E0\nstatic int death_2[DAMAGE_TYPE_COUNT] = {\n    ANIM_DANCING_AUTOFIRE,\n    ANIM_SLICED_IN_HALF,\n    ANIM_CHARRED_BODY,\n    ANIM_CHARRED_BODY,\n    ANIM_ELECTRIFY,\n    ANIM_FALL_BACK,\n    ANIM_BIG_HOLE,\n};\n\n// TODO: Strange location in debug build, check if it's static in |pick_death|.\n//\n// 0x5106FC\nstatic int death_3[DAMAGE_TYPE_COUNT] = {\n    ANIM_CHUNKS_OF_FLESH,\n    ANIM_SLICED_IN_HALF,\n    ANIM_FIRE_DANCE,\n    ANIM_MELTED_TO_NOTHING,\n    ANIM_ELECTRIFIED_TO_NOTHING,\n    ANIM_FALL_BACK,\n    ANIM_EXPLODED_TO_NOTHING,\n};\n\n// NOTE: Unused.\n//\n// 0x410410\nvoid switch_dude()\n{\n    Object* critter;\n    int gender;\n\n    critter = pick_object(OBJ_TYPE_CRITTER, false);\n    if (critter != NULL) {\n        gender = critterGetStat(critter, STAT_GENDER);\n        stat_set_base(obj_dude, STAT_GENDER, gender);\n\n        obj_dude = critter;\n        obj_fid = critter->fid;\n        obj_pid_old = critter->pid;\n        critter->pid = 0x1000000;\n    }\n}\n\n// 0x410468\nint action_knockback(Object* obj, int* anim, int maxDistance, int rotation, int delay)\n{\n    if (critter_flag_check(obj->pid, CRITTER_NO_KNOCKBACK)) {\n        return -1;\n    }\n\n    if (*anim == ANIM_FALL_FRONT) {\n        int fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, *anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n        if (!art_exists(fid)) {\n            *anim = ANIM_FALL_BACK;\n        }\n    }\n\n    int distance;\n    int tile;\n    for (distance = 1; distance <= maxDistance; distance++) {\n        tile = tile_num_in_direction(obj->tile, rotation, distance);\n        if (obj_blocking_at(obj, tile, obj->elevation) != NULL) {\n            distance--;\n            break;\n        }\n    }\n\n    const char* soundEffectName = gsnd_build_character_sfx_name(obj, *anim, CHARACTER_SOUND_EFFECT_KNOCKDOWN);\n    register_object_play_sfx(obj, soundEffectName, delay);\n\n    // TODO: Check, probably step back because we've started with 1?\n    distance--;\n\n    if (distance <= 0) {\n        tile = obj->tile;\n        register_object_animate(obj, *anim, 0);\n    } else {\n        tile = tile_num_in_direction(obj->tile, rotation, distance);\n        register_object_animate_and_move_straight(obj, tile, obj->elevation, *anim, 0);\n    }\n\n    return tile;\n}\n\n// 0x410568\nint action_blood(Object* obj, int anim, int delay)\n{\n\n    int violence_level = VIOLENCE_LEVEL_MAXIMUM_BLOOD;\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violence_level);\n    if (violence_level == VIOLENCE_LEVEL_NONE) {\n        return anim;\n    }\n\n    int bloodyAnim;\n    if (anim == ANIM_FALL_BACK) {\n        bloodyAnim = ANIM_FALL_BACK_BLOOD;\n    } else if (anim == ANIM_FALL_FRONT) {\n        bloodyAnim = ANIM_FALL_FRONT_BLOOD;\n    } else {\n        return anim;\n    }\n\n    int fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, bloodyAnim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    if (art_exists(fid)) {\n        register_object_animate(obj, bloodyAnim, delay);\n    } else {\n        bloodyAnim = anim;\n    }\n\n    return bloodyAnim;\n}\n\n// 0x41060C\nstatic int pick_death(Object* attacker, Object* defender, Object* weapon, int damage, int anim, bool isFallingBack)\n{\n    int normalViolenceLevelDamageThreshold = 15;\n    int maximumBloodViolenceLevelDamageThreshold = 45;\n\n    int damageType = item_w_damage_type(attacker, weapon);\n\n    if (weapon != NULL && weapon->pid == PROTO_ID_MOLOTOV_COCKTAIL) {\n        normalViolenceLevelDamageThreshold = 5;\n        maximumBloodViolenceLevelDamageThreshold = 15;\n        damageType = DAMAGE_TYPE_FIRE;\n        anim = ANIM_FIRE_SINGLE;\n    }\n\n    if (attacker == obj_dude && perkHasRank(attacker, PERK_PYROMANIAC) && damageType == DAMAGE_TYPE_FIRE) {\n        normalViolenceLevelDamageThreshold = 1;\n        maximumBloodViolenceLevelDamageThreshold = 1;\n    }\n\n    if (weapon != NULL && item_w_perk(weapon) == PERK_WEAPON_FLAMEBOY) {\n        normalViolenceLevelDamageThreshold /= 3;\n        maximumBloodViolenceLevelDamageThreshold /= 3;\n    }\n\n    int violenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD;\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel);\n\n    if (critter_flag_check(defender->pid, CRITTER_SPECIAL_DEATH)) {\n        return check_death(defender, ANIM_EXPLODED_TO_NOTHING, VIOLENCE_LEVEL_NORMAL, isFallingBack);\n    }\n\n    bool hasBloodyMess = false;\n    if (attacker == obj_dude && trait_level(TRAIT_BLOODY_MESS)) {\n        hasBloodyMess = true;\n    }\n\n    // NOTE: Original code is slightly different. There are lots of jumps and\n    // conditions. It's easier to set the default in advance, rather than catch\n    // it with bunch of \"else\" statements.\n    int deathAnim = ANIM_FALL_BACK;\n\n    if ((anim == ANIM_THROW_PUNCH && damageType == DAMAGE_TYPE_NORMAL)\n        || anim == ANIM_KICK_LEG\n        || anim == ANIM_THRUST_ANIM\n        || anim == ANIM_SWING_ANIM\n        || (anim == ANIM_THROW_ANIM && damageType != DAMAGE_TYPE_EXPLOSION)) {\n        if (violenceLevel == VIOLENCE_LEVEL_MAXIMUM_BLOOD && hasBloodyMess) {\n            deathAnim = ANIM_BIG_HOLE;\n        }\n    } else {\n        if (anim == ANIM_FIRE_SINGLE && damageType == DAMAGE_TYPE_NORMAL) {\n            if (violenceLevel == VIOLENCE_LEVEL_MAXIMUM_BLOOD) {\n                if (hasBloodyMess || maximumBloodViolenceLevelDamageThreshold <= damage) {\n                    deathAnim = ANIM_BIG_HOLE;\n                }\n            }\n        } else {\n            if (violenceLevel > VIOLENCE_LEVEL_MINIMAL && (hasBloodyMess || normalViolenceLevelDamageThreshold <= damage)) {\n                if (violenceLevel > VIOLENCE_LEVEL_NORMAL && (hasBloodyMess || maximumBloodViolenceLevelDamageThreshold <= damage)) {\n                    deathAnim = death_3[damageType];\n                    if (check_death(defender, deathAnim, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack) != deathAnim) {\n                        deathAnim = death_2[damageType];\n                    }\n                } else {\n                    deathAnim = death_2[damageType];\n                }\n            }\n        }\n    }\n\n    if (!isFallingBack && deathAnim == ANIM_FALL_BACK) {\n        deathAnim = ANIM_FALL_FRONT;\n    }\n\n    return check_death(defender, deathAnim, VIOLENCE_LEVEL_NONE, isFallingBack);\n}\n\n// 0x410814\nstatic int check_death(Object* obj, int anim, int minViolenceLevel, bool isFallingBack)\n{\n    int fid;\n\n    int violenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD;\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel);\n    if (violenceLevel >= minViolenceLevel) {\n        fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n        if (art_exists(fid)) {\n            return anim;\n        }\n    }\n\n    if (isFallingBack) {\n        return ANIM_FALL_BACK;\n    }\n\n    fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, ANIM_FALL_FRONT, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    if (art_exists(fid)) {\n        return ANIM_FALL_BACK;\n    }\n\n    return ANIM_FALL_FRONT;\n}\n\n// 0x4108C8\nstatic int internal_destroy(Object* a1, Object* a2)\n{\n    return obj_destroy(a2);\n}\n\n// TODO: Check very carefully, lots of conditions and jumps.\n//\n// 0x4108D0\nvoid show_damage_to_object(Object* a1, int damage, int flags, Object* weapon, bool isFallingBack, int knockbackDistance, int knockbackRotation, int a8, Object* a9, int a10)\n{\n    int anim;\n    int fid;\n    const char* sfx_name;\n\n    if (critter_flag_check(a1->pid, CRITTER_NO_KNOCKBACK)) {\n        knockbackDistance = 0;\n    }\n\n    anim = FID_ANIM_TYPE(a1->fid);\n    if (!critter_is_prone(a1)) {\n        if ((flags & DAM_DEAD) != 0) {\n            fid = art_id(OBJ_TYPE_MISC, 10, 0, 0, 0);\n            if (fid == a9->fid) {\n                anim = check_death(a1, ANIM_EXPLODED_TO_NOTHING, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack);\n            } else if (a9->pid == PROTO_ID_0x20001EB) {\n                anim = check_death(a1, ANIM_ELECTRIFIED_TO_NOTHING, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack);\n            } else if (a9->fid == FID_0x20001F5) {\n                anim = check_death(a1, a8, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack);\n            } else {\n                anim = pick_death(a9, a1, weapon, damage, a8, isFallingBack);\n            }\n\n            if (anim != ANIM_FIRE_DANCE) {\n                if (knockbackDistance != 0 && (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK)) {\n                    action_knockback(a1, &anim, knockbackDistance, knockbackRotation, a10);\n                    anim = action_blood(a1, anim, -1);\n                } else {\n                    sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_DIE);\n                    register_object_play_sfx(a1, sfx_name, a10);\n\n                    anim = pick_fall(a1, anim);\n                    register_object_animate(a1, anim, 0);\n\n                    if (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK) {\n                        anim = action_blood(a1, anim, -1);\n                    }\n                }\n            } else {\n                fid = art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_FIRE_DANCE, (a1->fid & 0xF000) >> 12, a1->rotation + 1);\n                if (art_exists(fid)) {\n                    sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED);\n                    register_object_play_sfx(a1, sfx_name, a10);\n\n                    register_object_animate(a1, anim, 0);\n\n                    int randomDistance = roll_random(2, 5);\n                    int randomRotation = roll_random(0, 5);\n\n                    while (randomDistance > 0) {\n                        int tile = tile_num_in_direction(a1->tile, randomRotation, randomDistance);\n                        Object* v35 = NULL;\n                        make_straight_path(a1, a1->tile, tile, NULL, &v35, 4);\n                        if (v35 == NULL) {\n                            register_object_turn_towards(a1, tile);\n                            register_object_move_straight_to_tile(a1, tile, a1->elevation, anim, 0);\n                            break;\n                        }\n                        randomDistance--;\n                    }\n                }\n\n                anim = ANIM_BURNED_TO_NOTHING;\n                sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED);\n                register_object_play_sfx(a1, sfx_name, -1);\n                register_object_animate(a1, anim, 0);\n            }\n        } else {\n            if ((flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) {\n                anim = isFallingBack ? ANIM_FALL_BACK : ANIM_FALL_FRONT;\n                sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED);\n                register_object_play_sfx(a1, sfx_name, a10);\n                if (knockbackDistance != 0) {\n                    action_knockback(a1, &anim, knockbackDistance, knockbackRotation, 0);\n                } else {\n                    anim = pick_fall(a1, anim);\n                    register_object_animate(a1, anim, 0);\n                }\n            } else if ((flags & DAM_ON_FIRE) != 0 && art_exists(art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_FIRE_DANCE, (a1->fid & 0xF000) >> 12, a1->rotation + 1))) {\n                register_object_animate(a1, ANIM_FIRE_DANCE, a10);\n\n                fid = art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_STAND, (a1->fid & 0xF000) >> 12, a1->rotation + 1);\n                register_object_change_fid(a1, fid, -1);\n            } else {\n                if (knockbackDistance != 0) {\n                    anim = isFallingBack ? ANIM_FALL_BACK : ANIM_FALL_FRONT;\n                    action_knockback(a1, &anim, knockbackDistance, knockbackRotation, a10);\n                    if (anim == ANIM_FALL_BACK) {\n                        register_object_animate(a1, ANIM_BACK_TO_STANDING, -1);\n                    } else {\n                        register_object_animate(a1, ANIM_PRONE_TO_STANDING, -1);\n                    }\n                } else {\n                    if (isFallingBack || !art_exists(art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_HIT_FROM_BACK, (a1->fid & 0xF000) >> 12, a1->rotation + 1))) {\n                        anim = ANIM_HIT_FROM_FRONT;\n                    } else {\n                        anim = ANIM_HIT_FROM_BACK;\n                    }\n\n                    sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED);\n                    register_object_play_sfx(a1, sfx_name, a10);\n\n                    register_object_animate(a1, anim, 0);\n                }\n            }\n        }\n    } else {\n        if ((flags & DAM_DEAD) != 0 && (a1->data.critter.combat.results & DAM_DEAD) == 0) {\n            anim = action_blood(a1, anim, a10);\n        } else {\n            return;\n        }\n    }\n\n    if (weapon != NULL) {\n        if ((flags & DAM_EXPLODE) != 0) {\n            register_object_must_call(a1, weapon, obj_drop, -1);\n            fid = art_id(OBJ_TYPE_MISC, 10, 0, 0, 0);\n            register_object_change_fid(weapon, fid, 0);\n            register_object_animate_and_hide(weapon, ANIM_STAND, 0);\n\n            sfx_name = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_HIT, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, a1);\n            register_object_play_sfx(weapon, sfx_name, 0);\n\n            register_object_must_erase(weapon);\n        } else if ((flags & DAM_DESTROY) != 0) {\n            register_object_must_call(a1, weapon, internal_destroy, -1);\n        } else if ((flags & DAM_DROP) != 0) {\n            register_object_must_call(a1, weapon, obj_drop, -1);\n        }\n    }\n\n    if ((flags & DAM_DEAD) != 0) {\n        // TODO: Get rid of casts.\n        register_object_must_call(a1, (void*)anim, (AnimationCallback*)show_death, -1);\n    }\n}\n\n// 0x410E24\nstatic int show_death(Object* obj, int anim)\n{\n    Rect v7;\n    Rect v8;\n    int fid;\n\n    obj_bound(obj, &v8);\n    if (anim < 48 && anim > 63) {\n        fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim + 28, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n        if (obj_change_fid(obj, fid, &v7) == 0) {\n            rect_min_bound(&v8, &v7, &v8);\n        }\n\n        if (obj_set_frame(obj, 0, &v7) == 0) {\n            rect_min_bound(&v8, &v7, &v8);\n        }\n    }\n\n    if (critter_flag_check(obj->pid, CRITTER_FLAT) == 0) {\n        obj->flags |= OBJECT_NO_BLOCK;\n        if (obj_toggle_flat(obj, &v7) == 0) {\n            rect_min_bound(&v8, &v7, &v8);\n        }\n    }\n\n    if (obj_turn_off_outline(obj, &v7) == 0) {\n        rect_min_bound(&v8, &v7, &v8);\n    }\n\n    if (anim >= 30 && anim <= 31 && critter_flag_check(obj->pid, CRITTER_SPECIAL_DEATH) == 0 && critter_flag_check(obj->pid, CRITTER_NO_DROP) == 0) {\n        item_drop_all(obj, obj->tile);\n    }\n\n    tile_refresh_rect(&v8, obj->elevation);\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x410F48\nint show_damage_target(Attack* attack)\n{\n    int frontHit;\n\n    if (FID_TYPE(attack->defender->fid) == OBJ_TYPE_CRITTER) {\n        // NOTE: Uninline.\n        frontHit = is_hit_from_front(attack->attacker, attack->defender);\n\n        register_begin(ANIMATION_REQUEST_RESERVED);\n        register_priority(1);\n        show_damage_to_object(attack->defender,\n            attack->defenderDamage,\n            attack->defenderFlags,\n            attack->weapon,\n            frontHit,\n            attack->defenderKnockback,\n            tile_dir(attack->attacker->tile, attack->defender->tile),\n            item_w_anim(attack->attacker, attack->hitMode),\n            attack->attacker,\n            0);\n        register_end();\n    }\n\n    return 0;\n}\n\n// 0x410FEC\nint show_damage_extras(Attack* attack)\n{\n    int v6;\n    int v8;\n    int v9;\n\n    for (int index = 0; index < attack->extrasLength; index++) {\n        Object* obj = attack->extras[index];\n        if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) {\n            int delta = attack->attacker->rotation - obj->rotation;\n            if (delta < 0) {\n                delta = -delta;\n            }\n\n            v6 = delta != 0 && delta != 1 && delta != 5;\n            register_begin(ANIMATION_REQUEST_RESERVED);\n            register_priority(1);\n            v8 = item_w_anim(attack->attacker, attack->hitMode);\n            v9 = tile_dir(attack->attacker->tile, obj->tile);\n            show_damage_to_object(obj, attack->extrasDamage[index], attack->extrasFlags[index], attack->weapon, v6, attack->extrasKnockback[index], v9, v8, attack->attacker, 0);\n            register_end();\n        }\n    }\n\n    return 0;\n}\n\n// 0x4110AC\nvoid show_damage(Attack* attack, int a2, int a3)\n{\n    int v5;\n    int v14;\n    int v17;\n    int v15;\n\n    v5 = a3;\n    for (int index = 0; index < attack->extrasLength; index++) {\n        Object* object = attack->extras[index];\n        if (FID_TYPE(object->fid) == OBJ_TYPE_CRITTER) {\n            register_ping(2, v5);\n            v5 = 0;\n        }\n    }\n\n    if ((attack->attackerFlags & DAM_HIT) == 0) {\n        if ((attack->attackerFlags & DAM_CRITICAL) != 0) {\n            show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, a2, attack->attacker, -1);\n        } else if ((attack->attackerFlags & DAM_BACKWASH) != 0) {\n            show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, a2, attack->attacker, -1);\n        }\n    } else {\n        if (attack->defender != NULL) {\n            // TODO: Looks very similar to show_damage_extras.\n            int delta = attack->defender->rotation - attack->attacker->rotation;\n            if (delta < 0) {\n                delta = -delta;\n            }\n\n            v15 = delta != 0 && delta != 1 && delta != 5;\n\n            if (FID_TYPE(attack->defender->fid) == OBJ_TYPE_CRITTER) {\n                if (attack->attacker->fid == 33554933) {\n                    v14 = tile_dir(attack->attacker->tile, attack->defender->tile);\n                    show_damage_to_object(attack->defender, attack->defenderDamage, attack->defenderFlags, attack->weapon, v15, attack->defenderKnockback, v14, a2, attack->attacker, a3);\n                } else {\n                    v17 = item_w_anim(attack->attacker, attack->hitMode);\n                    v14 = tile_dir(attack->attacker->tile, attack->defender->tile);\n                    show_damage_to_object(attack->defender, attack->defenderDamage, attack->defenderFlags, attack->weapon, v15, attack->defenderKnockback, v14, v17, attack->attacker, a3);\n                }\n            } else {\n                tile_dir(attack->attacker->tile, attack->defender->tile);\n                item_w_anim(attack->attacker, attack->hitMode);\n            }\n        }\n\n        if ((attack->attackerFlags & DAM_DUD) != 0) {\n            show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, a2, attack->attacker, -1);\n        }\n    }\n}\n\n// 0x411224\nint action_attack(Attack* attack)\n{\n    if (register_clear(attack->attacker) == -2) {\n        return -1;\n    }\n\n    if (register_clear(attack->defender) == -2) {\n        return -1;\n    }\n\n    for (int index = 0; index < attack->extrasLength; index++) {\n        if (register_clear(attack->extras[index]) == -2) {\n            return -1;\n        }\n    }\n\n    int anim = item_w_anim(attack->attacker, attack->hitMode);\n    if (anim < ANIM_FIRE_SINGLE && anim != ANIM_THROW_ANIM) {\n        return action_melee(attack, anim);\n    } else {\n        return action_ranged(attack, anim);\n    }\n}\n\n// 0x4112B4\nstatic int action_melee(Attack* attack, int anim)\n{\n    int fid;\n    Art* art;\n    CacheEntry* cache_entry;\n    int v17;\n    int v18;\n    int delta;\n    int flag;\n    const char* sfx_name;\n    char sfx_name_temp[16];\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n    register_priority(1);\n\n    fid = art_id(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, anim, (attack->attacker->fid & 0xF000) >> 12, attack->attacker->rotation + 1);\n    art = art_ptr_lock(fid, &cache_entry);\n    if (art != NULL) {\n        v17 = art_frame_action_frame(art);\n    } else {\n        v17 = 0;\n    }\n    art_ptr_unlock(cache_entry);\n\n    tile_num_in_direction(attack->attacker->tile, attack->attacker->rotation, 1);\n    register_object_turn_towards(attack->attacker, attack->defender->tile);\n\n    delta = attack->attacker->rotation - attack->defender->rotation;\n    if (delta < 0) {\n        delta = -delta;\n    }\n    flag = delta != 0 && delta != 1 && delta != 5;\n\n    if (anim != ANIM_THROW_PUNCH && anim != ANIM_KICK_LEG) {\n        sfx_name = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_ATTACK, attack->weapon, attack->hitMode, attack->defender);\n    } else {\n        sfx_name = gsnd_build_character_sfx_name(attack->attacker, anim, CHARACTER_SOUND_EFFECT_UNUSED);\n    }\n\n    strcpy(sfx_name_temp, sfx_name);\n\n    combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_ATTACK, 0);\n\n    if (attack->attackerFlags & 0x0300) {\n        register_object_play_sfx(attack->attacker, sfx_name_temp, 0);\n        if (anim != ANIM_THROW_PUNCH && anim != ANIM_KICK_LEG) {\n            sfx_name = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_HIT, attack->weapon, attack->hitMode, attack->defender);\n        } else {\n            sfx_name = gsnd_build_character_sfx_name(attack->attacker, anim, CHARACTER_SOUND_EFFECT_CONTACT);\n        }\n\n        strcpy(sfx_name_temp, sfx_name);\n\n        register_object_animate(attack->attacker, anim, 0);\n        register_object_play_sfx(attack->attacker, sfx_name_temp, v17);\n        show_damage(attack, anim, 0);\n    } else {\n        if (attack->defender->data.critter.combat.results & 0x03) {\n            register_object_play_sfx(attack->attacker, sfx_name_temp, -1);\n            register_object_animate(attack->attacker, anim, 0);\n        } else {\n            fid = art_id(OBJ_TYPE_CRITTER, attack->defender->fid & 0xFFF, ANIM_DODGE_ANIM, (attack->defender->fid & 0xF000) >> 12, attack->defender->rotation + 1);\n            art = art_ptr_lock(fid, &cache_entry);\n            if (art != NULL) {\n                v18 = art_frame_action_frame(art);\n                art_ptr_unlock(cache_entry);\n\n                if (v18 <= v17) {\n                    register_object_play_sfx(attack->attacker, sfx_name_temp, -1);\n                    register_object_animate(attack->attacker, anim, 0);\n\n                    sfx_name = gsnd_build_character_sfx_name(attack->defender, ANIM_DODGE_ANIM, CHARACTER_SOUND_EFFECT_UNUSED);\n                    register_object_play_sfx(attack->defender, sfx_name, v17 - v18);\n                    register_object_animate(attack->defender, ANIM_DODGE_ANIM, 0);\n                } else {\n                    sfx_name = gsnd_build_character_sfx_name(attack->defender, ANIM_DODGE_ANIM, CHARACTER_SOUND_EFFECT_UNUSED);\n                    register_object_play_sfx(attack->defender, sfx_name, -1);\n                    register_object_animate(attack->defender, ANIM_DODGE_ANIM, 0);\n                    register_object_play_sfx(attack->attacker, sfx_name_temp, v18 - v17);\n                    register_object_animate(attack->attacker, anim, 0);\n                }\n            }\n        }\n    }\n\n    if ((attack->attackerFlags & DAM_HIT) != 0) {\n        if ((attack->defenderFlags & DAM_DEAD) == 0) {\n            combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_HIT, -1);\n        }\n    } else {\n        combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_MISS, -1);\n    }\n\n    if (register_end() == -1) {\n        return -1;\n    }\n\n    show_damage_extras(attack);\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4115CC\nint throw_change_fid(Object* object, int fid)\n{\n    Rect rect;\n\n    debug_printf(\"\\n[throw_change_fid!]: %d\", fid);\n    obj_change_fid(object, fid, &rect);\n    tile_refresh_rect(&rect, map_elevation);\n\n    return 0;\n}\n\n// 0x411600\nstatic int action_ranged(Attack* attack, int anim)\n{\n    Object* neighboors[6];\n    memset(neighboors, 0, sizeof(neighboors));\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n    register_priority(1);\n\n    Object* projectile = NULL;\n    Object* v50 = NULL;\n    int weaponFid = -1;\n\n    Proto* weaponProto;\n    Object* weapon = attack->weapon;\n    proto_ptr(weapon->pid, &weaponProto);\n\n    int fid = art_id(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, anim, (attack->attacker->fid & 0xF000) >> 12, attack->attacker->rotation + 1);\n    CacheEntry* artHandle;\n    Art* art = art_ptr_lock(fid, &artHandle);\n    int actionFrame = (art != NULL) ? art_frame_action_frame(art) : 0;\n    art_ptr_unlock(artHandle);\n\n    item_w_range(attack->attacker, attack->hitMode);\n\n    int damageType = item_w_damage_type(attack->attacker, attack->weapon);\n\n    tile_num_in_direction(attack->attacker->tile, attack->attacker->rotation, 1);\n\n    register_object_turn_towards(attack->attacker, attack->defender->tile);\n\n    bool isGrenade = false;\n    if (anim == ANIM_THROW_ANIM) {\n        if (damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP) {\n            isGrenade = true;\n        }\n    } else {\n        register_object_animate(attack->attacker, ANIM_POINT, -1);\n    }\n\n    combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_ATTACK, 0);\n\n    const char* sfx;\n    if (((attack->attacker->fid & 0xF000) >> 12) != 0) {\n        sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_ATTACK, weapon, attack->hitMode, attack->defender);\n    } else {\n        sfx = gsnd_build_character_sfx_name(attack->attacker, anim, CHARACTER_SOUND_EFFECT_UNUSED);\n    }\n    register_object_play_sfx(attack->attacker, sfx, -1);\n\n    register_object_animate(attack->attacker, anim, 0);\n\n    if (anim != ANIM_FIRE_CONTINUOUS) {\n        if ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0) {\n            bool l56 = false;\n\n            int projectilePid = item_w_proj_pid(weapon);\n            Proto* projectileProto;\n            if (proto_ptr(projectilePid, &projectileProto) != -1 && projectileProto->fid != -1) {\n                if (anim == ANIM_THROW_ANIM) {\n                    projectile = weapon;\n                    weaponFid = weapon->fid;\n                    int weaponFlags = weapon->flags;\n\n                    int leftItemAction;\n                    int rightItemAction;\n                    intface_get_item_states(&leftItemAction, &rightItemAction);\n\n                    item_remove_mult(attack->attacker, weapon, 1);\n                    v50 = item_replace(attack->attacker, weapon, weaponFlags & OBJECT_IN_ANY_HAND);\n                    obj_change_fid(projectile, projectileProto->fid, NULL);\n                    cAIPrepWeaponItem(attack->attacker, weapon);\n\n                    if (attack->attacker == obj_dude) {\n                        if (v50 == NULL) {\n                            if ((weaponFlags & OBJECT_IN_LEFT_HAND) != 0) {\n                                leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                            } else if ((weaponFlags & OBJECT_IN_RIGHT_HAND) != 0) {\n                                rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                            }\n                        }\n                        intface_update_items(false, leftItemAction, rightItemAction);\n                    }\n\n                    obj_connect(weapon, attack->attacker->tile, attack->attacker->elevation, NULL);\n                } else {\n                    obj_new(&projectile, projectileProto->fid, -1);\n                }\n\n                obj_turn_off(projectile, NULL);\n\n                obj_set_light(projectile, 9, projectile->lightIntensity, NULL);\n\n                int projectileOrigin = combat_bullet_start(attack->attacker, attack->defender);\n                obj_move_to_tile(projectile, projectileOrigin, attack->attacker->elevation, NULL);\n\n                int projectileRotation = tile_dir(attack->attacker->tile, attack->defender->tile);\n                obj_set_rotation(projectile, projectileRotation, NULL);\n\n                register_object_funset(projectile, OBJECT_HIDDEN, actionFrame);\n\n                const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_AMMO_FLYING, weapon, attack->hitMode, attack->defender);\n                register_object_play_sfx(projectile, sfx, 0);\n\n                int v24;\n                if ((attack->attackerFlags & DAM_HIT) != 0) {\n                    register_object_move_straight_to_tile(projectile, attack->defender->tile, attack->defender->elevation, ANIM_WALK, 0);\n                    actionFrame = make_straight_path(projectile, projectileOrigin, attack->defender->tile, NULL, NULL, 32) - 1;\n                    v24 = attack->defender->tile;\n                } else {\n                    register_object_move_straight_to_tile(projectile, attack->tile, attack->defender->elevation, ANIM_WALK, 0);\n                    actionFrame = 0;\n                    v24 = attack->tile;\n                }\n\n                if (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION) {\n                    if ((attack->attackerFlags & DAM_DROP) == 0) {\n                        int explosionFrmId;\n                        if (isGrenade) {\n                            switch (damageType) {\n                            case DAMAGE_TYPE_EMP:\n                                explosionFrmId = 2;\n                                break;\n                            case DAMAGE_TYPE_PLASMA:\n                                explosionFrmId = 31;\n                                break;\n                            default:\n                                explosionFrmId = 29;\n                                break;\n                            }\n                        } else {\n                            explosionFrmId = 10;\n                        }\n\n                        if (isGrenade) {\n                            register_object_change_fid(projectile, weaponFid, -1);\n                        }\n\n                        int explosionFid = art_id(OBJ_TYPE_MISC, explosionFrmId, 0, 0, 0);\n                        register_object_change_fid(projectile, explosionFid, -1);\n\n                        const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender);\n                        register_object_play_sfx(projectile, sfx, 0);\n\n                        register_object_animate_and_hide(projectile, ANIM_STAND, 0);\n\n                        for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n                            if (obj_new(&(neighboors[rotation]), explosionFid, -1) != -1) {\n                                obj_turn_off(neighboors[rotation], NULL);\n\n                                int v31 = tile_num_in_direction(v24, rotation, 1);\n                                obj_move_to_tile(neighboors[rotation], v31, projectile->elevation, NULL);\n\n                                int delay;\n                                if (rotation != ROTATION_NE) {\n                                    delay = 0;\n                                } else {\n                                    if (damageType == DAMAGE_TYPE_PLASMA) {\n                                        delay = 4;\n                                    } else {\n                                        delay = 2;\n                                    }\n                                }\n\n                                register_object_funset(neighboors[rotation], OBJECT_HIDDEN, delay);\n                                register_object_animate_and_hide(neighboors[rotation], ANIM_STAND, 0);\n                            }\n                        }\n\n                        l56 = true;\n                    }\n                } else {\n                    if (anim != ANIM_THROW_ANIM) {\n                        register_object_must_erase(projectile);\n                    }\n                }\n\n                if (!l56) {\n                    const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender);\n                    register_object_play_sfx(weapon, sfx, actionFrame);\n                }\n\n                actionFrame = 0;\n            } else {\n                if ((attack->attackerFlags & DAM_HIT) == 0) {\n                    Object* defender = attack->defender;\n                    if ((defender->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) == 0) {\n                        register_object_animate(defender, ANIM_DODGE_ANIM, actionFrame);\n                        l56 = true;\n                    }\n                }\n            }\n        }\n    }\n\n    show_damage(attack, anim, actionFrame);\n\n    if ((attack->attackerFlags & DAM_HIT) == 0) {\n        combatai_msg(attack->defender, attack, AI_MESSAGE_TYPE_MISS, -1);\n    } else {\n        if ((attack->defenderFlags & DAM_DEAD) == 0) {\n            combatai_msg(attack->defender, attack, AI_MESSAGE_TYPE_HIT, -1);\n        }\n    }\n\n    if (projectile != NULL && (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION)) {\n        register_object_must_erase(projectile);\n    } else if (anim == ANIM_THROW_ANIM && projectile != NULL) {\n        register_object_change_fid(projectile, weaponFid, -1);\n    }\n\n    for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n        if (neighboors[rotation] != NULL) {\n            register_object_must_erase(neighboors[rotation]);\n        }\n    }\n\n    if ((attack->attackerFlags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_DEAD)) == 0) {\n        if (anim == ANIM_THROW_ANIM) {\n            bool l9 = false;\n            if (v50 != NULL) {\n                int v38 = item_w_anim_code(v50);\n                if (v38 != 0) {\n                    register_object_take_out(attack->attacker, v38, -1);\n                    l9 = true;\n                }\n            }\n\n            if (!l9) {\n                int fid = art_id(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, ANIM_STAND, 0, attack->attacker->rotation + 1);\n                register_object_change_fid(attack->attacker, fid, -1);\n            }\n        } else {\n            register_object_animate(attack->attacker, ANIM_UNPOINT, -1);\n        }\n    }\n\n    if (register_end() == -1) {\n        debug_printf(\"Something went wrong with a ranged attack sequence!\\n\");\n        if (projectile != NULL && (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION || anim != ANIM_THROW_ANIM)) {\n            obj_erase_object(projectile, NULL);\n        }\n\n        for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n            if (neighboors[rotation] != NULL) {\n                obj_erase_object(neighboors[rotation], NULL);\n            }\n        }\n\n        return -1;\n    }\n\n    show_damage_extras(attack);\n\n    return 0;\n}\n\n// 0x411D68\nstatic int is_next_to(Object* a1, Object* a2)\n{\n    if (obj_dist(a1, a2) > 1) {\n        if (a2 == obj_dude) {\n            MessageListItem messageListItem;\n            // You cannot get there.\n            messageListItem.num = 2000;\n            if (message_search(&misc_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x411DB4\nstatic int action_climb_ladder(Object* a1, Object* a2)\n{\n    if (a1 == obj_dude) {\n        int anim = FID_ANIM_TYPE(obj_dude->fid);\n        if (anim == ANIM_WALK || anim == ANIM_RUNNING) {\n            register_clear(obj_dude);\n        }\n    }\n\n    int animationRequestOptions;\n    int actionPoints;\n    if (isInCombat()) {\n        animationRequestOptions = ANIMATION_REQUEST_RESERVED;\n        actionPoints = a1->data.critter.combat.ap;\n    } else {\n        animationRequestOptions = ANIMATION_REQUEST_UNRESERVED;\n        actionPoints = -1;\n    }\n\n    if (a1 == obj_dude) {\n        animationRequestOptions = ANIMATION_REQUEST_RESERVED;\n    }\n\n    animationRequestOptions |= ANIMATION_REQUEST_NO_STAND;\n    register_begin(animationRequestOptions);\n\n    int tile = tile_num_in_direction(a2->tile, ROTATION_SE, 1);\n    if (actionPoints != -1 || obj_dist(a1, a2) < 5) {\n        register_object_move_to_tile(a1, tile, a2->elevation, actionPoints, 0);\n    } else {\n        register_object_run_to_tile(a1, tile, a2->elevation, actionPoints, 0);\n    }\n\n    register_object_must_call(a1, a2, is_next_to, -1);\n    register_object_turn_towards(a1, a2->tile);\n    register_object_must_call(a1, a2, check_scenery_ap_cost, -1);\n\n    int weaponAnimationCode = (a1->fid & 0xF000) >> 12;\n    if (weaponAnimationCode != 0) {\n        const char* puttingAwaySfx = gsnd_build_character_sfx_name(a1, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);\n        register_object_play_sfx(a1, puttingAwaySfx, -1);\n        register_object_animate(a1, ANIM_PUT_AWAY, 0);\n    }\n\n    const char* climbingSfx = gsnd_build_character_sfx_name(a1, ANIM_CLIMB_LADDER, CHARACTER_SOUND_EFFECT_UNUSED);\n    register_object_play_sfx(a1, climbingSfx, -1);\n    register_object_animate(a1, ANIM_CLIMB_LADDER, 0);\n    register_object_call(a1, a2, obj_use, -1);\n\n    if (weaponAnimationCode != 0) {\n        register_object_take_out(a1, weaponAnimationCode, -1);\n    }\n\n    return register_end();\n}\n\n// 0x411F2C\nint a_use_obj(Object* a1, Object* a2, Object* a3)\n{\n    Proto* proto = NULL;\n    int type = FID_TYPE(a2->fid);\n    int sceneryType = -1;\n    if (type == OBJ_TYPE_SCENERY) {\n        if (proto_ptr(a2->pid, &proto) == -1) {\n            return -1;\n        }\n\n        sceneryType = proto->scenery.type;\n    }\n\n    if (sceneryType != SCENERY_TYPE_LADDER_UP || a3 != NULL) {\n        if (a1 == obj_dude) {\n            int anim = FID_ANIM_TYPE(obj_dude->fid);\n            if (anim == ANIM_WALK || anim == ANIM_RUNNING) {\n                register_clear(obj_dude);\n            }\n        }\n\n        int animationRequestOptions;\n        int actionPoints;\n        if (isInCombat()) {\n            animationRequestOptions = ANIMATION_REQUEST_RESERVED;\n            actionPoints = a1->data.critter.combat.ap;\n        } else {\n            animationRequestOptions = ANIMATION_REQUEST_UNRESERVED;\n            actionPoints = -1;\n        }\n\n        if (a1 == obj_dude) {\n            animationRequestOptions = ANIMATION_REQUEST_RESERVED;\n        }\n\n        register_begin(animationRequestOptions);\n\n        if (actionPoints != -1 || obj_dist(a1, a2) < 5) {\n            register_object_move_to_object(a1, a2, actionPoints, 0);\n        } else {\n            register_object_run_to_object(a1, a2, -1, 0);\n        }\n\n        register_object_must_call(a1, a2, is_next_to, -1);\n\n        if (a3 == NULL) {\n            register_object_call(a1, a2, check_scenery_ap_cost, -1);\n        }\n\n        int a2a = (a1->fid & 0xF000) >> 12;\n        if (a2a != 0) {\n            const char* sfx = gsnd_build_character_sfx_name(a1, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);\n            register_object_play_sfx(a1, sfx, -1);\n            register_object_animate(a1, ANIM_PUT_AWAY, 0);\n        }\n\n        int anim;\n        int objectType = FID_TYPE(a2->fid);\n        if (objectType == OBJ_TYPE_CRITTER && critter_is_prone(a2)) {\n            anim = ANIM_MAGIC_HANDS_GROUND;\n        } else if (objectType == OBJ_TYPE_SCENERY && (proto->scenery.extendedFlags & 0x01) != 0) {\n            anim = ANIM_MAGIC_HANDS_GROUND;\n        } else {\n            anim = ANIM_MAGIC_HANDS_MIDDLE;\n        }\n\n        if (sceneryType != SCENERY_TYPE_STAIRS && a3 == NULL) {\n            register_object_animate(a1, anim, -1);\n        }\n\n        if (a3 != NULL) {\n            // TODO: Get rid of cast.\n            register_object_call3(a1, a2, a3, obj_use_item_on, -1);\n        } else {\n            register_object_call(a1, a2, obj_use, -1);\n        }\n\n        if (a2a != 0) {\n            register_object_take_out(a1, a2a, -1);\n        }\n\n        return register_end();\n    }\n\n    return action_climb_ladder(a1, a2);\n}\n\n// 0x411F2C\nint action_use_an_item_on_object(Object* a1, Object* a2, Object* a3)\n{\n    return a_use_obj(a1, a2, a3);\n}\n\n// 0x412114\nint action_use_an_object(Object* a1, Object* a2)\n{\n    return a_use_obj(a1, a2, NULL);\n}\n\n// NOTE: Unused.\n//\n// 0x412120\nint get_an_object(Object* item)\n{\n    return action_get_an_object(obj_dude, item);\n}\n\n// 0x412134\nint action_get_an_object(Object* critter, Object* item)\n{\n    if (FID_TYPE(item->fid) != OBJ_TYPE_ITEM) {\n        return -1;\n    }\n\n    if (critter == obj_dude) {\n        int animationCode = FID_ANIM_TYPE(obj_dude->fid);\n        if (animationCode == ANIM_WALK || animationCode == ANIM_RUNNING) {\n            register_clear(obj_dude);\n        }\n    }\n\n    if (isInCombat()) {\n        register_begin(ANIMATION_REQUEST_RESERVED);\n        register_object_move_to_object(critter, item, critter->data.critter.combat.ap, 0);\n    } else {\n        register_begin(critter == obj_dude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);\n        if (obj_dist(critter, item) >= 5) {\n            register_object_run_to_object(critter, item, -1, 0);\n        } else {\n            register_object_move_to_object(critter, item, -1, 0);\n        }\n    }\n\n    register_object_must_call(critter, item, is_next_to, -1);\n    register_object_call(critter, item, check_scenery_ap_cost, -1);\n\n    Proto* itemProto;\n    proto_ptr(item->pid, &itemProto);\n\n    if (itemProto->item.type != ITEM_TYPE_CONTAINER || proto_action_can_pickup(item->pid)) {\n        register_object_animate(critter, ANIM_MAGIC_HANDS_GROUND, 0);\n\n        int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_MAGIC_HANDS_GROUND, (critter->fid & 0xF000) >> 12, critter->rotation + 1);\n\n        int actionFrame;\n        CacheEntry* cacheEntry;\n        Art* art = art_ptr_lock(fid, &cacheEntry);\n        if (art != NULL) {\n            actionFrame = art_frame_action_frame(art);\n        } else {\n            actionFrame = -1;\n        }\n\n        char sfx[16];\n        if (art_get_base_name(FID_TYPE(item->fid), item->fid & 0xFFF, sfx) == 0) {\n            // NOTE: looks like they copy sfx one more time, what for?\n            register_object_play_sfx(item, sfx, actionFrame);\n        }\n\n        register_object_call(critter, item, obj_pickup, actionFrame);\n    } else {\n        int weaponAnimationCode = (critter->fid & 0xF000) >> 12;\n        if (weaponAnimationCode != 0) {\n            const char* sfx = gsnd_build_character_sfx_name(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);\n            register_object_play_sfx(critter, sfx, -1);\n            register_object_animate(critter, ANIM_PUT_AWAY, -1);\n        }\n\n        // ground vs middle animation\n        int anim = (itemProto->item.data.container.openFlags & 0x01) == 0\n            ? ANIM_MAGIC_HANDS_MIDDLE\n            : ANIM_MAGIC_HANDS_GROUND;\n        register_object_animate(critter, anim, 0);\n\n        int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, 0, critter->rotation + 1);\n\n        int actionFrame;\n        CacheEntry* cacheEntry;\n        Art* art = art_ptr_lock(fid, &cacheEntry);\n        if (art == NULL) {\n            actionFrame = art_frame_action_frame(art);\n            art_ptr_unlock(cacheEntry);\n        } else {\n            actionFrame = -1;\n        }\n\n        if (item->frame != 1) {\n            register_object_call(critter, item, obj_use_container, actionFrame);\n        }\n\n        if (weaponAnimationCode != 0) {\n            register_object_take_out(critter, weaponAnimationCode, -1);\n        }\n\n        if (item->frame == 0 || item->frame == 1) {\n            register_object_call(critter, item, scripts_request_loot_container, -1);\n        }\n    }\n\n    return register_end();\n}\n\n// TODO: Looks like the name is a little misleading, container can only be a\n// critter, which is enforced by this function as well as at the call sites.\n// Used to loot corpses, so probably should be something like actionLootCorpse.\n// Check if it can be called with a living critter.\n//\n// 0x4123E8\nint action_loot_container(Object* critter, Object* container)\n{\n    if (FID_TYPE(container->fid) != OBJ_TYPE_CRITTER) {\n        return -1;\n    }\n\n    if (critter == obj_dude) {\n        int anim = FID_ANIM_TYPE(obj_dude->fid);\n        if (anim == ANIM_WALK || anim == ANIM_RUNNING) {\n            register_clear(obj_dude);\n        }\n    }\n\n    if (isInCombat()) {\n        register_begin(ANIMATION_REQUEST_RESERVED);\n        register_object_move_to_object(critter, container, critter->data.critter.combat.ap, 0);\n    } else {\n        register_begin(critter == obj_dude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);\n\n        if (obj_dist(critter, container) < 5) {\n            register_object_move_to_object(critter, container, -1, 0);\n        } else {\n            register_object_run_to_object(critter, container, -1, 0);\n        }\n    }\n\n    register_object_must_call(critter, container, is_next_to, -1);\n    register_object_call(critter, container, check_scenery_ap_cost, -1);\n    register_object_call(critter, container, scripts_request_loot_container, -1);\n    return register_end();\n}\n\n// 0x4124E0\nint action_skill_use(int skill)\n{\n    if (skill == SKILL_SNEAK) {\n        register_clear(obj_dude);\n        pc_flag_toggle(DUDE_STATE_SNEAKING);\n        return 0;\n    }\n\n    return -1;\n}\n\n// NOTE: Inlined.\n//\n// 0x412500\nint action_use_skill_in_combat_error(Object* critter)\n{\n    MessageListItem messageListItem;\n\n    if (critter == obj_dude) {\n        messageListItem.num = 902;\n        if (message_search(&proto_main_msg_file, &messageListItem) == 1) {\n            display_print(messageListItem.text);\n        }\n    }\n\n    return -1;\n}\n\n// skill_use\n// 0x41255C\nint action_use_skill_on(Object* a1, Object* a2, int skill)\n{\n    switch (skill) {\n    case SKILL_FIRST_AID:\n    case SKILL_DOCTOR:\n        if (isInCombat()) {\n            // NOTE: Uninline.\n            return action_use_skill_in_combat_error(a1);\n        }\n\n        if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) {\n            return -1;\n        }\n        break;\n    case SKILL_LOCKPICK:\n        if (isInCombat()) {\n            // NOTE: Uninline.\n            return action_use_skill_in_combat_error(a1);\n        }\n\n        if (PID_TYPE(a2->pid) != OBJ_TYPE_ITEM && PID_TYPE(a2->pid) != OBJ_TYPE_SCENERY) {\n            return -1;\n        }\n\n        break;\n    case SKILL_STEAL:\n        if (isInCombat()) {\n            // NOTE: Uninline.\n            return action_use_skill_in_combat_error(a1);\n        }\n\n        if (PID_TYPE(a2->pid) != OBJ_TYPE_ITEM && PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) {\n            return -1;\n        }\n\n        if (a2 == a1) {\n            return -1;\n        }\n\n        break;\n    case SKILL_TRAPS:\n        if (isInCombat()) {\n            // NOTE: Uninline.\n            return action_use_skill_in_combat_error(a1);\n        }\n\n        if (PID_TYPE(a2->pid) == OBJ_TYPE_CRITTER) {\n            return -1;\n        }\n\n        break;\n    case SKILL_SCIENCE:\n    case SKILL_REPAIR:\n        if (isInCombat()) {\n            // NOTE: Uninline.\n            return action_use_skill_in_combat_error(a1);\n        }\n\n        if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) {\n            break;\n        }\n\n        if (critterGetKillType(a2) == KILL_TYPE_ROBOT) {\n            break;\n        }\n\n        if (critterGetKillType(a2) == KILL_TYPE_BRAHMIN\n            && skill == SKILL_SCIENCE) {\n            break;\n        }\n\n        return -1;\n    case SKILL_SNEAK:\n        pc_flag_toggle(DUDE_STATE_SNEAKING);\n        return 0;\n    default:\n        debug_printf(\"\\nskill_use: invalid skill used.\");\n    }\n\n    // Performer is either dude, or party member who's best at the specified\n    // skill in entire party, and this skill is his/her own best.\n    Object* performer = obj_dude;\n\n    if (a1 == obj_dude) {\n        Object* partyMember = partyMemberWithHighestSkill(skill);\n\n        if (partyMember == obj_dude) {\n            partyMember = NULL;\n        }\n\n        // Only dude can perform stealing.\n        if (skill == SKILL_STEAL) {\n            partyMember = NULL;\n        }\n\n        if (partyMember != NULL) {\n            if (partyMemberSkill(partyMember) != skill) {\n                partyMember = NULL;\n            }\n        }\n\n        if (partyMember != NULL) {\n            performer = partyMember;\n            int anim = FID_ANIM_TYPE(partyMember->fid);\n            if (anim != ANIM_WALK && anim != ANIM_RUNNING) {\n                if (anim != ANIM_STAND) {\n                    performer = obj_dude;\n                    partyMember = NULL;\n                }\n            } else {\n                register_clear(partyMember);\n            }\n        }\n\n        if (partyMember != NULL) {\n            bool v32 = false;\n            if (obj_dist(obj_dude, a2) <= 1) {\n                v32 = true;\n            }\n\n            char* msg = skillGetPartyMemberString(partyMember, v32);\n\n            Rect rect;\n            if (text_object_create(partyMember, msg, 101, colorTable[32747], colorTable[0], &rect) == 0) {\n                tile_refresh_rect(&rect, map_elevation);\n            }\n\n            if (v32) {\n                performer = obj_dude;\n                partyMember = NULL;\n            }\n        }\n\n        if (partyMember == NULL) {\n            int anim = FID_ANIM_TYPE(performer->fid);\n            if (anim == ANIM_WALK || anim == ANIM_RUNNING) {\n                register_clear(performer);\n            }\n        }\n    }\n\n    if (isInCombat()) {\n        register_begin(ANIMATION_REQUEST_RESERVED);\n        register_object_move_to_object(performer, a2, performer->data.critter.combat.ap, 0);\n    } else {\n        register_begin(a1 == obj_dude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);\n        if (a2 != obj_dude) {\n            if (obj_dist(performer, a2) >= 5) {\n                register_object_run_to_object(performer, a2, -1, 0);\n            } else {\n                register_object_move_to_object(performer, a2, -1, 0);\n            }\n        }\n    }\n\n    register_object_must_call(performer, a2, is_next_to, -1);\n\n    int anim = (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER && critter_is_prone(a2)) ? ANIM_MAGIC_HANDS_GROUND : ANIM_MAGIC_HANDS_MIDDLE;\n    int fid = art_id(OBJ_TYPE_CRITTER, performer->fid & 0xFFF, anim, 0, performer->rotation + 1);\n\n    CacheEntry* artHandle;\n    Art* art = art_ptr_lock(fid, &artHandle);\n    if (art != NULL) {\n        art_frame_action_frame(art);\n        art_ptr_unlock(artHandle);\n    }\n\n    register_object_animate(performer, anim, -1);\n    // TODO: Get rid of casts.\n    register_object_call3(performer, a2, (void*)skill, (AnimationCallback3*)obj_use_skill_on, -1);\n    return register_end();\n}\n\n// NOTE: Unused.\n//\n// 0x4129CC\nObject* pick_object(int objectType, bool a2)\n{\n    Object* foundObject;\n    int mouseEvent;\n    int keyCode;\n\n    foundObject = NULL;\n\n    do {\n        get_input();\n    } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0);\n\n    gmouse_set_cursor(MOUSE_CURSOR_PLUS);\n    gmouse_3d_off();\n\n    do {\n        if (get_input() == -2) {\n            mouseEvent = mouse_get_buttons();\n            if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n                keyCode = 0;\n                foundObject = object_under_mouse(objectType, a2, map_elevation);\n                break;\n            }\n\n            if ((mouseEvent & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) {\n                keyCode = KEY_ESCAPE;\n                break;\n            }\n        }\n    } while (game_user_wants_to_quit == 0);\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    gmouse_3d_on();\n\n    if (keyCode == KEY_ESCAPE) {\n        return NULL;\n    }\n\n    return foundObject;\n}\n\n// NOTE: Unused.\n//\n// 0x412A54\nint pick_hex()\n{\n    int elevation;\n    int inputEvent;\n    int tile;\n    Rect rect;\n\n    elevation = map_elevation;\n\n    while (1) {\n        inputEvent = get_input();\n        if (inputEvent == -2) {\n            break;\n        }\n\n        if (inputEvent == KEY_CTRL_ARROW_RIGHT) {\n            rotation++;\n            if (rotation > 5) {\n                rotation = 0;\n            }\n\n            obj_set_rotation(obj_mouse, rotation, &rect);\n            tile_refresh_rect(&rect, obj_mouse->elevation);\n        }\n\n        if (inputEvent == KEY_CTRL_ARROW_LEFT) {\n            rotation--;\n            if (rotation == -1) {\n                rotation = 5;\n            }\n\n            obj_set_rotation(obj_mouse, rotation, &rect);\n            tile_refresh_rect(&rect, obj_mouse->elevation);\n        }\n\n        if (inputEvent == KEY_PAGE_UP || inputEvent == KEY_PAGE_DOWN) {\n            if (inputEvent == KEY_PAGE_UP) {\n                map_set_elevation(map_elevation + 1);\n            } else {\n                map_set_elevation(map_elevation - 1);\n            }\n\n            rect.ulx = 30;\n            rect.uly = 62;\n            rect.lrx = 50;\n            rect.lry = 88;\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            return -1;\n        }\n    }\n\n    if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) == 0) {\n        return -1;\n    }\n\n    if (!mouse_click_in(0, 0, scr_size.lrx - scr_size.ulx, scr_size.lry - scr_size.uly - 100)) {\n        return -1;\n    }\n\n    mouse_get_position(&(rect.ulx), &(rect.uly));\n\n    tile = tile_num(rect.ulx, rect.uly, elevation);\n    if (tile == -1) {\n        return -1;\n    }\n\n    return tile;\n}\n\n// 0x412BC4\nbool is_hit_from_front(Object* a1, Object* a2)\n{\n    int diff = a1->rotation - a2->rotation;\n    if (diff < 0) {\n        diff = -diff;\n    }\n\n    return diff != 0 && diff != 1 && diff != 5;\n}\n\n// 0x412BEC\nbool can_see(Object* a1, Object* a2)\n{\n    int diff;\n\n    diff = a1->rotation - tile_dir(a1->tile, a2->tile);\n    if (diff < 0) {\n        diff = -diff;\n    }\n\n    return diff == 0 || diff == 1 || diff == 5;\n}\n\n// looks like it tries to change fall animation depending on object's current rotation\n// 0x412C1C\nint pick_fall(Object* obj, int anim)\n{\n    int i;\n    int rotation;\n    int tile_num;\n    int fid;\n\n    if (anim == ANIM_FALL_FRONT) {\n        rotation = obj->rotation;\n        for (i = 1; i < 3; i++) {\n            tile_num = tile_num_in_direction(obj->tile, rotation, i);\n            if (obj_blocking_at(obj, tile_num, obj->elevation) != NULL) {\n                anim = ANIM_FALL_BACK;\n                break;\n            }\n        }\n    } else if (anim == ANIM_FALL_BACK) {\n        rotation = (obj->rotation + 3) % ROTATION_COUNT;\n        for (i = 1; i < 3; i++) {\n            tile_num = tile_num_in_direction(obj->tile, rotation, i);\n            if (obj_blocking_at(obj, tile_num, obj->elevation) != NULL) {\n                anim = ANIM_FALL_FRONT;\n                break;\n            }\n        }\n    }\n\n    if (anim == ANIM_FALL_FRONT) {\n        fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, ANIM_FALL_FRONT, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n        if (!art_exists(fid)) {\n            anim = ANIM_FALL_BACK;\n        }\n    }\n\n    return anim;\n}\n\n// 0x412CE4\nbool action_explode_running()\n{\n    return action_in_explode;\n}\n\n// action_explode\n// 0x412CF4\nint action_explode(int tile, int elevation, int minDamage, int maxDamage, Object* a5, bool a6)\n{\n    if (a6 && action_in_explode) {\n        return -2;\n    }\n\n    Attack* attack = (Attack*)mem_malloc(sizeof(*attack));\n    if (attack == NULL) {\n        return -1;\n    }\n\n    Object* explosion;\n    int fid = art_id(OBJ_TYPE_MISC, 10, 0, 0, 0);\n    if (obj_new(&explosion, fid, -1) == -1) {\n        mem_free(attack);\n        return -1;\n    }\n\n    obj_turn_off(explosion, NULL);\n    explosion->flags |= OBJECT_TEMPORARY;\n\n    obj_move_to_tile(explosion, tile, elevation, NULL);\n\n    Object* adjacentExplosions[ROTATION_COUNT];\n    for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n        int fid = art_id(OBJ_TYPE_MISC, 10, 0, 0, 0);\n        if (obj_new(&(adjacentExplosions[rotation]), fid, -1) == -1) {\n            while (--rotation >= 0) {\n                obj_erase_object(adjacentExplosions[rotation], NULL);\n            }\n\n            obj_erase_object(explosion, NULL);\n            mem_free(attack);\n            return -1;\n        }\n\n        obj_turn_off(adjacentExplosions[rotation], NULL);\n        adjacentExplosions[rotation]->flags |= OBJECT_TEMPORARY;\n\n        int adjacentTile = tile_num_in_direction(tile, rotation, 1);\n        obj_move_to_tile(adjacentExplosions[rotation], adjacentTile, elevation, NULL);\n    }\n\n    Object* critter = obj_blocking_at(NULL, tile, elevation);\n    if (critter != NULL) {\n        if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER || (critter->data.critter.combat.results & DAM_DEAD) != 0) {\n            critter = NULL;\n        }\n    }\n\n    combat_ctd_init(attack, explosion, critter, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);\n\n    attack->tile = tile;\n    attack->attackerFlags = DAM_HIT;\n\n    game_ui_disable(1);\n\n    if (critter != NULL) {\n        if (register_clear(critter) == -2) {\n            debug_printf(\"Cannot clear target's animation for action_explode!\\n\");\n        }\n        attack->defenderDamage = compute_explosion_damage(minDamage, maxDamage, critter, &(attack->defenderKnockback));\n    }\n\n    compute_explosion_on_extras(attack, 0, 0, 1);\n\n    for (int index = 0; index < attack->extrasLength; index++) {\n        Object* critter = attack->extras[index];\n        if (register_clear(critter) == -2) {\n            debug_printf(\"Cannot clear extra's animation for action_explode!\\n\");\n        }\n\n        attack->extrasDamage[index] = compute_explosion_damage(minDamage, maxDamage, critter, &(attack->extrasKnockback[index]));\n    }\n\n    death_checks(attack);\n\n    if (a6) {\n        action_in_explode = true;\n\n        register_begin(ANIMATION_REQUEST_RESERVED);\n        register_priority(1);\n        register_object_play_sfx(explosion, \"whn1xxx1\", 0);\n        register_object_funset(explosion, OBJECT_HIDDEN, 0);\n        register_object_animate_and_hide(explosion, ANIM_STAND, 0);\n        show_damage(attack, 0, 1);\n\n        for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n            register_object_funset(adjacentExplosions[rotation], OBJECT_HIDDEN, 0);\n            register_object_animate_and_hide(adjacentExplosions[rotation], ANIM_STAND, 0);\n        }\n\n        register_object_must_call(explosion, 0, combat_explode_scenery, -1);\n        register_object_must_erase(explosion);\n\n        for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n            register_object_must_erase(adjacentExplosions[rotation]);\n        }\n\n        register_object_must_call(attack, a5, report_explosion, -1);\n        register_object_must_call(NULL, NULL, finished_explosion, -1);\n        if (register_end() == -1) {\n            action_in_explode = false;\n\n            obj_erase_object(explosion, NULL);\n\n            for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n                obj_erase_object(adjacentExplosions[rotation], NULL);\n            }\n\n            mem_free(attack);\n\n            game_ui_enable();\n            return -1;\n        }\n\n        show_damage_extras(attack);\n    } else {\n        if (critter != NULL) {\n            if ((attack->defenderFlags & DAM_DEAD) != 0) {\n                critter_kill(critter, -1, false);\n            }\n        }\n\n        for (int index = 0; index < attack->extrasLength; index++) {\n            if ((attack->extrasFlags[index] & DAM_DEAD) != 0) {\n                critter_kill(attack->extras[index], -1, false);\n            }\n        }\n\n        report_explosion(attack, a5);\n\n        combat_explode_scenery(explosion, NULL);\n\n        obj_erase_object(explosion, NULL);\n\n        for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n            obj_erase_object(adjacentExplosions[rotation], NULL);\n        }\n    }\n\n    return 0;\n}\n\n// 0x413144\nstatic int report_explosion(Attack* attack, Object* a2)\n{\n    bool mainTargetWasDead;\n    if (attack->defender != NULL) {\n        mainTargetWasDead = (attack->defender->data.critter.combat.results & DAM_DEAD) != 0;\n    } else {\n        mainTargetWasDead = false;\n    }\n\n    bool extrasWasDead[6];\n    for (int index = 0; index < attack->extrasLength; index++) {\n        extrasWasDead[index] = (attack->extras[index]->data.critter.combat.results & DAM_DEAD) != 0;\n    }\n\n    death_checks(attack);\n    combat_display(attack);\n    apply_damage(attack, false);\n\n    Object* anyDefender = NULL;\n    int xp = 0;\n    if (a2 != NULL) {\n        if (attack->defender != NULL && attack->defender != a2) {\n            if ((attack->defender->data.critter.combat.results & DAM_DEAD) != 0) {\n                if (a2 == obj_dude && !mainTargetWasDead) {\n                    xp += critter_kill_exps(attack->defender);\n                }\n            } else {\n                critter_set_who_hit_me(attack->defender, a2);\n                anyDefender = attack->defender;\n            }\n        }\n\n        for (int index = 0; index < attack->extrasLength; index++) {\n            Object* critter = attack->extras[index];\n            if (critter != a2) {\n                if ((critter->data.critter.combat.results & DAM_DEAD) != 0) {\n                    if (a2 == obj_dude && !extrasWasDead[index]) {\n                        xp += critter_kill_exps(critter);\n                    }\n                } else {\n                    critter_set_who_hit_me(critter, a2);\n\n                    if (anyDefender == NULL) {\n                        anyDefender = critter;\n                    }\n                }\n            }\n        }\n\n        if (anyDefender != NULL) {\n            if (!isInCombat()) {\n                STRUCT_664980 combat;\n                combat.attacker = anyDefender;\n                combat.defender = a2;\n                combat.actionPointsBonus = 0;\n                combat.accuracyBonus = 0;\n                combat.damageBonus = 0;\n                combat.minDamage = 0;\n                combat.maxDamage = INT_MAX;\n                combat.field_1C = 0;\n                scripts_request_combat(&combat);\n            }\n        }\n    }\n\n    mem_free(attack);\n    game_ui_enable();\n\n    if (a2 == obj_dude) {\n        combat_give_exps(xp);\n    }\n\n    return 0;\n}\n\n// 0x4132C0\nstatic int finished_explosion(Object* a1, Object* a2)\n{\n    action_in_explode = false;\n    return 0;\n}\n\n// calculate explosion damage applying threshold and resistances\n// 0x4132CC\nstatic int compute_explosion_damage(int min, int max, Object* a3, int* a4)\n{\n    int v5 = roll_random(min, max);\n    int v7 = v5 - critterGetStat(a3, STAT_DAMAGE_THRESHOLD_EXPLOSION);\n    if (v7 > 0) {\n        v7 -= critterGetStat(a3, STAT_DAMAGE_RESISTANCE_EXPLOSION) * v7 / 100;\n    }\n\n    if (v7 < 0) {\n        v7 = 0;\n    }\n\n    if (a4 != NULL) {\n        if ((a3->flags & OBJECT_MULTIHEX) == 0) {\n            *a4 = v7 / 10;\n        }\n    }\n\n    return v7;\n}\n\n// 0x413330\nint action_talk_to(Object* a1, Object* a2)\n{\n    if (a1 != obj_dude) {\n        return -1;\n    }\n\n    if (FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) {\n        return -1;\n    }\n\n    int anim = FID_ANIM_TYPE(obj_dude->fid);\n    if (anim == ANIM_WALK || anim == ANIM_RUNNING) {\n        register_clear(obj_dude);\n    }\n\n    if (isInCombat()) {\n        register_begin(ANIMATION_REQUEST_RESERVED);\n        register_object_move_to_object(a1, a2, a1->data.critter.combat.ap, 0);\n    } else {\n        register_begin(a1 == obj_dude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED);\n\n        if (obj_dist(a1, a2) >= 9 || combat_is_shot_blocked(a1, a1->tile, a2->tile, a2, NULL)) {\n            register_object_run_to_object(a1, a2, -1, 0);\n        }\n    }\n\n    register_object_must_call(a1, a2, can_talk_to, -1);\n    register_object_call(a1, a2, talk_to, -1);\n    return register_end();\n}\n\n// 0x413420\nstatic int can_talk_to(Object* a1, Object* a2)\n{\n    if (combat_is_shot_blocked(a1, a1->tile, a2->tile, a2, NULL) || obj_dist(a1, a2) >= 9) {\n        if (a1 == obj_dude) {\n            // You cannot get there. (used in actions.c)\n            MessageListItem messageListItem;\n            messageListItem.num = 2000;\n            if (message_search(&misc_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x413488\nstatic int talk_to(Object* a1, Object* a2)\n{\n    scripts_request_dialog(a2);\n    return 0;\n}\n\n// 0x413494\nvoid action_dmg(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor)\n{\n    Attack* attack = (Attack*)mem_malloc(sizeof(*attack));\n    if (attack == NULL) {\n        return;\n    }\n\n    Object* attacker;\n    if (obj_new(&attacker, FID_0x20001F5, -1) == -1) {\n        mem_free(attack);\n        return;\n    }\n\n    obj_turn_off(attacker, NULL);\n\n    attacker->flags |= OBJECT_TEMPORARY;\n\n    obj_move_to_tile(attacker, tile, elevation, NULL);\n\n    Object* defender = obj_blocking_at(NULL, tile, elevation);\n    combat_ctd_init(attack, attacker, defender, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);\n    attack->tile = tile;\n    attack->attackerFlags = DAM_HIT;\n    game_ui_disable(1);\n\n    if (defender != NULL) {\n        register_clear(defender);\n\n        int damage;\n        if (bypassArmor) {\n            damage = maxDamage;\n        } else {\n            damage = compute_dmg_damage(minDamage, maxDamage, defender, &(attack->defenderKnockback), damageType);\n        }\n\n        attack->defenderDamage = damage;\n    }\n\n    death_checks(attack);\n\n    if (animated) {\n        register_begin(ANIMATION_REQUEST_RESERVED);\n        register_object_play_sfx(attacker, \"whc1xxx1\", 0);\n        show_damage(attack, death_3[damageType], 0);\n        register_object_must_call(attack, NULL, report_dmg, 0);\n        register_object_must_erase(attacker);\n\n        if (register_end() == -1) {\n            obj_erase_object(attacker, NULL);\n            mem_free(attack);\n            return;\n        }\n    } else {\n        if (defender != NULL) {\n            if ((attack->defenderFlags & DAM_DEAD) != 0) {\n                critter_kill(defender, -1, 1);\n            }\n        }\n\n        // NOTE: Uninline.\n        report_dmg(attack, NULL);\n\n        obj_erase_object(attacker, NULL);\n    }\n\n    game_ui_enable();\n}\n\n// 0x41363C\nstatic int report_dmg(Attack* attack, Object* a2)\n{\n    combat_display(attack);\n    apply_damage(attack, false);\n    mem_free(attack);\n    game_ui_enable();\n    return 0;\n}\n\n// Calculate damage by applying threshold and resistances.\n//\n// 0x413660\nstatic int compute_dmg_damage(int min, int max, Object* obj, int* a4, int damageType)\n{\n    if (!critter_flag_check(obj->pid, CRITTER_NO_KNOCKBACK)) {\n        a4 = NULL;\n    }\n\n    int v8 = roll_random(min, max);\n    int v10 = v8 - critterGetStat(obj, STAT_DAMAGE_THRESHOLD + damageType);\n    if (v10 > 0) {\n        v10 -= critterGetStat(obj, STAT_DAMAGE_RESISTANCE + damageType) * v10 / 100;\n    }\n\n    if (v10 < 0) {\n        v10 = 0;\n    }\n\n    if (a4 != NULL) {\n        if ((obj->flags & OBJECT_MULTIHEX) == 0 && damageType != DAMAGE_TYPE_ELECTRICAL) {\n            *a4 = v10 / 10;\n        }\n    }\n\n    return v10;\n}\n\n// 0x4136EC\nbool action_can_be_pushed(Object* a1, Object* a2)\n{\n    // Cannot push anything but critters.\n    if (FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    // Cannot push itself.\n    if (a1 == a2) {\n        return false;\n    }\n\n    // Cannot push inactive critters.\n    if (!critter_is_active(a2)) {\n        return false;\n    }\n\n    if (action_can_talk_to(a1, a2) != 0) {\n        return false;\n    }\n\n    // Can only push critters that have push handler.\n    if (!scriptHasProc(a2->sid, SCRIPT_PROC_PUSH)) {\n        return false;\n    }\n\n    if (isInCombat()) {\n        if (a2->data.critter.combat.team == a1->data.critter.combat.team\n            && a2 == a1->data.critter.combat.whoHitMe) {\n            return false;\n        }\n\n        // TODO: Check.\n        Object* whoHitMe = a2->data.critter.combat.whoHitMe;\n        if (whoHitMe != NULL\n            && whoHitMe->data.critter.combat.team == a1->data.critter.combat.team) {\n            return false;\n        }\n    }\n\n    return true;\n}\n\n// 0x413790\nint action_push_critter(Object* a1, Object* a2)\n{\n    if (!action_can_be_pushed(a1, a2)) {\n        return -1;\n    }\n\n    int sid;\n    if (obj_sid(a2, &sid) == 0) {\n        scr_set_objs(sid, a1, a2);\n        exec_script_proc(sid, SCRIPT_PROC_PUSH);\n\n        bool scriptOverrides = false;\n\n        Script* scr;\n        if (scr_ptr(sid, &scr) != -1) {\n            scriptOverrides = scr->scriptOverrides;\n        }\n\n        if (scriptOverrides) {\n            return -1;\n        }\n    }\n\n    int rotation = tile_dir(a1->tile, a2->tile);\n    int tile;\n    do {\n        tile = tile_num_in_direction(a2->tile, rotation, 1);\n        if (obj_blocking_at(a2, tile, a2->elevation) == NULL) {\n            break;\n        }\n\n        tile = tile_num_in_direction(a2->tile, (rotation + 1) % ROTATION_COUNT, 1);\n        if (obj_blocking_at(a2, tile, a2->elevation) == NULL) {\n            break;\n        }\n\n        tile = tile_num_in_direction(a2->tile, (rotation + 5) % ROTATION_COUNT, 1);\n        if (obj_blocking_at(a2, tile, a2->elevation) == NULL) {\n            break;\n        }\n\n        tile = tile_num_in_direction(a2->tile, (rotation + 2) % ROTATION_COUNT, 1);\n        if (obj_blocking_at(a2, tile, a2->elevation) == NULL) {\n            break;\n        }\n\n        tile = tile_num_in_direction(a2->tile, (rotation + 4) % ROTATION_COUNT, 1);\n        if (obj_blocking_at(a2, tile, a2->elevation) == NULL) {\n            break;\n        }\n\n        tile = tile_num_in_direction(a2->tile, (rotation + 3) % ROTATION_COUNT, 1);\n        if (obj_blocking_at(a2, tile, a2->elevation) == NULL) {\n            break;\n        }\n\n        return -1;\n    } while (0);\n\n    int actionPoints;\n    if (isInCombat()) {\n        actionPoints = a2->data.critter.combat.ap;\n    } else {\n        actionPoints = -1;\n    }\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n    register_object_turn_towards(a2, tile);\n    register_object_move_to_tile(a2, tile, a2->elevation, actionPoints, 0);\n    return register_end();\n}\n\n// Returns -1 if can't see there (can't find a path there)\n// Returns -2 if it's too far (> 12 tiles).\n//\n// 0x413970\nint action_can_talk_to(Object* a1, Object* a2)\n{\n    if (make_path_func(a1, a1->tile, a2->tile, NULL, 0, obj_sight_blocking_at) == 0) {\n        return -1;\n    }\n\n    if (tile_dist(a1->tile, a2->tile) > 12) {\n        return -2;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/actions.h",
    "content": "#ifndef FALLOUT_GAME_ACTIONS_H_\n#define FALLOUT_GAME_ACTIONS_H_\n\n#include <stdbool.h>\n\n#include \"game/combat_defs.h\"\n#include \"game/object_types.h\"\n#include \"game/proto_types.h\"\n\nextern unsigned int rotation;\nextern int obj_fid;\nextern int obj_pid_old;\n\nvoid switch_dude();\nint action_knockback(Object* obj, int* anim, int maxDistance, int rotation, int delay);\nint action_blood(Object* obj, int anim, int delay);\nvoid show_damage_to_object(Object* a1, int damage, int flags, Object* weapon, bool isFallingBack, int knockbackDistance, int knockbackRotation, int a8, Object* a9, int a10);\nint show_damage_target(Attack* attack);\nint show_damage_extras(Attack* attack);\nvoid show_damage(Attack* attack, int a2, int a3);\nint action_attack(Attack* attack);\nint throw_change_fid(Object* object, int fid);\nint a_use_obj(Object* a1, Object* a2, Object* a3);\nint action_use_an_item_on_object(Object* a1, Object* a2, Object* a3);\nint action_use_an_object(Object* a1, Object* a2);\nint get_an_object(Object* item);\nint action_get_an_object(Object* critter, Object* item);\nint action_loot_container(Object* critter, Object* container);\nint action_skill_use(int a1);\nint action_use_skill_in_combat_error(Object* critter);\nint action_use_skill_on(Object* a1, Object* a2, int skill);\nObject* pick_object(int objectType, bool a2);\nint pick_hex();\nbool is_hit_from_front(Object* a1, Object* a2);\nbool can_see(Object* a1, Object* a2);\nint pick_fall(Object* obj, int anim);\nbool action_explode_running();\nint action_explode(int tile, int elevation, int minDamage, int maxDamage, Object* a5, bool a6);\nint action_talk_to(Object* a1, Object* a2);\nvoid action_dmg(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor);\nbool action_can_be_pushed(Object* a1, Object* a2);\nint action_push_critter(Object* a1, Object* a2);\nint action_can_talk_to(Object* a1, Object* a2);\n\n#endif /* FALLOUT_GAME_ACTIONS_H_ */\n"
  },
  {
    "path": "src/game/amutex.c",
    "content": "#include \"game/amutex.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n// 0x530010\nstatic HANDLE autorun_mutex;\n\n// 0x4139C0\nbool autorun_mutex_create()\n{\n    autorun_mutex = CreateMutexA(NULL, FALSE, \"InterplayGenericAutorunMutex\");\n    if (GetLastError() == ERROR_ALREADY_EXISTS) {\n        CloseHandle(autorun_mutex);\n        return false;\n    }\n\n    return true;\n}\n\n// 0x413A00\nvoid autorun_mutex_destroy()\n{\n    if (autorun_mutex != NULL) {\n        CloseHandle(autorun_mutex);\n    }\n}\n"
  },
  {
    "path": "src/game/amutex.h",
    "content": "#ifndef FALLOUT_GAME_AMUTEX_H_\n#define FALLOUT_GAME_AMUTEX_H_\n\n#include <stdbool.h>\n\nbool autorun_mutex_create();\nvoid autorun_mutex_destroy();\n\n#endif /* FALLOUT_GAME_AMUTEX_H_ */\n"
  },
  {
    "path": "src/game/anim.c",
    "content": "#include \"game/anim.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"game/combat_defs.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/stat.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n#include \"plib/gnw/svga.h\"\n#include \"plib/gnw/vcr.h\"\n\n#define ANIMATION_SEQUENCE_LIST_CAPACITY 32\n#define ANIMATION_DESCRIPTION_LIST_CAPACITY 55\n#define ANIMATION_SAD_LIST_CAPACITY 24\n\n#define ANIMATION_SEQUENCE_FORCED 0x01\n\ntypedef enum AnimationKind {\n    ANIM_KIND_MOVE_TO_OBJECT = 0,\n    ANIM_KIND_MOVE_TO_TILE = 1,\n    ANIM_KIND_MOVE_TO_TILE_STRAIGHT = 2,\n    ANIM_KIND_MOVE_TO_TILE_STRAIGHT_AND_WAIT_FOR_COMPLETE = 3,\n    ANIM_KIND_ANIMATE = 4,\n    ANIM_KIND_ANIMATE_REVERSED = 5,\n    ANIM_KIND_ANIMATE_AND_HIDE = 6,\n    ANIM_KIND_ROTATE_TO_TILE = 7,\n    ANIM_KIND_ROTATE_CLOCKWISE = 8,\n    ANIM_KIND_ROTATE_COUNTER_CLOCKWISE = 9,\n    ANIM_KIND_HIDE = 10,\n    ANIM_KIND_CALLBACK = 11,\n    ANIM_KIND_CALLBACK3 = 12,\n    ANIM_KIND_SET_FLAG = 14,\n    ANIM_KIND_UNSET_FLAG = 15,\n    ANIM_KIND_TOGGLE_FLAT = 16,\n    ANIM_KIND_SET_FID = 17,\n    ANIM_KIND_TAKE_OUT_WEAPON = 18,\n    ANIM_KIND_SET_LIGHT_DISTANCE = 19,\n    ANIM_KIND_MOVE_ON_STAIRS = 20,\n    ANIM_KIND_CHECK_FALLING = 23,\n    ANIM_KIND_TOGGLE_OUTLINE = 24,\n    ANIM_KIND_ANIMATE_FOREVER = 25,\n    ANIM_KIND_26 = 26,\n    ANIM_KIND_27 = 27,\n    ANIM_KIND_NOOP = 28,\n} AnimationKind;\n\ntypedef enum AnimationSequenceFlags {\n    // Specifies that the animation sequence has high priority, it cannot be\n    // cleared.\n    ANIM_SEQ_PRIORITIZED = 0x01,\n\n    // Specifies that the animation sequence started combat animation mode and\n    // therefore should balance it with appropriate finish call.\n    ANIM_SEQ_COMBAT_ANIM_STARTED = 0x02,\n\n    // Specifies that the animation sequence is reserved (TODO: explain what it\n    // actually means).\n    ANIM_SEQ_RESERVED = 0x04,\n\n    // Specifies that the animation sequence is in the process of adding actions\n    // to it (that is in the middle of begin/end calls).\n    ANIM_SEQ_ACCUMULATING = 0x08,\n\n    // TODO: Add description.\n    ANIM_SEQ_0x10 = 0x10,\n\n    // TODO: Add description.\n    ANIM_SEQ_0x20 = 0x20,\n\n    // Specifies that the animation sequence is negligible and will be end\n    // immediately when a new animation sequence is requested for the same\n    // object.\n    ANIM_SEQ_INSIGNIFICANT = 0x40,\n\n    // Specifies that the animation sequence should not return to ANIM_STAND\n    // when it's completed.\n    ANIM_SEQ_NO_STAND = 0x80,\n} AnimationSequenceFlags;\n\ntypedef enum AnimationSadFlags {\n    // Specifies that the animation should play from end to start.\n    ANIM_SAD_REVERSE = 0x01,\n\n    // Specifies that the animation should use straight movement mode (as\n    // opposed to normal movement mode).\n    ANIM_SAD_STRAIGHT = 0x02,\n\n    // Specifies that no frame change should occur during animation.\n    ANIM_SAD_NO_ANIM = 0x04,\n\n    // Specifies that the animation should be played fully from start to finish.\n    //\n    // NOTE: This flag is only used together with straight movement mode to\n    // implement knockdown. Without this flag when the knockdown distance is\n    // short, say 1 or 2 tiles, knockdown animation might not be completed by\n    // the time critter reached it's destination. With this flag set animation\n    // will be played to it's final frame.\n    ANIM_SAD_WAIT_FOR_COMPLETION = 0x10,\n\n    // Unknown, set once, never read.\n    ANIM_SAD_0x20 = 0x20,\n\n    // Specifies that the owner of the animation should be hidden when animation\n    // is completed.\n    ANIM_SAD_HIDE_ON_END = 0x40,\n\n    // Specifies that the animation should never end.\n    ANIM_SAD_FOREVER = 0x80,\n} AnimationSadFlags;\n\ntypedef struct AnimationDescription {\n    int kind;\n    union {\n        Object* owner;\n\n        // - ANIM_KIND_CALLBACK\n        // - ANIM_KIND_CALLBACK3\n        void* param2;\n    };\n\n    union {\n        // - ANIM_KIND_MOVE_TO_OBJECT\n        Object* destination;\n\n        // - ANIM_KIND_CALLBACK\n        void* param1;\n    };\n    union {\n        // - ANIM_KIND_MOVE_TO_TILE\n        // - ANIM_KIND_ANIMATE_AND_MOVE_TO_TILE_STRAIGHT\n        // - ANIM_KIND_MOVE_TO_TILE_STRAIGHT\n        struct {\n            int tile;\n            int elevation;\n        };\n\n        // ANIM_KIND_SET_FID\n        int fid;\n\n        // ANIM_KIND_TAKE_OUT_WEAPON\n        int weaponAnimationCode;\n\n        // ANIM_KIND_SET_LIGHT_DISTANCE\n        int lightDistance;\n\n        // ANIM_KIND_TOGGLE_OUTLINE\n        bool outline;\n    };\n    int anim;\n    int delay;\n\n    // ANIM_KIND_CALLBACK\n    AnimationCallback* callback;\n\n    // ANIM_KIND_CALLBACK3\n    AnimationCallback3* callback3;\n\n    union {\n        // - ANIM_KIND_SET_FLAG\n        // - ANIM_KIND_UNSET_FLAG\n        unsigned int objectFlag;\n\n        // - ANIM_KIND_HIDE\n        // - ANIM_KIND_CALLBACK\n        unsigned int extendedFlags;\n    };\n\n    union {\n        // - ANIM_KIND_MOVE_TO_TILE\n        // - ANIM_KIND_MOVE_TO_OBJECT\n        int actionPoints;\n\n        // ANIM_KIND_26\n        int animationSequenceIndex;\n\n        // ANIM_KIND_CALLBACK3\n        void* param3;\n    };\n    CacheEntry* artCacheKey;\n} AnimationDescription;\n\ntypedef struct AnimationSequence {\n    int field_0;\n    // Index of current animation in [animations] array or -1 if animations in\n    // this sequence is not playing.\n    int animationIndex;\n    // Number of scheduled animations in [animations] array.\n    int length;\n    unsigned int flags;\n    AnimationDescription animations[ANIMATION_DESCRIPTION_LIST_CAPACITY];\n} AnimationSequence;\n\ntypedef struct PathNode {\n    int tile;\n    int from;\n    // actual type is likely char\n    int rotation;\n    int field_C;\n    int field_10;\n} PathNode;\n\n// TODO: I don't know what `sad` means, but it's definitely better than\n// `STRUCT_530014`. Find a better name.\ntypedef struct AnimationSad {\n    unsigned int flags;\n    Object* obj;\n    int fid; // fid\n    int anim;\n\n    // Timestamp (in game ticks) when animation last occurred.\n    unsigned int animationTimestamp;\n\n    // Number of ticks per frame (taking art's fps and overall animation speed\n    // settings into account).\n    unsigned int ticksPerFrame;\n\n    int animationSequenceIndex;\n    int field_1C; // length of field_28\n    int field_20; // current index in field_28\n    int field_24;\n    union {\n        unsigned char rotations[3200];\n        StraightPathNode field_28[200];\n    };\n} AnimationSad;\n\nstatic_assert(sizeof(AnimationSad) == 3240, \"wrong size\");\n\nstatic int anim_free_slot(int a1);\nstatic int anim_preload(Object* object, int fid, CacheEntry** cacheEntryPtr);\nstatic void anim_cleanup();\nstatic int anim_set_check(int a1);\nstatic int anim_set_continue(int a1, int a2);\nstatic int anim_set_end(int a1);\nstatic bool anim_can_use_door(Object* critter, Object* door);\nstatic int anim_move_to_object(Object* from, Object* to, int a3, int anim, int animationSequenceIndex);\nstatic int make_stair_path(Object* object, int from, int fromElevation, int to, int toElevation, StraightPathNode* a6, Object** obstaclePtr);\nstatic int anim_move_to_tile(Object* obj, int tile_num, int elev, int a4, int anim, int animationSequenceIndex);\nstatic int anim_move(Object* obj, int tile, int elev, int a3, int anim, int a5, int animationSequenceIndex);\nstatic int anim_move_straight_to_tile(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex, int flags);\nstatic void object_move(int index);\nstatic void object_straight_move(int index);\nstatic int anim_animate(Object* obj, int anim, int animationSequenceIndex, int flags);\nstatic void object_anim_compact();\nstatic int anim_turn_towards(Object* obj, int delta, int animationSequenceIndex);\nstatic int check_gravity(int tile, int elevation);\n\n// 0x510718\nstatic int curr_sad = 0;\n\n// 0x51071C\nstatic int curr_anim_set = -1;\n\n// 0x510720\nstatic bool anim_in_init = false;\n\n// 0x510724\nstatic bool anim_in_anim_stop = false;\n\n// 0x510728\nstatic bool anim_in_bk = false;\n\n// 0x530014\nstatic AnimationSad sad[ANIMATION_SAD_LIST_CAPACITY];\n\n// 0x542FD4\nstatic PathNode dad[2000];\n\n// 0x54CC14\nstatic AnimationSequence anim_set[ANIMATION_SEQUENCE_LIST_CAPACITY];\n\n// 0x561814\nstatic unsigned char seen[5000];\n\n// 0x562B9C\nstatic PathNode child[2000];\n\n// 0x56C7DC\nstatic int curr_anim_counter;\n\n// anim_init\n// 0x413A20\nvoid anim_init()\n{\n    anim_in_init = true;\n    anim_reset();\n    anim_in_init = false;\n}\n\n// 0x413A40\nvoid anim_reset()\n{\n    if (!anim_in_init) {\n        // NOTE: Uninline.\n        anim_stop();\n    }\n\n    curr_sad = 0;\n    curr_anim_set = -1;\n\n    for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) {\n        anim_set[index].field_0 = -1000;\n        anim_set[index].flags = 0;\n    }\n}\n\n// 0x413AB8\nvoid anim_exit()\n{\n    // NOTE: Uninline.\n    anim_stop();\n}\n\n// 0x413AF4\nint register_begin(int requestOptions)\n{\n    if (curr_anim_set != -1) {\n        return -1;\n    }\n\n    if (anim_in_anim_stop) {\n        return -1;\n    }\n\n    int v1 = anim_free_slot(requestOptions);\n    if (v1 == -1) {\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[v1]);\n    animationSequence->flags |= ANIM_SEQ_ACCUMULATING;\n\n    if ((requestOptions & ANIMATION_REQUEST_RESERVED) != 0) {\n        animationSequence->flags |= ANIM_SEQ_RESERVED;\n    }\n\n    if ((requestOptions & ANIMATION_REQUEST_INSIGNIFICANT) != 0) {\n        animationSequence->flags |= ANIM_SEQ_INSIGNIFICANT;\n    }\n\n    if ((requestOptions & ANIMATION_REQUEST_NO_STAND) != 0) {\n        animationSequence->flags |= ANIM_SEQ_NO_STAND;\n    }\n\n    curr_anim_set = v1;\n\n    curr_anim_counter = 0;\n\n    return 0;\n}\n\n// 0x413B80\nstatic int anim_free_slot(int requestOptions)\n{\n    int v1 = -1;\n    int v2 = 0;\n    for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) {\n        AnimationSequence* animationSequence = &(anim_set[index]);\n        if (animationSequence->field_0 != -1000 || (animationSequence->flags & ANIM_SEQ_ACCUMULATING) != 0 || (animationSequence->flags & ANIM_SEQ_0x20) != 0) {\n            if (!(animationSequence->flags & ANIM_SEQ_RESERVED)) {\n                v2++;\n            }\n        } else if (v1 == -1 && ((requestOptions & ANIMATION_REQUEST_0x100) == 0 || (animationSequence->flags & ANIM_SEQ_0x10) == 0)) {\n            v1 = index;\n        }\n    }\n\n    if (v1 == -1) {\n        if ((requestOptions & ANIMATION_REQUEST_RESERVED) != 0) {\n            debug_printf(\"Unable to begin reserved animation!\\n\");\n        }\n\n        return -1;\n    } else if ((requestOptions & ANIMATION_REQUEST_RESERVED) != 0 || v2 < 20) {\n        return v1;\n    }\n\n    return -1;\n}\n\n// 0x413C20\nint register_priority(int a1)\n{\n    if (curr_anim_set == -1) {\n        return -1;\n    }\n\n    if (a1 == 0) {\n        return -1;\n    }\n\n    anim_set[curr_anim_set].flags |= ANIM_SEQ_PRIORITIZED;\n\n    return 0;\n}\n\n// 0x413C4C\nint register_clear(Object* a1)\n{\n    for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) {\n        AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]);\n        if (animationSequence->field_0 == -1000) {\n            continue;\n        }\n\n        int animationDescriptionIndex;\n        for (animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) {\n            AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]);\n            if (a1 != animationDescription->owner || animationDescription->kind == 11) {\n                continue;\n            }\n\n            break;\n        }\n\n        if (animationDescriptionIndex == animationSequence->length) {\n            continue;\n        }\n\n        if ((animationSequence->flags & ANIM_SEQ_PRIORITIZED) != 0) {\n            return -2;\n        }\n\n        anim_set_end(animationSequenceIndex);\n\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x413CCC\nint register_end()\n{\n    if (curr_anim_set == -1) {\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    animationSequence->field_0 = 0;\n    animationSequence->length = curr_anim_counter;\n    animationSequence->animationIndex = -1;\n    animationSequence->flags &= ~ANIM_SEQ_ACCUMULATING;\n    animationSequence->animations[0].delay = 0;\n    if (isInCombat()) {\n        combat_anim_begin();\n        animationSequence->flags |= ANIM_SEQ_COMBAT_ANIM_STARTED;\n    }\n\n    int v1 = curr_anim_set;\n    curr_anim_set = -1;\n\n    if (!(animationSequence->flags & ANIM_SEQ_0x10)) {\n        anim_set_continue(v1, 1);\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x413D6C\nstatic int anim_preload(Object* object, int fid, CacheEntry** cacheEntryPtr)\n{\n    *cacheEntryPtr = NULL;\n\n    if (art_ptr_lock(fid, cacheEntryPtr) != NULL) {\n        art_ptr_unlock(*cacheEntryPtr);\n        *cacheEntryPtr = NULL;\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x413D98\nstatic void anim_cleanup()\n{\n    if (curr_anim_set == -1) {\n        return;\n    }\n\n    for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) {\n        anim_set[index].flags &= ~(ANIM_SEQ_ACCUMULATING | ANIM_SEQ_0x10);\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    for (int index = 0; index < curr_anim_counter; index++) {\n        AnimationDescription* animationDescription = &(animationSequence->animations[index]);\n        if (animationDescription->artCacheKey != NULL) {\n            art_ptr_unlock(animationDescription->artCacheKey);\n        }\n\n        if (animationDescription->kind == ANIM_KIND_CALLBACK && animationDescription->callback == gsnd_anim_sound) {\n            gsound_delete_sfx(animationDescription->param1);\n        }\n    }\n\n    curr_anim_set = -1;\n}\n\n// 0x413E2C\nint check_registry(Object* obj)\n{\n    if (curr_anim_set == -1) {\n        return -1;\n    }\n\n    if (curr_anim_counter >= ANIMATION_DESCRIPTION_LIST_CAPACITY) {\n        return -1;\n    }\n\n    if (obj == NULL) {\n        return 0;\n    }\n\n    for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) {\n        AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]);\n\n        if (animationSequenceIndex != curr_anim_set && animationSequence->field_0 != -1000) {\n            for (int animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) {\n                AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]);\n                if (obj == animationDescription->owner && animationDescription->kind != 11) {\n                    if ((animationSequence->flags & ANIM_SEQ_INSIGNIFICANT) == 0) {\n                        return -1;\n                    }\n\n                    anim_set_end(animationSequenceIndex);\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\n// Returns -1 if object is playing some animation.\n//\n// 0x413EC8\nint anim_busy(Object* a1)\n{\n    if (curr_anim_counter >= ANIMATION_DESCRIPTION_LIST_CAPACITY || a1 == NULL) {\n        return 0;\n    }\n\n    for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) {\n        AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]);\n        if (animationSequenceIndex != curr_anim_set && animationSequence->field_0 != -1000) {\n            for (int animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) {\n                AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]);\n                if (a1 != animationDescription->owner) {\n                    continue;\n                }\n\n                if (animationDescription->kind == ANIM_KIND_CALLBACK) {\n                    continue;\n                }\n\n                if (animationSequence->length == 1 && animationDescription->anim == ANIM_STAND) {\n                    continue;\n                }\n\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x413F5C\nint register_object_move_to_object(Object* owner, Object* destination, int actionPoints, int delay)\n{\n    if (check_registry(owner) == -1 || actionPoints == 0) {\n        anim_cleanup();\n        return -1;\n    }\n\n    if (owner->tile == destination->tile && owner->elevation == destination->elevation) {\n        return 0;\n    }\n\n    AnimationDescription* animationDescription = &(anim_set[curr_anim_set].animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_MOVE_TO_OBJECT;\n    animationDescription->anim = ANIM_WALK;\n    animationDescription->owner = owner;\n    animationDescription->destination = destination;\n    animationDescription->actionPoints = actionPoints;\n    animationDescription->delay = delay;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return register_object_turn_towards(owner, destination->tile);\n}\n\n// 0x41405C\nint register_object_run_to_object(Object* owner, Object* destination, int actionPoints, int delay)\n{\n    if (check_registry(owner) == -1 || actionPoints == 0) {\n        anim_cleanup();\n        return -1;\n    }\n\n    if (owner->tile == destination->tile && owner->elevation == destination->elevation) {\n        return 0;\n    }\n\n    if (critterIsOverloaded(owner)) {\n        if (isPartyMember(owner)) {\n            char formattedText[92];\n            MessageListItem messageListItem;\n\n            if (owner == obj_dude) {\n                // You are overloaded.\n                strcpy(formattedText, getmsg(&misc_message_file, &messageListItem, 8000));\n            } else {\n                // %s is overloaded.\n                sprintf(formattedText,\n                    getmsg(&misc_message_file, &messageListItem, 8001),\n                    critter_name(owner));\n            }\n            display_print(formattedText);\n        }\n        return register_object_move_to_object(owner, destination, actionPoints, delay);\n    }\n\n    AnimationDescription* animationDescription = &(anim_set[curr_anim_set].animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_MOVE_TO_OBJECT;\n    animationDescription->owner = owner;\n    animationDescription->destination = destination;\n\n    if ((FID_TYPE(owner->fid) == OBJ_TYPE_CRITTER && (owner->data.critter.combat.results & DAM_CRIP_LEG_ANY) != 0)\n        || (owner == obj_dude && is_pc_flag(0) && !perk_level(obj_dude, PERK_SILENT_RUNNING))\n        || (!art_exists(art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, ANIM_RUNNING, 0, owner->rotation + 1)))) {\n        animationDescription->anim = ANIM_WALK;\n    } else {\n        animationDescription->anim = ANIM_RUNNING;\n    }\n\n    animationDescription->actionPoints = actionPoints;\n    animationDescription->delay = delay;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n    return register_object_turn_towards(owner, destination->tile);\n}\n\n// 0x414294\nint register_object_move_to_tile(Object* owner, int tile, int elevation, int actionPoints, int delay)\n{\n    if (check_registry(owner) == -1 || actionPoints == 0) {\n        anim_cleanup();\n        return -1;\n    }\n\n    if (tile == owner->tile && elevation == owner->elevation) {\n        return 0;\n    }\n\n    AnimationDescription* animationDescription = &(anim_set[curr_anim_set].animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_MOVE_TO_TILE;\n    animationDescription->anim = ANIM_WALK;\n    animationDescription->owner = owner;\n    animationDescription->tile = tile;\n    animationDescription->elevation = elevation;\n    animationDescription->actionPoints = actionPoints;\n    animationDescription->delay = delay;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414394\nint register_object_run_to_tile(Object* owner, int tile, int elevation, int actionPoints, int delay)\n{\n    if (check_registry(owner) == -1 || actionPoints == 0) {\n        anim_cleanup();\n        return -1;\n    }\n\n    if (tile == owner->tile && elevation == owner->elevation) {\n        return 0;\n    }\n\n    if (critterIsOverloaded(owner)) {\n        if (isPartyMember(owner)) {\n            MessageListItem messageListItem;\n            char formattedText[72];\n\n            if (owner == obj_dude) {\n                // You are overloaded.\n                strcpy(formattedText, getmsg(&misc_message_file, &messageListItem, 8000));\n            } else {\n                // %s is overloaded.\n                sprintf(formattedText,\n                    getmsg(&misc_message_file, &messageListItem, 8001),\n                    critter_name(owner));\n            }\n\n            display_print(formattedText);\n        }\n\n        return register_object_move_to_tile(owner, tile, elevation, actionPoints, delay);\n    }\n\n    AnimationDescription* animationDescription = &(anim_set[curr_anim_set].animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_MOVE_TO_TILE;\n    animationDescription->owner = owner;\n    animationDescription->tile = tile;\n    animationDescription->elevation = elevation;\n\n    if ((FID_TYPE(owner->fid) == OBJ_TYPE_CRITTER && (owner->data.critter.combat.results & DAM_CRIP_LEG_ANY) != 0)\n        || (owner == obj_dude && is_pc_flag(0) && !perk_level(obj_dude, PERK_SILENT_RUNNING))\n        || (!art_exists(art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, ANIM_RUNNING, 0, owner->rotation + 1)))) {\n        animationDescription->anim = ANIM_WALK;\n    } else {\n        animationDescription->anim = ANIM_RUNNING;\n    }\n\n    animationDescription->actionPoints = actionPoints;\n    animationDescription->delay = delay;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x4145D0\nint register_object_move_straight_to_tile(Object* object, int tile, int elevation, int anim, int delay)\n{\n    if (check_registry(object) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    if (tile == object->tile && elevation == object->elevation) {\n        return 0;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_MOVE_TO_TILE_STRAIGHT;\n    animationDescription->owner = object;\n    animationDescription->tile = tile;\n    animationDescription->elevation = elevation;\n    animationDescription->anim = anim;\n    animationDescription->delay = delay;\n\n    int fid = art_id(FID_TYPE(object->fid), object->fid & 0xFFF, animationDescription->anim, (object->fid & 0xF000) >> 12, object->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(object, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x4146C4\nint register_object_animate_and_move_straight(Object* owner, int tile, int elevation, int anim, int delay)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    if (tile == owner->tile && elevation == owner->elevation) {\n        return 0;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_MOVE_TO_TILE_STRAIGHT_AND_WAIT_FOR_COMPLETE;\n    animationDescription->owner = owner;\n    animationDescription->tile = tile;\n    animationDescription->elevation = elevation;\n    animationDescription->anim = anim;\n    animationDescription->delay = delay;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4147B\nint register_object_move_on_stairs(Object* owner, Object* stairs, int delay)\n{\n    int anim;\n    int destTile;\n    int destElevation;\n    AnimationSequence* animationSequence;\n    AnimationDescription* animationDescription;\n    int fid;\n\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    if (owner->elevation == stairs->elevation) {\n        anim = ANIM_UP_STAIRS_LEFT;\n        destTile = stairs->tile + 4;\n        destElevation = stairs->elevation + 1;\n    } else {\n        anim = ANIM_DOWN_STAIRS_RIGHT;\n        destTile = stairs->tile + 200;\n        destElevation = stairs->elevation;\n    }\n\n    if (destTile == owner->tile && destElevation == owner->elevation) {\n        return 0;\n    }\n\n    animationSequence = &(anim_set[curr_anim_set]);\n    animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_MOVE_ON_STAIRS;\n    animationDescription->owner = owner;\n    animationDescription->tile = destTile;\n    animationDescription->elevation = destElevation;\n    animationDescription->anim = anim;\n    animationDescription->delay = delay;\n\n    fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4148F0\nint register_object_check_falling(Object* owner, int delay)\n{\n    AnimationSequence* animationSequence;\n    AnimationDescription* animationDescription;\n    int fid;\n\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    animationSequence = &(anim_set[curr_anim_set]);\n    animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_CHECK_FALLING;\n    animationDescription->anim = ANIM_FALLING;\n    animationDescription->owner = owner;\n    animationDescription->delay = delay;\n\n    fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x4149D0\nint register_object_animate(Object* owner, int anim, int delay)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_ANIMATE;\n    animationDescription->owner = owner;\n    animationDescription->anim = anim;\n    animationDescription->delay = delay;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414AA8\nint register_object_animate_reverse(Object* owner, int anim, int delay)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_ANIMATE_REVERSED;\n    animationDescription->owner = owner;\n    animationDescription->anim = anim;\n    animationDescription->delay = delay;\n    animationDescription->artCacheKey = NULL;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414B7C\nint register_object_animate_and_hide(Object* owner, int anim, int delay)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_ANIMATE_AND_HIDE;\n    animationDescription->owner = owner;\n    animationDescription->anim = anim;\n    animationDescription->delay = delay;\n    animationDescription->artCacheKey = NULL;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414C50\nint register_object_turn_towards(Object* owner, int tile)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_ROTATE_TO_TILE;\n    animationDescription->delay = -1;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->owner = owner;\n    animationDescription->tile = tile;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414CC8\nint register_object_inc_rotation(Object* owner)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_ROTATE_CLOCKWISE;\n    animationDescription->delay = -1;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->owner = owner;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414D38\nint register_object_dec_rotation(Object* owner)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_ROTATE_COUNTER_CLOCKWISE;\n    animationDescription->delay = -1;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->owner = owner;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x414DA8\nint register_object_erase(Object* object)\n{\n    if (check_registry(object) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_HIDE;\n    animationDescription->delay = -1;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->extendedFlags = 0;\n    animationDescription->owner = object;\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414E20\nint register_object_must_erase(Object* object)\n{\n    if (check_registry(object) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_HIDE;\n    animationDescription->delay = -1;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->extendedFlags = ANIMATION_SEQUENCE_FORCED;\n    animationDescription->owner = object;\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414E98\nint register_object_call(void* a1, void* a2, AnimationCallback* proc, int delay)\n{\n    if (check_registry(NULL) == -1 || proc == NULL) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_CALLBACK;\n    animationDescription->extendedFlags = 0;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->param2 = a2;\n    animationDescription->param1 = a1;\n    animationDescription->callback = proc;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// Same as `register_object_call` but accepting 3 parameters.\n//\n// 0x414F20\nint register_object_call3(void* a1, void* a2, void* a3, AnimationCallback3* proc, int delay)\n{\n    if (check_registry(NULL) == -1 || proc == NULL) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_CALLBACK3;\n    animationDescription->extendedFlags = 0;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->param2 = a2;\n    animationDescription->param1 = a1;\n    animationDescription->callback3 = proc;\n    animationDescription->param3 = a3;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x414FAC\nint register_object_must_call(void* a1, void* a2, AnimationCallback* proc, int delay)\n{\n    if (check_registry(NULL) == -1 || proc == NULL) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_CALLBACK;\n    animationDescription->extendedFlags = ANIMATION_SEQUENCE_FORCED;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->param2 = a2;\n    animationDescription->param1 = a1;\n    animationDescription->callback = proc;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// The [flag] parameter should be one of OBJECT_* flags. The way it's handled\n// down the road implies it should not be a group of flags (joined with bitwise\n// OR), but a one particular flag.\n//\n// 0x415034\nint register_object_fset(Object* object, int flag, int delay)\n{\n    if (check_registry(object) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_SET_FLAG;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->owner = object;\n    animationDescription->objectFlag = flag;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// The [flag] parameter should be one of OBJECT_* flags. The way it's handled\n// down the road implies it should not be a group of flags (joined with bitwise\n// OR), but a one particular flag.\n//\n// 0x4150A8\nint register_object_funset(Object* object, int flag, int delay)\n{\n    if (check_registry(object) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_UNSET_FLAG;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->owner = object;\n    animationDescription->objectFlag = flag;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x41518C\nint register_object_change_fid(Object* owner, int fid, int delay)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_SET_FID;\n    animationDescription->owner = owner;\n    animationDescription->fid = fid;\n    animationDescription->delay = delay;\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x415238\nint register_object_take_out(Object* owner, int weaponAnimationCode, int delay)\n{\n    const char* sfx = gsnd_build_character_sfx_name(owner, ANIM_TAKE_OUT, weaponAnimationCode);\n    if (register_object_play_sfx(owner, sfx, delay) == -1) {\n        return -1;\n    }\n\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_TAKE_OUT_WEAPON;\n    animationDescription->anim = ANIM_TAKE_OUT;\n    animationDescription->delay = 0;\n    animationDescription->owner = owner;\n    animationDescription->weaponAnimationCode = weaponAnimationCode;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, ANIM_TAKE_OUT, weaponAnimationCode, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x415334\nint register_object_light(Object* owner, int lightDistance, int delay)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_SET_LIGHT_DISTANCE;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->owner = owner;\n    animationDescription->lightDistance = lightDistance;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4153A8\nint register_object_outline(Object* object, bool outline, int delay)\n{\n    if (check_registry(object) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_TOGGLE_OUTLINE;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->owner = object;\n    animationDescription->outline = outline;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x41541C\nint register_object_play_sfx(Object* owner, const char* soundEffectName, int delay)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_CALLBACK;\n    animationDescription->owner = owner;\n    if (soundEffectName != NULL) {\n        int volume = gsound_compute_relative_volume(owner);\n        animationDescription->param1 = gsound_load_sound_volume(soundEffectName, owner, volume);\n        if (animationDescription->param1 != NULL) {\n            animationDescription->callback = gsnd_anim_sound;\n        } else {\n            animationDescription->kind = ANIM_KIND_NOOP;\n        }\n    } else {\n        animationDescription->kind = ANIM_KIND_NOOP;\n    }\n\n    animationDescription->artCacheKey = NULL;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x4154C4\nint register_object_animate_forever(Object* owner, int anim, int delay)\n{\n    if (check_registry(owner) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->kind = ANIM_KIND_ANIMATE_FOREVER;\n    animationDescription->owner = owner;\n    animationDescription->anim = anim;\n    animationDescription->delay = delay;\n\n    int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1);\n\n    // NOTE: Uninline.\n    if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x415598\nint register_ping(int a1, int delay)\n{\n    if (check_registry(NULL) == -1) {\n        anim_cleanup();\n        return -1;\n    }\n\n    int animationSequenceIndex = anim_free_slot(a1 | ANIMATION_REQUEST_0x100);\n    if (animationSequenceIndex == -1) {\n        return -1;\n    }\n\n    anim_set[animationSequenceIndex].flags = ANIM_SEQ_0x10;\n\n    AnimationSequence* animationSequence = &(anim_set[curr_anim_set]);\n    AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]);\n    animationDescription->owner = NULL;\n    animationDescription->kind = ANIM_KIND_26;\n    animationDescription->artCacheKey = NULL;\n    animationDescription->animationSequenceIndex = animationSequenceIndex;\n    animationDescription->delay = delay;\n\n    curr_anim_counter++;\n\n    return 0;\n}\n\n// 0x4156A8\nstatic int anim_set_check(int animationSequenceIndex)\n{\n    if (animationSequenceIndex == -1) {\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]);\n    if (animationSequence->field_0 == -1000) {\n        return -1;\n    }\n\n    while (1) {\n        if (animationSequence->field_0 >= animationSequence->length) {\n            return 0;\n        }\n\n        if (animationSequence->field_0 > animationSequence->animationIndex) {\n            AnimationDescription* animationDescription = &(animationSequence->animations[animationSequence->field_0]);\n            if (animationDescription->delay < 0) {\n                return 0;\n            }\n\n            if (animationDescription->delay > 0) {\n                animationDescription->delay--;\n                return 0;\n            }\n        }\n\n        AnimationDescription* animationDescription = &(animationSequence->animations[animationSequence->field_0++]);\n\n        int rc;\n        Rect rect;\n        switch (animationDescription->kind) {\n        case ANIM_KIND_MOVE_TO_OBJECT:\n            rc = anim_move_to_object(animationDescription->owner, animationDescription->destination, animationDescription->actionPoints, animationDescription->anim, animationSequenceIndex);\n            break;\n        case ANIM_KIND_MOVE_TO_TILE:\n            rc = anim_move_to_tile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->actionPoints, animationDescription->anim, animationSequenceIndex);\n            break;\n        case ANIM_KIND_MOVE_TO_TILE_STRAIGHT:\n            rc = anim_move_straight_to_tile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex, 0);\n            break;\n        case ANIM_KIND_MOVE_TO_TILE_STRAIGHT_AND_WAIT_FOR_COMPLETE:\n            rc = anim_move_straight_to_tile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex, ANIM_SAD_WAIT_FOR_COMPLETION);\n            break;\n        case ANIM_KIND_ANIMATE:\n            rc = anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, 0);\n            break;\n        case ANIM_KIND_ANIMATE_REVERSED:\n            rc = anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, ANIM_SAD_REVERSE);\n            break;\n        case ANIM_KIND_ANIMATE_AND_HIDE:\n            rc = anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, ANIM_SAD_HIDE_ON_END);\n            if (rc == -1) {\n                // NOTE: Uninline.\n                rc = anim_hide(animationDescription->owner, animationSequenceIndex);\n            }\n            break;\n        case ANIM_KIND_ANIMATE_FOREVER:\n            rc = anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, ANIM_SAD_FOREVER);\n            break;\n        case ANIM_KIND_ROTATE_TO_TILE:\n            if (!critter_is_prone(animationDescription->owner)) {\n                int rotation = tile_dir(animationDescription->owner->tile, animationDescription->tile);\n                dude_stand(animationDescription->owner, rotation, -1);\n            }\n            anim_set_continue(animationSequenceIndex, 0);\n            rc = 0;\n            break;\n        case ANIM_KIND_ROTATE_CLOCKWISE:\n            rc = anim_turn_towards(animationDescription->owner, 1, animationSequenceIndex);\n            break;\n        case ANIM_KIND_ROTATE_COUNTER_CLOCKWISE:\n            rc = anim_turn_towards(animationDescription->owner, -1, animationSequenceIndex);\n            break;\n        case ANIM_KIND_HIDE:\n            // NOTE: Uninline.\n            rc = anim_hide(animationDescription->owner, animationSequenceIndex);\n            break;\n        case ANIM_KIND_CALLBACK:\n            rc = animationDescription->callback(animationDescription->param1, animationDescription->param2);\n            if (rc == 0) {\n                rc = anim_set_continue(animationSequenceIndex, 0);\n            }\n            break;\n        case ANIM_KIND_CALLBACK3:\n            rc = animationDescription->callback3(animationDescription->param1, animationDescription->param2, animationDescription->param3);\n            if (rc == 0) {\n                rc = anim_set_continue(animationSequenceIndex, 0);\n            }\n            break;\n        case ANIM_KIND_SET_FLAG:\n            if (animationDescription->objectFlag == OBJECT_LIGHTING) {\n                if (obj_turn_on_light(animationDescription->owner, &rect) == 0) {\n                    tile_refresh_rect(&rect, animationDescription->owner->elevation);\n                }\n            } else if (animationDescription->objectFlag == OBJECT_HIDDEN) {\n                if (obj_turn_off(animationDescription->owner, &rect) == 0) {\n                    tile_refresh_rect(&rect, animationDescription->owner->elevation);\n                }\n            } else {\n                animationDescription->owner->flags |= animationDescription->objectFlag;\n            }\n\n            rc = anim_set_continue(animationSequenceIndex, 0);\n            break;\n        case ANIM_KIND_UNSET_FLAG:\n            if (animationDescription->objectFlag == OBJECT_LIGHTING) {\n                if (obj_turn_off_light(animationDescription->owner, &rect) == 0) {\n                    tile_refresh_rect(&rect, animationDescription->owner->elevation);\n                }\n            } else if (animationDescription->objectFlag == OBJECT_HIDDEN) {\n                if (obj_turn_on(animationDescription->owner, &rect) == 0) {\n                    tile_refresh_rect(&rect, animationDescription->owner->elevation);\n                }\n            } else {\n                animationDescription->owner->flags &= ~animationDescription->objectFlag;\n            }\n\n            rc = anim_set_continue(animationSequenceIndex, 0);\n            break;\n        case ANIM_KIND_TOGGLE_FLAT:\n            if (obj_toggle_flat(animationDescription->owner, &rect) == 0) {\n                tile_refresh_rect(&rect, animationDescription->owner->elevation);\n            }\n            rc = anim_set_continue(animationSequenceIndex, 0);\n            break;\n        case ANIM_KIND_SET_FID:\n            rc = anim_change_fid(animationDescription->owner, animationSequenceIndex, animationDescription->fid);\n            break;\n        case ANIM_KIND_TAKE_OUT_WEAPON:\n            rc = anim_animate(animationDescription->owner, ANIM_TAKE_OUT, animationSequenceIndex, animationDescription->tile);\n            break;\n        case ANIM_KIND_SET_LIGHT_DISTANCE:\n            obj_set_light(animationDescription->owner, animationDescription->lightDistance, animationDescription->owner->lightIntensity, &rect);\n            tile_refresh_rect(&rect, animationDescription->owner->elevation);\n            rc = anim_set_continue(animationSequenceIndex, 0);\n            break;\n        case ANIM_KIND_MOVE_ON_STAIRS:\n            rc = anim_move_on_stairs(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex);\n            break;\n        case ANIM_KIND_CHECK_FALLING:\n            rc = check_for_falling(animationDescription->owner, animationDescription->anim, animationSequenceIndex);\n            break;\n        case ANIM_KIND_TOGGLE_OUTLINE:\n            if (animationDescription->outline) {\n                if (obj_turn_on_outline(animationDescription->owner, &rect) == 0) {\n                    tile_refresh_rect(&rect, animationDescription->owner->elevation);\n                }\n            } else {\n                if (obj_turn_off_outline(animationDescription->owner, &rect) == 0) {\n                    tile_refresh_rect(&rect, animationDescription->owner->elevation);\n                }\n            }\n            rc = anim_set_continue(animationSequenceIndex, 0);\n            break;\n        case ANIM_KIND_26:\n            anim_set[animationDescription->animationSequenceIndex].flags &= ~ANIM_SEQ_0x10;\n            rc = anim_set_continue(animationDescription->animationSequenceIndex, 1);\n            if (rc != -1) {\n                rc = anim_set_continue(animationSequenceIndex, 0);\n            }\n            break;\n        case ANIM_KIND_NOOP:\n            rc = anim_set_continue(animationSequenceIndex, 0);\n            break;\n        default:\n            rc = -1;\n            break;\n        }\n\n        if (rc == -1) {\n            anim_set_end(animationSequenceIndex);\n        }\n\n        if (animationSequence->field_0 == -1000) {\n            return -1;\n        }\n    }\n}\n\n// 0x415B44\nstatic int anim_set_continue(int animationSequenceIndex, int a2)\n{\n    if (animationSequenceIndex == -1) {\n        return -1;\n    }\n\n    AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]);\n    if (animationSequence->field_0 == -1000) {\n        return -1;\n    }\n\n    animationSequence->animationIndex++;\n    if (animationSequence->animationIndex == animationSequence->length) {\n        return anim_set_end(animationSequenceIndex);\n    } else {\n        if (a2) {\n            return anim_set_check(animationSequenceIndex);\n        }\n    }\n\n    return 0;\n}\n\n// 0x415B9C\nstatic int anim_set_end(int animationSequenceIndex)\n{\n    AnimationSequence* animationSequence;\n    AnimationDescription* animationDescription;\n    int i;\n    Rect v27;\n\n    if (animationSequenceIndex == -1) {\n        return -1;\n    }\n\n    animationSequence = &(anim_set[animationSequenceIndex]);\n    if (animationSequence->field_0 == -1000) {\n        return -1;\n    }\n\n    for (i = 0; i < curr_sad; i++) {\n        AnimationSad* sad_entry = &(sad[i]);\n        if (sad_entry->animationSequenceIndex == animationSequenceIndex) {\n            sad_entry->field_20 = -1000;\n        }\n    }\n\n    for (i = 0; i < animationSequence->length; i++) {\n        animationDescription = &(animationSequence->animations[i]);\n        if (animationDescription->kind == ANIM_KIND_HIDE && ((i < animationSequence->animationIndex) || (animationDescription->extendedFlags & ANIMATION_SEQUENCE_FORCED))) {\n            obj_erase_object(animationDescription->owner, &v27);\n            tile_refresh_rect(&v27, animationDescription->owner->elevation);\n        }\n    }\n\n    for (i = 0; i < animationSequence->length; i++) {\n        animationDescription = &(animationSequence->animations[i]);\n        if (animationDescription->artCacheKey) {\n            art_ptr_unlock(animationDescription->artCacheKey);\n        }\n\n        if (animationDescription->kind != 11 && animationDescription->kind != 12) {\n            // TODO: Check.\n            if (animationDescription->kind != ANIM_KIND_26) {\n                Object* owner = animationDescription->owner;\n                if (FID_TYPE(owner->fid) == OBJ_TYPE_CRITTER) {\n                    int j = 0;\n                    for (; j < i; j++) {\n                        AnimationDescription* ad = &(animationSequence->animations[j]);\n                        if (owner == ad->owner) {\n                            if (ad->kind != ANIM_KIND_CALLBACK && ad->kind != ANIM_KIND_CALLBACK3) {\n                                break;\n                            }\n                        }\n                    }\n\n                    if (i == j) {\n                        int k = 0;\n                        for (; k < animationSequence->animationIndex; k++) {\n                            AnimationDescription* ad = &(animationSequence->animations[k]);\n                            if (ad->kind == ANIM_KIND_HIDE && ad->owner == owner) {\n                                break;\n                            }\n                        }\n\n                        if (k == animationSequence->animationIndex) {\n                            for (int m = 0; m < curr_sad; m++) {\n                                if (sad[m].obj == owner) {\n                                    sad[m].field_20 = -1000;\n                                    break;\n                                }\n                            }\n\n                            if ((animationSequence->flags & ANIM_SEQ_NO_STAND) == 0 && !critter_is_prone(owner)) {\n                                dude_stand(owner, owner->rotation, -1);\n                            }\n                        }\n                    }\n                }\n            }\n        } else if (i >= animationSequence->field_0) {\n            if (animationDescription->extendedFlags & ANIMATION_SEQUENCE_FORCED) {\n                animationDescription->callback(animationDescription->param1, animationDescription->param2);\n            } else {\n                if (animationDescription->kind == ANIM_KIND_CALLBACK && animationDescription->callback == gsnd_anim_sound) {\n                    gsound_delete_sfx(animationDescription->param1);\n                }\n            }\n        }\n    }\n\n    animationSequence->animationIndex = -1;\n    animationSequence->field_0 = -1000;\n    if ((animationSequence->flags & ANIM_SEQ_COMBAT_ANIM_STARTED) != 0) {\n        combat_anim_finished();\n    }\n\n    if (anim_in_bk) {\n        animationSequence->flags = ANIM_SEQ_0x20;\n    } else {\n        animationSequence->flags = 0;\n    }\n\n    return 0;\n}\n\n// 0x415E24\nstatic bool anim_can_use_door(Object* critter, Object* door)\n{\n    if (critter == obj_dude) {\n        if (!obj_portal_is_walk_thru(door)) {\n            return false;\n        }\n    }\n\n    if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (FID_TYPE(door->fid) != OBJ_TYPE_SCENERY) {\n        return false;\n    }\n\n    int bodyType = critter_body_type(critter);\n    if (bodyType != BODY_TYPE_BIPED && bodyType != BODY_TYPE_ROBOTIC) {\n        return false;\n    }\n\n    Proto* proto;\n    if (proto_ptr(door->pid, &proto) == -1) {\n        return false;\n    }\n\n    if (proto->scenery.type != SCENERY_TYPE_DOOR) {\n        return false;\n    }\n\n    if (obj_is_locked(door)) {\n        return false;\n    }\n\n    if (critterGetKillType(critter) == KILL_TYPE_GECKO) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x415EE8\nint make_path(Object* object, int from, int to, unsigned char* rotations, int a5)\n{\n    return make_path_func(object, from, to, rotations, a5, obj_blocking_at);\n}\n\n// 0x415EFC\nint make_path_func(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback)\n{\n    if (a5) {\n        if (callback(object, to, object->elevation) != NULL) {\n            return 0;\n        }\n    }\n\n    bool isCritter = false;\n    int kt = 0;\n    if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n        isCritter = true;\n        kt = critterGetKillType(object);\n    }\n\n    bool isNotInCombat = !isInCombat();\n\n    memset(seen, 0, sizeof(seen));\n\n    seen[from / 8] |= 1 << (from & 7);\n\n    child[0].tile = from;\n    child[0].from = -1;\n    child[0].rotation = 0;\n    child[0].field_C = EST(from, to);\n    child[0].field_10 = 0;\n\n    for (int index = 1; index < 2000; index += 1) {\n        child[index].tile = -1;\n    }\n\n    int toScreenX;\n    int toScreenY;\n    tile_coord(to, &toScreenX, &toScreenY, object->elevation);\n\n    int closedPathNodeListLength = 0;\n    int openPathNodeListLength = 1;\n    PathNode temp;\n\n    while (1) {\n        int v63 = -1;\n\n        PathNode* prev = NULL;\n        int v12 = 0;\n        for (int index = 0; v12 < openPathNodeListLength; index += 1) {\n            PathNode* curr = &(child[index]);\n            if (curr->tile != -1) {\n                v12++;\n                if (v63 == -1 || (curr->field_C + curr->field_10) < (prev->field_C + prev->field_10)) {\n                    prev = curr;\n                    v63 = index;\n                }\n            }\n        }\n\n        PathNode* curr = &(child[v63]);\n\n        memcpy(&temp, curr, sizeof(temp));\n\n        openPathNodeListLength -= 1;\n\n        curr->tile = -1;\n\n        if (temp.tile == to) {\n            if (openPathNodeListLength == 0) {\n                openPathNodeListLength = 1;\n            }\n            break;\n        }\n\n        PathNode* curr1 = &(dad[closedPathNodeListLength]);\n        memcpy(curr1, &temp, sizeof(temp));\n\n        closedPathNodeListLength += 1;\n\n        if (closedPathNodeListLength == 2000) {\n            return 0;\n        }\n\n        for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n            int tile = tile_num_in_direction(temp.tile, rotation, 1);\n            int bit = 1 << (tile & 7);\n            if ((seen[tile / 8] & bit) != 0) {\n                continue;\n            }\n\n            if (tile != to) {\n                Object* v24 = callback(object, tile, object->elevation);\n                if (v24 != NULL) {\n                    if (!anim_can_use_door(object, v24)) {\n                        continue;\n                    }\n                }\n            }\n\n            int v25 = 0;\n            for (; v25 < 2000; v25++) {\n                if (child[v25].tile == -1) {\n                    break;\n                }\n            }\n\n            openPathNodeListLength += 1;\n\n            if (openPathNodeListLength == 2000) {\n                return 0;\n            }\n\n            seen[tile / 8] |= bit;\n\n            PathNode* v27 = &(child[v25]);\n            v27->tile = tile;\n            v27->from = temp.tile;\n            v27->rotation = rotation;\n\n            int newX;\n            int newY;\n            tile_coord(tile, &newX, &newY, object->elevation);\n\n            v27->field_C = idist(newX, newY, toScreenX, toScreenY);\n            v27->field_10 = temp.field_10 + 50;\n\n            if (isNotInCombat && temp.rotation != rotation) {\n                v27->field_10 += 10;\n            }\n\n            if (isCritter) {\n                Object* o = obj_find_first_at_tile(object->elevation, v27->tile);\n                while (o != NULL) {\n                    if (o->pid >= 0x20003D9 && o->pid <= 0x20003DC) {\n                        break;\n                    }\n                    o = obj_find_next_at_tile();\n                }\n\n                if (o != NULL) {\n                    if (kt == KILL_TYPE_GECKO) {\n                        v27->field_10 += 100;\n                    } else {\n                        v27->field_10 += 400;\n                    }\n                }\n            }\n        }\n\n        if (openPathNodeListLength == 0) {\n            break;\n        }\n    }\n\n    if (openPathNodeListLength != 0) {\n        unsigned char* v39 = rotations;\n        int index = 0;\n        for (; index < 800; index++) {\n            if (temp.tile == from) {\n                break;\n            }\n\n            if (v39 != NULL) {\n                *v39 = temp.rotation & 0xFF;\n                v39 += 1;\n            }\n\n            int j = 0;\n            while (dad[j].tile != temp.from) {\n                j++;\n            }\n\n            PathNode* v36 = &(dad[j]);\n            memcpy(&temp, v36, sizeof(temp));\n        }\n\n        if (rotations != NULL) {\n            // Looks like array resevering, probably because A* finishes it's path from end to start,\n            // this probably reverses it start-to-end.\n            unsigned char* beginning = rotations;\n            unsigned char* ending = rotations + index - 1;\n            int middle = index / 2;\n            for (int index = 0; index < middle; index++) {\n                unsigned char rotation = *ending;\n                *ending = *beginning;\n                *beginning = rotation;\n\n                ending -= 1;\n                beginning += 1;\n            }\n        }\n\n        return index;\n    }\n\n    return 0;\n}\n\n// 0x41633C\nint idist(int x1, int y1, int x2, int y2)\n{\n    int dx = x2 - x1;\n    if (dx < 0) {\n        dx = -dx;\n    }\n\n    int dy = y2 - y1;\n    if (dy < 0) {\n        dy = -dy;\n    }\n\n    int dm = (dx <= dy) ? dx : dy;\n\n    return dx + dy - (dm / 2);\n}\n\n// 0x416360\nint EST(int tile1, int tile2)\n{\n    int x1;\n    int y1;\n    tile_coord(tile1, &x1, &y1, map_elevation);\n\n    int x2;\n    int y2;\n    tile_coord(tile2, &x2, &y2, map_elevation);\n\n    return idist(x1, y1, x2, y2);\n}\n\n// 0x4163AC\nint make_straight_path(Object* a1, int from, int to, StraightPathNode* pathNodes, Object** a5, int a6)\n{\n    return make_straight_path_func(a1, from, to, pathNodes, a5, a6, obj_blocking_at);\n}\n\n// TODO: Rather complex, but understandable, needs testing.\n//\n// 0x4163C8\nint make_straight_path_func(Object* a1, int from, int to, StraightPathNode* pathNodes, Object** a5, int a6, PathBuilderCallback* callback)\n{\n    if (a5 != NULL) {\n        Object* v11 = callback(a1, from, a1->elevation);\n        if (v11 != NULL) {\n            if (v11 != *a5 && (a6 != 32 || (v11->flags & OBJECT_SHOOT_THRU) == 0)) {\n                *a5 = v11;\n                return 0;\n            }\n        }\n    }\n\n    int fromX;\n    int fromY;\n    tile_coord(from, &fromX, &fromY, a1->elevation);\n    fromX += 16;\n    fromY += 8;\n\n    int toX;\n    int toY;\n    tile_coord(to, &toX, &toY, a1->elevation);\n    toX += 16;\n    toY += 8;\n\n    int stepX;\n    int deltaX = toX - fromX;\n    if (deltaX > 0)\n        stepX = 1;\n    else if (deltaX < 0)\n        stepX = -1;\n    else\n        stepX = 0;\n\n    int stepY;\n    int deltaY = toY - fromY;\n    if (deltaY > 0)\n        stepY = 1;\n    else if (deltaY < 0)\n        stepY = -1;\n    else\n        stepY = 0;\n\n    int v48 = 2 * abs(toX - fromX);\n    int v47 = 2 * abs(toY - fromY);\n\n    int tileX = fromX;\n    int tileY = fromY;\n\n    int pathNodeIndex = 0;\n    int prevTile = from;\n    int v22 = 0;\n    int tile;\n\n    if (v48 <= v47) {\n        int middle = v48 - v47 / 2;\n        while (true) {\n            tile = tile_num(tileX, tileY, a1->elevation);\n\n            v22 += 1;\n            if (v22 == a6) {\n                if (pathNodeIndex >= 200) {\n                    return 0;\n                }\n\n                if (pathNodes != NULL) {\n                    StraightPathNode* pathNode = &(pathNodes[pathNodeIndex]);\n                    pathNode->tile = tile;\n                    pathNode->elevation = a1->elevation;\n\n                    tile_coord(tile, &fromX, &fromY, a1->elevation);\n                    pathNode->x = tileX - fromX - 16;\n                    pathNode->y = tileY - fromY - 8;\n                }\n\n                v22 = 0;\n                pathNodeIndex++;\n            }\n\n            if (tileY == toY) {\n                if (a5 != NULL) {\n                    *a5 = NULL;\n                }\n                break;\n            }\n\n            if (middle >= 0) {\n                tileX += stepX;\n                middle -= v47;\n            }\n\n            tileY += stepY;\n            middle += v48;\n\n            if (tile != prevTile) {\n                if (a5 != NULL) {\n                    Object* obj = callback(a1, tile, a1->elevation);\n                    if (obj != NULL) {\n                        if (obj != *a5 && (a6 != 32 || (obj->flags & OBJECT_SHOOT_THRU) == 0)) {\n                            *a5 = obj;\n                            break;\n                        }\n                    }\n                }\n                prevTile = tile;\n            }\n        }\n    } else {\n        int middle = v47 - v48 / 2;\n        while (true) {\n            tile = tile_num(tileX, tileY, a1->elevation);\n\n            v22 += 1;\n            if (v22 == a6) {\n                if (pathNodeIndex >= 200) {\n                    return 0;\n                }\n\n                if (pathNodes != NULL) {\n                    StraightPathNode* pathNode = &(pathNodes[pathNodeIndex]);\n                    pathNode->tile = tile;\n                    pathNode->elevation = a1->elevation;\n\n                    tile_coord(tile, &fromX, &fromY, a1->elevation);\n                    pathNode->x = tileX - fromX - 16;\n                    pathNode->y = tileY - fromY - 8;\n                }\n\n                v22 = 0;\n                pathNodeIndex++;\n            }\n\n            if (tileX == toX) {\n                if (a5 != NULL) {\n                    *a5 = NULL;\n                }\n                break;\n            }\n\n            if (middle >= 0) {\n                tileY += stepY;\n                middle -= v48;\n            }\n\n            tileX += stepX;\n            middle += v47;\n\n            if (tile != prevTile) {\n                if (a5 != NULL) {\n                    Object* obj = callback(a1, tile, a1->elevation);\n                    if (obj != NULL) {\n                        if (obj != *a5 && (a6 != 32 || (obj->flags & OBJECT_SHOOT_THRU) == 0)) {\n                            *a5 = obj;\n                            break;\n                        }\n                    }\n                }\n                prevTile = tile;\n            }\n        }\n    }\n\n    if (v22 != 0) {\n        if (pathNodeIndex >= 200) {\n            return 0;\n        }\n\n        if (pathNodes != NULL) {\n            StraightPathNode* pathNode = &(pathNodes[pathNodeIndex]);\n            pathNode->tile = tile;\n            pathNode->elevation = a1->elevation;\n\n            tile_coord(tile, &fromX, &fromY, a1->elevation);\n            pathNode->x = tileX - fromX - 16;\n            pathNode->y = tileY - fromY - 8;\n        }\n\n        pathNodeIndex += 1;\n    } else {\n        if (pathNodeIndex > 0 && pathNodes != NULL) {\n            pathNodes[pathNodeIndex - 1].elevation = a1->elevation;\n        }\n    }\n\n    return pathNodeIndex;\n}\n\n// 0x4167F8\nstatic int anim_move_to_object(Object* from, Object* to, int a3, int anim, int animationSequenceIndex)\n{\n    bool hidden = (to->flags & OBJECT_HIDDEN);\n    to->flags |= OBJECT_HIDDEN;\n\n    int moveSadIndex = anim_move(from, to->tile, to->elevation, -1, anim, 0, animationSequenceIndex);\n\n    if (!hidden) {\n        to->flags &= ~OBJECT_HIDDEN;\n    }\n\n    if (moveSadIndex == -1) {\n        return -1;\n    }\n\n    AnimationSad* sad_entry = &(sad[moveSadIndex]);\n    // NOTE: Original code is somewhat different. Due to some kind of\n    // optimization this value is either 1 or 2, which is later used in\n    // subsequent calculations and rotations array lookup.\n    bool isMultihex = (from->flags & OBJECT_MULTIHEX);\n    sad_entry->field_1C -= (isMultihex ? 2 : 1);\n    if (sad_entry->field_1C <= 0) {\n        sad_entry->field_20 = -1000;\n        anim_set_continue(animationSequenceIndex, 0);\n    }\n\n    sad_entry->field_24 = tile_num_in_direction(to->tile, sad_entry->rotations[isMultihex ? sad_entry->field_1C + 1 : sad_entry->field_1C], 1);\n\n    if (isMultihex) {\n        sad_entry->field_24 = tile_num_in_direction(sad_entry->field_24, sad_entry->rotations[sad_entry->field_1C], 1);\n    }\n\n    if (a3 != -1 && a3 < sad_entry->field_1C) {\n        sad_entry->field_1C = a3;\n    }\n\n    return 0;\n}\n\n// 0x41695C\nstatic int make_stair_path(Object* object, int from, int fromElevation, int to, int toElevation, StraightPathNode* a6, Object** obstaclePtr)\n{\n    int elevation = fromElevation;\n    if (elevation > toElevation) {\n        elevation = toElevation;\n    }\n\n    int fromX;\n    int fromY;\n    tile_coord(from, &fromX, &fromY, fromElevation);\n    fromX += 16;\n    fromY += 8;\n\n    int toX;\n    int toY;\n    tile_coord(to, &toX, &toY, toElevation);\n    toX += 16;\n    toY += 8;\n\n    if (obstaclePtr != NULL) {\n        *obstaclePtr = NULL;\n    }\n\n    int ddx = 2 * abs(toX - fromX);\n\n    int stepX;\n    int deltaX = toX - fromX;\n    if (deltaX > 0) {\n        stepX = 1;\n    } else if (deltaX < 0) {\n        stepX = -1;\n    } else {\n        stepX = 0;\n    }\n\n    int ddy = 2 * abs(toY - fromY);\n\n    int stepY;\n    int deltaY = toY - fromY;\n    if (deltaY > 0) {\n        stepY = 1;\n    } else if (deltaY < 0) {\n        stepY = -1;\n    } else {\n        stepY = 0;\n    }\n\n    int tileX = fromX;\n    int tileY = fromY;\n\n    int pathNodeIndex = 0;\n    int prevTile = from;\n    int iteration = 0;\n    int tile;\n\n    if (ddx > ddy) {\n        int middle = ddy - ddx / 2;\n        while (true) {\n            tile = tile_num(tileX, tileY, elevation);\n\n            iteration += 1;\n            if (iteration == 16) {\n                if (pathNodeIndex >= 200) {\n                    return 0;\n                }\n\n                if (a6 != NULL) {\n                    StraightPathNode* pathNode = &(a6[pathNodeIndex]);\n                    pathNode->tile = tile;\n                    pathNode->elevation = elevation;\n\n                    tile_coord(tile, &fromX, &fromY, elevation);\n                    pathNode->x = tileX - fromX - 16;\n                    pathNode->y = tileY - fromY - 8;\n                }\n\n                iteration = 0;\n                pathNodeIndex++;\n            }\n\n            if (tileX == toX) {\n                break;\n            }\n\n            if (middle >= 0) {\n                tileY += stepY;\n                middle -= ddx;\n            }\n\n            tileX += stepX;\n            middle += ddy;\n\n            if (tile != prevTile) {\n                if (obstaclePtr != NULL) {\n                    *obstaclePtr = obj_blocking_at(object, tile, object->elevation);\n                    if (*obstaclePtr != NULL) {\n                        break;\n                    }\n                }\n                prevTile = tile;\n            }\n        }\n    } else {\n        int middle = ddx - ddy / 2;\n        while (true) {\n            tile = tile_num(tileX, tileY, elevation);\n\n            iteration += 1;\n            if (iteration == 16) {\n                if (pathNodeIndex >= 200) {\n                    return 0;\n                }\n\n                if (a6 != NULL) {\n                    StraightPathNode* pathNode = &(a6[pathNodeIndex]);\n                    pathNode->tile = tile;\n                    pathNode->elevation = elevation;\n\n                    tile_coord(tile, &fromX, &fromY, elevation);\n                    pathNode->x = tileX - fromX - 16;\n                    pathNode->y = tileY - fromY - 8;\n                }\n\n                iteration = 0;\n                pathNodeIndex++;\n            }\n\n            if (tileY == toY) {\n                break;\n            }\n\n            if (middle >= 0) {\n                tileX += stepX;\n                middle -= ddy;\n            }\n\n            tileY += stepY;\n            middle += ddx;\n\n            if (tile != prevTile) {\n                if (obstaclePtr != NULL) {\n                    *obstaclePtr = obj_blocking_at(object, tile, object->elevation);\n                    if (*obstaclePtr != NULL) {\n                        break;\n                    }\n                }\n                prevTile = tile;\n            }\n        }\n    }\n\n    if (iteration != 0) {\n        if (pathNodeIndex >= 200) {\n            return 0;\n        }\n\n        if (a6 != NULL) {\n            StraightPathNode* pathNode = &(a6[pathNodeIndex]);\n            pathNode->tile = tile;\n            pathNode->elevation = elevation;\n\n            tile_coord(tile, &fromX, &fromY, elevation);\n            pathNode->x = tileX - fromX - 16;\n            pathNode->y = tileY - fromY - 8;\n        }\n\n        pathNodeIndex++;\n    } else {\n        if (pathNodeIndex > 0) {\n            if (a6 != NULL) {\n                a6[pathNodeIndex - 1].elevation = toElevation;\n            }\n        }\n    }\n\n    return pathNodeIndex;\n}\n\n// 0x416CFC\nstatic int anim_move_to_tile(Object* obj, int tile, int elev, int a4, int anim, int animationSequenceIndex)\n{\n    int v1;\n\n    v1 = anim_move(obj, tile, elev, -1, anim, 0, animationSequenceIndex);\n    if (v1 == -1) {\n        return -1;\n    }\n\n    if (obj_blocking_at(obj, tile, elev)) {\n        AnimationSad* sad_entry = &(sad[v1]);\n        sad_entry->field_1C--;\n        if (sad_entry->field_1C <= 0) {\n            sad_entry->field_20 = -1000;\n            anim_set_continue(animationSequenceIndex, 0);\n        }\n\n        sad_entry->field_24 = tile_num_in_direction(tile, sad_entry->rotations[sad_entry->field_1C], 1);\n        if (a4 != -1 && a4 < sad_entry->field_1C) {\n            sad_entry->field_1C = a4;\n        }\n    }\n\n    return 0;\n}\n\n// 0x416DFC\nstatic int anim_move(Object* obj, int tile, int elev, int a3, int anim, int a5, int animationSequenceIndex)\n{\n    if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) {\n        return -1;\n    }\n\n    AnimationSad* sad_entry = &(sad[curr_sad]);\n    sad_entry->obj = obj;\n\n    if (a5) {\n        sad_entry->flags = ANIM_SAD_0x20;\n    } else {\n        sad_entry->flags = 0;\n    }\n\n    sad_entry->field_20 = -2000;\n    sad_entry->fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    sad_entry->animationTimestamp = 0;\n    sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid);\n    sad_entry->field_24 = tile;\n    sad_entry->animationSequenceIndex = animationSequenceIndex;\n    sad_entry->anim = anim;\n\n    sad_entry->field_1C = make_path(obj, obj->tile, tile, sad_entry->rotations, a5);\n    if (sad_entry->field_1C == 0) {\n        sad_entry->field_20 = -1000;\n        return -1;\n    }\n\n    if (a3 != -1 && sad_entry->field_1C > a3) {\n        sad_entry->field_1C = a3;\n    }\n\n    return curr_sad++;\n}\n\n// 0x416F54\nstatic int anim_move_straight_to_tile(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex, int flags)\n{\n    if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) {\n        return -1;\n    }\n\n    AnimationSad* sad_entry = &(sad[curr_sad]);\n    sad_entry->obj = obj;\n    sad_entry->flags = flags | ANIM_SAD_STRAIGHT;\n    if (anim == -1) {\n        sad_entry->fid = obj->fid;\n        sad_entry->flags |= ANIM_SAD_NO_ANIM;\n    } else {\n        sad_entry->fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    }\n    sad_entry->field_20 = -2000;\n    sad_entry->animationTimestamp = 0;\n    sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid);\n    sad_entry->animationSequenceIndex = animationSequenceIndex;\n\n    int v15;\n    if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) {\n        if (FID_ANIM_TYPE(obj->fid) == ANIM_JUMP_BEGIN)\n            v15 = 16;\n        else\n            v15 = 4;\n    } else {\n        v15 = 32;\n    }\n\n    sad_entry->field_1C = make_straight_path(obj, obj->tile, tile, sad_entry->field_28, NULL, v15);\n    if (sad_entry->field_1C == 0) {\n        sad_entry->field_20 = -1000;\n        return -1;\n    }\n\n    curr_sad++;\n\n    return 0;\n}\n\n// 0x41712C\nint anim_move_on_stairs(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex)\n{\n    if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) {\n        return -1;\n    }\n\n    AnimationSad* sad_entry = &(sad[curr_sad]);\n    sad_entry->flags = ANIM_SAD_STRAIGHT;\n    sad_entry->obj = obj;\n    if (anim == -1) {\n        sad_entry->fid = obj->fid;\n        sad_entry->flags |= ANIM_SAD_NO_ANIM;\n    } else {\n        sad_entry->fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    }\n    sad_entry->field_20 = -2000;\n    sad_entry->animationTimestamp = 0;\n    sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid);\n    sad_entry->animationSequenceIndex = animationSequenceIndex;\n    sad_entry->field_1C = make_stair_path(obj, obj->tile, obj->elevation, tile, elevation, sad_entry->field_28, NULL);\n    if (sad_entry->field_1C == 0) {\n        sad_entry->field_20 = -1000;\n        return -1;\n    }\n\n    curr_sad++;\n\n    return 0;\n}\n\n// 0x417248\nint check_for_falling(Object* obj, int anim, int a3)\n{\n    if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) {\n        return -1;\n    }\n\n    if (check_gravity(obj->tile, obj->elevation) == obj->elevation) {\n        return -1;\n    }\n\n    AnimationSad* sad_entry = &(sad[curr_sad]);\n    sad_entry->flags = ANIM_SAD_STRAIGHT;\n    sad_entry->obj = obj;\n    if (anim == -1) {\n        sad_entry->fid = obj->fid;\n        sad_entry->flags |= ANIM_SAD_NO_ANIM;\n    } else {\n        sad_entry->fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    }\n    sad_entry->field_20 = -2000;\n    sad_entry->animationTimestamp = 0;\n    sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid);\n    sad_entry->animationSequenceIndex = a3;\n    sad_entry->field_1C = make_straight_path_func(obj, obj->tile, obj->tile, sad_entry->field_28, 0, 16, obj_blocking_at);\n    if (sad_entry->field_1C == 0) {\n        sad_entry->field_20 = -1000;\n        return -1;\n    }\n\n    curr_sad++;\n\n    return 0;\n}\n\n// 0x417360\nstatic void object_move(int index)\n{\n    AnimationSad* sad_entry = &(sad[index]);\n    Object* object = sad_entry->obj;\n\n    Rect dirty;\n    Rect temp;\n\n    if (sad_entry->field_20 == -2000) {\n        obj_move_to_tile(object, object->tile, object->elevation, &dirty);\n\n        obj_set_frame(object, 0, &temp);\n        rect_min_bound(&dirty, &temp, &dirty);\n\n        obj_set_rotation(object, sad_entry->rotations[0], &temp);\n        rect_min_bound(&dirty, &temp, &dirty);\n\n        int fid = art_id(FID_TYPE(object->fid), object->fid & 0xFFF, sad_entry->anim, (object->fid & 0xF000) >> 12, object->rotation + 1);\n        obj_change_fid(object, fid, &temp);\n        rect_min_bound(&dirty, &temp, &dirty);\n\n        sad_entry->field_20 = 0;\n    } else {\n        obj_inc_frame(object, &dirty);\n    }\n\n    int frameX;\n    int frameY;\n\n    CacheEntry* cacheHandle;\n    Art* art = art_ptr_lock(object->fid, &cacheHandle);\n    if (art != NULL) {\n        art_frame_hot(art, object->frame, object->rotation, &frameX, &frameY);\n        art_ptr_unlock(cacheHandle);\n    } else {\n        frameX = 0;\n        frameY = 0;\n    }\n\n    obj_offset(object, frameX, frameY, &temp);\n    rect_min_bound(&dirty, &temp, &dirty);\n\n    int rotation = sad_entry->rotations[sad_entry->field_20];\n    int y = off_tile[1][rotation];\n    int x = off_tile[0][rotation];\n    if ((x > 0 && x <= object->x) || (x < 0 && x >= object->x) || (y > 0 && y <= object->y) || (y < 0 && y >= object->y)) {\n        x = object->x - x;\n        y = object->y - y;\n\n        int v10 = tile_num_in_direction(object->tile, rotation, 1);\n        Object* v12 = obj_blocking_at(object, v10, object->elevation);\n        if (v12 != NULL) {\n            if (!anim_can_use_door(object, v12)) {\n                sad_entry->field_1C = make_path(object, object->tile, sad_entry->field_24, sad_entry->rotations, 1);\n                if (sad_entry->field_1C != 0) {\n                    obj_move_to_tile(object, object->tile, object->elevation, &temp);\n                    rect_min_bound(&dirty, &temp, &dirty);\n\n                    obj_set_frame(object, 0, &temp);\n                    rect_min_bound(&dirty, &temp, &dirty);\n\n                    obj_set_rotation(object, sad_entry->rotations[0], &temp);\n                    rect_min_bound(&dirty, &temp, &dirty);\n\n                    sad_entry->field_20 = 0;\n                } else {\n                    sad_entry->field_20 = -1000;\n                }\n                v10 = -1;\n            } else {\n                obj_use_door(object, v12, 0);\n            }\n        }\n\n        if (v10 != -1) {\n            obj_move_to_tile(object, v10, object->elevation, &temp);\n            rect_min_bound(&dirty, &temp, &dirty);\n\n            int v17 = 0;\n            if (isInCombat() && FID_TYPE(object->fid) == OBJ_TYPE_CRITTER) {\n                int v18 = critter_compute_ap_from_distance(object, 1);\n                if (combat_free_move < v18) {\n                    int ap = object->data.critter.combat.ap;\n                    int v20 = v18 - combat_free_move;\n                    combat_free_move = 0;\n                    if (v20 > ap) {\n                        object->data.critter.combat.ap = 0;\n                    } else {\n                        object->data.critter.combat.ap = ap - v20;\n                    }\n                } else {\n                    combat_free_move -= v18;\n                }\n\n                if (object == obj_dude) {\n                    intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n                }\n\n                v17 = (object->data.critter.combat.ap + combat_free_move) <= 0;\n            }\n\n            sad_entry->field_20 += 1;\n\n            if (sad_entry->field_20 == sad_entry->field_1C || v17) {\n                sad_entry->field_20 = -1000;\n            } else {\n                obj_set_rotation(object, sad_entry->rotations[sad_entry->field_20], &temp);\n                rect_min_bound(&dirty, &temp, &dirty);\n\n                obj_offset(object, x, y, &temp);\n                rect_min_bound(&dirty, &temp, &dirty);\n            }\n        }\n    }\n\n    tile_refresh_rect(&dirty, object->elevation);\n    if (sad_entry->field_20 == -1000) {\n        anim_set_continue(sad_entry->animationSequenceIndex, 1);\n    }\n}\n\n// 0x4177C0\nstatic void object_straight_move(int index)\n{\n    AnimationSad* sad_entry = &(sad[index]);\n    Object* object = sad_entry->obj;\n\n    Rect dirtyRect;\n    Rect temp;\n\n    if (sad_entry->field_20 == -2000) {\n        obj_change_fid(object, sad_entry->fid, &dirtyRect);\n        sad_entry->field_20 = 0;\n    } else {\n        obj_bound(object, &dirtyRect);\n    }\n\n    CacheEntry* cacheHandle;\n    Art* art = art_ptr_lock(object->fid, &cacheHandle);\n    if (art != NULL) {\n        int lastFrame = art_frame_max_frame(art) - 1;\n        art_ptr_unlock(cacheHandle);\n\n        if ((sad_entry->flags & ANIM_SAD_NO_ANIM) == 0) {\n            if ((sad_entry->flags & ANIM_SAD_WAIT_FOR_COMPLETION) == 0 || object->frame < lastFrame) {\n                obj_inc_frame(object, &temp);\n                rect_min_bound(&dirtyRect, &temp, &dirtyRect);\n            }\n        }\n\n        if (sad_entry->field_20 < sad_entry->field_1C) {\n            StraightPathNode* v12 = &(sad_entry->field_28[sad_entry->field_20]);\n\n            obj_move_to_tile(object, v12->tile, v12->elevation, &temp);\n            rect_min_bound(&dirtyRect, &temp, &dirtyRect);\n\n            obj_offset(object, v12->x, v12->y, &temp);\n            rect_min_bound(&dirtyRect, &temp, &dirtyRect);\n\n            sad_entry->field_20++;\n        }\n\n        if (sad_entry->field_20 == sad_entry->field_1C) {\n            if ((sad_entry->flags & ANIM_SAD_WAIT_FOR_COMPLETION) == 0 || object->frame == lastFrame) {\n                sad_entry->field_20 = -1000;\n            }\n        }\n\n        tile_refresh_rect(&dirtyRect, sad_entry->obj->elevation);\n\n        if (sad_entry->field_20 == -1000) {\n            anim_set_continue(sad_entry->animationSequenceIndex, 1);\n        }\n    }\n}\n\n// 0x4179B8\nstatic int anim_animate(Object* obj, int anim, int animationSequenceIndex, int flags)\n{\n    if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) {\n        return -1;\n    }\n\n    AnimationSad* sad_entry = &(sad[curr_sad]);\n\n    int fid;\n    if (anim == ANIM_TAKE_OUT) {\n        sad_entry->flags = 0;\n        fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_TAKE_OUT, flags, obj->rotation + 1);\n    } else {\n        sad_entry->flags = flags;\n        fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    }\n\n    if (!art_exists(fid)) {\n        return -1;\n    }\n\n    sad_entry->obj = obj;\n    sad_entry->fid = fid;\n    sad_entry->animationSequenceIndex = animationSequenceIndex;\n    sad_entry->animationTimestamp = 0;\n    sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid);\n    sad_entry->field_20 = 0;\n    sad_entry->field_1C = 0;\n\n    curr_sad++;\n\n    return 0;\n}\n\n// 0x417B30\nvoid object_animate()\n{\n    if (curr_sad == 0) {\n        return;\n    }\n\n    anim_in_bk = 1;\n\n    for (int index = 0; index < curr_sad; index++) {\n        AnimationSad* sad_entry = &(sad[index]);\n        if (sad_entry->field_20 == -1000) {\n            continue;\n        }\n\n        Object* object = sad_entry->obj;\n\n        unsigned int time = get_time();\n        if (elapsed_tocks(time, sad_entry->animationTimestamp) < sad_entry->ticksPerFrame) {\n            continue;\n        }\n\n        sad_entry->animationTimestamp = time;\n\n        if (anim_set_check(sad_entry->animationSequenceIndex) == -1) {\n            continue;\n        }\n\n        if (sad_entry->field_1C > 0) {\n            if ((sad_entry->flags & ANIM_SAD_STRAIGHT) != 0) {\n                object_straight_move(index);\n            } else {\n                int savedTile = object->tile;\n                object_move(index);\n                if (savedTile != object->tile) {\n                    scr_chk_spatials_in(object, object->tile, object->elevation);\n                }\n            }\n            continue;\n        }\n\n        if (sad_entry->field_20 == 0) {\n            for (int index = 0; index < curr_sad; index++) {\n                AnimationSad* otherSad = &(sad[index]);\n                if (object == otherSad->obj && otherSad->field_20 == -2000) {\n                    otherSad->field_20 = -1000;\n                    anim_set_continue(otherSad->animationSequenceIndex, 1);\n                }\n            }\n            sad_entry->field_20 = -2000;\n        }\n\n        Rect dirtyRect;\n        Rect tempRect;\n\n        obj_bound(object, &dirtyRect);\n\n        if (object->fid == sad_entry->fid) {\n            if ((sad_entry->flags & ANIM_SAD_REVERSE) == 0) {\n                CacheEntry* cacheHandle;\n                Art* art = art_ptr_lock(object->fid, &cacheHandle);\n                if (art != NULL) {\n                    if ((sad_entry->flags & ANIM_SAD_FOREVER) == 0 && object->frame == art_frame_max_frame(art) - 1) {\n                        sad_entry->field_20 = -1000;\n                        art_ptr_unlock(cacheHandle);\n\n                        if ((sad_entry->flags & ANIM_SAD_HIDE_ON_END) != 0) {\n                            // NOTE: Uninline.\n                            anim_hide(object, -1);\n                        }\n\n                        anim_set_continue(sad_entry->animationSequenceIndex, 1);\n                        continue;\n                    } else {\n                        obj_inc_frame(object, &tempRect);\n                        rect_min_bound(&dirtyRect, &tempRect, &dirtyRect);\n\n                        int frameX;\n                        int frameY;\n                        art_frame_hot(art, object->frame, object->rotation, &frameX, &frameY);\n\n                        obj_offset(object, frameX, frameY, &tempRect);\n                        rect_min_bound(&dirtyRect, &tempRect, &dirtyRect);\n\n                        art_ptr_unlock(cacheHandle);\n                    }\n                }\n\n                tile_refresh_rect(&dirtyRect, map_elevation);\n\n                continue;\n            }\n\n            if ((sad_entry->flags & ANIM_SAD_FOREVER) != 0 || object->frame != 0) {\n                int x;\n                int y;\n\n                CacheEntry* cacheHandle;\n                Art* art = art_ptr_lock(object->fid, &cacheHandle);\n                if (art != NULL) {\n                    art_frame_hot(art, object->frame, object->rotation, &x, &y);\n                    art_ptr_unlock(cacheHandle);\n                }\n\n                obj_dec_frame(object, &tempRect);\n                rect_min_bound(&dirtyRect, &tempRect, &dirtyRect);\n\n                obj_offset(object, -x, -y, &tempRect);\n                rect_min_bound(&dirtyRect, &tempRect, &dirtyRect);\n\n                tile_refresh_rect(&dirtyRect, map_elevation);\n                continue;\n            }\n\n            sad_entry->field_20 = -1000;\n            anim_set_continue(sad_entry->animationSequenceIndex, 1);\n        } else {\n            int x;\n            int y;\n\n            CacheEntry* cacheHandle;\n            Art* art = art_ptr_lock(object->fid, &cacheHandle);\n            if (art != NULL) {\n                art_frame_offset(art, object->rotation, &x, &y);\n                art_ptr_unlock(cacheHandle);\n            } else {\n                x = 0;\n                y = 0;\n            }\n\n            Rect v29;\n            obj_change_fid(object, sad_entry->fid, &v29);\n            rect_min_bound(&dirtyRect, &v29, &dirtyRect);\n\n            art = art_ptr_lock(object->fid, &cacheHandle);\n            if (art != NULL) {\n                int frame;\n                if ((sad_entry->flags & ANIM_SAD_REVERSE) != 0) {\n                    frame = art_frame_max_frame(art) - 1;\n                } else {\n                    frame = 0;\n                }\n\n                obj_set_frame(object, frame, &v29);\n                rect_min_bound(&dirtyRect, &v29, &dirtyRect);\n\n                int frameX;\n                int frameY;\n                art_frame_hot(art, object->frame, object->rotation, &frameX, &frameY);\n\n                Rect v19;\n                obj_offset(object, x + frameX, y + frameY, &v19);\n                rect_min_bound(&dirtyRect, &v19, &dirtyRect);\n\n                art_ptr_unlock(cacheHandle);\n            } else {\n                obj_set_frame(object, 0, &v29);\n                rect_min_bound(&dirtyRect, &v29, &dirtyRect);\n            }\n\n            tile_refresh_rect(&dirtyRect, map_elevation);\n        }\n    }\n\n    anim_in_bk = 0;\n\n    object_anim_compact();\n}\n\n// 0x417F18\nstatic void object_anim_compact()\n{\n    for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) {\n        AnimationSequence* animationSequence = &(anim_set[index]);\n        if ((animationSequence->flags & ANIM_SEQ_0x20) != 0) {\n            animationSequence->flags = 0;\n        }\n    }\n\n    int index = 0;\n    for (; index < curr_sad; index++) {\n        if (sad[index].field_20 == -1000) {\n            int v2 = index + 1;\n            for (; v2 < curr_sad; v2++) {\n                if (sad[v2].field_20 != -1000) {\n                    break;\n                }\n            }\n\n            if (v2 == curr_sad) {\n                break;\n            }\n\n            if (index != v2) {\n                memcpy(&(sad[index]), &(sad[v2]), sizeof(AnimationSad));\n                sad[v2].field_20 = -1000;\n                sad[v2].flags = 0;\n            }\n        }\n    }\n    curr_sad = index;\n}\n\n// 0x417FFC\nint check_move(int* a1)\n{\n    int x;\n    int y;\n    mouse_get_position(&x, &y);\n\n    int tile = tile_num(x, y, map_elevation);\n    if (tile == -1) {\n        return -1;\n    }\n\n    if (isInCombat()) {\n        if (*a1 != -1) {\n            if (keys[DIK_LCONTROL] || keys[DIK_RCONTROL]) {\n                int hitMode;\n                bool aiming;\n                intface_get_attack(&hitMode, &aiming);\n\n                int v6 = item_mp_cost(obj_dude, hitMode, aiming);\n                *a1 = *a1 - v6;\n                if (*a1 <= 0) {\n                    return -1;\n                }\n            }\n        }\n    } else {\n        bool interruptWalk;\n        configGetBool(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_INTERRUPT_WALK_KEY, &interruptWalk);\n        if (interruptWalk) {\n            register_clear(obj_dude);\n        }\n    }\n\n    return tile;\n}\n\n// 0x4180B4\nint dude_move(int a1)\n{\n    // 0x51072C\n    static int lastDest = -2;\n\n    int v1;\n    int tile = check_move(&v1);\n    if (tile == -1) {\n        return -1;\n    }\n\n    if (lastDest == tile) {\n        return dude_run(a1);\n    }\n\n    lastDest = tile;\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n\n    register_object_move_to_tile(obj_dude, tile, obj_dude->elevation, a1, 0);\n\n    return register_end();\n}\n\n// 0x41810C\nint dude_run(int a1)\n{\n    int a4;\n    int tile_num;\n\n    a4 = a1;\n    tile_num = check_move(&a4);\n    if (tile_num == -1) {\n        return -1;\n    }\n\n    if (!perk_level(obj_dude, PERK_SILENT_RUNNING)) {\n        pc_flag_off(DUDE_STATE_SNEAKING);\n    }\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n\n    register_object_run_to_tile(obj_dude, tile_num, obj_dude->elevation, a4, 0);\n\n    return register_end();\n}\n\n// 0x418168\nvoid dude_fidget()\n{\n    // 0x510730\n    static unsigned int last_time = 0;\n\n    // 0x510734\n    static unsigned int next_time = 0;\n\n    // 0x56C7E0\n    static Object* fidget_ptr[100];\n\n    if (game_user_wants_to_quit != 0) {\n        return;\n    }\n\n    if (isInCombat()) {\n        return;\n    }\n\n    if (vcr_status() != VCR_STATE_TURNED_OFF) {\n        return;\n    }\n\n    if ((obj_dude->flags & OBJECT_HIDDEN) != 0) {\n        return;\n    }\n\n    unsigned int v0 = get_bk_time();\n    if (elapsed_tocks(v0, last_time) <= next_time) {\n        return;\n    }\n\n    last_time = v0;\n\n    int v5 = 0;\n    Object* object = obj_find_first_at(obj_dude->elevation);\n    while (object != NULL) {\n        if (v5 >= 100) {\n            break;\n        }\n\n        if ((object->flags & OBJECT_HIDDEN) == 0 && FID_TYPE(object->fid) == OBJ_TYPE_CRITTER && FID_ANIM_TYPE(object->fid) == ANIM_STAND && !critter_is_dead(object)) {\n            Rect rect;\n            obj_bound(object, &rect);\n\n            Rect intersection;\n            if (rect_inside_bound(&rect, &scr_size, &intersection) == 0 && (map_data.field_34 != 97 || object->pid != 0x10000FA)) {\n                fidget_ptr[v5++] = object;\n            }\n        }\n\n        object = obj_find_next_at();\n    }\n\n    int v13;\n    if (v5 != 0) {\n        int r = roll_random(0, v5 - 1);\n        Object* object = fidget_ptr[r];\n\n        register_begin(ANIMATION_REQUEST_UNRESERVED | ANIMATION_REQUEST_INSIGNIFICANT);\n\n        bool v8 = false;\n        if (object == obj_dude) {\n            v8 = true;\n        } else {\n            char v15[16];\n            v15[0] = '\\0';\n            art_get_base_name(1, object->fid & 0xFFF, v15);\n            if (v15[0] == 'm' || v15[0] == 'M') {\n                if (obj_dist(object, obj_dude) < critterGetStat(obj_dude, STAT_PERCEPTION) * 2) {\n                    v8 = true;\n                }\n            }\n        }\n\n        if (v8) {\n            const char* sfx = gsnd_build_character_sfx_name(object, ANIM_STAND, CHARACTER_SOUND_EFFECT_UNUSED);\n            register_object_play_sfx(object, sfx, 0);\n        }\n\n        register_object_animate(object, ANIM_STAND, 0);\n        register_end();\n\n        v13 = 20 / v5;\n    } else {\n        v13 = 7;\n    }\n\n    if (v13 < 1) {\n        v13 = 1;\n    } else if (v13 > 7) {\n        v13 = 7;\n    }\n\n    next_time = roll_random(0, 3000) + 1000 * v13;\n}\n\n// 0x418378\nvoid dude_stand(Object* obj, int rotation, int fid)\n{\n    Rect rect;\n\n    obj_set_rotation(obj, rotation, &rect);\n\n    int x = 0;\n    int y = 0;\n\n    int weaponAnimationCode = (obj->fid & 0xF000) >> 12;\n    if (weaponAnimationCode != 0) {\n        if (fid == -1) {\n            int takeOutFid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_TAKE_OUT, weaponAnimationCode, obj->rotation + 1);\n            CacheEntry* takeOutFrmHandle;\n            Art* takeOutFrm = art_ptr_lock(takeOutFid, &takeOutFrmHandle);\n            if (takeOutFrm != NULL) {\n                int frameCount = art_frame_max_frame(takeOutFrm);\n                for (int frame = 0; frame < frameCount; frame++) {\n                    int offsetX;\n                    int offsetY;\n                    art_frame_hot(takeOutFrm, frame, obj->rotation, &offsetX, &offsetY);\n                    x += offsetX;\n                    y += offsetY;\n                }\n                art_ptr_unlock(takeOutFrmHandle);\n\n                CacheEntry* standFrmHandle;\n                int standFid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_STAND, 0, obj->rotation + 1);\n                Art* standFrm = art_ptr_lock(standFid, &standFrmHandle);\n                if (standFrm != NULL) {\n                    int offsetX;\n                    int offsetY;\n                    if (art_frame_offset(standFrm, obj->rotation, &offsetX, &offsetY) == 0) {\n                        x += offsetX;\n                        y += offsetY;\n                    }\n                    art_ptr_unlock(standFrmHandle);\n                }\n            }\n        }\n    }\n\n    if (fid == -1) {\n        int anim;\n        if (FID_ANIM_TYPE(obj->fid) == ANIM_FIRE_DANCE) {\n            anim = ANIM_FIRE_DANCE;\n        } else {\n            anim = ANIM_STAND;\n        }\n        fid = art_id(FID_TYPE(obj->fid), (obj->fid & 0xFFF), anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    }\n\n    Rect temp;\n    obj_change_fid(obj, fid, &temp);\n    rect_min_bound(&rect, &temp, &rect);\n\n    obj_move_to_tile(obj, obj->tile, obj->elevation, &temp);\n    rect_min_bound(&rect, &temp, &rect);\n\n    obj_set_frame(obj, 0, &temp);\n    rect_min_bound(&rect, &temp, &rect);\n\n    obj_offset(obj, x, y, &temp);\n    rect_min_bound(&rect, &temp, &rect);\n\n    tile_refresh_rect(&rect, obj->elevation);\n}\n\n// 0x418574\nvoid dude_standup(Object* a1)\n{\n    register_begin(ANIMATION_REQUEST_RESERVED);\n\n    int anim;\n    if (FID_ANIM_TYPE(a1->fid) == ANIM_FALL_BACK) {\n        anim = ANIM_BACK_TO_STANDING;\n    } else {\n        anim = ANIM_PRONE_TO_STANDING;\n    }\n\n    register_object_animate(a1, anim, 0);\n    register_end();\n    a1->data.critter.combat.results &= ~DAM_KNOCKED_DOWN;\n}\n\n// 0x4185EC\nstatic int anim_turn_towards(Object* obj, int delta, int animationSequenceIndex)\n{\n    if (!critter_is_prone(obj)) {\n        int rotation = obj->rotation + delta;\n        if (rotation >= ROTATION_COUNT) {\n            rotation = ROTATION_NE;\n        } else if (rotation < 0) {\n            rotation = ROTATION_NW;\n        }\n\n        dude_stand(obj, rotation, -1);\n    }\n\n    anim_set_continue(animationSequenceIndex, 0);\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x41862C\nint anim_hide(Object* object, int animationSequenceIndex)\n{\n    Rect rect;\n\n    if (obj_turn_off(object, &rect) == 0) {\n        tile_refresh_rect(&rect, object->elevation);\n    }\n\n    if (animationSequenceIndex != -1) {\n        anim_set_continue(animationSequenceIndex, 0);\n    }\n\n    return 0;\n}\n\n// 0x418660\nint anim_change_fid(Object* obj, int animationSequenceIndex, int fid)\n{\n    Rect rect;\n    Rect v7;\n\n    if (FID_ANIM_TYPE(fid)) {\n        obj_change_fid(obj, fid, &rect);\n        obj_set_frame(obj, 0, &v7);\n        rect_min_bound(&rect, &v7, &rect);\n        tile_refresh_rect(&rect, obj->elevation);\n    } else {\n        dude_stand(obj, obj->rotation, fid);\n    }\n\n    anim_set_continue(animationSequenceIndex, 0);\n\n    return 0;\n}\n\n// 0x4186CC\nvoid anim_stop()\n{\n    anim_in_anim_stop = true;\n    curr_anim_set = -1;\n\n    for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) {\n        anim_set_end(index);\n    }\n\n    anim_in_anim_stop = false;\n    curr_sad = 0;\n}\n\n// 0x418708\nstatic int check_gravity(int tile, int elevation)\n{\n    for (; elevation > 0; elevation--) {\n        int x;\n        int y;\n        tile_coord(tile, &x, &y, elevation);\n\n        int squareTile = square_num(x + 2, y + 8, elevation);\n        int fid = art_id(OBJ_TYPE_TILE, square[elevation]->field_0[squareTile] & 0xFFF, 0, 0, 0);\n        if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) {\n            break;\n        }\n    }\n    return elevation;\n}\n\n// 0x418794\nunsigned int compute_tpf(Object* object, int fid)\n{\n    int fps;\n\n    CacheEntry* handle;\n    Art* frm = art_ptr_lock(fid, &handle);\n    if (frm != NULL) {\n        fps = art_frame_fps(frm);\n        art_ptr_unlock(handle);\n    } else {\n        fps = 10;\n    }\n\n    if (isInCombat()) {\n        if (FID_ANIM_TYPE(fid) == ANIM_WALK) {\n            int playerSpeedup = 0;\n            config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, &playerSpeedup);\n\n            if (object != obj_dude || playerSpeedup == 1) {\n                int combatSpeed = 0;\n                config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, &combatSpeed);\n                fps += combatSpeed;\n            }\n        }\n    }\n\n    return 1000 / fps;\n}\n"
  },
  {
    "path": "src/game/anim.h",
    "content": "#ifndef ANIMATION_H\n#define ANIMATION_H\n\n#include <stdbool.h>\n\n#include \"game/object_types.h\"\n\ntypedef enum AnimationRequestOptions {\n    ANIMATION_REQUEST_UNRESERVED = 0x01,\n    ANIMATION_REQUEST_RESERVED = 0x02,\n    ANIMATION_REQUEST_NO_STAND = 0x04,\n    ANIMATION_REQUEST_0x100 = 0x100,\n    ANIMATION_REQUEST_INSIGNIFICANT = 0x200,\n} AnimationRequestOptions;\n\n// Basic animations: 0-19\n// Knockdown and death: 20-35\n// Change positions: 36-37\n// Weapon: 38-47\n// Single-frame death animations (the last frame of knockdown and death animations): 48-63\ntypedef enum AnimationType {\n    ANIM_STAND = 0,\n    ANIM_WALK = 1,\n    ANIM_JUMP_BEGIN = 2,\n    ANIM_JUMP_END = 3,\n    ANIM_CLIMB_LADDER = 4,\n    ANIM_FALLING = 5,\n    ANIM_UP_STAIRS_RIGHT = 6,\n    ANIM_UP_STAIRS_LEFT = 7,\n    ANIM_DOWN_STAIRS_RIGHT = 8,\n    ANIM_DOWN_STAIRS_LEFT = 9,\n    ANIM_MAGIC_HANDS_GROUND = 10,\n    ANIM_MAGIC_HANDS_MIDDLE = 11,\n    ANIM_MAGIC_HANDS_UP = 12,\n    ANIM_DODGE_ANIM = 13,\n    ANIM_HIT_FROM_FRONT = 14,\n    ANIM_HIT_FROM_BACK = 15,\n    ANIM_THROW_PUNCH = 16,\n    ANIM_KICK_LEG = 17,\n    ANIM_THROW_ANIM = 18,\n    ANIM_RUNNING = 19,\n    ANIM_FALL_BACK = 20,\n    ANIM_FALL_FRONT = 21,\n    ANIM_BAD_LANDING = 22,\n    ANIM_BIG_HOLE = 23,\n    ANIM_CHARRED_BODY = 24,\n    ANIM_CHUNKS_OF_FLESH = 25,\n    ANIM_DANCING_AUTOFIRE = 26,\n    ANIM_ELECTRIFY = 27,\n    ANIM_SLICED_IN_HALF = 28,\n    ANIM_BURNED_TO_NOTHING = 29,\n    ANIM_ELECTRIFIED_TO_NOTHING = 30,\n    ANIM_EXPLODED_TO_NOTHING = 31,\n    ANIM_MELTED_TO_NOTHING = 32,\n    ANIM_FIRE_DANCE = 33,\n    ANIM_FALL_BACK_BLOOD = 34,\n    ANIM_FALL_FRONT_BLOOD = 35,\n    ANIM_PRONE_TO_STANDING = 36,\n    ANIM_BACK_TO_STANDING = 37,\n    ANIM_TAKE_OUT = 38,\n    ANIM_PUT_AWAY = 39,\n    ANIM_PARRY_ANIM = 40,\n    ANIM_THRUST_ANIM = 41,\n    ANIM_SWING_ANIM = 42,\n    ANIM_POINT = 43,\n    ANIM_UNPOINT = 44,\n    ANIM_FIRE_SINGLE = 45,\n    ANIM_FIRE_BURST = 46,\n    ANIM_FIRE_CONTINUOUS = 47,\n    ANIM_FALL_BACK_SF = 48,\n    ANIM_FALL_FRONT_SF = 49,\n    ANIM_BAD_LANDING_SF = 50,\n    ANIM_BIG_HOLE_SF = 51,\n    ANIM_CHARRED_BODY_SF = 52,\n    ANIM_CHUNKS_OF_FLESH_SF = 53,\n    ANIM_DANCING_AUTOFIRE_SF = 54,\n    ANIM_ELECTRIFY_SF = 55,\n    ANIM_SLICED_IN_HALF_SF = 56,\n    ANIM_BURNED_TO_NOTHING_SF = 57,\n    ANIM_ELECTRIFIED_TO_NOTHING_SF = 58,\n    ANIM_EXPLODED_TO_NOTHING_SF = 59,\n    ANIM_MELTED_TO_NOTHING_SF = 60,\n    ANIM_FIRE_DANCE_SF = 61,\n    ANIM_FALL_BACK_BLOOD_SF = 62,\n    ANIM_FALL_FRONT_BLOOD_SF = 63,\n    ANIM_CALLED_SHOT_PIC = 64,\n    ANIM_COUNT = 65,\n    FIRST_KNOCKDOWN_AND_DEATH_ANIM = ANIM_FALL_BACK,\n    LAST_KNOCKDOWN_AND_DEATH_ANIM = ANIM_FALL_FRONT_BLOOD,\n    FIRST_SF_DEATH_ANIM = ANIM_FALL_BACK_SF,\n    LAST_SF_DEATH_ANIM = ANIM_FALL_FRONT_BLOOD_SF,\n} AnimationType;\n\n#define FID_ANIM_TYPE(value) ((value) & 0xFF0000) >> 16\n\n// Signature of animation callback accepting 2 parameters.\ntypedef int AnimationCallback(void*, void*);\n\n// Signature of animation callback accepting 3 parameters.\ntypedef int AnimationCallback3(void*, void*, void*);\n\ntypedef Object* PathBuilderCallback(Object* object, int tile, int elevation);\n\ntypedef struct StraightPathNode {\n    int tile;\n    int elevation;\n    int x;\n    int y;\n} StraightPathNode;\n\nvoid anim_init();\nvoid anim_reset();\nvoid anim_exit();\nint register_begin(int a1);\nint register_priority(int a1);\nint register_clear(Object* a1);\nint register_end();\nint check_registry(Object* obj);\nint anim_busy(Object* a1);\nint register_object_move_to_object(Object* owner, Object* destination, int actionPoints, int delay);\nint register_object_run_to_object(Object* owner, Object* destination, int actionPoints, int delay);\nint register_object_move_to_tile(Object* owner, int tile, int elevation, int actionPoints, int delay);\nint register_object_run_to_tile(Object* owner, int tile, int elevation, int actionPoints, int delay);\nint register_object_move_straight_to_tile(Object* object, int tile, int elevation, int anim, int delay);\nint register_object_animate_and_move_straight(Object* owner, int tile, int elev, int anim, int delay);\nint register_object_move_on_stairs(Object* owner, Object* stairs, int delay);\nint register_object_check_falling(Object* owner, int delay);\nint register_object_animate(Object* owner, int anim, int delay);\nint register_object_animate_reverse(Object* owner, int anim, int delay);\nint register_object_animate_and_hide(Object* owner, int anim, int delay);\nint register_object_turn_towards(Object* owner, int tile);\nint register_object_inc_rotation(Object* owner);\nint register_object_dec_rotation(Object* owner);\nint register_object_erase(Object* object);\nint register_object_must_erase(Object* object);\nint register_object_call(void* a1, void* a2, AnimationCallback* proc, int delay);\nint register_object_call3(void* a1, void* a2, void* a3, AnimationCallback3* proc, int delay);\nint register_object_must_call(void* a1, void* a2, AnimationCallback* proc, int delay);\nint register_object_fset(Object* object, int flag, int delay);\nint register_object_funset(Object* object, int flag, int delay);\nint register_object_change_fid(Object* owner, int fid, int delay);\nint register_object_take_out(Object* owner, int weaponAnimationCode, int delay);\nint register_object_light(Object* owner, int lightDistance, int delay);\nint register_object_outline(Object* object, bool outline, int delay);\nint register_object_play_sfx(Object* owner, const char* soundEffectName, int delay);\nint register_object_animate_forever(Object* owner, int anim, int delay);\nint register_ping(int a1, int a2);\nint make_path(Object* object, int from, int to, unsigned char* a4, int a5);\nint make_path_func(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback);\nint idist(int a1, int a2, int a3, int a4);\nint EST(int tile1, int tile2);\nint make_straight_path(Object* a1, int from, int to, StraightPathNode* pathNodes, Object** a5, int a6);\nint make_straight_path_func(Object* a1, int from, int to, StraightPathNode* pathNodes, Object** a5, int a6, PathBuilderCallback* callback);\nint anim_move_on_stairs(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex);\nint check_for_falling(Object* obj, int anim, int a3);\nvoid object_animate();\nint check_move(int* a1);\nint dude_move(int a1);\nint dude_run(int a1);\nvoid dude_fidget();\nvoid dude_stand(Object* obj, int rotation, int fid);\nvoid dude_standup(Object* a1);\nint anim_hide(Object* object, int animationSequenceIndex);\nint anim_change_fid(Object* obj, int animationSequenceIndex, int fid);\nvoid anim_stop();\nunsigned int compute_tpf(Object* object, int fid);\n\n#endif /* ANIMATION_H */\n"
  },
  {
    "path": "src/game/art.c",
    "content": "#include \"game/art.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"game/artload.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/proto.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n// 0x510738\nstatic ArtListDescription art[OBJ_TYPE_COUNT] = {\n    { 0, \"items\", 0, 0, 0 },\n    { 0, \"critters\", 0, 0, 0 },\n    { 0, \"scenery\", 0, 0, 0 },\n    { 0, \"walls\", 0, 0, 0 },\n    { 0, \"tiles\", 0, 0, 0 },\n    { 0, \"misc\", 0, 0, 0 },\n    { 0, \"intrface\", 0, 0, 0 },\n    { 0, \"inven\", 0, 0, 0 },\n    { 0, \"heads\", 0, 0, 0 },\n    { 0, \"backgrnd\", 0, 0, 0 },\n    { 0, \"skilldex\", 0, 0, 0 },\n};\n\n// This flag denotes that localized arts should be looked up first. Used\n// together with [darn_foreign_sub_path].\n//\n// 0x510898\nstatic bool darn_foreigners = false;\n\n// 0x51089C\nstatic const char* head1 = \"gggnnnbbbgnb\";\n\n// 0x5108A0\nstatic const char* head2 = \"vfngfbnfvppp\";\n\n// Current native look base fid.\n//\n// 0x5108A4\nint art_vault_guy_num = 0;\n\n// Base fids for unarmored dude.\n//\n// Outfit file names:\n// - tribal: \"hmwarr\", \"hfprim\"\n// - jumpsuit: \"hmjmps\", \"hfjmps\"\n//\n// NOTE: This value could have been done with two separate arrays - one for\n// tribal look, and one for jumpsuit look. However in this case it would have\n// been accessed differently in 0x49F984, which clearly uses look type as an\n// index, not gender.\n//\n// 0x5108A8\nint art_vault_person_nums[DUDE_NATIVE_LOOK_COUNT][GENDER_COUNT];\n\n// Index of \"grid001.frm\" in tiles.lst.\n//\n// 0x5108B8\nint art_mapper_blank_tile = 1;\n\n// Non-english language name.\n//\n// This value is used as a directory name to display localized arts.\n//\n// 0x56C970\nstatic char darn_foreign_sub_path[32];\n\n// 0x56C990\nCache art_cache;\n\n// 0x56C9E4\nstatic char art_name[MAX_PATH];\n\n// 0x56CAE8\nHeadDescription* head_info;\n\n// 0x56CAEC\nstatic int* anon_alias;\n\n// 0x56CAF0\nstatic int* artCritterFidShouldRunData;\n\n// 0x418840\nint art_init()\n{\n    char path[MAX_PATH];\n    File* stream;\n    char string[200];\n\n    int cacheSize;\n    if (!config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_ART_CACHE_SIZE_KEY, &cacheSize)) {\n        cacheSize = 8;\n    }\n\n    if (!cache_init(&art_cache, art_data_size, art_data_load, art_data_free, cacheSize << 20)) {\n        debug_printf(\"cache_init failed in art_init\\n\");\n        return -1;\n    }\n\n    char* language;\n    if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language) && stricmp(language, ENGLISH) != 0) {\n        strcpy(darn_foreign_sub_path, language);\n        darn_foreigners = true;\n    }\n\n    bool critterDbSelected = false;\n    for (int objectType = 0; objectType < OBJ_TYPE_COUNT; objectType++) {\n        art[objectType].flags = 0;\n        sprintf(path, \"%s%s%s\\\\%s.lst\", cd_path_base, \"art\\\\\", art[objectType].name, art[objectType].name);\n\n        int oldDb;\n        if (objectType == OBJ_TYPE_CRITTER) {\n            oldDb = db_current();\n            critterDbSelected = true;\n            db_select(critter_db_handle);\n        }\n\n        if (art_read_lst(path, &(art[objectType].fileNames), &(art[objectType].fileNamesLength)) != 0) {\n            debug_printf(\"art_read_lst failed in art_init\\n\");\n            if (critterDbSelected) {\n                db_select(oldDb);\n            }\n            cache_exit(&art_cache);\n            return -1;\n        }\n\n        if (objectType == OBJ_TYPE_CRITTER) {\n            critterDbSelected = false;\n            db_select(oldDb);\n        }\n    }\n\n    anon_alias = (int*)mem_malloc(sizeof(*anon_alias) * art[OBJ_TYPE_CRITTER].fileNamesLength);\n    if (anon_alias == NULL) {\n        art[OBJ_TYPE_CRITTER].fileNamesLength = 0;\n        debug_printf(\"Out of memory for anon_alias in art_init\\n\");\n        cache_exit(&art_cache);\n        return -1;\n    }\n\n    artCritterFidShouldRunData = (int*)mem_malloc(sizeof(*artCritterFidShouldRunData) * art[1].fileNamesLength);\n    if (artCritterFidShouldRunData == NULL) {\n        art[OBJ_TYPE_CRITTER].fileNamesLength = 0;\n        debug_printf(\"Out of memory for artCritterFidShouldRunData in art_init\\n\");\n        cache_exit(&art_cache);\n        return -1;\n    }\n\n    for (int critterIndex = 0; critterIndex < art[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {\n        artCritterFidShouldRunData[critterIndex] = 0;\n    }\n\n    sprintf(path, \"%s%s%s\\\\%s.lst\", cd_path_base, \"art\\\\\", art[OBJ_TYPE_CRITTER].name, art[OBJ_TYPE_CRITTER].name);\n\n    stream = db_fopen(path, \"rt\");\n    if (stream == NULL) {\n        debug_printf(\"Unable to open %s in art_init\\n\", path);\n        cache_exit(&art_cache);\n        return -1;\n    }\n\n    char* critterFileNames = art[OBJ_TYPE_CRITTER].fileNames;\n    for (int critterIndex = 0; critterIndex < art[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {\n        if (stricmp(critterFileNames, \"hmjmps\") == 0) {\n            art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_MALE] = critterIndex;\n        } else if (stricmp(critterFileNames, \"hfjmps\") == 0) {\n            art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_FEMALE] = critterIndex;\n        }\n\n        if (stricmp(critterFileNames, \"hmwarr\") == 0) {\n            art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_MALE] = critterIndex;\n            art_vault_guy_num = critterIndex;\n        } else if (stricmp(critterFileNames, \"hfprim\") == 0) {\n            art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_FEMALE] = critterIndex;\n        }\n\n        critterFileNames += 13;\n    }\n\n    for (int critterIndex = 0; critterIndex < art[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) {\n        if (!db_fgets(string, sizeof(string), stream)) {\n            break;\n        }\n\n        char* sep1 = strchr(string, ',');\n        if (sep1 != NULL) {\n            anon_alias[critterIndex] = atoi(sep1 + 1);\n\n            char* sep2 = strchr(sep1 + 1, ',');\n            if (sep2 != NULL) {\n                artCritterFidShouldRunData[critterIndex] = atoi(sep2 + 1);\n            } else {\n                artCritterFidShouldRunData[critterIndex] = 0;\n            }\n        } else {\n            anon_alias[critterIndex] = art_vault_guy_num;\n            artCritterFidShouldRunData[critterIndex] = 1;\n        }\n    }\n\n    db_fclose(stream);\n\n    char* tileFileNames = art[OBJ_TYPE_TILE].fileNames;\n    for (int tileIndex = 0; tileIndex < art[OBJ_TYPE_TILE].fileNamesLength; tileIndex++) {\n        if (stricmp(tileFileNames, \"grid001.frm\") == 0) {\n            art_mapper_blank_tile = tileIndex;\n        }\n        tileFileNames += 13;\n    }\n\n    head_info = (HeadDescription*)mem_malloc(sizeof(*head_info) * art[OBJ_TYPE_HEAD].fileNamesLength);\n    if (head_info == NULL) {\n        art[OBJ_TYPE_HEAD].fileNamesLength = 0;\n        debug_printf(\"Out of memory for head_info in art_init\\n\");\n        cache_exit(&art_cache);\n        return -1;\n    }\n\n    sprintf(path, \"%s%s%s\\\\%s.lst\", cd_path_base, \"art\\\\\", art[OBJ_TYPE_HEAD].name, art[OBJ_TYPE_HEAD].name);\n\n    stream = db_fopen(path, \"rt\");\n    if (stream == NULL) {\n        debug_printf(\"Unable to open %s in art_init\\n\", path);\n        cache_exit(&art_cache);\n        return -1;\n    }\n\n    for (int headIndex = 0; headIndex < art[OBJ_TYPE_HEAD].fileNamesLength; headIndex++) {\n        if (!db_fgets(string, sizeof(string), stream)) {\n            break;\n        }\n\n        char* sep1 = strchr(string, ',');\n        if (sep1 != NULL) {\n            *sep1 = '\\0';\n        } else {\n            sep1 = string;\n        }\n\n        char* sep2 = strchr(sep1, ',');\n        if (sep2 != NULL) {\n            *sep2 = '\\0';\n        } else {\n            sep2 = sep1;\n        }\n\n        head_info[headIndex].goodFidgetCount = atoi(sep1 + 1);\n\n        char* sep3 = strchr(sep2, ',');\n        if (sep3 != NULL) {\n            *sep3 = '\\0';\n        } else {\n            sep3 = sep2;\n        }\n\n        head_info[headIndex].neutralFidgetCount = atoi(sep2 + 1);\n\n        char* sep4 = strpbrk(sep3 + 1, \" ,;\\t\\n\");\n        if (sep4 != NULL) {\n            *sep4 = '\\0';\n        }\n\n        head_info[headIndex].badFidgetCount = atoi(sep3 + 1);\n    }\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// 0x418EB8\nvoid art_reset()\n{\n}\n\n// 0x418EBC\nvoid art_exit()\n{\n    cache_exit(&art_cache);\n\n    mem_free(anon_alias);\n    mem_free(artCritterFidShouldRunData);\n\n    for (int index = 0; index < OBJ_TYPE_COUNT; index++) {\n        mem_free(art[index].fileNames);\n        art[index].fileNames = NULL;\n\n        mem_free(art[index].field_18);\n        art[index].field_18 = NULL;\n    }\n\n    mem_free(head_info);\n}\n\n// 0x418F1C\nchar* art_dir(int objectType)\n{\n    return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? art[objectType].name : NULL;\n}\n\n// 0x418F34\nint art_get_disable(int objectType)\n{\n    return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? art[objectType].flags & 1 : 0;\n}\n\n// NOTE: Unused.\n//\n// 0x418F50\nvoid art_toggle_disable(int objectType)\n{\n    if (objectType >= 0 && objectType < OBJ_TYPE_COUNT) {\n        art[objectType].flags ^= 1;\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x418F64\nint art_total(int objectType)\n{\n    return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? art[objectType].fileNamesLength : 0;\n}\n\n// 0x418F7C\nint art_head_fidgets(int headFid)\n{\n    if (FID_TYPE(headFid) != OBJ_TYPE_HEAD) {\n        return 0;\n    }\n\n    int head = headFid & 0xFFF;\n\n    if (head > art[OBJ_TYPE_HEAD].fileNamesLength) {\n        return 0;\n    }\n\n    HeadDescription* headDescription = &(head_info[head]);\n\n    int fidget = (headFid & 0xFF0000) >> 16;\n    switch (fidget) {\n    case FIDGET_GOOD:\n        return headDescription->goodFidgetCount;\n    case FIDGET_NEUTRAL:\n        return headDescription->neutralFidgetCount;\n    case FIDGET_BAD:\n        return headDescription->badFidgetCount;\n    }\n    return 0;\n}\n\n// 0x418FFC\nvoid scale_art(int fid, unsigned char* dest, int width, int height, int pitch)\n{\n    // NOTE: Original code is different. For unknown reason it directly calls\n    // many art functions, for example instead of [art_ptr_lock] it calls lower level\n    // [cache_lock], instead of [art_frame_width] is calls [frame_ptr], then get\n    // width from frame's struct field. I don't know if this was intentional or\n    // not. I've replaced these calls with higher level functions where\n    // appropriate.\n\n    CacheEntry* handle;\n    Art* frm = art_ptr_lock(fid, &handle);\n    if (frm == NULL) {\n        return;\n    }\n\n    unsigned char* frameData = art_frame_data(frm, 0, 0);\n    int frameWidth = art_frame_width(frm, 0, 0);\n    int frameHeight = art_frame_length(frm, 0, 0);\n\n    int remainingWidth = width - frameWidth;\n    int remainingHeight = height - frameHeight;\n    if (remainingWidth < 0 || remainingHeight < 0) {\n        if (height * frameWidth >= width * frameHeight) {\n            trans_cscale(frameData,\n                frameWidth,\n                frameHeight,\n                frameWidth,\n                dest + pitch * ((height - width * frameHeight / frameWidth) / 2),\n                width,\n                width * frameHeight / frameWidth,\n                pitch);\n        } else {\n            trans_cscale(frameData,\n                frameWidth,\n                frameHeight,\n                frameWidth,\n                dest + (width - height * frameWidth / frameHeight) / 2,\n                height * frameWidth / frameHeight,\n                height,\n                pitch);\n        }\n    } else {\n        trans_buf_to_buf(frameData,\n            frameWidth,\n            frameHeight,\n            frameWidth,\n            dest + pitch * (remainingHeight / 2) + remainingWidth / 2,\n            pitch);\n    }\n\n    art_ptr_unlock(handle);\n}\n\n// 0x419160\nArt* art_ptr_lock(int fid, CacheEntry** handlePtr)\n{\n    if (handlePtr == NULL) {\n        return NULL;\n    }\n\n    Art* art = NULL;\n    cache_lock(&art_cache, fid, (void**)&art, handlePtr);\n    return art;\n}\n\n// 0x419188\nunsigned char* art_ptr_lock_data(int fid, int frame, int direction, CacheEntry** handlePtr)\n{\n    Art* art;\n    ArtFrame* frm;\n\n    art = NULL;\n    if (handlePtr) {\n        cache_lock(&art_cache, fid, (void**)&art, handlePtr);\n    }\n\n    if (art != NULL) {\n        frm = frame_ptr(art, frame, direction);\n        if (frm != NULL) {\n\n            return (unsigned char*)frm + sizeof(*frm);\n        }\n    }\n\n    return NULL;\n}\n\n// 0x4191CC\nunsigned char* art_lock(int fid, CacheEntry** handlePtr, int* widthPtr, int* heightPtr)\n{\n    *handlePtr = NULL;\n\n    Art* art;\n    cache_lock(&art_cache, fid, (void**)&art, handlePtr);\n\n    if (art == NULL) {\n        return NULL;\n    }\n\n    // NOTE: Uninline.\n    *widthPtr = art_frame_width(art, 0, 0);\n    if (*widthPtr == -1) {\n        return NULL;\n    }\n\n    // NOTE: Uninline.\n    *heightPtr = art_frame_length(art, 0, 0);\n    if (*heightPtr == -1) {\n        return NULL;\n    }\n\n    // NOTE: Uninline.\n    return art_frame_data(art, 0, 0);\n}\n\n// 0x419260\nint art_ptr_unlock(CacheEntry* handle)\n{\n    return cache_unlock(&art_cache, handle);\n}\n\n// 0x41927C\nint art_flush()\n{\n    return cache_flush(&art_cache);\n}\n\n// NOTE: Unused.\n//\n// 0x419294\nint art_discard(int fid)\n{\n    if (cache_discard(&art_cache, fid) == 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4192B0\nint art_get_base_name(int objectType, int id, char* dest)\n{\n    ArtListDescription* ptr;\n\n    if (objectType < OBJ_TYPE_ITEM && objectType >= OBJ_TYPE_COUNT) {\n        return -1;\n    }\n\n    ptr = &(art[objectType]);\n\n    if (id >= ptr->fileNamesLength) {\n        return -1;\n    }\n\n    strcpy(dest, ptr->fileNames + id * 13);\n\n    return 0;\n}\n\n// 0x419314\nint art_get_code(int animation, int weaponType, char* a3, char* a4)\n{\n    if (weaponType < 0 || weaponType >= WEAPON_ANIMATION_COUNT) {\n        return -1;\n    }\n\n    if (animation >= ANIM_TAKE_OUT && animation <= ANIM_FIRE_CONTINUOUS) {\n        *a4 = 'c' + (animation - ANIM_TAKE_OUT);\n        if (weaponType == WEAPON_ANIMATION_NONE) {\n            return -1;\n        }\n\n        *a3 = 'd' + (weaponType - 1);\n        return 0;\n    } else if (animation == ANIM_PRONE_TO_STANDING) {\n        *a4 = 'h';\n        *a3 = 'c';\n        return 0;\n    } else if (animation == ANIM_BACK_TO_STANDING) {\n        *a4 = 'j';\n        *a3 = 'c';\n        return 0;\n    } else if (animation == ANIM_CALLED_SHOT_PIC) {\n        *a4 = 'a';\n        *a3 = 'n';\n        return 0;\n    } else if (animation >= FIRST_SF_DEATH_ANIM) {\n        *a4 = 'a' + (animation - FIRST_SF_DEATH_ANIM);\n        *a3 = 'r';\n        return 0;\n    } else if (animation >= FIRST_KNOCKDOWN_AND_DEATH_ANIM) {\n        *a4 = 'a' + (animation - FIRST_KNOCKDOWN_AND_DEATH_ANIM);\n        *a3 = 'b';\n        return 0;\n    } else if (animation == ANIM_THROW_ANIM) {\n        if (weaponType == WEAPON_ANIMATION_KNIFE) {\n            // knife\n            *a3 = 'd';\n            *a4 = 'm';\n        } else if (weaponType == WEAPON_ANIMATION_SPEAR) {\n            // spear\n            *a3 = 'g';\n            *a4 = 'm';\n        } else {\n            // other -> probably rock or grenade\n            *a3 = 'a';\n            *a4 = 's';\n        }\n        return 0;\n    } else if (animation == ANIM_DODGE_ANIM) {\n        if (weaponType <= 0) {\n            *a3 = 'a';\n            *a4 = 'n';\n        } else {\n            *a3 = 'd' + (weaponType - 1);\n            *a4 = 'e';\n        }\n        return 0;\n    }\n\n    *a4 = 'a' + animation;\n    if (animation <= ANIM_WALK && weaponType > 0) {\n        *a3 = 'd' + (weaponType - 1);\n        return 0;\n    }\n    *a3 = 'a';\n\n    return 0;\n}\n\n// 0x419428\nchar* art_get_name(int fid)\n{\n    int v1, v2, v3, v4, v5, type, v8, v10;\n    char v9, v11, v12;\n\n    v2 = fid;\n\n    v10 = (fid & 0x70000000) >> 28;\n\n    v1 = art_alias_fid(fid);\n    if (v1 != -1) {\n        v2 = v1;\n    }\n\n    *art_name = '\\0';\n\n    v3 = v2 & 0xFFF;\n    v4 = FID_ANIM_TYPE(v2);\n    v5 = (v2 & 0xF000) >> 12;\n    type = FID_TYPE(v2);\n\n    if (v3 >= art[type].fileNamesLength) {\n        return NULL;\n    }\n\n    if (type < OBJ_TYPE_ITEM || type >= OBJ_TYPE_COUNT) {\n        return NULL;\n    }\n\n    v8 = v3 * 13;\n\n    if (type == 1) {\n        if (art_get_code(v4, v5, &v11, &v12) == -1) {\n            return NULL;\n        }\n        if (v10) {\n            sprintf(art_name, \"%s%s%s\\\\%s%c%c.fr%c\", cd_path_base, \"art\\\\\", art[1].name, art[1].fileNames + v8, v11, v12, v10 + 47);\n        } else {\n            sprintf(art_name, \"%s%s%s\\\\%s%c%c.frm\", cd_path_base, \"art\\\\\", art[1].name, art[1].fileNames + v8, v11, v12);\n        }\n    } else if (type == 8) {\n        v9 = head2[v4];\n        if (v9 == 'f') {\n            sprintf(art_name, \"%s%s%s\\\\%s%c%c%d.frm\", cd_path_base, \"art\\\\\", art[8].name, art[8].fileNames + v8, head1[v4], 102, v5);\n        } else {\n            sprintf(art_name, \"%s%s%s\\\\%s%c%c.frm\", cd_path_base, \"art\\\\\", art[8].name, art[8].fileNames + v8, head1[v4], v9);\n        }\n    } else {\n        sprintf(art_name, \"%s%s%s\\\\%s\", cd_path_base, \"art\\\\\", art[type].name, art[type].fileNames + v8);\n    }\n\n    return art_name;\n}\n\n// art_read_lst\n// 0x419664\nint art_read_lst(const char* path, char** artListPtr, int* artListSizePtr)\n{\n    File* stream = db_fopen(path, \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int count = 0;\n    char string[200];\n    while (db_fgets(string, sizeof(string), stream)) {\n        count++;\n    }\n\n    db_fseek(stream, 0, SEEK_SET);\n\n    *artListSizePtr = count;\n\n    char* artList = (char*)mem_malloc(13 * count);\n    *artListPtr = artList;\n    if (artList == NULL) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    while (db_fgets(string, sizeof(string), stream)) {\n        char* brk = strpbrk(string, \" ,;\\r\\t\\n\");\n        if (brk != NULL) {\n            *brk = '\\0';\n        }\n\n        strncpy(artList, string, 12);\n        artList[12] = '\\0';\n\n        artList += 13;\n    }\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// 0x419760\nint art_frame_fps(Art* art)\n{\n    if (art == NULL) {\n        return 10;\n    }\n\n    return art->framesPerSecond == 0 ? 10 : art->framesPerSecond;\n}\n\n// 0x419778\nint art_frame_action_frame(Art* art)\n{\n    return art == NULL ? -1 : art->actionFrame;\n}\n\n// 0x41978C\nint art_frame_max_frame(Art* art)\n{\n    return art == NULL ? -1 : art->frameCount;\n}\n\n// 0x4197A0\nint art_frame_width(Art* art, int frame, int direction)\n{\n    ArtFrame* frm;\n\n    frm = frame_ptr(art, frame, direction);\n    if (frm == NULL) {\n        return -1;\n    }\n\n    return frm->width;\n}\n\n// 0x4197B8\nint art_frame_length(Art* art, int frame, int direction)\n{\n    ArtFrame* frm;\n\n    frm = frame_ptr(art, frame, direction);\n    if (frm == NULL) {\n        return -1;\n    }\n\n    return frm->height;\n}\n\n// 0x4197D4\nint art_frame_width_length(Art* art, int frame, int direction, int* widthPtr, int* heightPtr)\n{\n    ArtFrame* frm;\n\n    frm = frame_ptr(art, frame, direction);\n    if (frm == NULL) {\n        if (widthPtr != NULL) {\n            *widthPtr = 0;\n        }\n\n        if (heightPtr != NULL) {\n            *heightPtr = 0;\n        }\n\n        return -1;\n    }\n\n    if (widthPtr != NULL) {\n        *widthPtr = frm->width;\n    }\n\n    if (heightPtr != NULL) {\n        *heightPtr = frm->height;\n    }\n\n    return 0;\n}\n\n// 0x419820\nint art_frame_hot(Art* art, int frame, int direction, int* xPtr, int* yPtr)\n{\n    ArtFrame* frm;\n\n    frm = frame_ptr(art, frame, direction);\n    if (frm == NULL) {\n        return -1;\n    }\n\n    *xPtr = frm->x;\n    *yPtr = frm->y;\n\n    return 0;\n}\n\n// 0x41984C\nint art_frame_offset(Art* art, int rotation, int* xPtr, int* yPtr)\n{\n    if (art == NULL) {\n        return -1;\n    }\n\n    *xPtr = art->xOffsets[rotation];\n    *yPtr = art->yOffsets[rotation];\n\n    return 0;\n}\n\n// 0x419870\nunsigned char* art_frame_data(Art* art, int frame, int direction)\n{\n    ArtFrame* frm;\n\n    frm = frame_ptr(art, frame, direction);\n    if (frm == NULL) {\n        return NULL;\n    }\n\n    return (unsigned char*)frm + sizeof(*frm);\n}\n\n// 0x419880\nArtFrame* frame_ptr(Art* art, int frame, int rotation)\n{\n    if (rotation < 0 || rotation >= 6) {\n        return NULL;\n    }\n\n    if (art == NULL) {\n        return NULL;\n    }\n\n    if (frame < 0 || frame >= art->frameCount) {\n        return NULL;\n    }\n\n    ArtFrame* frm = (ArtFrame*)((unsigned char*)art + sizeof(*art) + art->dataOffsets[rotation]);\n    for (int index = 0; index < frame; index++) {\n        frm = (ArtFrame*)((unsigned char*)frm + sizeof(*frm) + frm->size);\n    }\n    return frm;\n}\n\n// 0x4198C8\nbool art_exists(int fid)\n{\n    bool result = false;\n    int oldDb = -1;\n\n    if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {\n        oldDb = db_current();\n        db_select(critter_db_handle);\n    }\n\n    char* filePath = art_get_name(fid);\n    if (filePath != NULL) {\n        int fileSize;\n        if (db_dir_entry(filePath, &fileSize) != -1) {\n            result = true;\n        }\n    }\n\n    if (oldDb != -1) {\n        db_select(oldDb);\n    }\n\n    return result;\n}\n\n// NOTE: Exactly the same implementation as `art_exists`.\n//\n// 0x419930\nbool art_fid_valid(int fid)\n{\n    bool result = false;\n    int oldDb = -1;\n\n    if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {\n        oldDb = db_current();\n        db_select(critter_db_handle);\n    }\n\n    char* filePath = art_get_name(fid);\n    if (filePath != NULL) {\n        int fileSize;\n        if (db_dir_entry(filePath, &fileSize) != -1) {\n            result = true;\n        }\n    }\n\n    if (oldDb != -1) {\n        db_select(oldDb);\n    }\n\n    return result;\n}\n\n// 0x419998\nint art_alias_num(int index)\n{\n    return anon_alias[index];\n}\n\n// 0x4199AC\nint artCritterFidShouldRun(int fid)\n{\n    if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {\n        return artCritterFidShouldRunData[fid & 0xFFF];\n    }\n\n    return 0;\n}\n\n// 0x4199D4\nint art_alias_fid(int fid)\n{\n    int type = FID_TYPE(fid);\n    int anim = FID_ANIM_TYPE(fid);\n    if (type == OBJ_TYPE_CRITTER) {\n        if (anim == ANIM_ELECTRIFY\n            || anim == ANIM_BURNED_TO_NOTHING\n            || anim == ANIM_ELECTRIFIED_TO_NOTHING\n            || anim == ANIM_ELECTRIFY_SF\n            || anim == ANIM_BURNED_TO_NOTHING_SF\n            || anim == ANIM_ELECTRIFIED_TO_NOTHING_SF\n            || anim == ANIM_FIRE_DANCE\n            || anim == ANIM_CALLED_SHOT_PIC) {\n            // NOTE: Original code is slightly different. It uses many mutually\n            // mirrored bitwise operators. Probably result of some macros for\n            // getting/setting individual bits on fid.\n            return (fid & 0x70000000) | ((anim << 16) & 0xFF0000) | 0x1000000 | (fid & 0xF000) | (anon_alias[fid & 0xFFF] & 0xFFF);\n        }\n    }\n\n    return -1;\n}\n\n// 0x419A78\nint art_data_size(int fid, int* sizePtr)\n{\n    int oldDb = -1;\n    int result = -1;\n\n    if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {\n        oldDb = db_current();\n        db_select(critter_db_handle);\n    }\n\n    char* artFilePath = art_get_name(fid);\n    if (artFilePath != NULL) {\n        int fileSize;\n        bool loaded = false;\n\n        if (darn_foreigners) {\n            char* pch = strchr(artFilePath, '\\\\');\n            if (pch == NULL) {\n                pch = artFilePath;\n            }\n\n            char localizedPath[MAX_PATH];\n            sprintf(localizedPath, \"art\\\\%s\\\\%s\", darn_foreign_sub_path, pch);\n\n            if (db_dir_entry(localizedPath, &fileSize) == 0) {\n                loaded = true;\n            }\n        }\n\n        if (!loaded) {\n            if (db_dir_entry(artFilePath, &fileSize) == 0) {\n                loaded = true;\n            }\n        }\n\n        if (loaded) {\n            *sizePtr = fileSize;\n            result = 0;\n        }\n    }\n\n    if (oldDb != -1) {\n        db_select(oldDb);\n    }\n\n    return result;\n}\n\n// 0x419B78\nint art_data_load(int fid, int* sizePtr, unsigned char* data)\n{\n    int oldDb = -1;\n    int result = -1;\n\n    if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) {\n        oldDb = db_current();\n        db_select(critter_db_handle);\n    }\n\n    char* artFileName = art_get_name(fid);\n    if (artFileName != NULL) {\n        bool loaded = false;\n        if (darn_foreigners) {\n            char* pch = strchr(artFileName, '\\\\');\n            if (pch == NULL) {\n                pch = artFileName;\n            }\n\n            char localizedPath[MAX_PATH];\n            sprintf(localizedPath, \"art\\\\%s\\\\%s\", darn_foreign_sub_path, pch);\n\n            if (load_frame_into(localizedPath, data) == 0) {\n                loaded = true;\n            }\n        }\n\n        if (!loaded) {\n            if (load_frame_into(artFileName, data) == 0) {\n                loaded = true;\n            }\n        }\n\n        if (loaded) {\n            // TODO: Why it adds 74?\n            *sizePtr = ((Art*)data)->field_3A + 74;\n            result = 0;\n        }\n    }\n\n    if (oldDb != -1) {\n        db_select(oldDb);\n    }\n\n    return result;\n}\n\n// 0x419C80\nvoid art_data_free(void* ptr)\n{\n    mem_free(ptr);\n}\n\n// 0x419C88\nint art_id(int objectType, int frmId, int animType, int a3, int rotation)\n{\n    int v7, v8, v9, v10;\n\n    v10 = rotation;\n\n    if (objectType != OBJ_TYPE_CRITTER) {\n        goto zero;\n    }\n\n    if (animType == ANIM_FIRE_DANCE || animType < ANIM_FALL_BACK || animType > ANIM_FALL_FRONT_BLOOD) {\n        goto zero;\n    }\n\n    v7 = ((a3 << 12) & 0xF000) | ((animType << 16) & 0xFF0000) | 0x1000000;\n    v8 = ((rotation << 28) & 0x70000000) | v7;\n    v9 = frmId & 0xFFF;\n\n    if (art_exists(v9 | v8) != 0) {\n        goto out;\n    }\n\n    if (objectType == rotation) {\n        goto zero;\n    }\n\n    v10 = objectType;\n    if (art_exists(v9 | v7 | 0x10000000) != 0) {\n        goto out;\n    }\n\nzero:\n\n    v10 = 0;\n\nout:\n\n    return ((v10 << 28) & 0x70000000) | (objectType << 24) | ((animType << 16) & 0xFF0000) | ((a3 << 12) & 0xF000) | (frmId & 0xFFF);\n}\n"
  },
  {
    "path": "src/game/art.h",
    "content": "#ifndef FALLOUT_GAME_ART_H_\n#define FALLOUT_GAME_ART_H_\n\n#include \"game/cache.h\"\n#include \"game/heap.h\"\n#include \"game/object_types.h\"\n#include \"game/proto_types.h\"\n\ntypedef enum Head {\n    HEAD_INVALID,\n    HEAD_MARCUS,\n    HEAD_MYRON,\n    HEAD_ELDER,\n    HEAD_LYNETTE,\n    HEAD_HAROLD,\n    HEAD_TANDI,\n    HEAD_COM_OFFICER,\n    HEAD_SULIK,\n    HEAD_PRESIDENT,\n    HEAD_HAKUNIN,\n    HEAD_BOSS,\n    HEAD_DYING_HAKUNIN,\n    HEAD_COUNT,\n} Head;\n\ntypedef enum HeadAnimation {\n    HEAD_ANIMATION_VERY_GOOD_REACTION = 0,\n    FIDGET_GOOD = 1,\n    HEAD_ANIMATION_GOOD_TO_NEUTRAL = 2,\n    HEAD_ANIMATION_NEUTRAL_TO_GOOD = 3,\n    FIDGET_NEUTRAL = 4,\n    HEAD_ANIMATION_NEUTRAL_TO_BAD = 5,\n    HEAD_ANIMATION_BAD_TO_NEUTRAL = 6,\n    FIDGET_BAD = 7,\n    HEAD_ANIMATION_VERY_BAD_REACTION = 8,\n    HEAD_ANIMATION_GOOD_PHONEMES = 9,\n    HEAD_ANIMATION_NEUTRAL_PHONEMES = 10,\n    HEAD_ANIMATION_BAD_PHONEMES = 11,\n} HeadAnimation;\n\ntypedef enum Background {\n    BACKGROUND_0,\n    BACKGROUND_1,\n    BACKGROUND_2,\n    BACKGROUND_HUB,\n    BACKGROUND_NECROPOLIS,\n    BACKGROUND_BROTHERHOOD,\n    BACKGROUND_MILITARY_BASE,\n    BACKGROUND_JUNK_TOWN,\n    BACKGROUND_CATHEDRAL,\n    BACKGROUND_SHADY_SANDS,\n    BACKGROUND_VAULT,\n    BACKGROUND_MASTER,\n    BACKGROUND_FOLLOWER,\n    BACKGROUND_RAIDERS,\n    BACKGROUND_CAVE,\n    BACKGROUND_ENCLAVE,\n    BACKGROUND_WASTELAND,\n    BACKGROUND_BOSS,\n    BACKGROUND_PRESIDENT,\n    BACKGROUND_TENT,\n    BACKGROUND_ADOBE,\n    BACKGROUND_COUNT,\n} Background;\n\n#pragma pack(2)\ntypedef struct Art {\n    int field_0;\n    short framesPerSecond;\n    short actionFrame;\n    short frameCount;\n    short xOffsets[6];\n    short yOffsets[6];\n    int dataOffsets[6];\n    int field_3A;\n} Art;\n#pragma pack()\n\nstatic_assert(sizeof(Art) == 62, \"wrong size\");\n\ntypedef struct ArtFrame {\n    short width;\n    short height;\n    int size;\n    short x;\n    short y;\n} ArtFrame;\n\ntypedef struct ArtListDescription {\n    int flags;\n    char name[16];\n    char* fileNames; // dynamic array of null terminated strings 13 bytes long each\n    void* field_18;\n    int fileNamesLength; // number of entries in list\n} ArtListDescription;\n\ntypedef struct HeadDescription {\n    int goodFidgetCount;\n    int neutralFidgetCount;\n    int badFidgetCount;\n} HeadDescription;\n\ntypedef enum WeaponAnimation {\n    WEAPON_ANIMATION_NONE,\n    WEAPON_ANIMATION_KNIFE, // d\n    WEAPON_ANIMATION_CLUB, // e\n    WEAPON_ANIMATION_HAMMER, // f\n    WEAPON_ANIMATION_SPEAR, // g\n    WEAPON_ANIMATION_PISTOL, // h\n    WEAPON_ANIMATION_SMG, // i\n    WEAPON_ANIMATION_SHOTGUN, // j\n    WEAPON_ANIMATION_LASER_RIFLE, // k\n    WEAPON_ANIMATION_MINIGUN, // l\n    WEAPON_ANIMATION_LAUNCHER, // m\n    WEAPON_ANIMATION_COUNT,\n} WeaponAnimation;\n\ntypedef enum DudeNativeLook {\n    // Hero looks as one the tribals (before finishing Temple of Trails).\n    DUDE_NATIVE_LOOK_TRIBAL,\n\n    // Hero have finished Temple of Trails and received Vault Jumpsuit.\n    DUDE_NATIVE_LOOK_JUMPSUIT,\n    DUDE_NATIVE_LOOK_COUNT,\n} DudeNativeLook;\n\nextern int art_vault_guy_num;\nextern int art_vault_person_nums[DUDE_NATIVE_LOOK_COUNT][GENDER_COUNT];\nextern int art_mapper_blank_tile;\n\nextern Cache art_cache;\nextern HeadDescription* head_info;\nextern int* anon_alias;\nextern int* artCritterFidShouldRunData;\n\nint art_init();\nvoid art_reset();\nvoid art_exit();\nchar* art_dir(int objectType);\nint art_get_disable(int objectType);\nvoid art_toggle_disable(int objectType);\nint art_total(int objectType);\nint art_head_fidgets(int headFid);\nvoid scale_art(int fid, unsigned char* dest, int width, int height, int pitch);\nArt* art_ptr_lock(int fid, CacheEntry** cache_entry);\nunsigned char* art_ptr_lock_data(int fid, int frame, int direction, CacheEntry** out_cache_entry);\nunsigned char* art_lock(int fid, CacheEntry** out_cache_entry, int* widthPtr, int* heightPtr);\nint art_ptr_unlock(CacheEntry* cache_entry);\nint art_discard(int fid);\nint art_flush();\nint art_get_base_name(int objectType, int a2, char* a3);\nint art_get_code(int a1, int a2, char* a3, char* a4);\nchar* art_get_name(int a1);\nint art_read_lst(const char* path, char** artListPtr, int* artListSizePtr);\nint art_frame_fps(Art* art);\nint art_frame_action_frame(Art* art);\nint art_frame_max_frame(Art* art);\nint art_frame_width(Art* art, int frame, int direction);\nint art_frame_length(Art* art, int frame, int direction);\nint art_frame_width_length(Art* art, int frame, int direction, int* out_width, int* out_height);\nint art_frame_hot(Art* art, int frame, int direction, int* a4, int* a5);\nint art_frame_offset(Art* art, int rotation, int* out_offset_x, int* out_offset_y);\nunsigned char* art_frame_data(Art* art, int frame, int direction);\nArtFrame* frame_ptr(Art* art, int frame, int direction);\nbool art_exists(int fid);\nbool art_fid_valid(int fid);\nint art_alias_num(int a1);\nint artCritterFidShouldRun(int a1);\nint art_alias_fid(int fid);\nint art_data_size(int a1, int* out_size);\nint art_data_load(int a1, int* a2, unsigned char* data);\nvoid art_data_free(void* ptr);\nint art_id(int objectType, int frmId, int animType, int a4, int rotation);\n\n#endif /* FALLOUT_GAME_ART_H_ */\n"
  },
  {
    "path": "src/game/artload.c",
    "content": "#include \"game/artload.h\"\n\n#include \"plib/gnw/memory.h\"\n\nstatic int art_readSubFrameData(unsigned char* data, File* stream, int count);\nstatic int art_readFrameData(Art* art, File* stream);\n\n// 0x419D60\nstatic int art_readSubFrameData(unsigned char* data, File* stream, int count)\n{\n    unsigned char* ptr = data;\n    for (int index = 0; index < count; index++) {\n        ArtFrame* frame = (ArtFrame*)ptr;\n\n        if (db_freadShort(stream, &(frame->width)) == -1) return -1;\n        if (db_freadShort(stream, &(frame->height)) == -1) return -1;\n        if (db_freadInt(stream, &(frame->size)) == -1) return -1;\n        if (db_freadShort(stream, &(frame->x)) == -1) return -1;\n        if (db_freadShort(stream, &(frame->y)) == -1) return -1;\n        if (db_fread(ptr + sizeof(ArtFrame), frame->size, 1, stream) != 1) return -1;\n\n        ptr += sizeof(ArtFrame) + frame->size;\n    }\n\n    return 0;\n}\n\n// 0x419E1C\nstatic int art_readFrameData(Art* art, File* stream)\n{\n    if (db_freadInt(stream, &(art->field_0)) == -1) return -1;\n    if (db_freadShort(stream, &(art->framesPerSecond)) == -1) return -1;\n    if (db_freadShort(stream, &(art->actionFrame)) == -1) return -1;\n    if (db_freadShort(stream, &(art->frameCount)) == -1) return -1;\n    if (db_freadShortCount(stream, art->xOffsets, ROTATION_COUNT) == -1) return -1;\n    if (db_freadShortCount(stream, art->yOffsets, ROTATION_COUNT) == -1) return -1;\n    if (db_freadIntCount(stream, art->dataOffsets, ROTATION_COUNT) == -1) return -1;\n    if (db_freadInt(stream, &(art->field_3A)) == -1) return -1;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x419EC0\nint load_frame(const char* path, Art** artPtr)\n{\n    int size;\n    File* stream;\n    int index;\n\n    if (db_dir_entry(path, &size) == -1) {\n        return -2;\n    }\n\n    *artPtr = (Art*)mem_malloc(size);\n    if (*artPtr == NULL) {\n        return -1;\n    }\n\n    stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        return -2;\n    }\n\n    if (art_readFrameData(*artPtr, stream) != 0) {\n        db_fclose(stream);\n        mem_free(*artPtr);\n        return -3;\n    }\n\n    for (index = 0; index < ROTATION_COUNT; index++) {\n        if (index == 0 || (*artPtr)->dataOffsets[index - 1] != (*artPtr)->dataOffsets[index]) {\n            if (art_readSubFrameData((unsigned char*)(*artPtr) + sizeof(Art) + (*artPtr)->dataOffsets[index], stream, (*artPtr)->frameCount) != 0) {\n                break;\n            }\n        }\n    }\n\n    if (index < ROTATION_COUNT) {\n        db_fclose(stream);\n        mem_free(*artPtr);\n        return -5;\n    }\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// 0x419FC0\nint load_frame_into(const char* path, unsigned char* data)\n{\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        return -2;\n    }\n\n    Art* art = (Art*)data;\n    if (art_readFrameData(art, stream) != 0) {\n        db_fclose(stream);\n        return -3;\n    }\n\n    for (int index = 0; index < ROTATION_COUNT; index++) {\n        if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) {\n            if (art_readSubFrameData(data + sizeof(Art) + art->dataOffsets[index], stream, art->frameCount) != 0) {\n                db_fclose(stream);\n                return -5;\n            }\n        }\n    }\n\n    db_fclose(stream);\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x41A070\nint art_writeSubFrameData(unsigned char* data, File* stream, int count)\n{\n    unsigned char* ptr = data;\n    for (int index = 0; index < count; index++) {\n        ArtFrame* frame = (ArtFrame*)ptr;\n\n        if (db_fwriteShort(stream, frame->width) == -1) return -1;\n        if (db_fwriteShort(stream, frame->height) == -1) return -1;\n        if (db_fwriteInt(stream, frame->size) == -1) return -1;\n        if (db_fwriteShort(stream, frame->x) == -1) return -1;\n        if (db_fwriteShort(stream, frame->y) == -1) return -1;\n        if (db_fwrite(ptr + sizeof(ArtFrame), frame->size, 1, stream) != 1) return -1;\n\n        ptr += sizeof(ArtFrame) + frame->size;\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x41A138\nint art_writeFrameData(Art* art, File* stream)\n{\n    if (db_fwriteInt(stream, art->field_0) == -1) return -1;\n    if (db_fwriteShort(stream, art->framesPerSecond) == -1) return -1;\n    if (db_fwriteShort(stream, art->actionFrame) == -1) return -1;\n    if (db_fwriteShort(stream, art->frameCount) == -1) return -1;\n    if (db_fwriteShortCount(stream, art->xOffsets, ROTATION_COUNT) == -1) return -1;\n    if (db_fwriteShortCount(stream, art->yOffsets, ROTATION_COUNT) == -1) return -1;\n    if (db_fwriteIntCount(stream, art->dataOffsets, ROTATION_COUNT) == -1) return -1;\n    if (db_fwriteInt(stream, art->field_3A) == -1) return -1;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x41A1E8\nint save_frame(const char* path, unsigned char* data)\n{\n    if (data == NULL) {\n        return -1;\n    }\n\n    File* stream = db_fopen(path, \"wb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    Art* art = (Art*)data;\n    if (art_writeFrameData(art, stream) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    for (int index = 0; index < ROTATION_COUNT; index++) {\n        if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) {\n            if (art_writeSubFrameData(data + sizeof(Art) + art->dataOffsets[index], stream, art->frameCount) != 0) {\n                db_fclose(stream);\n                return -1;\n            }\n        }\n    }\n\n    db_fclose(stream);\n    return 0;\n}\n"
  },
  {
    "path": "src/game/artload.h",
    "content": "#ifndef FALLOUT_GAME_ARTLOAD_H_\n#define FALLOUT_GAME_ARTLOAD_H_\n\n#include \"game/art.h\"\n#include \"plib/db/db.h\"\n\nint load_frame(const char* path, Art** artPtr);\nint load_frame_into(const char* path, unsigned char* data);\nint art_writeSubFrameData(unsigned char* data, File* stream, int count);\nint art_writeFrameData(Art* art, File* stream);\nint save_frame(const char* path, unsigned char* data);\n\n#endif /* FALLOUT_GAME_ARTLOAD_H_ */\n"
  },
  {
    "path": "src/game/automap.c",
    "content": "#include \"game/automap.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"plib/color/color.h\"\n#include \"game/config.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/bmpdlog.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"game/graphlib.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define AUTOMAP_OFFSET_COUNT (AUTOMAP_MAP_COUNT * ELEVATION_COUNT)\n\n#define AUTOMAP_WINDOW_X 75\n#define AUTOMAP_WINDOW_Y 0\n#define AUTOMAP_WINDOW_WIDTH 519\n#define AUTOMAP_WINDOW_HEIGHT 480\n\n#define AUTOMAP_PIPBOY_VIEW_X 238\n#define AUTOMAP_PIPBOY_VIEW_Y 105\n\n// View options for rendering automap for map window. These are stored in\n// [autoflags] and is saved in save game file.\ntypedef enum AutomapFlags {\n    // NOTE: This is a special flag to denote the map is activated in the game (as\n    // opposed to the mapper). It's always on. Turning it off produces nice color\n    // coded map with all objects and their types visible, however there is no way\n    // you can do it within the game UI.\n    AUTOMAP_IN_GAME = 0x01,\n\n    // High details is on.\n    AUTOMAP_WTH_HIGH_DETAILS = 0x02,\n\n    // Scanner is active.\n    AUTOMAP_WITH_SCANNER = 0x04,\n} AutomapFlags;\n\ntypedef enum AutomapFrm {\n    AUTOMAP_FRM_BACKGROUND,\n    AUTOMAP_FRM_BUTTON_UP,\n    AUTOMAP_FRM_BUTTON_DOWN,\n    AUTOMAP_FRM_SWITCH_UP,\n    AUTOMAP_FRM_SWITCH_DOWN,\n    AUTOMAP_FRM_COUNT,\n} AutomapFrm;\n\nstatic void draw_top_down_map(int window, int elevation, unsigned char* backgroundData, int flags);\nstatic int WriteAM_Entry(File* stream);\nstatic int AM_ReadEntry(int map, int elevation);\nstatic int WriteAM_Header(File* stream);\nstatic int AM_ReadMainHeader(File* stream);\nstatic void decode_map_data(int elevation);\nstatic int am_pip_init();\nstatic int copy_file_data(File* stream1, File* stream2, int length);\n\n// 0x41ADE0\nstatic const int defam[AUTOMAP_MAP_COUNT][ELEVATION_COUNT] = {\n    { -1, -1, -1 },\n    { -1, -1, -1 },\n    { -1, -1, -1 },\n};\n\n// 0x41B560\nstatic const int displayMapList[AUTOMAP_MAP_COUNT] = {\n    -1,\n    -1,\n    -1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    -1,\n    -1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    -1,\n    -1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    0,\n    0,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    0,\n    0,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    0,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    0,\n    -1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    0,\n    0,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n    -1,\n};\n\n// 0x5108C4\nstatic int autoflags = 0;\n\n// 0x56CB18\nstatic AutomapHeader amdbhead;\n\n// 0x56D2A0\nstatic AutomapEntry amdbsubhead;\n\n// 0x56D2A8\nstatic unsigned char* cmpbuf;\n\n// 0x56D2A8\nstatic unsigned char* ambuf;\n\n// automap_init\n// 0x41B7F4\nint automap_init()\n{\n    autoflags = 0;\n    am_pip_init();\n    return 0;\n}\n\n// 0x41B808\nint automap_reset()\n{\n    autoflags = 0;\n    am_pip_init();\n    return 0;\n}\n\n// 0x41B81C\nvoid automap_exit()\n{\n    char* masterPatchesPath;\n    if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) {\n        char path[MAX_PATH];\n        sprintf(path, \"%s\\\\%s\\\\%s\", masterPatchesPath, \"MAPS\", AUTOMAP_DB);\n        remove(path);\n    }\n}\n\n// 0x41B87C\nint automap_load(File* stream)\n{\n    return db_freadInt(stream, &autoflags);\n}\n\n// 0x41B898\nint automap_save(File* stream)\n{\n    return db_fwriteInt(stream, autoflags);\n}\n\n// 0x41B8B4\nint automapDisplayMap(int map)\n{\n    return displayMapList[map];\n}\n\n// 0x41B8BC\nvoid automap(bool isInGame, bool isUsingScanner)\n{\n    // 0x41B7E0\n    static const int frmIds[AUTOMAP_FRM_COUNT] = {\n        171, // automap.frm - automap window\n        8, // lilredup.frm - little red button up\n        9, // lilreddn.frm - little red button down\n        172, // autoup.frm - switch up\n        173, // autodwn.frm - switch down\n    };\n\n    unsigned char* frmData[AUTOMAP_FRM_COUNT];\n    CacheEntry* frmHandle[AUTOMAP_FRM_COUNT];\n    for (int index = 0; index < AUTOMAP_FRM_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, frmIds[index], 0, 0, 0);\n        frmData[index] = art_ptr_lock_data(fid, 0, 0, &(frmHandle[index]));\n        if (frmData[index] == NULL) {\n            while (--index >= 0) {\n                art_ptr_unlock(frmHandle[index]);\n            }\n            return;\n        }\n    }\n\n    int color;\n    if (isInGame) {\n        color = colorTable[8456];\n        obj_process_seen();\n    } else {\n        color = colorTable[22025];\n    }\n\n    int oldFont = text_curr();\n    text_font(101);\n\n    int automapWindowX = AUTOMAP_WINDOW_X;\n    int automapWindowY = AUTOMAP_WINDOW_Y;\n    int window = win_add(automapWindowX, automapWindowY, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n\n    int scannerBtn = win_register_button(window, 111, 454, 15, 16, -1, -1, -1, KEY_LOWERCASE_S, frmData[AUTOMAP_FRM_BUTTON_UP], frmData[AUTOMAP_FRM_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT);\n    if (scannerBtn != -1) {\n        win_register_button_sound_func(scannerBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int cancelBtn = win_register_button(window, 277, 454, 15, 16, -1, -1, -1, KEY_ESCAPE, frmData[AUTOMAP_FRM_BUTTON_UP], frmData[AUTOMAP_FRM_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT);\n    if (cancelBtn != -1) {\n        win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int switchBtn = win_register_button(window, 457, 340, 42, 74, -1, -1, KEY_LOWERCASE_L, KEY_LOWERCASE_H, frmData[AUTOMAP_FRM_SWITCH_UP], frmData[AUTOMAP_FRM_SWITCH_DOWN], NULL, BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x01);\n    if (switchBtn != -1) {\n        win_register_button_sound_func(switchBtn, gsound_toggle_butt_press, gsound_toggle_butt_release);\n    }\n\n    if ((autoflags & AUTOMAP_WTH_HIGH_DETAILS) == 0) {\n        win_set_button_rest_state(switchBtn, 1, 0);\n    }\n\n    int elevation = map_elevation;\n\n    autoflags &= AUTOMAP_WTH_HIGH_DETAILS;\n\n    if (isInGame) {\n        autoflags |= AUTOMAP_IN_GAME;\n    }\n\n    if (isUsingScanner) {\n        autoflags |= AUTOMAP_WITH_SCANNER;\n    }\n\n    draw_top_down_map(window, elevation, frmData[AUTOMAP_FRM_BACKGROUND], autoflags);\n\n    bool isoWasEnabled = map_disable_bk_processes();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    bool done = false;\n    while (!done) {\n        bool needsRefresh = false;\n\n        // FIXME: There is minor bug in the interface - pressing H/L to toggle\n        // high/low details does not update switch state.\n        int keyCode = get_input();\n        switch (keyCode) {\n        case KEY_TAB:\n        case KEY_ESCAPE:\n        case KEY_UPPERCASE_A:\n        case KEY_LOWERCASE_A:\n            done = true;\n            break;\n        case KEY_UPPERCASE_H:\n        case KEY_LOWERCASE_H:\n            if ((autoflags & AUTOMAP_WTH_HIGH_DETAILS) == 0) {\n                autoflags |= AUTOMAP_WTH_HIGH_DETAILS;\n                needsRefresh = true;\n            }\n            break;\n        case KEY_UPPERCASE_L:\n        case KEY_LOWERCASE_L:\n            if ((autoflags & AUTOMAP_WTH_HIGH_DETAILS) != 0) {\n                autoflags &= ~AUTOMAP_WTH_HIGH_DETAILS;\n                needsRefresh = true;\n            }\n            break;\n        case KEY_UPPERCASE_S:\n        case KEY_LOWERCASE_S:\n            if (elevation != map_elevation) {\n                elevation = map_elevation;\n                needsRefresh = true;\n            }\n\n            if ((autoflags & AUTOMAP_WITH_SCANNER) == 0) {\n                Object* scanner = NULL;\n\n                Object* item1 = inven_left_hand(obj_dude);\n                if (item1 != NULL && item1->pid == PROTO_ID_MOTION_SENSOR) {\n                    scanner = item1;\n                } else {\n                    Object* item2 = inven_right_hand(obj_dude);\n                    if (item2 != NULL && item2->pid == PROTO_ID_MOTION_SENSOR) {\n                        scanner = item2;\n                    }\n                }\n\n                if (scanner != NULL && item_m_curr_charges(scanner) > 0) {\n                    needsRefresh = true;\n                    autoflags |= AUTOMAP_WITH_SCANNER;\n                    item_m_dec_charges(scanner);\n                } else {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    MessageListItem messageListItem;\n                    // 17 - The motion sensor is not installed.\n                    // 18 - The motion sensor has no charges remaining.\n                    const char* title = getmsg(&misc_message_file, &messageListItem, scanner != NULL ? 18 : 17);\n                    dialog_out(title, NULL, 0, 165, 140, colorTable[32328], NULL, colorTable[32328], 0);\n                }\n            }\n\n            break;\n        case KEY_CTRL_Q:\n        case KEY_ALT_X:\n        case KEY_F10:\n            game_quit_with_confirm();\n            break;\n        case KEY_F12:\n            dump_screen();\n            break;\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        if (needsRefresh) {\n            draw_top_down_map(window, elevation, frmData[AUTOMAP_FRM_BACKGROUND], autoflags);\n            needsRefresh = false;\n        }\n    }\n\n    if (isoWasEnabled) {\n        map_enable_bk_processes();\n    }\n\n    win_delete(window);\n    text_font(oldFont);\n\n    for (int index = 0; index < AUTOMAP_FRM_COUNT; index++) {\n        art_ptr_unlock(frmHandle[index]);\n    }\n}\n\n// Renders automap in Map window.\n//\n// 0x41BD1C\nstatic void draw_top_down_map(int window, int elevation, unsigned char* backgroundData, int flags)\n{\n    int color;\n    if ((flags & AUTOMAP_IN_GAME) != 0) {\n        color = colorTable[8456];\n    } else {\n        color = colorTable[22025];\n    }\n\n    win_fill(window, 0, 0, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color);\n    win_border(window);\n\n    unsigned char* windowBuffer = win_get_buf(window);\n    buf_to_buf(backgroundData, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, AUTOMAP_WINDOW_WIDTH, windowBuffer, AUTOMAP_WINDOW_WIDTH);\n\n    for (Object* object = obj_find_first_at(elevation); object != NULL; object = obj_find_next_at()) {\n        if (object->tile == -1) {\n            continue;\n        }\n\n        int objectType = FID_TYPE(object->fid);\n        unsigned char objectColor;\n\n        if ((flags & AUTOMAP_IN_GAME) != 0) {\n            if (objectType == OBJ_TYPE_CRITTER\n                && (object->flags & OBJECT_HIDDEN) == 0\n                && (flags & AUTOMAP_WITH_SCANNER) != 0\n                && (object->data.critter.combat.results & DAM_DEAD) == 0) {\n                objectColor = colorTable[31744];\n            } else {\n                if ((object->flags & OBJECT_SEEN) == 0) {\n                    continue;\n                }\n\n                if (object->pid == PROTO_ID_0x2000031) {\n                    objectColor = colorTable[32328];\n                } else if (objectType == OBJ_TYPE_WALL) {\n                    objectColor = colorTable[992];\n                } else if (objectType == OBJ_TYPE_SCENERY\n                    && (flags & AUTOMAP_WTH_HIGH_DETAILS) != 0\n                    && object->pid != PROTO_ID_0x2000158) {\n                    objectColor = colorTable[480];\n                } else if (object == obj_dude) {\n                    objectColor = colorTable[31744];\n                } else {\n                    objectColor = colorTable[0];\n                }\n            }\n        }\n\n        int v10 = -2 * (object->tile % 200) - 10 + AUTOMAP_WINDOW_WIDTH * (2 * (object->tile / 200) + 9) - 60;\n        if ((flags & AUTOMAP_IN_GAME) == 0) {\n            switch (objectType) {\n            case OBJ_TYPE_ITEM:\n                objectColor = colorTable[6513];\n                break;\n            case OBJ_TYPE_CRITTER:\n                objectColor = colorTable[28672];\n                break;\n            case OBJ_TYPE_SCENERY:\n                objectColor = colorTable[448];\n                break;\n            case OBJ_TYPE_WALL:\n                objectColor = colorTable[12546];\n                break;\n            case OBJ_TYPE_MISC:\n                objectColor = colorTable[31650];\n                break;\n            default:\n                objectColor = colorTable[0];\n            }\n        }\n\n        if (objectColor != colorTable[0]) {\n            unsigned char* v12 = windowBuffer + v10;\n            if ((flags & AUTOMAP_IN_GAME) != 0) {\n                if (*v12 != colorTable[992] || objectColor != colorTable[480]) {\n                    v12[0] = objectColor;\n                    v12[1] = objectColor;\n                }\n\n                if (object == obj_dude) {\n                    v12[-1] = objectColor;\n                    v12[-AUTOMAP_WINDOW_WIDTH] = objectColor;\n                    v12[AUTOMAP_WINDOW_WIDTH] = objectColor;\n                }\n            } else {\n                v12[0] = objectColor;\n                v12[1] = objectColor;\n                v12[AUTOMAP_WINDOW_WIDTH] = objectColor;\n                v12[AUTOMAP_WINDOW_WIDTH + 1] = objectColor;\n\n                v12[AUTOMAP_WINDOW_WIDTH - 1] = objectColor;\n                v12[AUTOMAP_WINDOW_WIDTH + 2] = objectColor;\n                v12[AUTOMAP_WINDOW_WIDTH * 2] = objectColor;\n                v12[AUTOMAP_WINDOW_WIDTH * 2 + 1] = objectColor;\n            }\n        }\n    }\n\n    int textColor;\n    if ((flags & AUTOMAP_IN_GAME) != 0) {\n        textColor = colorTable[992];\n    } else {\n        textColor = colorTable[12546];\n    }\n\n    if (map_get_index_number() != -1) {\n        char* areaName = map_get_short_name(map_get_index_number());\n        win_print(window, areaName, 240, 150, 380, textColor | 0x2000000);\n\n        char* mapName = map_get_elev_idx(map_get_index_number(), elevation);\n        win_print(window, mapName, 240, 150, 396, textColor | 0x2000000);\n    }\n\n    win_draw(window);\n}\n\n// Renders automap in Pipboy window.\n//\n// 0x41C004\nint draw_top_down_map_pipboy(int window, int map, int elevation)\n{\n    unsigned char* windowBuffer = win_get_buf(window) + 640 * AUTOMAP_PIPBOY_VIEW_Y + AUTOMAP_PIPBOY_VIEW_X;\n\n    unsigned char wallColor = colorTable[992];\n    unsigned char sceneryColor = colorTable[480];\n\n    ambuf = (unsigned char*)mem_malloc(11024);\n    if (ambuf == NULL) {\n        debug_printf(\"\\nAUTOMAP: Error allocating data buffer!\\n\");\n        return -1;\n    }\n\n    if (AM_ReadEntry(map, elevation) == -1) {\n        mem_free(ambuf);\n        return -1;\n    }\n\n    int v1 = 0;\n    unsigned char v2 = 0;\n    unsigned char* ptr = ambuf;\n\n    // FIXME: This loop is implemented incorrectly. Automap requires 400x400 px,\n    // but it's top offset is 105, which gives max y 505. It only works because\n    // lower portions of automap data contains zeroes. If it doesn't this loop\n    // will try to set pixels outside of window buffer, which usually leads to\n    // crash.\n    for (int y = 0; y < HEX_GRID_HEIGHT; y++) {\n        for (int x = 0; x < HEX_GRID_WIDTH; x++) {\n            v1 -= 1;\n            if (v1 <= 0) {\n                v1 = 4;\n                v2 = *ptr++;\n            }\n\n            switch ((v2 & 0xC0) >> 6) {\n            case 1:\n                *windowBuffer++ = wallColor;\n                *windowBuffer++ = wallColor;\n                break;\n            case 2:\n                *windowBuffer++ = sceneryColor;\n                *windowBuffer++ = sceneryColor;\n                break;\n            default:\n                windowBuffer += 2;\n                break;\n            }\n\n            v2 <<= 2;\n        }\n\n        windowBuffer += 640 + 240;\n    }\n\n    mem_free(ambuf);\n\n    return 0;\n}\n\n// automap_pip_save\n// 0x41C0F0\nint automap_pip_save()\n{\n    int map = map_get_index_number();\n    int elevation = map_elevation;\n\n    int entryOffset = amdbhead.offsets[map][elevation];\n    if (entryOffset < 0) {\n        return 0;\n    }\n\n    debug_printf(\"\\nAUTOMAP: Saving AutoMap DB index %d, level %d\\n\", map, elevation);\n\n    bool dataBuffersAllocated = false;\n    ambuf = (unsigned char*)mem_malloc(11024);\n    if (ambuf != NULL) {\n        cmpbuf = (unsigned char*)mem_malloc(11024);\n        if (cmpbuf != NULL) {\n            dataBuffersAllocated = true;\n        }\n    }\n\n    if (!dataBuffersAllocated) {\n        // FIXME: Leaking ambuf.\n        debug_printf(\"\\nAUTOMAP: Error allocating data buffers!\\n\");\n        return -1;\n    }\n\n    // NOTE: Not sure about the size.\n    char path[256];\n    sprintf(path, \"%s\\\\%s\", \"MAPS\", AUTOMAP_DB);\n\n    File* stream1 = db_fopen(path, \"r+b\");\n    if (stream1 == NULL) {\n        debug_printf(\"\\nAUTOMAP: Error opening automap database file!\\n\");\n        debug_printf(\"Error continued: automap_pip_save: path: %s\", path);\n        mem_free(ambuf);\n        mem_free(cmpbuf);\n        return -1;\n    }\n\n    if (AM_ReadMainHeader(stream1) == -1) {\n        debug_printf(\"\\nAUTOMAP: Error reading automap database file header!\\n\");\n        mem_free(ambuf);\n        mem_free(cmpbuf);\n        db_fclose(stream1);\n        return -1;\n    }\n\n    decode_map_data(elevation);\n\n    int compressedDataSize = CompLZS(ambuf, cmpbuf, 10000);\n    if (compressedDataSize == -1) {\n        amdbsubhead.dataSize = 10000;\n        amdbsubhead.isCompressed = 0;\n    } else {\n        amdbsubhead.dataSize = compressedDataSize;\n        amdbsubhead.isCompressed = 1;\n    }\n\n    if (entryOffset != 0) {\n        sprintf(path, \"%s\\\\%s\", \"MAPS\", AUTOMAP_TMP);\n\n        File* stream2 = db_fopen(path, \"wb\");\n        if (stream2 == NULL) {\n            debug_printf(\"\\nAUTOMAP: Error creating temp file!\\n\");\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            db_fclose(stream1);\n            return -1;\n        }\n\n        db_rewind(stream1);\n\n        if (copy_file_data(stream1, stream2, entryOffset) == -1) {\n            debug_printf(\"\\nAUTOMAP: Error copying file data!\\n\");\n            db_fclose(stream1);\n            db_fclose(stream2);\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            return -1;\n        }\n\n        if (WriteAM_Entry(stream2) == -1) {\n            db_fclose(stream1);\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            return -1;\n        }\n\n        int nextEntryDataSize;\n        if (db_freadLong(stream1, &nextEntryDataSize) == -1) {\n            debug_printf(\"\\nAUTOMAP: Error reading database #1!\\n\");\n            db_fclose(stream1);\n            db_fclose(stream2);\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            return -1;\n        }\n\n        int automapDataSize = db_filelength(stream1);\n        if (automapDataSize == -1) {\n            debug_printf(\"\\nAUTOMAP: Error reading database #2!\\n\");\n            db_fclose(stream1);\n            db_fclose(stream2);\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            return -1;\n        }\n\n        int nextEntryOffset = entryOffset + nextEntryDataSize + 5;\n        if (automapDataSize != nextEntryOffset) {\n            if (db_fseek(stream1, nextEntryOffset, SEEK_SET) == -1) {\n                debug_printf(\"\\nAUTOMAP: Error writing temp data!\\n\");\n                db_fclose(stream1);\n                db_fclose(stream2);\n                mem_free(ambuf);\n                mem_free(cmpbuf);\n                return -1;\n            }\n\n            if (copy_file_data(stream1, stream2, automapDataSize - nextEntryOffset) == -1) {\n                debug_printf(\"\\nAUTOMAP: Error copying file data!\\n\");\n                db_fclose(stream1);\n                db_fclose(stream2);\n                mem_free(ambuf);\n                mem_free(cmpbuf);\n                return -1;\n            }\n        }\n\n        int diff = amdbsubhead.dataSize - nextEntryDataSize;\n        for (int map = 0; map < AUTOMAP_MAP_COUNT; map++) {\n            for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n                if (amdbhead.offsets[map][elevation] > entryOffset) {\n                    amdbhead.offsets[map][elevation] += diff;\n                }\n            }\n        }\n\n        amdbhead.dataSize += diff;\n\n        if (WriteAM_Header(stream2) == -1) {\n            db_fclose(stream1);\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            return -1;\n        }\n\n        db_fseek(stream2, 0, SEEK_END);\n        db_fclose(stream2);\n        db_fclose(stream1);\n        mem_free(ambuf);\n        mem_free(cmpbuf);\n\n        char* masterPatchesPath;\n        if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) {\n            debug_printf(\"\\nAUTOMAP: Error reading config info!\\n\");\n            return -1;\n        }\n\n        // NOTE: Not sure about the size.\n        char automapDbPath[512];\n        sprintf(automapDbPath, \"%s\\\\%s\\\\%s\", masterPatchesPath, \"MAPS\", AUTOMAP_DB);\n        if (remove(automapDbPath) != 0) {\n            debug_printf(\"\\nAUTOMAP: Error removing database!\\n\");\n            return -1;\n        }\n\n        // NOTE: Not sure about the size.\n        char automapTmpPath[512];\n        sprintf(automapTmpPath, \"%s\\\\%s\\\\%s\", masterPatchesPath, \"MAPS\", AUTOMAP_TMP);\n        if (rename(automapTmpPath, automapDbPath) != 0) {\n            debug_printf(\"\\nAUTOMAP: Error renaming database!\\n\");\n            return -1;\n        }\n    } else {\n        bool proceed = true;\n        if (db_fseek(stream1, 0, SEEK_END) != -1) {\n            if (db_ftell(stream1) != amdbhead.dataSize) {\n                proceed = false;\n            }\n        } else {\n            proceed = false;\n        }\n\n        if (!proceed) {\n            debug_printf(\"\\nAUTOMAP: Error reading automap database file header!\\n\");\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            db_fclose(stream1);\n            return -1;\n        }\n\n        if (WriteAM_Entry(stream1) == -1) {\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            return -1;\n        }\n\n        amdbhead.offsets[map][elevation] = amdbhead.dataSize;\n        amdbhead.dataSize += amdbsubhead.dataSize + 5;\n\n        if (WriteAM_Header(stream1) == -1) {\n            mem_free(ambuf);\n            mem_free(cmpbuf);\n            return -1;\n        }\n\n        db_fseek(stream1, 0, SEEK_END);\n        db_fclose(stream1);\n        mem_free(ambuf);\n        mem_free(cmpbuf);\n    }\n\n    return 1;\n}\n\n// Saves automap entry into stream.\n//\n// 0x41C844\nstatic int WriteAM_Entry(File* stream)\n{\n    unsigned char* buffer;\n    if (amdbsubhead.isCompressed == 1) {\n        buffer = cmpbuf;\n    } else {\n        buffer = ambuf;\n    }\n\n    if (db_fwriteLong(stream, amdbsubhead.dataSize) == -1) {\n        goto err;\n    }\n\n    if (db_fwriteByte(stream, amdbsubhead.isCompressed) == -1) {\n        goto err;\n    }\n\n    if (db_fwriteByteCount(stream, buffer, amdbsubhead.dataSize) == -1) {\n        goto err;\n    }\n\n    return 0;\n\nerr:\n\n    debug_printf(\"\\nAUTOMAP: Error writing automap database entry data!\\n\");\n    db_fclose(stream);\n\n    return -1;\n}\n\n// 0x41C8CC\nstatic int AM_ReadEntry(int map, int elevation)\n{\n    cmpbuf = NULL;\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s\\\\%s\", \"MAPS\", AUTOMAP_DB);\n\n    bool success = true;\n\n    File* stream = db_fopen(path, \"r+b\");\n    if (stream == NULL) {\n        debug_printf(\"\\nAUTOMAP: Error opening automap database file!\\n\");\n        debug_printf(\"Error continued: AM_ReadEntry: path: %s\", path);\n        return -1;\n    }\n\n    if (AM_ReadMainHeader(stream) == -1) {\n        debug_printf(\"\\nAUTOMAP: Error reading automap database header!\\n\");\n        db_fclose(stream);\n        return -1;\n    }\n\n    if (amdbhead.offsets[map][elevation] <= 0) {\n        success = false;\n        goto out;\n    }\n\n    if (db_fseek(stream, amdbhead.offsets[map][elevation], SEEK_SET) == -1) {\n        success = false;\n        goto out;\n    }\n\n    if (db_freadLong(stream, &(amdbsubhead.dataSize)) == -1) {\n        success = false;\n        goto out;\n    }\n\n    if (db_freadByte(stream, &(amdbsubhead.isCompressed)) == -1) {\n        success = false;\n        goto out;\n    }\n\n    if (amdbsubhead.isCompressed == 1) {\n        cmpbuf = (unsigned char*)mem_malloc(11024);\n        if (cmpbuf == NULL) {\n            debug_printf(\"\\nAUTOMAP: Error allocating decompression buffer!\\n\");\n            db_fclose(stream);\n            return -1;\n        }\n\n        if (db_freadByteCount(stream, cmpbuf, amdbsubhead.dataSize) == -1) {\n            success = 0;\n            goto out;\n        }\n\n        if (DecodeLZS(cmpbuf, ambuf, 10000) == -1) {\n            debug_printf(\"\\nAUTOMAP: Error decompressing DB entry!\\n\");\n            db_fclose(stream);\n            return -1;\n        }\n    } else {\n        if (db_freadByteCount(stream, ambuf, amdbsubhead.dataSize) == -1) {\n            success = false;\n            goto out;\n        }\n    }\n\nout:\n\n    db_fclose(stream);\n\n    if (!success) {\n        debug_printf(\"\\nAUTOMAP: Error reading automap database entry data!\\n\");\n\n        return -1;\n    }\n\n    if (cmpbuf != NULL) {\n        mem_free(cmpbuf);\n    }\n\n    return 0;\n}\n\n// Saves automap.db header.\n//\n// 0x41CAD8\nstatic int WriteAM_Header(File* stream)\n{\n    db_rewind(stream);\n\n    if (db_fwriteByte(stream, amdbhead.version) == -1) {\n        goto err;\n    }\n\n    if (db_fwriteLong(stream, amdbhead.dataSize) == -1) {\n        goto err;\n    }\n\n    if (db_fwriteLongCount(stream, (int*)amdbhead.offsets, AUTOMAP_OFFSET_COUNT) == -1) {\n        goto err;\n    }\n\n    return 0;\n\nerr:\n\n    debug_printf(\"\\nAUTOMAP: Error writing automap database header!\\n\");\n\n    db_fclose(stream);\n\n    return -1;\n}\n\n// Loads automap.db header.\n//\n// 0x41CB50\nstatic int AM_ReadMainHeader(File* stream)\n{\n\n    if (db_freadByte(stream, &(amdbhead.version)) == -1) {\n        return -1;\n    }\n\n    if (db_freadLong(stream, &(amdbhead.dataSize)) == -1) {\n        return -1;\n    }\n\n    if (db_freadLongCount(stream, (int*)amdbhead.offsets, AUTOMAP_OFFSET_COUNT) == -1) {\n        return -1;\n    }\n\n    if (amdbhead.version != 1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x41CBA4\nstatic void decode_map_data(int elevation)\n{\n    memset(ambuf, 0, SQUARE_GRID_SIZE);\n\n    obj_process_seen();\n\n    Object* object = obj_find_first_at(elevation);\n    while (object != NULL) {\n        if (object->tile != -1 && (object->flags & OBJECT_SEEN) != 0) {\n            int contentType;\n\n            int objectType = FID_TYPE(object->fid);\n            if (objectType == OBJ_TYPE_SCENERY && object->pid != PROTO_ID_0x2000158) {\n                contentType = 2;\n            } else if (objectType == OBJ_TYPE_WALL) {\n                contentType = 1;\n            } else {\n                contentType = 0;\n            }\n\n            if (contentType != 0) {\n                int v1 = 200 - object->tile % 200;\n                int v2 = v1 / 4 + 50 * (object->tile / 200);\n                int v3 = 2 * (3 - v1 % 4);\n                ambuf[v2] &= ~(0x03 << v3);\n                ambuf[v2] |= (contentType << v3);\n            }\n        }\n        object = obj_find_next_at();\n    }\n}\n\n// 0x41CC98\nstatic int am_pip_init()\n{\n    amdbhead.version = 1;\n    amdbhead.dataSize = 1925;\n    memcpy(amdbhead.offsets, defam, sizeof(defam));\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s\\\\%s\", \"MAPS\", AUTOMAP_DB);\n\n    File* stream = db_fopen(path, \"wb\");\n    if (stream == NULL) {\n        debug_printf(\"\\nAUTOMAP: Error creating automap database file!\\n\");\n        return -1;\n    }\n\n    if (WriteAM_Header(stream) == -1) {\n        return -1;\n    }\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x41CD34\nint YesWriteIndex(int mapIndex, int elevation)\n{\n    if (mapIndex < AUTOMAP_MAP_COUNT && elevation < ELEVATION_COUNT && mapIndex >= 0 && elevation >= 0) {\n        return defam[mapIndex][elevation] >= 0;\n    }\n\n    return 0;\n}\n\n// Copy data from stream1 to stream2.\n//\n// 0x41CD6C\nstatic int copy_file_data(File* stream1, File* stream2, int length)\n{\n    void* buffer = mem_malloc(0xFFFF);\n    if (buffer == NULL) {\n        return -1;\n    }\n\n    // NOTE: Original code is slightly different, but does the same thing.\n    while (length != 0) {\n        int chunkLength = min(length, 0xFFFF);\n\n        if (db_fread(buffer, chunkLength, 1, stream1) != 1) {\n            break;\n        }\n\n        if (db_fwrite(buffer, chunkLength, 1, stream2) != 1) {\n            break;\n        }\n\n        length -= chunkLength;\n    }\n\n    mem_free(buffer);\n\n    if (length != 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x41CE74\nint ReadAMList(AutomapHeader** automapHeaderPtr)\n{\n    char path[MAX_PATH];\n    sprintf(path, \"%s\\\\%s\", \"MAPS\", AUTOMAP_DB);\n\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        debug_printf(\"\\nAUTOMAP: Error opening database file for reading!\\n\");\n        debug_printf(\"Error continued: ReadAMList: path: %s\", path);\n        return -1;\n    }\n\n    if (AM_ReadMainHeader(stream) == -1) {\n        debug_printf(\"\\nAUTOMAP: Error reading automap database header pt2!\\n\");\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fclose(stream);\n\n    *automapHeaderPtr = &amdbhead;\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/automap.h",
    "content": "#ifndef FALLOUT_GAME_AUTOMAP_H_\n#define FALLOUT_GAME_AUTOMAP_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/map_defs.h\"\n\n#define AUTOMAP_DB \"AUTOMAP.DB\"\n#define AUTOMAP_TMP \"AUTOMAP.TMP\"\n\n// The number of map entries that is stored in automap.db.\n//\n// NOTE: I don't know why this value is not equal to the number of maps.\n#define AUTOMAP_MAP_COUNT 160\n\ntypedef struct AutomapHeader {\n    unsigned char version;\n\n    // The size of entire automap database (including header itself).\n    int dataSize;\n\n    // Offsets from the beginning of the automap database file into\n    // entries data.\n    //\n    // These offsets are specified for every map/elevation combination. A value\n    // of 0 specifies that there is no data for appropriate map/elevation\n    // combination.\n    int offsets[AUTOMAP_MAP_COUNT][ELEVATION_COUNT];\n} AutomapHeader;\n\ntypedef struct AutomapEntry {\n    int dataSize;\n    unsigned char isCompressed;\n} AutomapEntry;\n\nint automap_init();\nint automap_reset();\nvoid automap_exit();\nint automap_load(File* stream);\nint automap_save(File* stream);\nint automapDisplayMap(int map);\nvoid automap(bool isInGame, bool isUsingScanner);\nint draw_top_down_map_pipboy(int win, int map, int elevation);\nint automap_pip_save();\nint YesWriteIndex(int mapIndex, int elevation);\nint ReadAMList(AutomapHeader** automapHeaderPtr);\n\n#endif /* FALLOUT_GAME_AUTOMAP_H_ */\n"
  },
  {
    "path": "src/game/bmpdlog.c",
    "content": "#include \"game/bmpdlog.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"game/editor.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gsound.h\"\n#include \"game/message.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"game/wordwrap.h\"\n\n#define FILE_DIALOG_LINE_COUNT 12\n\n#define FILE_DIALOG_DOUBLE_CLICK_DELAY 32\n\n#define LOAD_FILE_DIALOG_DONE_BUTTON_X 58\n#define LOAD_FILE_DIALOG_DONE_BUTTON_Y 187\n\n#define LOAD_FILE_DIALOG_DONE_LABEL_X 79\n#define LOAD_FILE_DIALOG_DONE_LABEL_Y 187\n\n#define LOAD_FILE_DIALOG_CANCEL_BUTTON_X 163\n#define LOAD_FILE_DIALOG_CANCEL_BUTTON_Y 187\n\n#define LOAD_FILE_DIALOG_CANCEL_LABEL_X 182\n#define LOAD_FILE_DIALOG_CANCEL_LABEL_Y 187\n\n#define SAVE_FILE_DIALOG_DONE_BUTTON_X 58\n#define SAVE_FILE_DIALOG_DONE_BUTTON_Y 214\n\n#define SAVE_FILE_DIALOG_DONE_LABEL_X 79\n#define SAVE_FILE_DIALOG_DONE_LABEL_Y 213\n\n#define SAVE_FILE_DIALOG_CANCEL_BUTTON_X 163\n#define SAVE_FILE_DIALOG_CANCEL_BUTTON_Y 214\n\n#define SAVE_FILE_DIALOG_CANCEL_LABEL_X 182\n#define SAVE_FILE_DIALOG_CANCEL_LABEL_Y 213\n\n#define FILE_DIALOG_TITLE_X 49\n#define FILE_DIALOG_TITLE_Y 16\n\n#define FILE_DIALOG_SCROLL_BUTTON_X 36\n#define FILE_DIALOG_SCROLL_BUTTON_Y 44\n\n#define FILE_DIALOG_FILE_LIST_X 55\n#define FILE_DIALOG_FILE_LIST_Y 49\n#define FILE_DIALOG_FILE_LIST_WIDTH 190\n#define FILE_DIALOG_FILE_LIST_HEIGHT 124\n\nstatic void PrntFlist(unsigned char* buffer, char** fileList, int pageOffset, int fileListLength, int selectedIndex, int pitch);\n\n// 0x5108C8\nint dbox[DIALOG_TYPE_COUNT] = {\n    218, // MEDIALOG.FRM - Medium generic dialog box\n    217, // LGDIALOG.FRM - Large generic dialog box\n};\n\n// 0x5108D0\nint ytable[DIALOG_TYPE_COUNT] = {\n    23,\n    27,\n};\n\n// 0x5108D8\nint xtable[DIALOG_TYPE_COUNT] = {\n    29,\n    29,\n};\n\n// 0x5108E0\nint doneY[DIALOG_TYPE_COUNT] = {\n    81,\n    98,\n};\n\n// 0x5108E8\nint doneX[DIALOG_TYPE_COUNT] = {\n    51,\n    37,\n};\n\n// 0x5108F0\nint dblines[DIALOG_TYPE_COUNT] = {\n    5,\n    6,\n};\n\n// 0x510900\nint flgids[FILE_DIALOG_FRM_COUNT] = {\n    224, // loadbox.frm - character editor\n    8, // lilredup.frm - little red button up\n    9, // lilreddn.frm - little red button down\n    181, // dnarwoff.frm - character editor\n    182, // dnarwon.frm - character editor\n    199, // uparwoff.frm - character editor\n    200, // uparwon.frm - character editor\n};\n\n// 0x51091C\nint flgids2[FILE_DIALOG_FRM_COUNT] = {\n    225, // savebox.frm - character editor\n    8, // lilredup.frm - little red button up\n    9, // lilreddn.frm - little red button down\n    181, // dnarwoff.frm - character editor\n    182, // dnarwon.frm - character editor\n    199, // uparwoff.frm - character editor\n    200, // uparwon.frm - character editor\n};\n\n// 0x41CF20\nint dialog_out(const char* title, const char** body, int bodyLength, int x, int y, int titleColor, const char* a8, int bodyColor, int flags)\n{\n    MessageList messageList;\n    MessageListItem messageListItem;\n    int savedFont = text_curr();\n\n    bool v86 = false;\n\n    bool hasTwoButtons = false;\n    if (a8 != NULL) {\n        hasTwoButtons = true;\n    }\n\n    bool hasTitle = false;\n    if (title != NULL) {\n        hasTitle = true;\n    }\n\n    if ((flags & DIALOG_BOX_YES_NO) != 0) {\n        hasTwoButtons = true;\n        flags |= DIALOG_BOX_LARGE;\n        flags &= ~DIALOG_BOX_0x20;\n    }\n\n    int maximumLineWidth = 0;\n    if (hasTitle) {\n        maximumLineWidth = text_width(title);\n    }\n\n    int linesCount = 0;\n    for (int index = 0; index < bodyLength; index++) {\n        // NOTE: Calls [text_width] twice because of [max] macro.\n        maximumLineWidth = max(text_width(body[index]), maximumLineWidth);\n        linesCount++;\n    }\n\n    int dialogType;\n    if ((flags & DIALOG_BOX_LARGE) != 0 || hasTwoButtons) {\n        dialogType = DIALOG_TYPE_LARGE;\n    } else if ((flags & DIALOG_BOX_MEDIUM) != 0) {\n        dialogType = DIALOG_TYPE_MEDIUM;\n    } else {\n        if (hasTitle) {\n            linesCount++;\n        }\n\n        dialogType = maximumLineWidth > 168 || linesCount > 5\n            ? DIALOG_TYPE_LARGE\n            : DIALOG_TYPE_MEDIUM;\n    }\n\n    CacheEntry* backgroundHandle;\n    int backgroundWidth;\n    int backgroundHeight;\n    int fid = art_id(OBJ_TYPE_INTERFACE, dbox[dialogType], 0, 0, 0);\n    unsigned char* background = art_lock(fid, &backgroundHandle, &backgroundWidth, &backgroundHeight);\n    if (background == NULL) {\n        text_font(savedFont);\n        return -1;\n    }\n\n    int win = win_add(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (win == -1) {\n        art_ptr_unlock(backgroundHandle);\n        text_font(savedFont);\n        return -1;\n    }\n\n    unsigned char* windowBuf = win_get_buf(win);\n    memcpy(windowBuf, background, backgroundWidth * backgroundHeight);\n\n    CacheEntry* doneBoxHandle = NULL;\n    unsigned char* doneBox = NULL;\n    int doneBoxWidth;\n    int doneBoxHeight;\n\n    CacheEntry* downButtonHandle = NULL;\n    unsigned char* downButton = NULL;\n    int downButtonWidth;\n    int downButtonHeight;\n\n    CacheEntry* upButtonHandle = NULL;\n    unsigned char* upButton = NULL;\n\n    if ((flags & DIALOG_BOX_0x20) == 0) {\n        int doneBoxFid = art_id(OBJ_TYPE_INTERFACE, 209, 0, 0, 0);\n        doneBox = art_lock(doneBoxFid, &doneBoxHandle, &doneBoxWidth, &doneBoxHeight);\n        if (doneBox == NULL) {\n            art_ptr_unlock(backgroundHandle);\n            text_font(savedFont);\n            win_delete(win);\n            return -1;\n        }\n\n        int downButtonFid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n        downButton = art_lock(downButtonFid, &downButtonHandle, &downButtonWidth, &downButtonHeight);\n        if (downButton == NULL) {\n            art_ptr_unlock(doneBoxHandle);\n            art_ptr_unlock(backgroundHandle);\n            text_font(savedFont);\n            win_delete(win);\n            return -1;\n        }\n\n        int upButtonFid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n        upButton = art_ptr_lock_data(upButtonFid, 0, 0, &upButtonHandle);\n        if (upButton == NULL) {\n            art_ptr_unlock(downButtonHandle);\n            art_ptr_unlock(doneBoxHandle);\n            art_ptr_unlock(backgroundHandle);\n            text_font(savedFont);\n            win_delete(win);\n            return -1;\n        }\n\n        int v27 = hasTwoButtons ? doneX[dialogType] : (backgroundWidth - doneBoxWidth) / 2;\n        buf_to_buf(doneBox, doneBoxWidth, doneBoxHeight, doneBoxWidth, windowBuf + backgroundWidth * doneY[dialogType] + v27, backgroundWidth);\n\n        if (!message_init(&messageList)) {\n            art_ptr_unlock(upButtonHandle);\n            art_ptr_unlock(downButtonHandle);\n            art_ptr_unlock(doneBoxHandle);\n            art_ptr_unlock(backgroundHandle);\n            text_font(savedFont);\n            win_delete(win);\n            return -1;\n        }\n\n        char path[MAX_PATH];\n        sprintf(path, \"%s%s\", msg_path, \"DBOX.MSG\");\n\n        if (!message_load(&messageList, path)) {\n            art_ptr_unlock(upButtonHandle);\n            art_ptr_unlock(downButtonHandle);\n            art_ptr_unlock(doneBoxHandle);\n            art_ptr_unlock(backgroundHandle);\n            text_font(savedFont);\n            // FIXME: Window is not removed.\n            return -1;\n        }\n\n        text_font(103);\n\n        // 100 - DONE\n        // 101 - YES\n        messageListItem.num = (flags & DIALOG_BOX_YES_NO) == 0 ? 100 : 101;\n        if (message_search(&messageList, &messageListItem)) {\n            text_to_buf(windowBuf + backgroundWidth * (doneY[dialogType] + 3) + v27 + 35, messageListItem.text, backgroundWidth, backgroundWidth, colorTable[18979]);\n        }\n\n        int btn = win_register_button(win, v27 + 13, doneY[dialogType] + 4, downButtonWidth, downButtonHeight, -1, -1, -1, 500, upButton, downButton, NULL, BUTTON_FLAG_TRANSPARENT);\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n        }\n\n        v86 = true;\n    }\n\n    if (hasTwoButtons && dialogType == DIALOG_TYPE_LARGE) {\n        if (v86) {\n            if ((flags & DIALOG_BOX_YES_NO) != 0) {\n                a8 = getmsg(&messageList, &messageListItem, 102);\n            }\n\n            text_font(103);\n\n            trans_buf_to_buf(doneBox,\n                doneBoxWidth,\n                doneBoxHeight,\n                doneBoxWidth,\n                windowBuf + backgroundWidth * doneY[dialogType] + doneX[dialogType] + doneBoxWidth + 24,\n                backgroundWidth);\n\n            text_to_buf(windowBuf + backgroundWidth * (doneY[dialogType] + 3) + doneX[dialogType] + doneBoxWidth + 59,\n                a8, backgroundWidth, backgroundWidth, colorTable[18979]);\n\n            int btn = win_register_button(win,\n                doneBoxWidth + doneX[dialogType] + 37,\n                doneY[dialogType] + 4,\n                downButtonWidth,\n                downButtonHeight,\n                -1, -1, -1, 501, upButton, downButton, 0, BUTTON_FLAG_TRANSPARENT);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n        } else {\n            int doneBoxFid = art_id(OBJ_TYPE_INTERFACE, 209, 0, 0, 0);\n            unsigned char* doneBox = art_lock(doneBoxFid, &doneBoxHandle, &doneBoxWidth, &doneBoxHeight);\n            if (doneBox == NULL) {\n                art_ptr_unlock(backgroundHandle);\n                text_font(savedFont);\n                win_delete(win);\n                return -1;\n            }\n\n            int downButtonFid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n            unsigned char* downButton = art_lock(downButtonFid, &downButtonHandle, &downButtonWidth, &downButtonHeight);\n            if (downButton == NULL) {\n                art_ptr_unlock(doneBoxHandle);\n                art_ptr_unlock(backgroundHandle);\n                text_font(savedFont);\n                win_delete(win);\n                return -1;\n            }\n\n            int upButtonFid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n            unsigned char* upButton = art_ptr_lock_data(upButtonFid, 0, 0, &upButtonHandle);\n            if (upButton == NULL) {\n                art_ptr_unlock(downButtonHandle);\n                art_ptr_unlock(doneBoxHandle);\n                art_ptr_unlock(backgroundHandle);\n                text_font(savedFont);\n                win_delete(win);\n                return -1;\n            }\n\n            if (!message_init(&messageList)) {\n                art_ptr_unlock(upButtonHandle);\n                art_ptr_unlock(downButtonHandle);\n                art_ptr_unlock(doneBoxHandle);\n                art_ptr_unlock(backgroundHandle);\n                text_font(savedFont);\n                win_delete(win);\n                return -1;\n            }\n\n            char path[MAX_PATH];\n            sprintf(path, \"%s%s\", msg_path, \"DBOX.MSG\");\n\n            if (!message_load(&messageList, path)) {\n                art_ptr_unlock(upButtonHandle);\n                art_ptr_unlock(downButtonHandle);\n                art_ptr_unlock(doneBoxHandle);\n                art_ptr_unlock(backgroundHandle);\n                text_font(savedFont);\n                win_delete(win);\n                return -1;\n            }\n\n            trans_buf_to_buf(doneBox,\n                doneBoxWidth,\n                doneBoxHeight,\n                doneBoxWidth,\n                windowBuf + backgroundWidth * doneY[dialogType] + doneX[dialogType],\n                backgroundWidth);\n\n            text_font(103);\n\n            text_to_buf(windowBuf + backgroundWidth * (doneY[dialogType] + 3) + doneX[dialogType] + 35,\n                a8, backgroundWidth, backgroundWidth, colorTable[18979]);\n\n            int btn = win_register_button(win,\n                doneX[dialogType] + 13,\n                doneY[dialogType] + 4,\n                downButtonWidth,\n                downButtonHeight,\n                -1,\n                -1,\n                -1,\n                501,\n                upButton,\n                downButton,\n                NULL,\n                BUTTON_FLAG_TRANSPARENT);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n\n            v86 = true;\n        }\n    }\n\n    text_font(101);\n\n    int v23 = ytable[dialogType];\n\n    if ((flags & DIALOG_BOX_NO_VERTICAL_CENTERING) == 0) {\n        int v41 = dblines[dialogType] * text_height() / 2 + v23;\n        v23 = v41 - ((bodyLength + 1) * text_height() / 2);\n    }\n\n    if (hasTitle) {\n        if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) {\n            text_to_buf(windowBuf + backgroundWidth * v23 + xtable[dialogType], title, backgroundWidth, backgroundWidth, titleColor);\n        } else {\n            int length = text_width(title);\n            text_to_buf(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, title, backgroundWidth, backgroundWidth, titleColor);\n        }\n        v23 += text_height();\n    }\n\n    for (int v94 = 0; v94 < bodyLength; v94++) {\n        int len = text_width(body[v94]);\n        if (len <= backgroundWidth - 26) {\n            if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) {\n                text_to_buf(windowBuf + backgroundWidth * v23 + xtable[dialogType], body[v94], backgroundWidth, backgroundWidth, bodyColor);\n            } else {\n                int length = text_width(body[v94]);\n                text_to_buf(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, body[v94], backgroundWidth, backgroundWidth, bodyColor);\n            }\n            v23 += text_height();\n        } else {\n            short beginnings[WORD_WRAP_MAX_COUNT];\n            short count;\n            if (word_wrap(body[v94], backgroundWidth - 26, beginnings, &count) != 0) {\n                debug_printf(\"\\nError: dialog_out\");\n            }\n\n            for (int v48 = 1; v48 < count; v48++) {\n                int v51 = beginnings[v48] - beginnings[v48 - 1];\n                if (v51 >= 260) {\n                    v51 = 259;\n                }\n\n                char string[260];\n                strncpy(string, body[v94] + beginnings[v48 - 1], v51);\n                string[v51] = '\\0';\n\n                if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) {\n                    text_to_buf(windowBuf + backgroundWidth * v23 + xtable[dialogType], string, backgroundWidth, backgroundWidth, bodyColor);\n                } else {\n                    int length = text_width(string);\n                    text_to_buf(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, string, backgroundWidth, backgroundWidth, bodyColor);\n                }\n                v23 += text_height();\n            }\n        }\n    }\n\n    win_draw(win);\n\n    int rc = -1;\n    while (rc == -1) {\n        int keyCode = get_input();\n\n        if (keyCode == 500) {\n            rc = 1;\n        } else if (keyCode == KEY_RETURN) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            rc = 1;\n        } else if (keyCode == KEY_ESCAPE || keyCode == 501) {\n            rc = 0;\n        } else {\n            if ((flags & 0x10) != 0) {\n                if (keyCode == KEY_UPPERCASE_Y || keyCode == KEY_LOWERCASE_Y) {\n                    rc = 1;\n                } else if (keyCode == KEY_UPPERCASE_N || keyCode == KEY_LOWERCASE_N) {\n                    rc = 0;\n                }\n            }\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            rc = 1;\n        }\n    }\n\n    win_delete(win);\n    art_ptr_unlock(backgroundHandle);\n    text_font(savedFont);\n\n    if (v86) {\n        art_ptr_unlock(doneBoxHandle);\n        art_ptr_unlock(downButtonHandle);\n        art_ptr_unlock(upButtonHandle);\n        message_exit(&messageList);\n    }\n\n    return rc;\n}\n\n// 0x41DE90\nint file_dialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags)\n{\n    int oldFont = text_curr();\n\n    bool isScrollable = false;\n    if (fileListLength > FILE_DIALOG_LINE_COUNT) {\n        isScrollable = true;\n    }\n\n    int selectedFileIndex = 0;\n    int pageOffset = 0;\n    int maxPageOffset = fileListLength - (FILE_DIALOG_LINE_COUNT + 1);\n    if (maxPageOffset < 0) {\n        maxPageOffset = fileListLength - 1;\n        if (maxPageOffset < 0) {\n            maxPageOffset = 0;\n        }\n    }\n\n    unsigned char* frmBuffers[FILE_DIALOG_FRM_COUNT];\n    CacheEntry* frmHandles[FILE_DIALOG_FRM_COUNT];\n    Size frmSizes[FILE_DIALOG_FRM_COUNT];\n\n    for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, flgids[index], 0, 0, 0);\n        frmBuffers[index] = art_lock(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height));\n        if (frmBuffers[index] == NULL) {\n            while (--index >= 0) {\n                art_ptr_unlock(frmHandles[index]);\n            }\n            return -1;\n        }\n    }\n\n    int backgroundWidth = frmSizes[FILE_DIALOG_FRM_BACKGROUND].width;\n    int backgroundHeight = frmSizes[FILE_DIALOG_FRM_BACKGROUND].height;\n\n    int win = win_add(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (win == -1) {\n        for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n            art_ptr_unlock(frmHandles[index]);\n        }\n        return -1;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(win);\n    memcpy(windowBuffer, frmBuffers[FILE_DIALOG_FRM_BACKGROUND], backgroundWidth * backgroundHeight);\n\n    MessageList messageList;\n    MessageListItem messageListItem;\n\n    if (!message_init(&messageList)) {\n        win_delete(win);\n\n        for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n            art_ptr_unlock(frmHandles[index]);\n        }\n\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"DBOX.MSG\");\n\n    if (!message_load(&messageList, path)) {\n        win_delete(win);\n\n        for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n            art_ptr_unlock(frmHandles[index]);\n        }\n\n        return -1;\n    }\n\n    text_font(103);\n\n    // DONE\n    const char* done = getmsg(&messageList, &messageListItem, 100);\n    text_to_buf(windowBuffer + LOAD_FILE_DIALOG_DONE_LABEL_Y * backgroundWidth + LOAD_FILE_DIALOG_DONE_LABEL_X, done, backgroundWidth, backgroundWidth, colorTable[18979]);\n\n    // CANCEL\n    const char* cancel = getmsg(&messageList, &messageListItem, 103);\n    text_to_buf(windowBuffer + LOAD_FILE_DIALOG_CANCEL_LABEL_Y * backgroundWidth + LOAD_FILE_DIALOG_CANCEL_LABEL_X, cancel, backgroundWidth, backgroundWidth, colorTable[18979]);\n\n    int doneBtn = win_register_button(win,\n        LOAD_FILE_DIALOG_DONE_BUTTON_X,\n        LOAD_FILE_DIALOG_DONE_BUTTON_Y,\n        frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width,\n        frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height,\n        -1,\n        -1,\n        -1,\n        500,\n        frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL],\n        frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (doneBtn != -1) {\n        win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int cancelBtn = win_register_button(win,\n        LOAD_FILE_DIALOG_CANCEL_BUTTON_X,\n        LOAD_FILE_DIALOG_CANCEL_BUTTON_Y,\n        frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width,\n        frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height,\n        -1,\n        -1,\n        -1,\n        501,\n        frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL],\n        frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (cancelBtn != -1) {\n        win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int scrollUpBtn = win_register_button(win,\n        FILE_DIALOG_SCROLL_BUTTON_X,\n        FILE_DIALOG_SCROLL_BUTTON_Y,\n        frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].width,\n        frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height,\n        -1,\n        505,\n        506,\n        505,\n        frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL],\n        frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (scrollUpBtn != -1) {\n        win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int scrollDownButton = win_register_button(win,\n        FILE_DIALOG_SCROLL_BUTTON_X,\n        FILE_DIALOG_SCROLL_BUTTON_Y + frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height,\n        frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].width,\n        frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].height,\n        -1,\n        503,\n        504,\n        503,\n        frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL],\n        frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (scrollUpBtn != -1) {\n        win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    win_register_button(\n        win,\n        FILE_DIALOG_FILE_LIST_X,\n        FILE_DIALOG_FILE_LIST_Y,\n        FILE_DIALOG_FILE_LIST_WIDTH,\n        FILE_DIALOG_FILE_LIST_HEIGHT,\n        -1,\n        -1,\n        -1,\n        502,\n        NULL,\n        NULL,\n        NULL,\n        0);\n\n    if (title != NULL) {\n        text_to_buf(windowBuffer + backgroundWidth * FILE_DIALOG_TITLE_Y + FILE_DIALOG_TITLE_X, title, backgroundWidth, backgroundWidth, colorTable[18979]);\n    }\n\n    text_font(101);\n\n    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n    win_draw(win);\n\n    int doubleClickSelectedFileIndex = -2;\n    int doubleClickTimer = FILE_DIALOG_DOUBLE_CLICK_DELAY;\n\n    int rc = -1;\n    while (rc == -1) {\n        unsigned int tick = get_time();\n        int keyCode = get_input();\n        int scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_NONE;\n        int scrollCounter = 0;\n        bool isScrolling = false;\n\n        if (keyCode == 500) {\n            if (fileListLength != 0) {\n                strncpy(dest, fileList[selectedFileIndex + pageOffset], 16);\n                rc = 0;\n            } else {\n                rc = 1;\n            }\n        } else if (keyCode == 501 || keyCode == KEY_ESCAPE) {\n            rc = 1;\n        } else if (keyCode == 502 && fileListLength != 0) {\n            int mouseX;\n            int mouseY;\n            mouse_get_position(&mouseX, &mouseY);\n\n            int selectedLine = (mouseY - y - FILE_DIALOG_FILE_LIST_Y) / text_height();\n            if (selectedLine - 1 < 0) {\n                selectedLine = 0;\n            }\n\n            if (isScrollable || selectedLine < fileListLength) {\n                if (selectedLine >= FILE_DIALOG_LINE_COUNT) {\n                    selectedLine = FILE_DIALOG_LINE_COUNT - 1;\n                }\n            } else {\n                selectedLine = fileListLength - 1;\n            }\n\n            selectedFileIndex = selectedLine;\n            if (selectedFileIndex == doubleClickSelectedFileIndex) {\n                gsound_play_sfx_file(\"ib1p1xx1\");\n                strncpy(dest, fileList[selectedFileIndex + pageOffset], 16);\n                rc = 0;\n            }\n\n            doubleClickSelectedFileIndex = selectedFileIndex;\n            PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n        } else if (keyCode == 506) {\n            scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_UP;\n        } else if (keyCode == 504) {\n            scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_DOWN;\n        } else {\n            switch (keyCode) {\n            case KEY_ARROW_UP:\n                pageOffset--;\n                if (pageOffset < 0) {\n                    selectedFileIndex--;\n                    if (selectedFileIndex < 0) {\n                        selectedFileIndex = 0;\n                    }\n                    pageOffset = 0;\n                }\n                PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                doubleClickSelectedFileIndex = -2;\n                break;\n            case KEY_ARROW_DOWN:\n                if (isScrollable) {\n                    pageOffset++;\n                    // FIXME: Should be >= maxPageOffset (as in save dialog).\n                    // Otherwise out of bounds index is considered selected.\n                    if (pageOffset > maxPageOffset) {\n                        selectedFileIndex++;\n                        // FIXME: Should be >= FILE_DIALOG_LINE_COUNT (as in\n                        // save dialog). Otherwise out of bounds index is\n                        // considered selected.\n                        if (selectedFileIndex > FILE_DIALOG_LINE_COUNT) {\n                            selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1;\n                        }\n                        pageOffset = maxPageOffset;\n                    }\n                } else {\n                    selectedFileIndex++;\n                    if (selectedFileIndex > maxPageOffset) {\n                        selectedFileIndex = maxPageOffset;\n                    }\n                }\n                PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                doubleClickSelectedFileIndex = -2;\n                break;\n            case KEY_HOME:\n                selectedFileIndex = 0;\n                pageOffset = 0;\n                PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                doubleClickSelectedFileIndex = -2;\n                break;\n            case KEY_END:\n                if (isScrollable) {\n                    selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1;\n                    pageOffset = maxPageOffset;\n                } else {\n                    selectedFileIndex = maxPageOffset;\n                    pageOffset = 0;\n                }\n                PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                doubleClickSelectedFileIndex = -2;\n                break;\n            }\n        }\n\n        if (scrollDirection != FILE_DIALOG_SCROLL_DIRECTION_NONE) {\n            unsigned int scrollDelay = 4;\n            doubleClickSelectedFileIndex = -2;\n            while (1) {\n                unsigned int scrollTick = get_time();\n                scrollCounter += 1;\n                if ((!isScrolling && scrollCounter == 1) || (isScrolling && scrollCounter > 14.4)) {\n                    isScrolling = true;\n\n                    if (scrollCounter > 14.4) {\n                        scrollDelay += 1;\n                        if (scrollDelay > 24) {\n                            scrollDelay = 24;\n                        }\n                    }\n\n                    if (scrollDirection == FILE_DIALOG_SCROLL_DIRECTION_UP) {\n                        pageOffset--;\n                        if (pageOffset < 0) {\n                            selectedFileIndex--;\n                            if (selectedFileIndex < 0) {\n                                selectedFileIndex = 0;\n                            }\n                            pageOffset = 0;\n                        }\n                    } else {\n                        if (isScrollable) {\n                            pageOffset++;\n                            if (pageOffset > maxPageOffset) {\n                                selectedFileIndex++;\n                                if (selectedFileIndex >= FILE_DIALOG_LINE_COUNT) {\n                                    selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1;\n                                }\n                                pageOffset = maxPageOffset;\n                            }\n                        } else {\n                            selectedFileIndex++;\n                            if (selectedFileIndex > maxPageOffset) {\n                                selectedFileIndex = maxPageOffset;\n                            }\n                        }\n                    }\n\n                    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                    win_draw(win);\n                }\n\n                unsigned int delay = (scrollCounter > 14.4) ? 1000 / scrollDelay : 1000 / 24;\n                while (elapsed_time(scrollTick) < delay) {\n                }\n\n                if (game_user_wants_to_quit != 0) {\n                    rc = 1;\n                    break;\n                }\n\n                int keyCode = get_input();\n                if (keyCode == 505 || keyCode == 503) {\n                    break;\n                }\n            }\n        } else {\n            win_draw(win);\n\n            doubleClickTimer--;\n            if (doubleClickTimer == 0) {\n                doubleClickTimer = FILE_DIALOG_DOUBLE_CLICK_DELAY;\n                doubleClickSelectedFileIndex = -2;\n            }\n\n            while (elapsed_time(tick) < (1000 / 24)) {\n            }\n        }\n\n        if (game_user_wants_to_quit) {\n            rc = 1;\n        }\n    }\n\n    win_delete(win);\n\n    for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n        art_ptr_unlock(frmHandles[index]);\n    }\n\n    message_exit(&messageList);\n    text_font(oldFont);\n\n    return rc;\n}\n\n// 0x41EA78\nint save_file_dialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags)\n{\n    int oldFont = text_curr();\n\n    bool isScrollable = false;\n    if (fileListLength > FILE_DIALOG_LINE_COUNT) {\n        isScrollable = true;\n    }\n\n    int selectedFileIndex = 0;\n    int pageOffset = 0;\n    int maxPageOffset = fileListLength - (FILE_DIALOG_LINE_COUNT + 1);\n    if (maxPageOffset < 0) {\n        maxPageOffset = fileListLength - 1;\n        if (maxPageOffset < 0) {\n            maxPageOffset = 0;\n        }\n    }\n\n    unsigned char* frmBuffers[FILE_DIALOG_FRM_COUNT];\n    CacheEntry* frmHandles[FILE_DIALOG_FRM_COUNT];\n    Size frmSizes[FILE_DIALOG_FRM_COUNT];\n\n    for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, flgids2[index], 0, 0, 0);\n        frmBuffers[index] = art_lock(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height));\n        if (frmBuffers[index] == NULL) {\n            while (--index >= 0) {\n                art_ptr_unlock(frmHandles[index]);\n            }\n            return -1;\n        }\n    }\n\n    int backgroundWidth = frmSizes[FILE_DIALOG_FRM_BACKGROUND].width;\n    int backgroundHeight = frmSizes[FILE_DIALOG_FRM_BACKGROUND].height;\n\n    int win = win_add(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (win == -1) {\n        for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n            art_ptr_unlock(frmHandles[index]);\n        }\n        return -1;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(win);\n    memcpy(windowBuffer, frmBuffers[FILE_DIALOG_FRM_BACKGROUND], backgroundWidth * backgroundHeight);\n\n    MessageList messageList;\n    MessageListItem messageListItem;\n\n    if (!message_init(&messageList)) {\n        win_delete(win);\n\n        for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n            art_ptr_unlock(frmHandles[index]);\n        }\n\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"DBOX.MSG\");\n\n    if (!message_load(&messageList, path)) {\n        win_delete(win);\n\n        for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n            art_ptr_unlock(frmHandles[index]);\n        }\n\n        return -1;\n    }\n\n    text_font(103);\n\n    // DONE\n    const char* done = getmsg(&messageList, &messageListItem, 100);\n    text_to_buf(windowBuffer + backgroundWidth * SAVE_FILE_DIALOG_DONE_LABEL_Y + SAVE_FILE_DIALOG_DONE_LABEL_X, done, backgroundWidth, backgroundWidth, colorTable[18979]);\n\n    // CANCEL\n    const char* cancel = getmsg(&messageList, &messageListItem, 103);\n    text_to_buf(windowBuffer + backgroundWidth * SAVE_FILE_DIALOG_CANCEL_LABEL_Y + SAVE_FILE_DIALOG_CANCEL_LABEL_X, cancel, backgroundWidth, backgroundWidth, colorTable[18979]);\n\n    int doneBtn = win_register_button(win,\n        SAVE_FILE_DIALOG_DONE_BUTTON_X,\n        SAVE_FILE_DIALOG_DONE_BUTTON_Y,\n        frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width,\n        frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height,\n        -1,\n        -1,\n        -1,\n        500,\n        frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL],\n        frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (doneBtn != -1) {\n        win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int cancelBtn = win_register_button(win,\n        SAVE_FILE_DIALOG_CANCEL_BUTTON_X,\n        SAVE_FILE_DIALOG_CANCEL_BUTTON_Y,\n        frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width,\n        frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height,\n        -1,\n        -1,\n        -1,\n        501,\n        frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL],\n        frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (cancelBtn != -1) {\n        win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int scrollUpBtn = win_register_button(win,\n        FILE_DIALOG_SCROLL_BUTTON_X,\n        FILE_DIALOG_SCROLL_BUTTON_Y,\n        frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].width,\n        frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height,\n        -1,\n        505,\n        506,\n        505,\n        frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL],\n        frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (scrollUpBtn != -1) {\n        win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int scrollDownButton = win_register_button(win,\n        FILE_DIALOG_SCROLL_BUTTON_X,\n        FILE_DIALOG_SCROLL_BUTTON_Y + frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height,\n        frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].width,\n        frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].height,\n        -1,\n        503,\n        504,\n        503,\n        frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL],\n        frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (scrollUpBtn != -1) {\n        win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    win_register_button(\n        win,\n        FILE_DIALOG_FILE_LIST_X,\n        FILE_DIALOG_FILE_LIST_Y,\n        FILE_DIALOG_FILE_LIST_WIDTH,\n        FILE_DIALOG_FILE_LIST_HEIGHT,\n        -1,\n        -1,\n        -1,\n        502,\n        NULL,\n        NULL,\n        NULL,\n        0);\n\n    if (title != NULL) {\n        text_to_buf(windowBuffer + backgroundWidth * FILE_DIALOG_TITLE_Y + FILE_DIALOG_TITLE_X, title, backgroundWidth, backgroundWidth, colorTable[18979]);\n    }\n\n    text_font(101);\n\n    int cursorHeight = text_height();\n    int cursorWidth = text_width(\"_\") - 4;\n    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n\n    int fileNameLength = 0;\n    char* pch = dest;\n    while (*pch != '\\0' && *pch != '.') {\n        fileNameLength++;\n        if (fileNameLength >= 12) {\n            break;\n        }\n    }\n    dest[fileNameLength] = '\\0';\n\n    char fileNameCopy[32];\n    strncpy(fileNameCopy, dest, 32);\n\n    int fileNameCopyLength = strlen(fileNameCopy);\n    fileNameCopy[fileNameCopyLength + 1] = '\\0';\n    fileNameCopy[fileNameCopyLength] = ' ';\n\n    unsigned char* fileNameBufferPtr = windowBuffer + backgroundWidth * 190 + 57;\n\n    buf_fill(fileNameBufferPtr, text_width(fileNameCopy), cursorHeight, backgroundWidth, 100);\n    text_to_buf(fileNameBufferPtr, fileNameCopy, backgroundWidth, backgroundWidth, colorTable[992]);\n\n    win_draw(win);\n\n    int blinkingCounter = 3;\n    bool blink = false;\n\n    int doubleClickSelectedFileIndex = -2;\n    int doubleClickTimer = FILE_DIALOG_DOUBLE_CLICK_DELAY;\n\n    int rc = -1;\n    while (rc == -1) {\n        unsigned int tick = get_time();\n        int keyCode = get_input();\n        int scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_NONE;\n        int scrollCounter = 0;\n        bool isScrolling = false;\n\n        if (keyCode == 500) {\n            rc = 0;\n        } else if (keyCode == KEY_RETURN) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            rc = 0;\n        } else if (keyCode == 501 || keyCode == KEY_ESCAPE) {\n            rc = 1;\n        } else if ((keyCode == KEY_DELETE || keyCode == KEY_BACKSPACE) && fileNameCopyLength > 0) {\n            buf_fill(fileNameBufferPtr, text_width(fileNameCopy), cursorHeight, backgroundWidth, 100);\n            fileNameCopy[fileNameCopyLength - 1] = ' ';\n            fileNameCopy[fileNameCopyLength] = '\\0';\n            text_to_buf(fileNameBufferPtr, fileNameCopy, backgroundWidth, backgroundWidth, colorTable[992]);\n            fileNameCopyLength--;\n            win_draw(win);\n        } else if (keyCode < KEY_FIRST_INPUT_CHARACTER || keyCode > KEY_LAST_INPUT_CHARACTER || fileNameCopyLength >= 8) {\n            if (keyCode == 502 && fileListLength != 0) {\n                int mouseX;\n                int mouseY;\n                mouse_get_position(&mouseX, &mouseY);\n\n                int selectedLine = (mouseY - y - FILE_DIALOG_FILE_LIST_Y) / text_height();\n                if (selectedLine - 1 < 0) {\n                    selectedLine = 0;\n                }\n\n                if (isScrollable || selectedLine < fileListLength) {\n                    if (selectedLine >= FILE_DIALOG_LINE_COUNT) {\n                        selectedLine = FILE_DIALOG_LINE_COUNT - 1;\n                    }\n                } else {\n                    selectedLine = fileListLength - 1;\n                }\n\n                selectedFileIndex = selectedLine;\n                if (selectedFileIndex == doubleClickSelectedFileIndex) {\n                    gsound_play_sfx_file(\"ib1p1xx1\");\n                    strncpy(dest, fileList[selectedFileIndex + pageOffset], 16);\n\n                    int index;\n                    for (index = 0; index < 12; index++) {\n                        if (dest[index] == '.' || dest[index] == '\\0') {\n                            break;\n                        }\n                    }\n\n                    dest[index] = '\\0';\n                    rc = 2;\n                } else {\n                    doubleClickSelectedFileIndex = selectedFileIndex;\n                    buf_fill(fileNameBufferPtr, text_width(fileNameCopy), cursorHeight, backgroundWidth, 100);\n                    strncpy(fileNameCopy, fileList[selectedFileIndex + pageOffset], 16);\n\n                    int index;\n                    for (index = 0; index < 12; index++) {\n                        if (fileNameCopy[index] == '.' || fileNameCopy[index] == '\\0') {\n                            break;\n                        }\n                    }\n\n                    fileNameCopy[index] = '\\0';\n                    fileNameCopyLength = strlen(fileNameCopy);\n                    fileNameCopy[fileNameCopyLength] = ' ';\n                    fileNameCopy[fileNameCopyLength + 1] = '\\0';\n\n                    text_to_buf(fileNameBufferPtr, fileNameCopy, backgroundWidth, backgroundWidth, colorTable[992]);\n                    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                }\n            } else if (keyCode == 506) {\n                scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_UP;\n            } else if (keyCode == 504) {\n                scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_DOWN;\n            } else {\n                switch (keyCode) {\n                case KEY_ARROW_UP:\n                    pageOffset--;\n                    if (pageOffset < 0) {\n                        selectedFileIndex--;\n                        if (selectedFileIndex < 0) {\n                            selectedFileIndex = 0;\n                        }\n                        pageOffset = 0;\n                    }\n                    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                    doubleClickSelectedFileIndex = -2;\n                    break;\n                case KEY_ARROW_DOWN:\n                    if (isScrollable) {\n                        pageOffset++;\n                        if (pageOffset >= maxPageOffset) {\n                            selectedFileIndex++;\n                            if (selectedFileIndex >= FILE_DIALOG_LINE_COUNT) {\n                                selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1;\n                            }\n                            pageOffset = maxPageOffset;\n                        }\n                    } else {\n                        selectedFileIndex++;\n                        if (selectedFileIndex > maxPageOffset) {\n                            selectedFileIndex = maxPageOffset;\n                        }\n                    }\n                    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                    doubleClickSelectedFileIndex = -2;\n                    break;\n                case KEY_HOME:\n                    selectedFileIndex = 0;\n                    pageOffset = 0;\n                    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                    doubleClickSelectedFileIndex = -2;\n                    break;\n                case KEY_END:\n                    if (isScrollable) {\n                        selectedFileIndex = 11;\n                        pageOffset = maxPageOffset;\n                    } else {\n                        selectedFileIndex = maxPageOffset;\n                        pageOffset = 0;\n                    }\n                    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                    doubleClickSelectedFileIndex = -2;\n                    break;\n                }\n            }\n        } else if (isdoschar(keyCode)) {\n            buf_fill(fileNameBufferPtr, text_width(fileNameCopy), cursorHeight, backgroundWidth, 100);\n\n            fileNameCopy[fileNameCopyLength] = keyCode & 0xFF;\n            fileNameCopy[fileNameCopyLength + 1] = ' ';\n            fileNameCopy[fileNameCopyLength + 2] = '\\0';\n            text_to_buf(fileNameBufferPtr, fileNameCopy, backgroundWidth, backgroundWidth, colorTable[992]);\n            fileNameCopyLength++;\n\n            win_draw(win);\n        }\n\n        if (scrollDirection != FILE_DIALOG_SCROLL_DIRECTION_NONE) {\n            unsigned int scrollDelay = 4;\n            doubleClickSelectedFileIndex = -2;\n            while (1) {\n                unsigned int scrollTick = get_time();\n                scrollCounter += 1;\n                if ((!isScrolling && scrollCounter == 1) || (isScrolling && scrollCounter > 14.4)) {\n                    isScrolling = true;\n\n                    if (scrollCounter > 14.4) {\n                        scrollDelay += 1;\n                        if (scrollDelay > 24) {\n                            scrollDelay = 24;\n                        }\n                    }\n\n                    if (scrollDirection == FILE_DIALOG_SCROLL_DIRECTION_UP) {\n                        pageOffset--;\n                        if (pageOffset < 0) {\n                            selectedFileIndex--;\n                            if (selectedFileIndex < 0) {\n                                selectedFileIndex = 0;\n                            }\n                            pageOffset = 0;\n                        }\n                    } else {\n                        if (isScrollable) {\n                            pageOffset++;\n                            if (pageOffset > maxPageOffset) {\n                                selectedFileIndex++;\n                                if (selectedFileIndex >= FILE_DIALOG_LINE_COUNT) {\n                                    selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1;\n                                }\n                                pageOffset = maxPageOffset;\n                            }\n                        } else {\n                            selectedFileIndex++;\n                            if (selectedFileIndex > maxPageOffset) {\n                                selectedFileIndex = maxPageOffset;\n                            }\n                        }\n                    }\n\n                    PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth);\n                    win_draw(win);\n                }\n\n                // NOTE: Original code is slightly different. For unknown reason\n                // entire blinking stuff is placed into two different branches,\n                // which only differs by amount of delay. Probably result of\n                // using large blinking macro as there are no traces of inlined\n                // function.\n                blinkingCounter -= 1;\n                if (blinkingCounter == 0) {\n                    blinkingCounter = 3;\n\n                    int color = blink ? 100 : colorTable[992];\n                    blink = !blink;\n\n                    buf_fill(fileNameBufferPtr + text_width(fileNameCopy) - cursorWidth, cursorWidth, cursorHeight - 2, backgroundWidth, color);\n                }\n\n                // FIXME: Missing windowRefresh makes blinking useless.\n\n                unsigned int delay = (scrollCounter > 14.4) ? 1000 / scrollDelay : 1000 / 24;\n                while (elapsed_time(scrollTick) < delay) {\n                }\n\n                if (game_user_wants_to_quit != 0) {\n                    rc = 1;\n                    break;\n                }\n\n                int key = get_input();\n                if (key == 505 || key == 503) {\n                    break;\n                }\n            }\n        } else {\n            blinkingCounter -= 1;\n            if (blinkingCounter == 0) {\n                blinkingCounter = 3;\n\n                int color = blink ? 100 : colorTable[992];\n                blink = !blink;\n\n                buf_fill(fileNameBufferPtr + text_width(fileNameCopy) - cursorWidth, cursorWidth, cursorHeight - 2, backgroundWidth, color);\n            }\n\n            win_draw(win);\n\n            doubleClickTimer--;\n            if (doubleClickTimer == 0) {\n                doubleClickTimer = FILE_DIALOG_DOUBLE_CLICK_DELAY;\n                doubleClickSelectedFileIndex = -2;\n            }\n\n            while (elapsed_time(tick) < (1000 / 24)) {\n            }\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            rc = 1;\n        }\n    }\n\n    if (rc == 0) {\n        if (fileNameCopyLength != 0) {\n            fileNameCopy[fileNameCopyLength] = '\\0';\n            strcpy(dest, fileNameCopy);\n        } else {\n            rc = 1;\n        }\n    } else {\n        if (rc == 2) {\n            rc = 0;\n        }\n    }\n\n    win_delete(win);\n\n    for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) {\n        art_ptr_unlock(frmHandles[index]);\n    }\n\n    message_exit(&messageList);\n    text_font(oldFont);\n\n    return rc;\n}\n\n// 0x41FBDC\nstatic void PrntFlist(unsigned char* buffer, char** fileList, int pageOffset, int fileListLength, int selectedIndex, int pitch)\n{\n    int lineHeight = text_height();\n    int y = FILE_DIALOG_FILE_LIST_Y;\n    buf_fill(buffer + y * pitch + FILE_DIALOG_FILE_LIST_X, FILE_DIALOG_FILE_LIST_WIDTH, FILE_DIALOG_FILE_LIST_HEIGHT, pitch, 100);\n    if (fileListLength != 0) {\n        if (fileListLength - pageOffset > FILE_DIALOG_LINE_COUNT) {\n            fileListLength = FILE_DIALOG_LINE_COUNT;\n        }\n\n        for (int index = 0; index < fileListLength; index++) {\n            int color = index == selectedIndex ? colorTable[32747] : colorTable[992];\n            text_to_buf(buffer + pitch * y + FILE_DIALOG_FILE_LIST_X, fileList[pageOffset + index], pitch, pitch, color);\n            y += lineHeight;\n        }\n    }\n}\n"
  },
  {
    "path": "src/game/bmpdlog.h",
    "content": "#ifndef FALLOUT_GAME_BMPDLOG_H_\n#define FALLOUT_GAME_BMPDLOG_H_\n\ntypedef enum DialogBoxOptions {\n    DIALOG_BOX_LARGE = 0x01,\n    DIALOG_BOX_MEDIUM = 0x02,\n    DIALOG_BOX_NO_HORIZONTAL_CENTERING = 0x04,\n    DIALOG_BOX_NO_VERTICAL_CENTERING = 0x08,\n    DIALOG_BOX_YES_NO = 0x10,\n    DIALOG_BOX_0x20 = 0x20,\n} DialogBoxOptions;\n\ntypedef enum DialogType {\n    DIALOG_TYPE_MEDIUM,\n    DIALOG_TYPE_LARGE,\n    DIALOG_TYPE_COUNT,\n} DialogType;\n\ntypedef enum FileDialogFrm {\n    FILE_DIALOG_FRM_BACKGROUND,\n    FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL,\n    FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED,\n    FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL,\n    FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED,\n    FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL,\n    FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED,\n    FILE_DIALOG_FRM_COUNT,\n} FileDialogFrm;\n\ntypedef enum FileDialogScrollDirection {\n    FILE_DIALOG_SCROLL_DIRECTION_NONE,\n    FILE_DIALOG_SCROLL_DIRECTION_UP,\n    FILE_DIALOG_SCROLL_DIRECTION_DOWN,\n} FileDialogScrollDirection;\n\nextern int dbox[DIALOG_TYPE_COUNT];\nextern int ytable[DIALOG_TYPE_COUNT];\nextern int xtable[DIALOG_TYPE_COUNT];\nextern int doneY[DIALOG_TYPE_COUNT];\nextern int doneX[DIALOG_TYPE_COUNT];\nextern int dblines[DIALOG_TYPE_COUNT];\nextern int flgids[FILE_DIALOG_FRM_COUNT];\nextern int flgids2[FILE_DIALOG_FRM_COUNT];\n\nint dialog_out(const char* title, const char** body, int bodyLength, int x, int y, int titleColor, const char* a8, int bodyColor, int flags);\nint file_dialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags);\nint save_file_dialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags);\n\n#endif /* FALLOUT_GAME_BMPDLOG_H_ */\n"
  },
  {
    "path": "src/game/cache.c",
    "content": "#include \"game/cache.h\"\n\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/memory.h\"\n#include \"int/sound.h\"\n\nstatic_assert(sizeof(CacheEntry) == 32, \"wrong size\");\nstatic_assert(sizeof(Cache) == 84, \"wrong size\");\n\nstatic bool cache_add(Cache* cache, int key, int* indexPtr);\nstatic bool cache_insert(Cache* cache, CacheEntry* cacheEntry, int index);\nstatic int cache_find(Cache* cache, int key, int* indexPtr);\nstatic int cache_create_item(CacheEntry** cacheEntryPtr);\nstatic bool cache_init_item(CacheEntry* cacheEntry);\nstatic bool cache_destroy_item(Cache* cache, CacheEntry* cacheEntry);\nstatic bool cache_unlock_all(Cache* cache);\nstatic bool cache_reset_counter(Cache* cache);\nstatic bool cache_make_room(Cache* cache, int size);\nstatic bool cache_purge(Cache* cache);\nstatic bool cache_resize_array(Cache* cache, int newCapacity);\nstatic int cache_compare_make_room(const void* a1, const void* a2);\nstatic int cache_compare_reset_counter(const void* a1, const void* a2);\n\n// 0x510938\nstatic int lock_sound_ticker = 0;\n\n// cache_init\n// 0x41FCC0\nbool cache_init(Cache* cache, CacheSizeProc* sizeProc, CacheReadProc* readProc, CacheFreeProc* freeProc, int maxSize)\n{\n    if (!heap_init(&(cache->heap), maxSize)) {\n        return false;\n    }\n\n    cache->size = 0;\n    cache->maxSize = maxSize;\n    cache->entriesLength = 0;\n    cache->entriesCapacity = CACHE_ENTRIES_INITIAL_CAPACITY;\n    cache->hits = 0;\n    cache->entries = (CacheEntry**)mem_malloc(sizeof(*cache->entries) * cache->entriesCapacity);\n    cache->sizeProc = sizeProc;\n    cache->readProc = readProc;\n    cache->freeProc = freeProc;\n\n    if (cache->entries == NULL) {\n        return false;\n    }\n\n    memset(cache->entries, 0, sizeof(*cache->entries) * cache->entriesCapacity);\n\n    return true;\n}\n\n// cache_exit\n// 0x41FD50\nbool cache_exit(Cache* cache)\n{\n    if (cache == NULL) {\n        return false;\n    }\n\n    cache_unlock_all(cache);\n    cache_flush(cache);\n    heap_exit(&(cache->heap));\n\n    cache->size = 0;\n    cache->maxSize = 0;\n    cache->entriesLength = 0;\n    cache->entriesCapacity = 0;\n    cache->hits = 0;\n\n    if (cache->entries != NULL) {\n        mem_free(cache->entries);\n        cache->entries = NULL;\n    }\n\n    cache->sizeProc = NULL;\n    cache->readProc = NULL;\n    cache->freeProc = NULL;\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x41FDC0\nint cache_query(Cache* cache, int key)\n{\n    int index;\n\n    if (cache == NULL) {\n        return 0;\n    }\n\n    if (cache_find(cache, key, &index) != 2) {\n        return 0;\n    }\n\n    return 1;\n}\n\n// 0x41FDE8\nbool cache_lock(Cache* cache, int key, void** data, CacheEntry** cacheEntryPtr)\n{\n    if (cache == NULL || data == NULL || cacheEntryPtr == NULL) {\n        return false;\n    }\n\n    *cacheEntryPtr = NULL;\n\n    int index;\n    int rc = cache_find(cache, key, &index);\n    if (rc == 2) {\n        // Use existing cache entry.\n        CacheEntry* cacheEntry = cache->entries[index];\n        cacheEntry->hits++;\n    } else if (rc == 3) {\n        // New cache entry is required.\n        if (cache->entriesLength >= INT_MAX) {\n            return false;\n        }\n\n        if (!cache_add(cache, key, &index)) {\n            return false;\n        }\n\n        lock_sound_ticker %= 4;\n        if (lock_sound_ticker == 0) {\n            soundContinueAll();\n        }\n    } else {\n        return false;\n    }\n\n    CacheEntry* cacheEntry = cache->entries[index];\n    if (cacheEntry->referenceCount == 0) {\n        if (!heap_lock(&(cache->heap), cacheEntry->heapHandleIndex, &(cacheEntry->data))) {\n            return false;\n        }\n    }\n\n    cacheEntry->referenceCount++;\n\n    cache->hits++;\n    cacheEntry->mru = cache->hits;\n\n    if (cache->hits == UINT_MAX) {\n        cache_reset_counter(cache);\n    }\n\n    *data = cacheEntry->data;\n    *cacheEntryPtr = cacheEntry;\n\n    return true;\n}\n\n// 0x4200B8\nbool cache_unlock(Cache* cache, CacheEntry* cacheEntry)\n{\n    if (cache == NULL || cacheEntry == NULL) {\n        return false;\n    }\n\n    if (cacheEntry->referenceCount == 0) {\n        return false;\n    }\n\n    cacheEntry->referenceCount--;\n\n    if (cacheEntry->referenceCount == 0) {\n        heap_unlock(&(cache->heap), cacheEntry->heapHandleIndex);\n    }\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x4200EC\nint cache_discard(Cache* cache, int key)\n{\n    int index;\n    CacheEntry* cacheEntry;\n\n    if (cache == NULL) {\n        return 0;\n    }\n\n    if (cache_find(cache, key, &index) != 2) {\n        return 0;\n    }\n\n    cacheEntry = cache->entries[index];\n    if (cacheEntry->referenceCount != 0) {\n        return 0;\n    }\n\n    cacheEntry->flags |= CACHE_ENTRY_MARKED_FOR_EVICTION;\n\n    cache_purge(cache);\n\n    return 1;\n}\n\n// cache_flush\n// 0x42012C\nbool cache_flush(Cache* cache)\n{\n    if (cache == NULL) {\n        return false;\n    }\n\n    // Loop thru cache entries and mark those with no references for eviction.\n    for (int index = 0; index < cache->entriesLength; index++) {\n        CacheEntry* cacheEntry = cache->entries[index];\n        if (cacheEntry->referenceCount == 0) {\n            cacheEntry->flags |= CACHE_ENTRY_MARKED_FOR_EVICTION;\n        }\n    }\n\n    // Sweep cache entries marked earlier.\n    cache_purge(cache);\n\n    // Shrink cache entries array if it's too big.\n    int optimalCapacity = cache->entriesLength + CACHE_ENTRIES_GROW_CAPACITY;\n    if (optimalCapacity < cache->entriesCapacity) {\n        cache_resize_array(cache, optimalCapacity);\n    }\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x420184\nint cache_size(Cache* cache, int* sizePtr)\n{\n    if (cache == NULL) {\n        return 0;\n    }\n\n    if (sizePtr == NULL) {\n        return 0;\n    }\n\n    *sizePtr = cache->size;\n\n    return 1;\n}\n\n// 0x42019C\nbool cache_stats(Cache* cache, char* dest)\n{\n    if (cache == NULL || dest == NULL) {\n        return false;\n    }\n\n    sprintf(dest, \"Cache stats are disabled.%s\", \"\\n\");\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x4201C0\nint cache_create_list(Cache* cache, unsigned int a2, int** tagsPtr, int* tagsLengthPtr)\n{\n    int cacheItemIndex;\n    int tagIndex;\n\n    if (cache == NULL) {\n        return 0;\n    }\n\n    if (tagsPtr == NULL) {\n        return 0;\n    }\n\n    if (tagsLengthPtr == NULL) {\n        return 0;\n    }\n\n    *tagsLengthPtr = 0;\n\n    switch (a2) {\n    case CACHE_LIST_REQUEST_TYPE_ALL_ITEMS:\n        *tagsPtr = (int*)mem_malloc(sizeof(*tagsPtr) * cache->entriesLength);\n        if (*tagsPtr == NULL) {\n            return 0;\n        }\n\n        for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) {\n            (*tagsPtr)[cacheItemIndex] = cache->entries[cacheItemIndex]->key;\n        }\n\n        *tagsLengthPtr = cache->entriesLength;\n\n        break;\n    case CACHE_LIST_REQUEST_TYPE_LOCKED_ITEMS:\n        for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) {\n            if (cache->entries[cacheItemIndex]->referenceCount != 0) {\n                (*tagsLengthPtr)++;\n            }\n        }\n\n        *tagsPtr = (int*)mem_malloc(sizeof(*tagsPtr) * (*tagsLengthPtr));\n        if (*tagsPtr == NULL) {\n            return 0;\n        }\n\n        tagIndex = 0;\n        for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) {\n            if (cache->entries[cacheItemIndex]->referenceCount != 0) {\n                if (tagIndex < *tagsLengthPtr) {\n                    (*tagsPtr)[tagIndex++] = cache->entries[cacheItemIndex]->key;\n                }\n            }\n        }\n\n        break;\n    case CACHE_LIST_REQUEST_TYPE_UNLOCKED_ITEMS:\n        for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) {\n            if (cache->entries[cacheItemIndex]->referenceCount == 0) {\n                (*tagsLengthPtr)++;\n            }\n        }\n\n        *tagsPtr = (int*)mem_malloc(sizeof(*tagsPtr) * (*tagsLengthPtr));\n        if (*tagsPtr == NULL) {\n            return 0;\n        }\n\n        tagIndex = 0;\n        for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) {\n            if (cache->entries[cacheItemIndex]->referenceCount == 0) {\n                if (tagIndex < *tagsLengthPtr) {\n                    (*tagsPtr)[tagIndex++] = cache->entries[cacheItemIndex]->key;\n                }\n            }\n        }\n\n        break;\n    }\n\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x420384\nint cache_destroy_list(int** tagsPtr)\n{\n    if (tagsPtr == NULL) {\n        return 0;\n    }\n\n    if (*tagsPtr == NULL) {\n        return 0;\n    }\n\n    mem_free(*tagsPtr);\n    *tagsPtr = NULL;\n\n    return 1;\n}\n\n// Fetches entry for the specified key into the cache.\n//\n// 0x4203AC\nstatic bool cache_add(Cache* cache, int key, int* indexPtr)\n{\n    CacheEntry* cacheEntry;\n\n    // NOTE: Uninline.\n    if (cache_create_item(&cacheEntry) != 1) {\n        return 0;\n    }\n\n    do {\n        int size;\n        if (cache->sizeProc(key, &size) != 0) {\n            break;\n        }\n\n        if (!cache_make_room(cache, size)) {\n            break;\n        }\n\n        bool allocated = false;\n        int cacheEntrySize = size;\n        for (int attempt = 0; attempt < 10; attempt++) {\n            if (heap_allocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 1)) {\n                allocated = true;\n                break;\n            }\n\n            cacheEntrySize = (int)((double)cacheEntrySize + (double)size * 0.25);\n            if (cacheEntrySize > cache->maxSize) {\n                break;\n            }\n\n            if (!cache_make_room(cache, cacheEntrySize)) {\n                break;\n            }\n        }\n\n        if (!allocated) {\n            cache_flush(cache);\n\n            allocated = true;\n            if (!heap_allocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 1)) {\n                if (!heap_allocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 0)) {\n                    allocated = false;\n                }\n            }\n        }\n\n        if (!allocated) {\n            break;\n        }\n\n        do {\n            if (!heap_lock(&(cache->heap), cacheEntry->heapHandleIndex, &(cacheEntry->data))) {\n                break;\n            }\n\n            if (cache->readProc(key, &size, cacheEntry->data) != 0) {\n                break;\n            }\n\n            heap_unlock(&(cache->heap), cacheEntry->heapHandleIndex);\n\n            cacheEntry->size = size;\n            cacheEntry->key = key;\n\n            bool isNewKey = true;\n            if (*indexPtr < cache->entriesLength) {\n                if (key < cache->entries[*indexPtr]->key) {\n                    if (*indexPtr == 0 || key > cache->entries[*indexPtr - 1]->key) {\n                        isNewKey = false;\n                    }\n                }\n            }\n\n            if (isNewKey) {\n                if (cache_find(cache, key, indexPtr) != 3) {\n                    break;\n                }\n            }\n\n            if (!cache_insert(cache, cacheEntry, *indexPtr)) {\n                break;\n            }\n\n            return true;\n        } while (0);\n\n        heap_unlock(&(cache->heap), cacheEntry->heapHandleIndex);\n    } while (0);\n\n    // NOTE: Uninline.\n    cache_destroy_item(cache, cacheEntry);\n\n    return false;\n}\n\n// 0x4205E8\nstatic bool cache_insert(Cache* cache, CacheEntry* cacheEntry, int index)\n{\n    // Ensure cache have enough space for new entry.\n    if (cache->entriesLength == cache->entriesCapacity - 1) {\n        if (!cache_resize_array(cache, cache->entriesCapacity + CACHE_ENTRIES_GROW_CAPACITY)) {\n            return false;\n        }\n    }\n\n    // Move entries below insertion point.\n    memmove(&(cache->entries[index + 1]), &(cache->entries[index]), sizeof(*cache->entries) * (cache->entriesLength - index));\n\n    cache->entries[index] = cacheEntry;\n    cache->entriesLength++;\n    cache->size += cacheEntry->size;\n\n    return true;\n}\n\n// Finds index for given key.\n//\n// Returns 2 if entry already exists in cache, or 3 if entry does not exist. In\n// this case indexPtr represents insertion point.\n//\n// 0x420654\nstatic int cache_find(Cache* cache, int key, int* indexPtr)\n{\n    int length = cache->entriesLength;\n    if (length == 0) {\n        *indexPtr = 0;\n        return 3;\n    }\n\n    int r = length - 1;\n    int l = 0;\n    int mid;\n    int cmp;\n\n    do {\n        mid = (l + r) / 2;\n\n        cmp = key - cache->entries[mid]->key;\n        if (cmp == 0) {\n            *indexPtr = mid;\n            return 2;\n        }\n\n        if (cmp > 0) {\n            l = l + 1;\n        } else {\n            r = r - 1;\n        }\n    } while (r >= l);\n\n    if (cmp < 0) {\n        *indexPtr = mid;\n    } else {\n        *indexPtr = mid + 1;\n    }\n\n    return 3;\n}\n\n// NOTE: Inlined.\n//\n// 0x4206C0\nstatic int cache_create_item(CacheEntry** cacheEntryPtr)\n{\n    *cacheEntryPtr = (CacheEntry*)mem_malloc(sizeof(**cacheEntryPtr));\n\n    // FIXME: Wrong check, should be *cacheEntryPtr != NULL.\n    if (cacheEntryPtr != NULL) {\n        // NOTE: Uninline.\n        return cache_init_item(*cacheEntryPtr);\n    }\n\n    return 0;\n}\n\n// 0x420708\nstatic bool cache_init_item(CacheEntry* cacheEntry)\n{\n    cacheEntry->key = 0;\n    cacheEntry->size = 0;\n    cacheEntry->data = NULL;\n    cacheEntry->referenceCount = 0;\n    cacheEntry->hits = 0;\n    cacheEntry->flags = 0;\n    cacheEntry->mru = 0;\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x420740\nstatic bool cache_destroy_item(Cache* cache, CacheEntry* cacheEntry)\n{\n    if (cacheEntry->data != NULL) {\n        heap_deallocate(&(cache->heap), &(cacheEntry->heapHandleIndex));\n    }\n\n    mem_free(cacheEntry);\n\n    return true;\n}\n\n// 0x420764\nstatic bool cache_unlock_all(Cache* cache)\n{\n    Heap* heap = &(cache->heap);\n    for (int index = 0; index < cache->entriesLength; index++) {\n        CacheEntry* cacheEntry = cache->entries[index];\n\n        // NOTE: Original code is slightly different. For unknown reason it uses\n        // inner loop to decrement `referenceCount` one by one. Probably using\n        // some inlined function.\n        if (cacheEntry->referenceCount != 0) {\n            heap_unlock(heap, cacheEntry->heapHandleIndex);\n            cacheEntry->referenceCount = 0;\n        }\n    }\n\n    return true;\n}\n\n// 0x4207D4\nstatic bool cache_reset_counter(Cache* cache)\n{\n    if (cache == NULL) {\n        return false;\n    }\n\n    CacheEntry** entries = (CacheEntry**)mem_malloc(sizeof(*entries) * cache->entriesLength);\n    if (entries == NULL) {\n        return false;\n    }\n\n    memcpy(entries, cache->entries, sizeof(*entries) * cache->entriesLength);\n\n    qsort(entries, cache->entriesLength, sizeof(*entries), cache_compare_reset_counter);\n\n    for (int index = 0; index < cache->entriesLength; index++) {\n        CacheEntry* cacheEntry = entries[index];\n        cacheEntry->mru = index;\n    }\n\n    cache->hits = cache->entriesLength;\n\n    // FIXME: Obviously leak `entries`.\n\n    return true;\n}\n\n// Prepare cache for storing new entry with the specified size.\n//\n// 0x42084C\nstatic bool cache_make_room(Cache* cache, int size)\n{\n    if (size > cache->maxSize) {\n        // The entry of given size is too big for caching, no matter what.\n        return false;\n    }\n\n    if (cache->maxSize - cache->size >= size) {\n        // There is space available for entry of given size, there is no need to\n        // evict anything.\n        return true;\n    }\n\n    CacheEntry** entries = (CacheEntry**)mem_malloc(sizeof(*entries) * cache->entriesLength);\n    if (entries != NULL) {\n        memcpy(entries, cache->entries, sizeof(*entries) * cache->entriesLength);\n        qsort(entries, cache->entriesLength, sizeof(*entries), cache_compare_make_room);\n\n        // The sweeping threshold is 20% of cache size plus size for the new\n        // entry. Once the threshold is reached the marking process stops.\n        int threshold = size + (int)((double)cache->size * 0.2);\n\n        int accum = 0;\n        int index;\n        for (index = 0; index < cache->entriesLength; index++) {\n            CacheEntry* entry = entries[index];\n            if (entry->referenceCount == 0) {\n                if (entry->size >= threshold) {\n                    entry->flags |= CACHE_ENTRY_MARKED_FOR_EVICTION;\n\n                    // We've just found one huge entry, there is no point to\n                    // mark individual smaller entries in the code path below,\n                    // reset the accumulator to skip it entirely.\n                    accum = 0;\n                    break;\n                } else {\n                    accum += entry->size;\n\n                    if (accum >= threshold) {\n                        break;\n                    }\n                }\n            }\n        }\n\n        if (accum != 0) {\n            // The loop below assumes index to be positioned on the entry, where\n            // accumulator stopped. If we've reached the end, reposition\n            // it to the last entry.\n            if (index == cache->entriesLength) {\n                index -= 1;\n            }\n\n            // Loop backwards from the point we've stopped and mark all\n            // unreferenced entries for sweeping.\n            for (; index >= 0; index--) {\n                CacheEntry* entry = entries[index];\n                if (entry->referenceCount == 0) {\n                    entry->flags |= CACHE_ENTRY_MARKED_FOR_EVICTION;\n                }\n            }\n        }\n\n        mem_free(entries);\n    }\n\n    cache_purge(cache);\n\n    if (cache->maxSize - cache->size >= size) {\n        return true;\n    }\n\n    return false;\n}\n\n// 0x42099C\nstatic bool cache_purge(Cache* cache)\n{\n    for (int index = 0; index < cache->entriesLength; index++) {\n        CacheEntry* cacheEntry = cache->entries[index];\n        if ((cacheEntry->flags & CACHE_ENTRY_MARKED_FOR_EVICTION) != 0) {\n            if (cacheEntry->referenceCount != 0) {\n                // Entry was marked for eviction but still has references,\n                // unmark it.\n                cacheEntry->flags &= ~CACHE_ENTRY_MARKED_FOR_EVICTION;\n            } else {\n                int cacheEntrySize = cacheEntry->size;\n\n                // NOTE: Uninline.\n                cache_destroy_item(cache, cacheEntry);\n\n                // Move entries up.\n                memmove(&(cache->entries[index]), &(cache->entries[index + 1]), sizeof(*cache->entries) * ((cache->entriesLength - index) - 1));\n\n                cache->entriesLength--;\n                cache->size -= cacheEntrySize;\n\n                // The entry was removed, compensate index.\n                index--;\n            }\n        }\n    }\n\n    return true;\n}\n\n// 0x420A40\nstatic bool cache_resize_array(Cache* cache, int newCapacity)\n{\n    if (newCapacity < cache->entriesLength) {\n        return false;\n    }\n\n    CacheEntry** entries = (CacheEntry**)mem_realloc(cache->entries, sizeof(*cache->entries) * newCapacity);\n    if (entries == NULL) {\n        return false;\n    }\n\n    cache->entries = entries;\n    cache->entriesCapacity = newCapacity;\n\n    return true;\n}\n\n// 0x420A74\nstatic int cache_compare_make_room(const void* a1, const void* a2)\n{\n    CacheEntry* v1 = *(CacheEntry**)a1;\n    CacheEntry* v2 = *(CacheEntry**)a2;\n\n    if (v1->referenceCount != 0 && v2->referenceCount == 0) {\n        return 1;\n    }\n\n    if (v2->referenceCount != 0 && v1->referenceCount == 0) {\n        return -1;\n    }\n\n    if (v1->hits < v2->hits) {\n        return -1;\n    } else if (v1->hits > v2->hits) {\n        return 1;\n    }\n\n    if (v1->mru < v2->mru) {\n        return -1;\n    } else if (v1->mru > v2->mru) {\n        return 1;\n    }\n\n    return 0;\n}\n\n// 0x420AE8\nstatic int cache_compare_reset_counter(const void* a1, const void* a2)\n{\n    CacheEntry* v1 = *(CacheEntry**)a1;\n    CacheEntry* v2 = *(CacheEntry**)a2;\n\n    if (v1->mru < v2->mru) {\n        return 1;\n    } else if (v1->mru > v2->mru) {\n        return -1;\n    } else {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "src/game/cache.h",
    "content": "#ifndef FALLOUT_GAME_CACHE_H_\n#define FALLOUT_GAME_CACHE_H_\n\n#include <stdbool.h>\n\n#include \"game/heap.h\"\n\n#define INVALID_CACHE_ENTRY ((CacheEntry*)-1)\n\n// The initial number of cache entries in new cache.\n#define CACHE_ENTRIES_INITIAL_CAPACITY 100\n\n// The number of cache entries added when cache capacity is reached.\n#define CACHE_ENTRIES_GROW_CAPACITY 50\n\ntypedef enum CacheEntryFlags {\n    // Specifies that cache entry has no references as should be evicted during\n    // the next sweep operation.\n    CACHE_ENTRY_MARKED_FOR_EVICTION = 0x01,\n} CacheEntryFlags;\n\ntypedef enum CacheListRequestType {\n    CACHE_LIST_REQUEST_TYPE_ALL_ITEMS = 0,\n    CACHE_LIST_REQUEST_TYPE_LOCKED_ITEMS = 1,\n    CACHE_LIST_REQUEST_TYPE_UNLOCKED_ITEMS = 2,\n} CacheListRequestType;\n\ntypedef int CacheSizeProc(int key, int* sizePtr);\ntypedef int CacheReadProc(int key, int* sizePtr, unsigned char* buffer);\ntypedef void CacheFreeProc(void* ptr);\n\ntypedef struct CacheEntry {\n    int key;\n    int size;\n    unsigned char* data;\n    unsigned int referenceCount;\n\n    // Total number of hits that this cache entry received during it's\n    // lifetime.\n    unsigned int hits;\n\n    unsigned int flags;\n\n    // The most recent hit in terms of cache hit counter. Used to track most\n    // recently used entries in eviction strategy.\n    unsigned int mru;\n\n    int heapHandleIndex;\n} CacheEntry;\n\ntypedef struct Cache {\n    // Current size of entries in cache.\n    int size;\n\n    // Maximum size of entries in cache.\n    int maxSize;\n\n    // The length of `entries` array.\n    int entriesLength;\n\n    // The capacity of `entries` array.\n    int entriesCapacity;\n\n    // Total number of hits during cache lifetime.\n    unsigned int hits;\n\n    // List of cache entries.\n    CacheEntry** entries;\n\n    CacheSizeProc* sizeProc;\n    CacheReadProc* readProc;\n    CacheFreeProc* freeProc;\n    Heap heap;\n} Cache;\n\nbool cache_init(Cache* cache, CacheSizeProc* sizeProc, CacheReadProc* readProc, CacheFreeProc* freeProc, int maxSize);\nbool cache_exit(Cache* cache);\nint cache_query(Cache* cache, int key);\nbool cache_lock(Cache* cache, int key, void** data, CacheEntry** cacheEntryPtr);\nbool cache_unlock(Cache* cache, CacheEntry* cacheEntry);\nint cache_discard(Cache* cache, int key);\nbool cache_flush(Cache* cache);\nint cache_size(Cache* cache, int* sizePtr);\nbool cache_stats(Cache* cache, char* dest);\nint cache_create_list(Cache* cache, unsigned int a2, int** tagsPtr, int* tagsLengthPtr);\nint cache_destroy_list(int** tagsPtr);\n\n#endif /* FALLOUT_GAME_CACHE_H_ */\n"
  },
  {
    "path": "src/game/cd.c",
    "content": "#include \"game/cd.h\"\n\n// NOTE: Actual file name is unknown. Functions in this module do not present\n// in debug symbols from `mapper2.exe`, and does not appear in OS X binary. The\n// functions implement some sort of CD check, and they appear between `cache.c`\n// and `combat.c`, so based in it's intent and order `cd.c` is a nice candidate.\n//\n// Since there are no visibility hints in Windows binary, all functions are\n// public.\n\n#include <stdlib.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n// 0x420B10\nint sub_420B10(const char* a1)\n{\n    return GetDriveTypeA(a1) == DRIVE_CDROM;\n}\n\n// 0x420B28\nint sub_420B28(const char* a1, const char* a2)\n{\n    UINT oldErrorMode;\n    char volumeName[MAX_PATH];\n    BOOL success;\n\n    oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS);\n    success = GetVolumeInformationA(a1, volumeName, MAX_PATH, NULL, NULL, NULL, NULL, 0);\n    if (success) {\n        success = lstrcmpA(a2, volumeName) == 0;\n    }\n    SetErrorMode(oldErrorMode);\n\n    return success;\n}\n\n// 0x420B8C\nint sub_420B8C(const char* a1)\n{\n    DWORD attributes;\n\n    attributes = GetFileAttributesA(a1);\n    if (attributes != INVALID_FILE_ATTRIBUTES) {\n        if ((attributes & FILE_ATTRIBUTE_READONLY) != 0) {\n            if (!SetFileAttributesA(a1, FILE_ATTRIBUTE_NORMAL)) {\n                return 1;\n            }\n            SetFileAttributesA(a1, attributes);\n        }\n    }\n\n    return 0;\n}\n\n// 0x420BDC\nint sub_420BDC(const char* a1, int a2)\n{\n    DWORD sectorsPerCluster;\n    DWORD bytesPerSector;\n    DWORD numberOfFreeClusters;\n    DWORD totalNumberOfClusters;\n\n    if (GetDiskFreeSpaceA(a1, &sectorsPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters)) {\n        if (a2 == bytesPerSector) {\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x420C18\nint sub_420C18(const char* a1, const char* a2, int a3)\n{\n    char drive[_MAX_DRIVE + 1];\n\n    _splitpath(a1, drive, NULL, NULL, NULL);\n    lstrcatA(drive, \"\\\\\");\n\n    if ((a3 & 0x1) != 0) {\n        // NOTE: Uninline.\n        if (!sub_420B10(drive)) {\n            return 0;\n        }\n    }\n\n    if ((a3 & 0x02) != 0) {\n        if (a2 != NULL) {\n            if (!sub_420B28(drive, a2)) {\n                return 0;\n            }\n        }\n    }\n\n    if ((a3 & 0x04) != 0) {\n        if (!sub_420B8C(a1)) {\n            return 0;\n        }\n    }\n\n    if ((a3 & 0x08) != 0) {\n        if (!sub_420BDC(drive, 2048)) {\n            return 0;\n        }\n    }\n\n    return 1;\n}\n"
  },
  {
    "path": "src/game/cd.h",
    "content": "#ifndef FALLOUT_GAME_CD_H_\n#define FALLOUT_GAME_CD_H_\n\nint sub_420B10(const char* a1);\nint sub_420B28(const char* a1, const char* a2);\nint sub_420B8C(const char* a1);\nint sub_420BDC(const char* a1, int a2);\nint sub_420C18(const char* a1, const char* a2, int a3);\n\n#endif /* FALLOUT_GAME_CD_H_ */\n"
  },
  {
    "path": "src/game/combat.c",
    "content": "#include \"game/combat.h\"\n\n#include <limits.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/actions.h\"\n#include \"game/anim.h\"\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"game/combatai.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/elevator.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/loadsave.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/pipboy.h\"\n#include \"game/proto.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define CALLED_SHOT_WINDOW_X 68\n#define CALLED_SHOT_WINDOW_Y 20\n#define CALLED_SHOT_WINDOW_WIDTH 504\n#define CALLED_SHOT_WINDOW_HEIGHT 309\n\nstatic void combatInitAIInfoList();\nstatic int combatCopyAIInfo(int srcIndex, int destIndex);\nstatic void combat_begin(Object* a1);\nstatic void combat_begin_extra(Object* a1);\nstatic void combat_update_critters_in_los(int a1);\nstatic void combat_over();\nstatic void combat_add_noncoms();\nstatic int compare_faster(const void* a1, const void* a2);\nstatic void combat_sequence_init(Object* a1, Object* a2);\nstatic void combat_sequence();\nstatic int combat_input();\nstatic void combat_set_move_all();\nstatic int combat_turn(Object* a1, bool a2);\nstatic bool combat_should_end();\nstatic bool check_ranged_miss(Attack* attack);\nstatic int shoot_along_path(Attack* attack, int a2, int a3, int anim);\nstatic int compute_spray(Attack* attack, int accuracy, int* roundsHitMainTargetPtr, int* roundsSpentPtr, int anim);\nstatic int correctAttackForPerks(Attack* attack);\nstatic int compute_attack(Attack* attack);\nstatic int attack_crit_success(Attack* a1);\nstatic int attackFindInvalidFlags(Object* a1, Object* a2);\nstatic int attack_crit_failure(Attack* attack);\nstatic void do_random_cripple(int* flagsPtr);\nstatic int determine_to_hit_func(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, int a6);\nstatic void compute_damage(Attack* attack, int ammoQuantity, int bonusDamageMultiplier);\nstatic void check_for_death(Object* a1, int a2, int* a3);\nstatic void set_new_results(Object* a1, int a2);\nstatic void damage_object(Object* a1, int damage, bool animated, int a4, Object* a5);\nstatic void combat_display_hit(char* dest, Object* critter_obj, int damage);\nstatic void combat_display_flags(char* a1, int flags, Object* a3);\nstatic void combat_standup(Object* a1);\nstatic void print_tohit(unsigned char* dest, int dest_pitch, int a3);\nstatic char* combat_get_loc_name(Object* critter, int hitLocation);\nstatic void draw_loc_off(int a1, int a2);\nstatic void draw_loc_on(int a1, int a2);\nstatic void draw_loc(int eventCode, int color);\nstatic int get_called_shot_location(Object* critter, int* hitLocation, int hitMode);\n\n// TODO: Remove.\n//\n// 0x500B50\nstatic char _a_1[] = \".\";\n\n// 0x51093C\nstatic int combat_turn_running = 0;\n\n// 0x510940\nint combatNumTurns = 0;\n\n// 0x510944\nunsigned int combat_state = COMBAT_STATE_0x02;\n\n// 0x510948\nstatic CombatAiInfo* aiInfoList = NULL;\n\n// 0x51094C\nSTRUCT_664980* gcsd = NULL;\n\n// 0x510950\nbool combat_call_display = false;\n\n// Accuracy modifiers for hit locations.\n//\n// 0x510954\nstatic int hit_location_penalty[HIT_LOCATION_COUNT] = {\n    -40,\n    -30,\n    -30,\n    0,\n    -20,\n    -20,\n    -60,\n    -30,\n    0,\n};\n\n// Critical hit tables for every kill type.\n//\n// 0x510978\nstatic CriticalHitDescription crit_succ_eff[KILL_TYPE_COUNT][HIT_LOCATION_COUNT][CRTICIAL_EFFECT_COUNT] = {\n    // KILL_TYPE_MAN\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5002, 5003 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5002, 5003 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5004, 5003 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 5005, 5006 },\n            { 6, DAM_DEAD, -1, 0, 0, 5007, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5008, 5000 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 5009, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 5010, 5011 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5012, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5012, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5013, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5008, 5000 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 5009, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 5014, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5015, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5015, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5013, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5016, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5017, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5019, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5019, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5020, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5021, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5023, 5000 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5023, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 5023, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5025, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5025, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5026, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5023, 5000 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5023, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 5023, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5025, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5025, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5026, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, STAT_LUCK, 4, DAM_BLIND, 5027, 5028 },\n            { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 5029, 5028 },\n            { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 5029, 5028 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5030, 5000 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5031, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 5032, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5033, 5000 },\n            { 3, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5034, 5035 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5035, 5036 },\n            { 3, DAM_KNOCKED_OUT, -1, 0, 0, 5036, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5035, 5036 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5037, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5016, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5017, 5000 },\n            { 4, 0, -1, 0, 0, 5018, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5019, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5020, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5021, 5000 },\n        },\n    },\n    // KILL_TYPE_WOMAN\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5101, 5100 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5102, 5103 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5102, 5103 },\n            { 6, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5104, 5103 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 5105, 5106 },\n            { 6, DAM_DEAD, -1, 0, 0, 5107, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5108, 5100 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 5109, 5100 },\n            { 4, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_LEFT, 5110, 5111 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_LEFT, 5110, 5111 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5112, 5100 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5113, 5100 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5108, 5100 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 5109, 5100 },\n            { 4, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_RIGHT, 5114, 5100 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_RIGHT, 5114, 5100 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5115, 5100 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5113, 5100 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5116, 5100 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5117, 5100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5119, 5100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5119, 5100 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5120, 5100 },\n            { 6, DAM_DEAD, -1, 0, 0, 5121, 5100 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5123, 5100 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5123, 5124 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 5123, 5124 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5125, 5100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5125, 5126 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5126, 5100 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5123, 5100 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5123, 5124 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 5123, 5124 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5125, 5100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5125, 5126 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5126, 5100 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, STAT_LUCK, 4, DAM_BLIND, 5127, 5128 },\n            { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 5129, 5128 },\n            { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 5129, 5128 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5130, 5100 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5131, 5100 },\n            { 8, DAM_DEAD, -1, 0, 0, 5132, 5100 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5133, 5100 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5133, 5134 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5134, 5135 },\n            { 3, DAM_KNOCKED_OUT, -1, 0, 0, 5135, 5100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5134, 5135 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5135, 5100 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5116, 5100 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5117, 5100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5119, 5100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5119, 5100 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5120, 5100 },\n            { 6, DAM_DEAD, -1, 0, 0, 5121, 5100 },\n        },\n    },\n    // KILL_TYPE_CHILD\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5200, 5201 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_KNOCKED_OUT, 5202, 5203 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_KNOCKED_OUT, 5202, 5203 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5203, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5203, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5204, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5205, 5000 },\n            { 4, DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5206, 5207 },\n            { 4, DAM_LOSE_TURN, STAT_ENDURANCE, -2, DAM_CRIP_ARM_LEFT, 5206, 5207 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5208, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5208, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5208, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5209, 5000 },\n            { 4, DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5206, 5207 },\n            { 4, DAM_LOSE_TURN, STAT_ENDURANCE, -2, DAM_CRIP_ARM_RIGHT, 5206, 5207 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5208, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5208, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5208, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5210, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5211, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5212, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5212, 5000 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5213, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5214, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5215, 5000 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, DAM_CRIP_ARM_RIGHT | DAM_BLIND | DAM_ON_FIRE | DAM_EXPLODE, 5000, 0 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, DAM_CRIP_ARM_RIGHT | DAM_BLIND | DAM_ON_FIRE | DAM_EXPLODE, 5000, 0 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5217, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5217, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5217, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5215, 5000 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, DAM_CRIP_ARM_RIGHT | DAM_BLIND | DAM_ON_FIRE | DAM_EXPLODE, 5000, 0 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, DAM_CRIP_ARM_RIGHT | DAM_BLIND | DAM_ON_FIRE | DAM_EXPLODE, 5000, 0 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5217, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5217, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5217, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, STAT_LUCK, 5, DAM_BLIND, 5218, 5219 },\n            { 4, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 5220, 5221 },\n            { 6, DAM_BYPASS, STAT_LUCK, -1, DAM_BLIND, 5220, 5221 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5222, 5000 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5223, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 5224, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5225, 5000 },\n            { 3, 0, -1, 0, 0, 5225, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5226, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5226, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5226, 5000 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5226, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5210, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5211, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5211, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5212, 5000 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5213, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5214, 5000 },\n        },\n    },\n    // KILL_TYPE_SUPER_MUTANT\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5300, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, -1, DAM_KNOCKED_DOWN, 5301, 5302 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN, 5301, 5302 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5302, 5303 },\n            { 6, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5302, 5303 },\n            { 6, DAM_DEAD, -1, 0, 0, 5304, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5300, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_LOSE_TURN, 5300, 5306 },\n            { 4, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -1, DAM_CRIP_ARM_LEFT, 5307, 5308 },\n            { 4, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 5307, 5308 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5308, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5308, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5300, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_LOSE_TURN, 5300, 5006 },\n            { 4, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -1, DAM_CRIP_ARM_RIGHT, 5307, 5309 },\n            { 4, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 5307, 5309 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5309, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5309, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5300, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5301, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5302, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5302, 5000 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5310, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5311, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5300, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5300, 5312 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 5312, 5313 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5313, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5314, 5000 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5315, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5300, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5300, 5312 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 5312, 5313 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 5313, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5314, 5000 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5315, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 5300, 5000 },\n            { 4, DAM_BYPASS, STAT_LUCK, 5, DAM_BLIND, 5316, 5317 },\n            { 6, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 5316, 5317 },\n            { 6, DAM_BYPASS | DAM_LOSE_TURN, STAT_LUCK, 0, DAM_BLIND, 5318, 5319 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5320, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 5321, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5300, 5000 },\n            { 3, 0, STAT_LUCK, 0, DAM_BYPASS, 5300, 5017 },\n            { 3, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_KNOCKED_DOWN, 5301, 5302 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5312, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5302, 5303 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5303, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5300, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5301, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5302, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5302, 5000 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5310, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5311, 5000 },\n        },\n    },\n    // KILL_TYPE_GHOUL\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5400, 5003 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -1, DAM_KNOCKED_OUT, 5400, 5003 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -2, DAM_KNOCKED_OUT, 5004, 5005 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_STRENGTH, 0, 0, 5005, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5401, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5016, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_DROP | DAM_LOSE_TURN, 5001, 5402 },\n            { 4, DAM_DROP | DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5402, 5012 },\n            { 4, DAM_BYPASS | DAM_DROP | DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5403, 5404 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS | DAM_DROP, -1, 0, 0, 5404, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS | DAM_DROP, -1, 0, 0, 5404, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5016, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_DROP | DAM_LOSE_TURN, 5001, 5402 },\n            { 4, DAM_DROP | DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5402, 5015 },\n            { 4, DAM_BYPASS | DAM_DROP | DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5403, 5404 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS | DAM_DROP, -1, 0, 0, 5404, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS | DAM_DROP, -1, 0, 0, 5404, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5017, 5000 },\n            { 3, 0, -1, 0, 0, 5018, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5004, 5000 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5003, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5007, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5023 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5023, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5024, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5024, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5026, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5023 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5023, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 5024, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5024, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5026, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, STAT_LUCK, 3, DAM_BLIND, 5001, 5405 },\n            { 4, DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 5406, 5407 },\n            { 6, DAM_BYPASS, STAT_LUCK, -3, DAM_BLIND, 5406, 5407 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5030, 5000 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5031, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 5408, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_LUCK, 0, DAM_BYPASS, 5001, 5033 },\n            { 3, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5033, 5035 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5004, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5035, 5036 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5036, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5017, 5000 },\n            { 3, 0, -1, 0, 0, 5018, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5004, 5000 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5003, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5007, 5000 },\n        },\n    },\n    // KILL_TYPE_BRAHMIN\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 5, 0, STAT_ENDURANCE, 2, DAM_KNOCKED_DOWN, 5016, 5500 },\n            { 5, 0, STAT_ENDURANCE, -1, DAM_KNOCKED_DOWN, 5016, 5500 },\n            { 6, DAM_KNOCKED_OUT, STAT_STRENGTH, 0, 0, 5501, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5502, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5016, 5503 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5016, 5503 },\n            { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5503, 5000 },\n            { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5503, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5016, 5503 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5016, 5503 },\n            { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5503, 5000 },\n            { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5503, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5504, 5000 },\n            { 3, 0, -1, 0, 0, 5504, 5000 },\n            { 4, 0, -1, 0, 0, 5504, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5505, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5505, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5506, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5016, 5503 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5016, 5503 },\n            { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5503, 5000 },\n            { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5503, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5016, 5503 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5016, 5503 },\n            { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5503, 5000 },\n            { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5503, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 5029, 5507 },\n            { 6, DAM_BYPASS, STAT_LUCK, -3, DAM_BLIND, 5029, 5507 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5508, 5000 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5509, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 5510, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5511, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5511, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5512, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5512, 5000 },\n            { 6, DAM_BYPASS, -1, 0, 0, 5513, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5504, 5000 },\n            { 3, 0, -1, 0, 0, 5504, 5000 },\n            { 4, 0, -1, 0, 0, 5504, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5505, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5505, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5506, 5000 },\n        },\n    },\n    // KILL_TYPE_RADSCORPION\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 3, DAM_KNOCKED_DOWN, 5001, 5600 },\n            { 5, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5001, 5600 },\n            { 5, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5001, 5600 },\n            { 6, DAM_KNOCKED_DOWN, -1, 0, 0, 5600, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5601, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5016, 5602 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5602, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5602, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_ENDURANCE, 2, DAM_CRIP_ARM_RIGHT, 5016, 5603 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5016, 5603 },\n            { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5603, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5604, 5000 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5605, 5000 },\n            { 4, DAM_BYPASS, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5605, 5606 },\n            { 4, DAM_DEAD, -1, 0, 0, 5607, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, 2, 0, 5001, 5600 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5600, 5608 },\n            { 4, DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5609, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5608, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5608, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, 2, 0, 5001, 5600 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5600, 5008 },\n            { 4, DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5609, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5608, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5608, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_AGILITY, 3, DAM_BLIND, 5001, 5610 },\n            { 6, 0, STAT_AGILITY, 0, DAM_BLIND, 5016, 5610 },\n            { 6, 0, STAT_AGILITY, -3, DAM_BLIND, 5016, 5610 },\n            { 8, 0, STAT_AGILITY, -3, DAM_BLIND, 5611, 5612 },\n            { 8, DAM_DEAD, -1, 0, 0, 5613, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5614, 5000 },\n            { 3, 0, -1, 0, 0, 5614, 5000 },\n            { 4, 0, -1, 0, 0, 5614, 5000 },\n            { 4, DAM_KNOCKED_OUT, -1, 0, 0, 5615, 5000 },\n            { 4, DAM_KNOCKED_OUT, -1, 0, 0, 5615, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5616, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 5604, 5000 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5605, 5000 },\n            { 4, DAM_BYPASS, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5605, 5606 },\n            { 4, DAM_DEAD, -1, 0, 0, 5607, 5000 },\n        },\n    },\n    // KILL_TYPE_RAT\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, DAM_BYPASS, -1, 0, 0, 5700, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5700, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5701, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5701, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5701, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5701, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 },\n            { 3, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 },\n            { 3, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 },\n            { 3, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 },\n            { 3, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5706, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5708, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 },\n            { 3, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 },\n            { 3, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 },\n            { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 },\n            { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 },\n            { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 },\n            { 3, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 },\n            { 3, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 },\n            { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 },\n            { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 },\n            { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, DAM_BYPASS, -1, 0, 0, 5711, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5712, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5712, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5711, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5711, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5706, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5708, 5000 },\n        },\n    },\n    // KILL_TYPE_FLOATER\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 5, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5016, 5800 },\n            { 5, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5800, 5801 },\n            { 6, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5800, 5801 },\n            { 6, DAM_DEAD, -1, 0, 0, 5802, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 5001, 5803 },\n            { 4, 0, STAT_ENDURANCE, -2, DAM_LOSE_TURN, 5001, 5803 },\n            { 3, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5804, 5000 },\n            { 4, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5804, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5805, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 5001, 5803 },\n            { 4, 0, STAT_ENDURANCE, -2, DAM_LOSE_TURN, 5001, 5803 },\n            { 3, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5804, 5000 },\n            { 4, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5804, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5805, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 3, 0, STAT_AGILITY, -2, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5800, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5804, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5805, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, 1, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 4, 0, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -1, DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT, 5800, 5806 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT, 5804, 5806 },\n            { 6, DAM_DEAD | DAM_ON_FIRE, -1, 0, 0, 5807, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_LOSE_TURN, -1, 0, 0, 5803, 5000 },\n            { 4, DAM_LOSE_TURN, -1, 0, 0, 5803, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT | DAM_LOSE_TURN, -1, 0, 0, 5808, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT | DAM_LOSE_TURN, -1, 0, 0, 5808, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5809, 5000 },\n            { 5, 0, STAT_ENDURANCE, 0, DAM_BLIND, 5016, 5810 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_BLIND, 5809, 5810 },\n            { 6, DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5810, 5000 },\n            { 6, DAM_KNOCKED_DOWN | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5801, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 3, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5800, 5000 },\n            { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5800, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 3, 0, STAT_AGILITY, -2, DAM_KNOCKED_DOWN, 5001, 5800 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5800, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5804, 5000 },\n            { 4, DAM_DEAD, -1, 0, 0, 5805, 5000 },\n        },\n    },\n    // KILL_TYPE_CENTAUR\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5016, 5900 },\n            { 5, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5016, 5900 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5901, 5900 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5901, 5900 },\n            { 6, DAM_DEAD, -1, 0, 0, 5902, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 5016, 5903 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5016, 5904 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5904, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5905, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 5016, 5903 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5016, 5904 },\n            { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5904, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5905, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5901, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 2, 0, 5901, 5900 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5900, 5000 },\n            { 5, DAM_DEAD, -1, 0, 0, 5902, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5900, 5000 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5900, 5906 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5906, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5906, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_LOSE_TURN, -1, 0, 0, 5907, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5900, 5000 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5900, 5906 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 5906, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 5906, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_LOSE_TURN, -1, 0, 0, 5907, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 1, DAM_BLIND, 5001, 5908 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -1, DAM_BLIND, 5901, 5908 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5909, 5000 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5910, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 5911, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 2, 0, -1, 0, 0, 5912, 5000 },\n            { 2, 0, -1, 0, 0, 5912, 5000 },\n            { 2, 0, -1, 0, 0, 5912, 5000 },\n            { 2, 0, -1, 0, 0, 5912, 5000 },\n            { 2, 0, -1, 0, 0, 5912, 5000 },\n            { 2, 0, -1, 0, 0, 5912, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5901, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 2, 0, 5901, 5900 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5900, 5000 },\n            { 5, DAM_DEAD, -1, 0, 0, 5902, 5000 },\n        },\n    },\n    // KILL_TYPE_ROBOT\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 6000, 5000 },\n            { 4, 0, -1, 0, 0, 6000, 5000 },\n            { 5, 0, -1, 0, 0, 6000, 5000 },\n            { 5, DAM_KNOCKED_DOWN, -1, 0, 0, 6001, 5000 },\n            { 6, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6002, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6003, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 6000, 6004 },\n            { 3, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 6000, 6004 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 6004, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 6004, 6005 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 6000, 6004 },\n            { 3, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 6000, 6004 },\n            { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 6004, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 6004, 6005 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6006, 5000 },\n            { 4, 0, -1, 0, 0, 6007, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 6008, 5000 },\n            { 6, DAM_BYPASS, -1, 0, 0, 6009, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6010, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 4, 0, -1, 0, 0, 6007, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6000, 6004 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_LEG_RIGHT, 6007, 6004 },\n            { 4, DAM_CRIP_LEG_RIGHT, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 6004, 6011 },\n            { 4, DAM_CRIP_LEG_RIGHT, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, 6004, 6012 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 4, 0, -1, 0, 0, 6007, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6000, 6004 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_LEG_LEFT, 6007, 6004 },\n            { 4, DAM_CRIP_LEG_LEFT, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 6004, 6011 },\n            { 4, DAM_CRIP_LEG_LEFT, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, 6004, 6012 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_BLIND, 6000, 6013 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_BLIND, 6000, 6013 },\n            { 3, 0, STAT_ENDURANCE, -4, DAM_BLIND, 6000, 6013 },\n            { 3, 0, STAT_ENDURANCE, -6, DAM_BLIND, 6000, 6013 },\n            { 3, DAM_BLIND, -1, 0, 0, 6013, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, 0, STAT_ENDURANCE, -1, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 6000, 6002 },\n            { 3, 0, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 6000, 6002 },\n            { 3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, STAT_ENDURANCE, 0, 0, 6002, 6003 },\n            { 3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, STAT_ENDURANCE, -4, 0, 6002, 6003 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 6000, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6006, 5000 },\n            { 4, 0, -1, 0, 0, 6007, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 6008, 5000 },\n            { 6, DAM_BYPASS, -1, 0, 0, 6009, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6010, 5000 },\n        },\n    },\n    // KILL_TYPE_DOG\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5016, 6100 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5016, 6100 },\n            { 4, 0, STAT_ENDURANCE, -6, DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT, 5016, 6101 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6100, 6102 },\n            { 4, DAM_DEAD, -1, 0, 0, 6103, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, -1, DAM_CRIP_LEG_LEFT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, -5, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, 5001, 6105 },\n            { 3, DAM_CRIP_LEG_LEFT, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 6104, 6105 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 6105, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, -1, DAM_CRIP_LEG_RIGHT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, -5, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, 5001, 6105 },\n            { 3, DAM_CRIP_LEG_RIGHT, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 6104, 6105 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 6105, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 5001, 6100 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5016, 6100 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 6100, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6103, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, STAT_ENDURANCE, 1, DAM_CRIP_LEG_RIGHT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_LEG_RIGHT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, 5001, 6105 },\n            { 3, DAM_CRIP_LEG_RIGHT, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 6104, 6105 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 6105, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, STAT_ENDURANCE, 1, DAM_CRIP_LEG_LEFT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_LEG_LEFT, 5001, 6104 },\n            { 3, 0, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, 5001, 6105 },\n            { 3, DAM_CRIP_LEG_LEFT, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 6104, 6105 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 6105, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 5018, 5000 },\n            { 6, DAM_BYPASS, -1, 0, 0, 5018, 5000 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, 3, DAM_BLIND, 5018, 6106 },\n            { 8, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_BLIND, 5018, 6106 },\n            { 8, DAM_DEAD, -1, 0, 0, 6107, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, -2, DAM_KNOCKED_DOWN, 5001, 6100 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_AGILITY, -5, DAM_KNOCKED_DOWN, 5016, 6100 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 6100, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6103, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 5001, 6100 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5016, 6100 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 6100, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6103, 5000 },\n        },\n    },\n    // KILL_TYPE_MANTIS\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5001, 6200 },\n            { 5, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5016, 6200 },\n            { 5, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -1, DAM_KNOCKED_OUT, 6200, 6201 },\n            { 6, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6200, 6201 },\n            { 6, DAM_DEAD, -1, 0, 0, 6202, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5001, 6203 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5001, 6203 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_LEFT, 5001, 6203 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_LEFT, 5016, 6203 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_LEFT, 5016, 6203 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_LOSE_TURN, -1, 0, 0, 6204, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5001, 6203 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5001, 6203 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_RIGHT, 5001, 6203 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_RIGHT, 5016, 6203 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_RIGHT, 5016, 6203 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_LOSE_TURN, -1, 0, 0, 6204, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 1000, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_BYPASS, 5001, 6205 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_BYPASS, 5001, 6205 },\n            { 4, 0, STAT_ENDURANCE, -2, DAM_BYPASS, 5016, 6205 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_BYPASS, 5016, 6205 },\n            { 6, DAM_DEAD, -1, 0, 0, 6206, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 6201 },\n            { 3, 0, STAT_AGILITY, -2, DAM_KNOCKED_DOWN, 5001, 6201 },\n            { 4, 0, STAT_AGILITY, -4, DAM_KNOCKED_DOWN, 5001, 6201 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6201, 6203 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 6201, 6203 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 6207, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 6201 },\n            { 3, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5001, 6201 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -2, DAM_CRIP_LEG_LEFT, 6201, 6208 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -2, DAM_CRIP_LEG_LEFT, 6201, 6208 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -5, DAM_CRIP_LEG_LEFT, 6201, 6208 },\n            { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 6208, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 6205, 6209 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_LOSE_TURN, 6205, 6209 },\n            { 6, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -3, DAM_BLIND, 6209, 6210 },\n            { 8, DAM_KNOCKED_DOWN | DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -3, DAM_BLIND, 6209, 6210 },\n            { 8, DAM_DEAD, -1, 0, 0, 6202, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6205, 5000 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6209, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 1000, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_BYPASS, 5001, 6205 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_BYPASS, 5001, 6205 },\n            { 4, 0, STAT_ENDURANCE, -2, DAM_BYPASS, 5016, 6205 },\n            { 4, 0, STAT_ENDURANCE, -4, DAM_BYPASS, 5016, 6205 },\n            { 6, DAM_DEAD, -1, 0, 0, 6206, 5000 },\n        },\n    },\n    // KILL_TYPE_DEATH_CLAW\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5016, 5023 },\n            { 5, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5016, 5023 },\n            { 5, 0, STAT_ENDURANCE, -5, DAM_KNOCKED_DOWN, 5016, 5023 },\n            { 6, 0, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5016, 5004 },\n            { 6, 0, STAT_ENDURANCE, -5, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5016, 5004 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5001, 5011 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_LEFT, 5001, 5011 },\n            { 3, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_LEFT, 5001, 5011 },\n            { 3, 0, STAT_ENDURANCE, -6, DAM_CRIP_ARM_LEFT, 5001, 5011 },\n            { 3, 0, STAT_ENDURANCE, -8, DAM_CRIP_ARM_LEFT, 5001, 5011 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5001, 5014 },\n            { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_RIGHT, 5001, 5014 },\n            { 3, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_RIGHT, 5001, 5014 },\n            { 3, 0, STAT_ENDURANCE, -6, DAM_CRIP_ARM_RIGHT, 5001, 5014 },\n            { 3, 0, STAT_ENDURANCE, -8, DAM_CRIP_ARM_RIGHT, 5001, 5014 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, -1, DAM_BYPASS, 5001, 6300 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_ENDURANCE, -1, DAM_BYPASS, 5016, 6300 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5004, 5000 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5005, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5004 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5001, 5004 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -2, DAM_CRIP_LEG_RIGHT, 5001, 5004 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -4, DAM_CRIP_LEG_RIGHT, 5016, 5022 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -5, DAM_CRIP_LEG_RIGHT, 5023, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -6, DAM_CRIP_LEG_RIGHT, 5023, 5024 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5004 },\n            { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5001, 5004 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -2, DAM_CRIP_LEG_RIGHT, 5001, 5004 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -4, DAM_CRIP_LEG_RIGHT, 5016, 5022 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -5, DAM_CRIP_LEG_RIGHT, 5023, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -6, DAM_CRIP_LEG_RIGHT, 5023, 5024 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_LOSE_TURN, 5001, 6301 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -6, DAM_LOSE_TURN, 6300, 6301 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_BLIND, 6301, 6302 },\n            { 8, DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6302, 5000 },\n            { 8, DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6302, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, -1, 0, 0, 5001, 5000 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 5, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5016, 5004 },\n            { 5, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5016, 5004 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 5001, 5000 },\n            { 3, 0, STAT_ENDURANCE, -1, DAM_BYPASS, 5001, 6300 },\n            { 4, 0, -1, 0, 0, 5016, 5000 },\n            { 4, 0, STAT_ENDURANCE, -1, DAM_BYPASS, 5016, 6300 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5004, 5000 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5005, 5000 },\n        },\n    },\n    // KILL_TYPE_PLANT\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 6405, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 6400, 5000 },\n            { 5, 0, -1, 0, 0, 6401, 5000 },\n            { 5, DAM_BYPASS, -1, 0, 0, 6402, 5000 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_LOSE_TURN, 6402, 6403 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -6, DAM_LOSE_TURN, 6402, 6403 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6400, 5000 },\n            { 4, 0, -1, 0, 0, 6401, 5000 },\n            { 4, 0, -1, 0, 0, 6401, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 6402, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, -1, 0, 0, 6405, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 6400, 5000 },\n            { 5, 0, -1, 0, 0, 6401, 5000 },\n            { 5, DAM_BYPASS, -1, 0, 0, 6402, 5000 },\n            { 6, DAM_BYPASS, STAT_ENDURANCE, -4, DAM_BLIND, 6402, 6406 },\n            { 6, DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6406, 6404 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 6402, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 6402, 5000 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_LOSE_TURN, 6402, 6403 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -6, DAM_LOSE_TURN, 6402, 6403 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, 0, -1, 0, 0, 6405, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6400, 5000 },\n            { 4, 0, -1, 0, 0, 6401, 5000 },\n            { 4, 0, -1, 0, 0, 6401, 5000 },\n            { 4, DAM_BYPASS, -1, 0, 0, 6402, 5000 },\n        },\n    },\n    // KILL_TYPE_GECKO\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 6701, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6700, 5003 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6700, 5003 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6700, 5003 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 6700, 5006 },\n            { 6, DAM_DEAD, -1, 0, 0, 6700, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6702, 5000 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 6702, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 6702, 5011 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6702, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6702, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6702, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6702, 5000 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 6702, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 6702, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6702, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6702, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6702, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 6701, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6701, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6704, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6704, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6704, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6704, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6705, 5000 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6705, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 6705, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6705, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6705, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6705, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6705, 5000 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6705, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 6705, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6705, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6705, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6705, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, STAT_LUCK, 4, DAM_BLIND, 6700, 5028 },\n            { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 6700, 5028 },\n            { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 6700, 5028 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6700, 5000 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6700, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 6700, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 6703, 5000 },\n            { 3, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 6703, 5035 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6703, 5036 },\n            { 3, DAM_KNOCKED_OUT, -1, 0, 0, 6703, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6703, 5036 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6703, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 6700, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6700, 5000 },\n            { 4, 0, -1, 0, 0, 6700, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6700, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6700, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6700, 5000 },\n        },\n    },\n    // KILL_TYPE_ALIEN\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 6801, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6800, 5003 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6800, 5003 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6803, 5003 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 6804, 5006 },\n            { 6, DAM_DEAD, -1, 0, 0, 6804, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6806, 5000 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 6806, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 6806, 5011 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6806, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6806, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6806, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6806, 5000 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 6806, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 6806, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6806, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6806, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6806, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 6800, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6800, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6800, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6800, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6800, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6800, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6805, 5000 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6805, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 6805, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6805, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6805, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6805, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6805, 5000 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6805, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 6805, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6805, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6805, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6805, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, STAT_LUCK, 4, DAM_BLIND, 6803, 5028 },\n            { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 6803, 5028 },\n            { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 6803, 5028 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6803, 5000 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6803, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 6804, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 6801, 5000 },\n            { 3, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 6801, 5035 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6801, 5036 },\n            { 3, DAM_KNOCKED_OUT, -1, 0, 0, 6801, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6804, 5036 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6804, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 6800, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6800, 5000 },\n            { 4, 0, -1, 0, 0, 6800, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6800, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6800, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6800, 5000 },\n        },\n    },\n    // KILL_TYPE_GIANT_ANT\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 4, 0, -1, 0, 0, 6901, 5000 },\n            { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6901, 5003 },\n            { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6902, 5003 },\n            { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6902, 5003 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 6902, 5006 },\n            { 6, DAM_DEAD, -1, 0, 0, 6902, 5000 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6906, 5000 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 6906, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 6906, 5011 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6906, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6906, 5000 },\n            { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6906, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 6906, 5000 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 6906, 5000 },\n            { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 6906, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6906, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6906, 5000 },\n            { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6906, 5000 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 6900, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6900, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6904, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6904, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6904, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6904, 5000 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6905, 5000 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6905, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 6905, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6905, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6905, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6905, 5000 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6905, 5000 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6905, 5024 },\n            { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 6905, 5024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6905, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6905, 5026 },\n            { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6905, 5000 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 4, 0, STAT_LUCK, 4, DAM_BLIND, 6900, 5028 },\n            { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 6906, 5028 },\n            { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 6901, 5028 },\n            { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6901, 5000 },\n            { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6901, 5000 },\n            { 8, DAM_DEAD, -1, 0, 0, 6901, 5000 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 6900, 5000 },\n            { 3, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 6900, 5035 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6900, 5036 },\n            { 3, DAM_KNOCKED_OUT, -1, 0, 0, 6903, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6903, 5036 },\n            { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6903, 5000 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 6900, 5000 },\n            { 3, DAM_BYPASS, -1, 0, 0, 6900, 5000 },\n            { 4, 0, -1, 0, 0, 6904, 5000 },\n            { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6904, 5000 },\n            { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6904, 5000 },\n            { 6, DAM_DEAD, -1, 0, 0, 6904, 5000 },\n        },\n    },\n    // KILL_TYPE_BIG_BAD_BOSS\n    {\n        // HIT_LOCATION_HEAD\n        {\n            { 3, 0, -1, 0, 0, 7101, 7100 },\n            { 3, 0, -1, 0, 0, 7102, 7103 },\n            { 4, 0, -1, 0, 0, 7102, 7103 },\n            { 4, DAM_LOSE_TURN, -1, 0, 0, 7104, 7103 },\n            { 5, DAM_KNOCKED_DOWN, STAT_LUCK, 0, DAM_BLIND, 7105, 7106 },\n            { 6, DAM_KNOCKED_DOWN, -1, 0, 0, 7105, 7100 },\n        },\n        // HIT_LOCATION_LEFT_ARM\n        {\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 7106, 7011 },\n            { 4, DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 7106, 7100 },\n            { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 7106, 7100 },\n        },\n        // HIT_LOCATION_RIGHT_ARM\n        {\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 3, DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 },\n            { 4, DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 },\n            { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 7106, 7100 },\n            { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 7106, 7100 },\n        },\n        // HIT_LOCATION_TORSO\n        {\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 4, 0, -1, 0, 0, 7106, 7100 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 },\n            { 5, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 },\n        },\n        // HIT_LOCATION_RIGHT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 7106, 7106 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 7060, 7106 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 7106, 7100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 7106, 7106 },\n            { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 7106, 7100 },\n        },\n        // HIT_LOCATION_LEFT_LEG\n        {\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 7106, 7024 },\n            { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 7106, 7024 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 7106, 7100 },\n            { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 7106, 7106 },\n            { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 7106, 7100 },\n        },\n        // HIT_LOCATION_EYES\n        {\n            { 3, 0, -1, 0, 0, 7106, 7106 },\n            { 3, 0, -1, 0, 0, 7106, 7106 },\n            { 4, 0, STAT_LUCK, 2, DAM_BLIND, 7106, 7106 },\n            { 4, DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 },\n            { 5, DAM_BLIND | DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 },\n            { 5, DAM_BLIND | DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 },\n        },\n        // HIT_LOCATION_GROIN\n        {\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 3, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 7106, 7106 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7106 },\n            { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 },\n            { 4, 0, -1, 0, 0, 7106, 7106 },\n            { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 },\n        },\n        // HIT_LOCATION_UNCALLED\n        {\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 3, 0, -1, 0, 0, 7106, 7100 },\n            { 4, 0, -1, 0, 0, 7106, 7100 },\n            { 4, 0, -1, 0, 0, 7106, 7100 },\n            { 5, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 },\n            { 5, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 },\n        },\n    },\n};\n\n// Player's criticals effects.\n//\n// 0x5179B0\nstatic CriticalHitDescription pc_crit_succ_eff[HIT_LOCATION_COUNT][CRTICIAL_EFFECT_COUNT] = {\n    {\n        { 3, 0, -1, 0, 0, 6500, 5000 },\n        { 3, DAM_BYPASS, STAT_ENDURANCE, 3, DAM_KNOCKED_DOWN, 6501, 6503 },\n        { 3, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 6501, 6503 },\n        { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 2, DAM_KNOCKED_OUT, 6503, 6502 },\n        { 3, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 6502, 6504 },\n        { 6, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_DEAD, 6501, 6505 },\n    },\n    {\n        { 2, 0, -1, 0, 0, 6506, 5000 },\n        { 2, DAM_LOSE_TURN, -1, 0, 0, 6507, 5000 },\n        { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 6508, 6509 },\n        { 3, DAM_BYPASS, -1, 0, 0, 6501, 5000 },\n        { 3, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6510, 5000 },\n        { 3, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6510, 5000 },\n    },\n    {\n        { 2, 0, -1, 0, 0, 6506, 5000 },\n        { 2, DAM_LOSE_TURN, -1, 0, 0, 6507, 5000 },\n        { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 6508, 6509 },\n        { 3, DAM_BYPASS, -1, 0, 0, 6501, 5000 },\n        { 3, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6511, 5000 },\n        { 3, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6511, 5000 },\n    },\n    {\n        { 3, 0, -1, 0, 0, 6512, 5000 },\n        { 3, 0, -1, 0, 0, 6512, 5000 },\n        { 3, DAM_BYPASS, -1, 0, 0, 6508, 5000 },\n        { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6503, 5000 },\n        { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6503, 5000 },\n        { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_LUCK, 2, DAM_DEAD, 6503, 6513 },\n    },\n    {\n        { 3, 0, -1, 0, 0, 6512, 5000 },\n        { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6514, 5000 },\n        { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6514, 6515 },\n        { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6516, 5000 },\n        { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6516, 5000 },\n        { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6517, 5000 },\n    },\n    {\n        { 3, 0, -1, 0, 0, 6512, 5000 },\n        { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6514, 5000 },\n        { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6514, 6515 },\n        { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6516, 5000 },\n        { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6516, 5000 },\n        { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6517, 5000 },\n    },\n    {\n        { 3, 0, -1, 0, 0, 6518, 5000 },\n        { 3, 0, STAT_LUCK, 3, DAM_BLIND, 6518, 6519 },\n        { 3, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 6501, 6519 },\n        { 4, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6520, 5000 },\n        { 4, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6521, 5000 },\n        { 6, DAM_DEAD, -1, 0, 0, 6522, 5000 },\n    },\n    {\n        { 3, 0, -1, 0, 0, 6523, 5000 },\n        { 3, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 6523, 6524 },\n        { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6524, 5000 },\n        { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 4, DAM_KNOCKED_OUT, 6524, 6525 },\n        { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 2, DAM_KNOCKED_OUT, 6524, 6525 },\n        { 4, DAM_KNOCKED_OUT, -1, 0, 0, 6526, 5000 },\n    },\n    {\n        { 3, 0, -1, 0, 0, 6512, 5000 },\n        { 3, 0, -1, 0, 0, 6512, 5000 },\n        { 3, DAM_BYPASS, -1, 0, 0, 6508, 5000 },\n        { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6503, 5000 },\n        { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6503, 5000 },\n        { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_LUCK, 2, DAM_DEAD, 6503, 6513 },\n    },\n};\n\n// 0x517F98\nstatic int combat_end_due_to_load = 0;\n\n// 0x517F9C\nstatic bool combat_cleanup_enabled = false;\n\n// Provides effects caused by failing weapons.\n//\n// 0x517FA0\nint cf_table[WEAPON_CRITICAL_FAILURE_TYPE_COUNT][WEAPON_CRITICAL_FAILURE_EFFECT_COUNT] = {\n    { 0, DAM_LOSE_TURN, DAM_LOSE_TURN, DAM_HURT_SELF | DAM_KNOCKED_DOWN, DAM_CRIP_RANDOM },\n    { 0, DAM_LOSE_TURN, DAM_DROP, DAM_RANDOM_HIT, DAM_HIT_SELF },\n    { 0, DAM_LOSE_AMMO, DAM_DROP, DAM_RANDOM_HIT, DAM_DESTROY },\n    { DAM_LOSE_TURN, DAM_LOSE_TURN | DAM_LOSE_AMMO, DAM_DROP | DAM_LOSE_TURN, DAM_RANDOM_HIT, DAM_EXPLODE | DAM_LOSE_TURN },\n    { DAM_DUD, DAM_DROP, DAM_DROP | DAM_HURT_SELF, DAM_RANDOM_HIT, DAM_EXPLODE },\n    { DAM_LOSE_TURN, DAM_DUD, DAM_DESTROY, DAM_RANDOM_HIT, DAM_EXPLODE | DAM_LOSE_TURN | DAM_KNOCKED_DOWN },\n    { 0, DAM_LOSE_TURN, DAM_RANDOM_HIT, DAM_DESTROY, DAM_EXPLODE | DAM_LOSE_TURN | DAM_ON_FIRE },\n};\n\n// 0x51802C\nstatic int call_ty[4] = {\n    122,\n    188,\n    251,\n    316,\n};\n\n// 0x51803C\nstatic int hit_loc_left[4] = {\n    HIT_LOCATION_HEAD,\n    HIT_LOCATION_EYES,\n    HIT_LOCATION_RIGHT_ARM,\n    HIT_LOCATION_RIGHT_LEG,\n};\n\n// 0x51804C\nstatic int hit_loc_right[4] = {\n    HIT_LOCATION_TORSO,\n    HIT_LOCATION_GROIN,\n    HIT_LOCATION_LEFT_ARM,\n    HIT_LOCATION_LEFT_LEG,\n};\n\n// 0x56D2B0\nstatic Attack main_ctd;\n\n// combat.msg\n//\n// 0x56D368\nMessageList combat_message_file;\n\n// 0x56D370\nstatic Object* call_target;\n\n// 0x56D374\nstatic int call_win;\n\n// 0x56D378\nstatic int combat_elev;\n\n// 0x56D37C\nstatic int list_total;\n\n// Probably last who_hit_me of obj_dude\n//\n// 0x56D380\nstatic Object* combat_ending_guy;\n\n// 0x56D384\nstatic int list_noncom;\n\n// 0x56D388\nObject* combat_turn_obj;\n\n// target_highlight\n//\n// 0x56D38C\nstatic int combat_highlight;\n\n// 0x56D390\nstatic Object** combat_list;\n\n// 0x56D394\nstatic int list_com;\n\n// Experience received for killing critters during current combat.\n//\n// 0x56D398\nint combat_exps;\n\n// bonus action points from BONUS_MOVE perk.\n//\n// 0x56D39C\nint combat_free_move;\n\n// combat_init\n// 0x420CC0\nint combat_init()\n{\n    int max_action_points;\n    char path[MAX_PATH];\n\n    combat_turn_running = 0;\n    combatNumTurns = 0;\n    combat_list = 0;\n    aiInfoList = 0;\n    list_com = 0;\n    list_noncom = 0;\n    list_total = 0;\n    gcsd = 0;\n    combat_call_display = 0;\n    combat_state = COMBAT_STATE_0x02;\n\n    max_action_points = critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS);\n\n    combat_free_move = 0;\n    combat_ending_guy = NULL;\n    combat_end_due_to_load = 0;\n\n    obj_dude->data.critter.combat.ap = max_action_points;\n\n    combat_cleanup_enabled = 0;\n\n    if (!message_init(&combat_message_file)) {\n        return -1;\n    }\n\n    sprintf(path, \"%s%s\", msg_path, \"combat.msg\");\n\n    if (!message_load(&combat_message_file, path)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x420DA0\nvoid combat_reset()\n{\n    int max_action_points;\n\n    combat_turn_running = 0;\n    combatNumTurns = 0;\n    combat_list = 0;\n    aiInfoList = 0;\n    list_com = 0;\n    list_noncom = 0;\n    list_total = 0;\n    gcsd = 0;\n    combat_call_display = 0;\n    combat_state = COMBAT_STATE_0x02;\n\n    max_action_points = critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS);\n\n    combat_free_move = 0;\n    combat_ending_guy = NULL;\n\n    obj_dude->data.critter.combat.ap = max_action_points;\n}\n\n// 0x420E14\nvoid combat_exit()\n{\n    message_exit(&combat_message_file);\n}\n\n// 0x420E24\nint find_cid(int a1, int cid, Object** critterList, int critterListLength)\n{\n    int index;\n\n    for (index = a1; index < critterListLength; index++) {\n        if (critterList[index]->cid == cid) {\n            break;\n        }\n    }\n\n    return index;\n}\n\n// 0x420E4C\nint combat_load(File* stream)\n{\n    int v14;\n    int a2;\n    Object* obj;\n    int v24;\n    int i;\n    int j;\n\n    if (db_freadInt(stream, &combat_state) == -1) return -1;\n\n    if (!isInCombat()) {\n        obj = obj_find_first();\n        while (obj != NULL) {\n            if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n                if (obj->data.critter.combat.whoHitMeCid == -1) {\n                    obj->data.critter.combat.whoHitMe = NULL;\n                }\n            }\n            obj = obj_find_next();\n        }\n        return 0;\n    }\n\n    if (db_freadInt(stream, &combat_turn_running) == -1) return -1;\n    if (db_freadInt(stream, &combat_free_move) == -1) return -1;\n    if (db_freadInt(stream, &combat_exps) == -1) return -1;\n    if (db_freadInt(stream, &list_com) == -1) return -1;\n    if (db_freadInt(stream, &list_noncom) == -1) return -1;\n    if (db_freadInt(stream, &list_total) == -1) return -1;\n\n    if (obj_create_list(-1, map_elevation, 1, &combat_list) != list_total) {\n        obj_delete_list(combat_list);\n        return -1;\n    }\n\n    if (db_freadInt(stream, &v24) == -1) return -1;\n\n    obj_dude->cid = v24;\n\n    for (i = 0; i < list_total; i++) {\n        if (combat_list[i]->data.critter.combat.whoHitMeCid == -1) {\n            combat_list[i]->data.critter.combat.whoHitMe = NULL;\n        } else {\n            for (j = 0; j < list_total; j++) {\n                if (combat_list[i]->data.critter.combat.whoHitMeCid == combat_list[j]->cid) {\n                    break;\n                }\n            }\n\n            if (j == list_total) {\n                combat_list[i]->data.critter.combat.whoHitMe = NULL;\n            } else {\n                combat_list[i]->data.critter.combat.whoHitMe = combat_list[j];\n            }\n        }\n    }\n\n    for (i = 0; i < list_total; i++) {\n        if (db_freadInt(stream, &v24) == -1) return -1;\n\n        for (j = i; j < list_total; j++) {\n            if (v24 == combat_list[j]->cid) {\n                break;\n            }\n        }\n\n        if (j == list_total) {\n            return -1;\n        }\n\n        obj = combat_list[i];\n        combat_list[i] = combat_list[j];\n        combat_list[j] = obj;\n    }\n\n    for (i = 0; i < list_total; i++) {\n        combat_list[i]->cid = i;\n    }\n\n    if (aiInfoList) {\n        mem_free(aiInfoList);\n    }\n\n    aiInfoList = (CombatAiInfo*)mem_malloc(sizeof(*aiInfoList) * list_total);\n    if (aiInfoList == NULL) {\n        return -1;\n    }\n\n    for (v14 = 0; v14 < list_total; v14++) {\n        CombatAiInfo* aiInfo = &(aiInfoList[v14]);\n\n        if (db_freadInt(stream, &a2) == -1) return -1;\n\n        if (a2 == -1) {\n            aiInfo->friendlyDead = NULL;\n        } else {\n            aiInfo->friendlyDead = objFindObjPtrFromID(a2);\n            if (aiInfo->friendlyDead == NULL) return -1;\n        }\n\n        if (db_freadInt(stream, &a2) == -1) return -1;\n\n        if (a2 == -1) {\n            aiInfo->lastTarget = NULL;\n        } else {\n            aiInfo->lastTarget = objFindObjPtrFromID(a2);\n            if (aiInfo->lastTarget == NULL) return -1;\n        }\n\n        if (db_freadInt(stream, &a2) == -1) return -1;\n\n        if (a2 == -1) {\n            aiInfo->lastItem = NULL;\n        } else {\n            aiInfo->lastItem = objFindObjPtrFromID(a2);\n            if (aiInfo->lastItem == NULL) return -1;\n        }\n\n        if (db_freadInt(stream, &(aiInfo->lastMove)) == -1) return -1;\n    }\n\n    combat_begin_extra(obj_dude);\n\n    return 0;\n}\n\n// 0x421244\nint combat_save(File* stream)\n{\n    if (db_fwriteInt(stream, combat_state) == -1) return -1;\n\n    if (!isInCombat()) return 0;\n\n    if (db_fwriteInt(stream, combat_turn_running) == -1) return -1;\n    if (db_fwriteInt(stream, combat_free_move) == -1) return -1;\n    if (db_fwriteInt(stream, combat_exps) == -1) return -1;\n    if (db_fwriteInt(stream, list_com) == -1) return -1;\n    if (db_fwriteInt(stream, list_noncom) == -1) return -1;\n    if (db_fwriteInt(stream, list_total) == -1) return -1;\n    if (db_fwriteInt(stream, obj_dude->cid) == -1) return -1;\n\n    for (int index = 0; index < list_total; index++) {\n        if (db_fwriteInt(stream, combat_list[index]->cid) == -1) return -1;\n    }\n\n    if (aiInfoList == NULL) {\n        return -1;\n    }\n\n    for (int index = 0; index < list_total; index++) {\n        CombatAiInfo* aiInfo = &(aiInfoList[index]);\n\n        if (db_fwriteInt(stream, aiInfo->friendlyDead != NULL ? aiInfo->friendlyDead->id : -1) == -1) return -1;\n        if (db_fwriteInt(stream, aiInfo->lastTarget != NULL ? aiInfo->lastTarget->id : -1) == -1) return -1;\n        if (db_fwriteInt(stream, aiInfo->lastItem != NULL ? aiInfo->lastItem->id : -1) == -1) return -1;\n        if (db_fwriteInt(stream, aiInfo->lastMove) == -1) return -1;\n    }\n\n    return 0;\n}\n\n// 0x4213E8\nbool combat_safety_invalidate_weapon(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr)\n{\n    return combat_safety_invalidate_weapon_func(attacker, weapon, hitMode, defender, safeDistancePtr, NULL);\n}\n\n// 0x4213FC\nbool combat_safety_invalidate_weapon_func(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr, Object* attackerFriend)\n{\n    if (safeDistancePtr != NULL) {\n        *safeDistancePtr = 0;\n    }\n\n    if (attacker->pid == PROTO_ID_0x10001E0) {\n        return false;\n    }\n\n    int intelligence = critterGetStat(attacker, STAT_INTELLIGENCE);\n    int team = attacker->data.critter.combat.team;\n    int damageRadius = item_w_area_damage_radius(weapon, hitMode);\n    int maxDamage;\n    item_w_damage_min_max(weapon, NULL, &maxDamage);\n    int damageType = item_w_damage_type(attacker, weapon);\n\n    if (damageRadius > 0) {\n        if (intelligence < 5) {\n            damageRadius -= 5 - intelligence;\n            if (damageRadius < 0) {\n                damageRadius = 0;\n            }\n        }\n\n        if (attackerFriend != NULL) {\n            if (obj_dist(defender, attackerFriend) < damageRadius) {\n                debug_printf(\"Friendly was in the way!\");\n                return true;\n            }\n        }\n\n        for (int index = 0; index < list_total; index++) {\n            Object* candidate = combat_list[index];\n            if (candidate->data.critter.combat.team == team\n                && candidate != attacker\n                && candidate != defender\n                && !critter_is_dead(candidate)) {\n                int attackerDefenderDistance = obj_dist(defender, candidate);\n                if (attackerDefenderDistance < damageRadius && candidate != candidate->data.critter.combat.whoHitMe) {\n                    int damageThreshold = critterGetStat(candidate, STAT_DAMAGE_THRESHOLD + damageType);\n                    int damageResistance = critterGetStat(candidate, STAT_DAMAGE_RESISTANCE + damageType);\n                    if (damageResistance * (maxDamage - damageThreshold) / 100 > 0) {\n                        return true;\n                    }\n                }\n            }\n        }\n\n        if (obj_dist(defender, attacker) <= damageRadius) {\n            if (safeDistancePtr != NULL) {\n                *safeDistancePtr = damageRadius - obj_dist(defender, attacker) + 1;\n                return false;\n            }\n\n            return true;\n        }\n\n        return false;\n    }\n\n    int anim = item_w_anim_weap(weapon, hitMode);\n    if (anim != ANIM_FIRE_BURST && anim != ANIM_FIRE_CONTINUOUS) {\n        return false;\n    }\n\n    Attack attack;\n    combat_ctd_init(&attack, attacker, defender, hitMode, HIT_LOCATION_TORSO);\n\n    int accuracy = determine_to_hit_func(attacker, attacker->tile, defender, HIT_LOCATION_TORSO, hitMode, 1);\n    int roundsHitMainTarget;\n    int roundsSpent;\n    compute_spray(&attack, accuracy, &roundsHitMainTarget, &roundsSpent, anim);\n\n    if (attackerFriend != NULL) {\n        for (int index = 0; index < attack.extrasLength; index++) {\n            if (attack.extras[index] == attackerFriend) {\n                debug_printf(\"Friendly was in the way!\");\n                return true;\n            }\n        }\n    }\n\n    for (int index = 0; index < attack.extrasLength; index++) {\n        Object* candidate = attack.extras[index];\n        if (candidate->data.critter.combat.team == team\n            && candidate != attacker\n            && candidate != defender\n            && !critter_is_dead(candidate)\n            && candidate != candidate->data.critter.combat.whoHitMe) {\n            int damageThreshold = critterGetStat(candidate, STAT_DAMAGE_THRESHOLD + damageType);\n            int damageResistance = critterGetStat(candidate, STAT_DAMAGE_RESISTANCE + damageType);\n            if (damageResistance * (maxDamage - damageThreshold) / 100 > 0) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n// 0x4217BC\nbool combatTestIncidentalHit(Object* attacker, Object* defender, Object* attackerFriend, Object* weapon)\n{\n    return combat_safety_invalidate_weapon_func(attacker, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender, NULL, attackerFriend);\n}\n\n// 0x4217D4\nObject* combat_whose_turn()\n{\n    if (isInCombat()) {\n        return combat_turn_obj;\n    } else {\n        return NULL;\n    }\n}\n\n// 0x4217E8\nvoid combat_data_init(Object* obj)\n{\n    obj->data.critter.combat.damageLastTurn = 0;\n    obj->data.critter.combat.results = 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4217FC\nstatic void combatInitAIInfoList()\n{\n    int index;\n\n    for (index = 0; index < list_total; index++) {\n        aiInfoList[index].friendlyDead = NULL;\n        aiInfoList[index].lastTarget = NULL;\n        aiInfoList[index].lastItem = NULL;\n        aiInfoList[index].lastMove = 0;\n    }\n}\n\n// 0x421850\nstatic int combatCopyAIInfo(int srcIndex, int destIndex)\n{\n    CombatAiInfo* src = &aiInfoList[srcIndex];\n    CombatAiInfo* dest = &aiInfoList[destIndex];\n\n    dest->friendlyDead = src->friendlyDead;\n    dest->lastTarget = src->lastTarget;\n    dest->lastItem = src->lastItem;\n    dest->lastMove = src->lastMove;\n\n    return 0;\n}\n\n// 0x421880\nObject* combatAIInfoGetFriendlyDead(Object* obj)\n{\n    if (!isInCombat()) {\n        return NULL;\n    }\n\n    if (obj == NULL) {\n        return NULL;\n    }\n\n    if (obj->cid == -1) {\n        return NULL;\n    }\n\n    return aiInfoList[obj->cid].friendlyDead;\n}\n\n// 0x4218AC\nint combatAIInfoSetFriendlyDead(Object* a1, Object* a2)\n{\n    if (!isInCombat()) {\n        return 0;\n    }\n\n    if (a1 == NULL) {\n        return -1;\n    }\n\n    if (a1->cid == -1) {\n        return -1;\n    }\n\n    if (a1 == a2) {\n        return -1;\n    }\n\n    aiInfoList[a1->cid].friendlyDead = a2;\n\n    return 0;\n}\n\n// 0x4218EC\nObject* combatAIInfoGetLastTarget(Object* obj)\n{\n    if (!isInCombat()) {\n        return NULL;\n    }\n\n    if (obj == NULL) {\n        return NULL;\n    }\n\n    if (obj->cid == -1) {\n        return NULL;\n    }\n\n    return aiInfoList[obj->cid].lastTarget;\n}\n\n// 0x421918\nint combatAIInfoSetLastTarget(Object* a1, Object* a2)\n{\n    if (!isInCombat()) {\n        return 0;\n    }\n\n    if (a1 == NULL) {\n        return -1;\n    }\n\n    if (a1->cid == -1) {\n        return -1;\n    }\n\n    if (a1 == a2) {\n        return -1;\n    }\n\n    if (critter_is_dead(a2)) {\n        a2 = NULL;\n    }\n\n    aiInfoList[a1->cid].lastTarget = a2;\n\n    return 0;\n}\n\n// 0x42196C\nObject* combatAIInfoGetLastItem(Object* obj)\n{\n    int v1;\n\n    if (!isInCombat()) {\n        return NULL;\n    }\n\n    if (obj == NULL) {\n        return NULL;\n    }\n\n    v1 = obj->cid;\n    if (v1 == -1) {\n        return NULL;\n    }\n\n    return aiInfoList[v1].lastItem;\n}\n\n// 0x421998\nint combatAIInfoSetLastItem(Object* obj, Object* a2)\n{\n    int v2;\n\n    if (!isInCombat()) {\n        return 0;\n    }\n\n    if (obj == NULL) {\n        return -1;\n    }\n\n    v2 = obj->cid;\n    if (v2 == -1) {\n        return -1;\n    }\n\n    aiInfoList[v2].lastItem = NULL;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4219CC\nint combatAIInfoGetLastMove(Object* object)\n{\n    if (!isInCombat()) {\n        return 0;\n    }\n\n    if (object == NULL) {\n        return -1;\n    }\n\n    if (object->cid == -1) {\n        return -1;\n    }\n\n    return aiInfoList[object->cid].lastMove;\n}\n\n// NOTE: Inlined.\n//\n// 0x421A00\nint combatAIInfoSetLastMove(Object* object, int move)\n{\n    if (!isInCombat()) {\n        return 0;\n    }\n\n    if (object == NULL) {\n        return -1;\n    }\n\n    if (object->cid == -1) {\n        return -1;\n    }\n\n    aiInfoList[object->cid].lastMove = move;\n\n    return 0;\n}\n\n// 0x421A34\nstatic void combat_begin(Object* a1)\n{\n    combat_turn_running = 0;\n    anim_stop();\n    remove_bk_process(dude_fidget);\n    combat_elev = map_elevation;\n\n    if (!isInCombat()) {\n        combatNumTurns = 0;\n        combat_exps = 0;\n        combat_list = NULL;\n        list_total = obj_create_list(-1, combat_elev, OBJ_TYPE_CRITTER, &combat_list);\n        list_noncom = list_total;\n        list_com = 0;\n        aiInfoList = (CombatAiInfo*)mem_malloc(sizeof(*aiInfoList) * list_total);\n        if (aiInfoList == NULL) {\n            return;\n        }\n\n        // NOTE: Uninline.\n        combatInitAIInfoList();\n\n        Object* v1 = NULL;\n        for (int index = 0; index < list_total; index++) {\n            Object* critter = combat_list[index];\n            CritterCombatData* combatData = &(critter->data.critter.combat);\n            combatData->maneuver &= CRITTER_MANEUVER_0x01;\n            combatData->damageLastTurn = 0;\n            combatData->whoHitMe = NULL;\n            combatData->ap = 0;\n            critter->cid = index;\n\n            // NOTE: Uninline.\n            combatAIInfoSetLastMove(critter, 0);\n\n            scr_set_objs(critter->sid, NULL, NULL);\n            scr_set_ext_param(critter->sid, 0);\n            if (critter->pid == 0x1000098) {\n                if (!critter_is_dead(critter)) {\n                    v1 = critter;\n                }\n            }\n        }\n\n        combat_state |= COMBAT_STATE_0x01;\n\n        tile_refresh_display();\n        game_ui_disable(0);\n        gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH);\n        combat_ending_guy = NULL;\n        combat_begin_extra(a1);\n        caiTeamCombatInit(combat_list, list_total);\n        intface_end_window_open(true);\n        gmouse_enable_scrolling();\n\n        if (v1 != NULL && !isLoadingGame()) {\n            int fid = art_id(FID_TYPE(v1->fid),\n                100,\n                FID_ANIM_TYPE(v1->fid),\n                (v1->fid & 0xF000) >> 12,\n                (v1->fid & 0x70000000) >> 28);\n\n            register_clear(v1);\n            register_begin(ANIMATION_REQUEST_RESERVED);\n            register_object_animate(v1, ANIM_UP_STAIRS_RIGHT, -1);\n            register_object_change_fid(v1, fid, -1);\n            register_end();\n\n            while (anim_busy(v1)) {\n                process_bk();\n            }\n        }\n    }\n}\n\n// 0x421C8C\nstatic void combat_begin_extra(Object* a1)\n{\n    for (int index = 0; index < list_total; index++) {\n        combat_update_critter_outline_for_los(combat_list[index], 0);\n    }\n\n    combat_ctd_init(&main_ctd, a1, NULL, 4, 3);\n\n    combat_turn_obj = a1;\n\n    combat_ai_begin(list_total, combat_list);\n\n    combat_highlight = 2;\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &combat_highlight);\n}\n\n// NOTE: Inlined.\n//\n// 0x421D18\nstatic void combat_update_critters_in_los(int a1)\n{\n    int index;\n\n    for (index = 0; index < list_total; index++) {\n        combat_update_critter_outline_for_los(combat_list[index], a1);\n    }\n}\n\n// Something with outlining.\n//\n// 0x421D50\nvoid combat_update_critter_outline_for_los(Object* critter, bool a2)\n{\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    if (critter == obj_dude) {\n        return;\n    }\n\n    if (critter_is_dead(critter)) {\n        return;\n    }\n\n    bool v5 = false;\n    if (!combat_is_shot_blocked(obj_dude, obj_dude->tile, critter->tile, critter, 0)) {\n        v5 = true;\n    }\n\n    if (v5) {\n        int outlineType = critter->outline & OUTLINE_TYPE_MASK;\n        if (outlineType != OUTLINE_TYPE_HOSTILE && outlineType != OUTLINE_TYPE_FRIENDLY) {\n            int newOutlineType = obj_dude->data.critter.combat.team == critter->data.critter.combat.team\n                ? OUTLINE_TYPE_FRIENDLY\n                : OUTLINE_TYPE_HOSTILE;\n            obj_turn_off_outline(critter, NULL);\n            obj_remove_outline(critter, NULL);\n            obj_outline_object(critter, newOutlineType, NULL);\n            if (a2) {\n                obj_turn_on_outline(critter, NULL);\n            } else {\n                obj_turn_off_outline(critter, NULL);\n            }\n        } else {\n            if (critter->outline != 0 && (critter->outline & OUTLINE_DISABLED) == 0) {\n                if (!a2) {\n                    obj_turn_off_outline(critter, NULL);\n                }\n            } else {\n                if (a2) {\n                    obj_turn_on_outline(critter, NULL);\n                }\n            }\n        }\n    } else {\n        int v7 = obj_dist(obj_dude, critter);\n        int v8 = critterGetStat(obj_dude, STAT_PERCEPTION) * 5;\n        if ((critter->flags & OBJECT_TRANS_GLASS) != 0) {\n            v8 /= 2;\n        }\n\n        if (v7 <= v8) {\n            v5 = true;\n        }\n\n        int outlineType = critter->outline & OUTLINE_TYPE_MASK;\n        if (outlineType != OUTLINE_TYPE_32) {\n            obj_turn_off_outline(critter, NULL);\n            obj_remove_outline(critter, NULL);\n\n            if (v5) {\n                obj_outline_object(critter, OUTLINE_TYPE_32, NULL);\n\n                if (a2) {\n                    obj_turn_on_outline(critter, NULL);\n                } else {\n                    obj_turn_off_outline(critter, NULL);\n                }\n            }\n        } else {\n            if (critter->outline != 0 && (critter->outline & OUTLINE_DISABLED) == 0) {\n                if (!a2) {\n                    obj_turn_off_outline(critter, NULL);\n                }\n            } else {\n                if (a2) {\n                    obj_turn_on_outline(critter, NULL);\n                }\n            }\n        }\n    }\n}\n\n// Probably complete combat sequence.\n//\n// 0x421EFC\nstatic void combat_over()\n{\n    if (game_user_wants_to_quit == 0) {\n        for (int index = 0; index < list_com; index++) {\n            Object* critter = combat_list[index];\n            if (critter != obj_dude) {\n                cai_attempt_w_reload(critter, 0);\n            }\n        }\n    }\n\n    add_bk_process(dude_fidget);\n\n    for (int index = 0; index < list_noncom + list_com; index++) {\n        Object* critter = combat_list[index];\n        critter->data.critter.combat.damageLastTurn = 0;\n        critter->data.critter.combat.maneuver = CRITTER_MANEUVER_NONE;\n    }\n\n    for (int index = 0; index < list_total; index++) {\n        Object* critter = combat_list[index];\n        critter->data.critter.combat.ap = 0;\n        obj_remove_outline(critter, NULL);\n        critter->data.critter.combat.whoHitMe = NULL;\n\n        scr_set_objs(critter->sid, NULL, NULL);\n        scr_set_ext_param(critter->sid, 0);\n\n        if (critter->pid == 0x1000098 && !critter_is_dead(critter) && !isLoadingGame()) {\n            int fid = art_id(FID_TYPE(critter->fid),\n                99,\n                FID_ANIM_TYPE(critter->fid),\n                (critter->fid & 0xF000) >> 12,\n                (critter->fid & 0x70000000) >> 28);\n            register_clear(critter);\n            register_begin(ANIMATION_REQUEST_RESERVED);\n            register_object_animate(critter, ANIM_UP_STAIRS_RIGHT, -1);\n            register_object_change_fid(critter, fid, -1);\n            register_end();\n\n            while (anim_busy(critter)) {\n                process_bk();\n            }\n        }\n    }\n\n    tile_refresh_display();\n\n    int leftItemAction;\n    int rightItemAction;\n    intface_get_item_states(&leftItemAction, &rightItemAction);\n    intface_update_items(true, leftItemAction, rightItemAction);\n\n    obj_dude->data.critter.combat.ap = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n\n    intface_update_move_points(0, 0);\n\n    if (game_user_wants_to_quit == 0) {\n        combat_give_exps(combat_exps);\n    }\n\n    combat_exps = 0;\n\n    combat_state &= ~(COMBAT_STATE_0x01 | COMBAT_STATE_0x02);\n    combat_state |= COMBAT_STATE_0x02;\n\n    if (list_total != 0) {\n        obj_delete_list(combat_list);\n\n        if (aiInfoList != NULL) {\n            mem_free(aiInfoList);\n        }\n        aiInfoList = NULL;\n    }\n\n    list_total = 0;\n\n    combat_ai_over();\n    game_ui_enable();\n    gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE);\n    intface_update_ac(true);\n\n    if (critter_is_prone(obj_dude) && !critter_is_dead(obj_dude) && combat_ending_guy == NULL) {\n        queue_remove_this(obj_dude, EVENT_TYPE_KNOCKOUT);\n        critter_wake_up(obj_dude, NULL);\n    }\n}\n\n// 0x422194\nvoid combat_over_from_load()\n{\n    combat_over();\n    combat_state = 0;\n    combat_end_due_to_load = 1;\n}\n\n// Give exp for destroying critter.\n//\n// 0x4221B4\nvoid combat_give_exps(int exp_points)\n{\n    MessageListItem v7;\n    MessageListItem v9;\n    int current_hp;\n    int max_hp;\n    char text[132];\n\n    if (exp_points <= 0) {\n        return;\n    }\n\n    if (critter_is_dead(obj_dude)) {\n        return;\n    }\n\n    stat_pc_add_experience(exp_points);\n\n    v7.num = 621; // %s you earn %d exp. points.\n    if (!message_search(&proto_main_msg_file, &v7)) {\n        return;\n    }\n\n    v9.num = roll_random(0, 3) + 622; // generate prefix for message\n\n    current_hp = critterGetStat(obj_dude, STAT_CURRENT_HIT_POINTS);\n    max_hp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n    if (current_hp == max_hp && roll_random(0, 100) > 65) {\n        v9.num = 626; // Best possible prefix: For destroying your enemies without taking a scratch,\n    }\n\n    if (!message_search(&proto_main_msg_file, &v9)) {\n        return;\n    }\n\n    sprintf(text, v7.text, v9.text, exp_points);\n    display_print(text);\n}\n\n// 0x4222A8\nstatic void combat_add_noncoms()\n{\n    combatai_notify_friends(obj_dude);\n\n    for (int index = list_com; index < list_com + list_noncom; index++) {\n        Object* obj = combat_list[index];\n        if (combatai_want_to_join(obj)) {\n            obj->data.critter.combat.maneuver = CRITTER_MANEUVER_NONE;\n\n            Object** objectPtr1 = &(combat_list[index]);\n            Object** objectPtr2 = &(combat_list[list_com]);\n            Object* t = *objectPtr1;\n            *objectPtr1 = *objectPtr2;\n            *objectPtr2 = t;\n\n            list_com += 1;\n            list_noncom -= 1;\n\n            int actionPoints = 0;\n            if (obj != obj_dude) {\n                actionPoints = critterGetStat(obj, STAT_MAXIMUM_ACTION_POINTS);\n            }\n\n            if (gcsd != NULL) {\n                actionPoints += gcsd->actionPointsBonus;\n            }\n\n            obj->data.critter.combat.ap = actionPoints;\n\n            combat_turn(obj, false);\n        }\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x422374\nint combat_in_range(Object* critter)\n{\n    int perception;\n    int index;\n\n    perception = critterGetStat(critter, STAT_PERCEPTION);\n\n    for (index = 0; index < list_com; index++) {\n        if (obj_dist(combat_list[index], critter) <= perception) {\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// Compares critters by sequence.\n//\n// 0x4223C8\nstatic int compare_faster(const void* a1, const void* a2)\n{\n    Object* v1 = *(Object**)a1;\n    Object* v2 = *(Object**)a2;\n\n    int sequence1 = critterGetStat(v1, STAT_SEQUENCE);\n    int sequence2 = critterGetStat(v2, STAT_SEQUENCE);\n    if (sequence1 > sequence2) {\n        return -1;\n    } else if (sequence1 < sequence2) {\n        return 1;\n    }\n\n    int luck1 = critterGetStat(v1, STAT_LUCK);\n    int luck2 = critterGetStat(v2, STAT_LUCK);\n    if (luck1 > luck2) {\n        return -1;\n    } else if (luck1 < luck2) {\n        return 1;\n    }\n\n    return 0;\n}\n\n// 0x42243C\nstatic void combat_sequence_init(Object* a1, Object* a2)\n{\n    int next = 0;\n    if (a1 != NULL) {\n        for (int index = 0; index < list_total; index++) {\n            Object* obj = combat_list[index];\n            if (obj == a1) {\n                Object* temp = combat_list[next];\n                combat_list[index] = temp;\n                combat_list[next] = obj;\n                next += 1;\n                break;\n            }\n        }\n    }\n\n    if (a2 != NULL) {\n        for (int index = 0; index < list_total; index++) {\n            Object* obj = combat_list[index];\n            if (obj == a2) {\n                Object* temp = combat_list[next];\n                combat_list[index] = temp;\n                combat_list[next] = obj;\n                next += 1;\n                break;\n            }\n        }\n    }\n\n    if (a1 != obj_dude && a2 != obj_dude) {\n        for (int index = 0; index < list_total; index++) {\n            Object* obj = combat_list[index];\n            if (obj == obj_dude) {\n                Object* temp = combat_list[next];\n                combat_list[index] = temp;\n                combat_list[next] = obj;\n                next += 1;\n                break;\n            }\n        }\n    }\n\n    list_com = next;\n    list_noncom -= next;\n\n    if (a1 != NULL) {\n        critter_set_who_hit_me(a1, a2);\n    }\n\n    if (a2 != NULL) {\n        critter_set_who_hit_me(a2, a1);\n    }\n}\n\n// 0x422580\nstatic void combat_sequence()\n{\n    combat_add_noncoms();\n\n    int count = list_com;\n\n    for (int index = 0; index < count; index++) {\n        Object* critter = combat_list[index];\n        if ((critter->data.critter.combat.results & DAM_DEAD) != 0) {\n            combat_list[index] = combat_list[count - 1];\n            combat_list[count - 1] = critter;\n\n            combat_list[count - 1] = combat_list[list_noncom + count - 1];\n            combat_list[list_noncom + count - 1] = critter;\n\n            index -= 1;\n            count -= 1;\n        }\n    }\n\n    for (int index = 0; index < count; index++) {\n        Object* critter = combat_list[index];\n        if (critter != obj_dude) {\n            if ((critter->data.critter.combat.results & DAM_KNOCKED_OUT) != 0\n                || critter->data.critter.combat.maneuver == CRITTER_MANEUVER_STOP_ATTACKING) {\n                critter->data.critter.combat.maneuver &= ~CRITTER_MANEUVER_0x01;\n                list_noncom += 1;\n\n                combat_list[index] = combat_list[count - 1];\n                combat_list[count - 1] = critter;\n\n                count -= 1;\n                index -= 1;\n            }\n        }\n    }\n\n    if (count != 0) {\n        list_com = count;\n        qsort(combat_list, count, sizeof(*combat_list), compare_faster);\n        count = list_com;\n    }\n\n    list_com = count;\n\n    inc_game_time_in_seconds(5);\n}\n\n// 0x422694\nvoid combat_end()\n{\n    if (combat_elev == obj_dude->elevation) {\n        MessageListItem messageListItem;\n        int dudeTeam = obj_dude->data.critter.combat.team;\n\n        for (int index = 0; index < list_com; index++) {\n            Object* critter = combat_list[index];\n            if (critter != obj_dude) {\n                int critterTeam = critter->data.critter.combat.team;\n                Object* critterWhoHitMe = critter->data.critter.combat.whoHitMe;\n                if (critterTeam != dudeTeam || (critterWhoHitMe != NULL && critterWhoHitMe->data.critter.combat.team == critterTeam)) {\n                    if (!combatai_want_to_stop(critter)) {\n                        messageListItem.num = 103;\n                        if (message_search(&combat_message_file, &messageListItem)) {\n                            display_print(messageListItem.text);\n                        }\n                        return;\n                    }\n                }\n            }\n        }\n\n        for (int index = list_com; index < list_com + list_noncom; index++) {\n            Object* critter = combat_list[index];\n            if (critter != obj_dude) {\n                int critterTeam = critter->data.critter.combat.team;\n                Object* critterWhoHitMe = critter->data.critter.combat.whoHitMe;\n                if (critterTeam != dudeTeam || (critterWhoHitMe != NULL && critterWhoHitMe->data.critter.combat.team == critterTeam)) {\n                    if (combatai_want_to_join(critter)) {\n                        messageListItem.num = 103;\n                        if (message_search(&combat_message_file, &messageListItem)) {\n                            display_print(messageListItem.text);\n                        }\n                        return;\n                    }\n                }\n            }\n        }\n    }\n\n    combat_state |= COMBAT_STATE_0x08;\n    caiTeamCombatExit();\n}\n\n// 0x4227DC\nvoid combat_turn_run()\n{\n    while (combat_turn_running > 0) {\n        process_bk();\n    }\n}\n\n// 0x4227F4\nstatic int combat_input()\n{\n    while ((combat_state & COMBAT_STATE_0x02) != 0) {\n        if ((combat_state & COMBAT_STATE_0x08) != 0) {\n            break;\n        }\n\n        if ((obj_dude->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) {\n            break;\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        if (combat_end_due_to_load != 0) {\n            break;\n        }\n\n        int keyCode = get_input();\n        if (action_explode_running()) {\n            while (combat_turn_running > 0) {\n                process_bk();\n            }\n        }\n\n        if (obj_dude->data.critter.combat.ap <= 0 && combat_free_move <= 0) {\n            break;\n        }\n\n        if (keyCode == KEY_SPACE) {\n            break;\n        }\n\n        if (keyCode == KEY_RETURN) {\n            combat_end();\n        } else {\n            scripts_check_state_in_combat();\n            game_handle_input(keyCode, true);\n        }\n    }\n\n    int v4 = game_user_wants_to_quit;\n    if (game_user_wants_to_quit == 1) {\n        game_user_wants_to_quit = 0;\n    }\n\n    if ((combat_state & COMBAT_STATE_0x08) != 0) {\n        combat_state &= ~COMBAT_STATE_0x08;\n        return -1;\n    }\n\n    if (game_user_wants_to_quit != 0 || v4 != 0 || combat_end_due_to_load != 0) {\n        return -1;\n    }\n\n    scripts_check_state_in_combat();\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x42290C\nvoid combat_end_turn()\n{\n    combat_state &= ~COMBAT_STATE_0x02;\n}\n\n// 0x422914\nstatic void combat_set_move_all()\n{\n    for (int index = 0; index < list_com; index++) {\n        Object* object = combat_list[index];\n\n        int actionPoints = critterGetStat(object, STAT_MAXIMUM_ACTION_POINTS);\n\n        if (gcsd) {\n            actionPoints += gcsd->actionPointsBonus;\n        }\n\n        object->data.critter.combat.ap = actionPoints;\n\n        // NOTE: Uninline.\n        combatAIInfoSetLastMove(object, 0);\n    }\n}\n\n// 0x42299C\nstatic int combat_turn(Object* a1, bool a2)\n{\n    combat_turn_obj = a1;\n\n    combat_ctd_init(&main_ctd, a1, NULL, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);\n\n    if ((a1->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) {\n        a1->data.critter.combat.results &= ~DAM_LOSE_TURN;\n    } else {\n        if (a1 == obj_dude) {\n            kb_clear();\n            intface_update_ac(true);\n            combat_free_move = 2 * perk_level(obj_dude, PERK_BONUS_MOVE);\n            intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n        } else {\n            soundContinueAll();\n        }\n\n        bool scriptOverrides = false;\n        if (a1->sid != -1) {\n            scr_set_objs(a1->sid, NULL, NULL);\n            scr_set_ext_param(a1->sid, 4);\n            exec_script_proc(a1->sid, SCRIPT_PROC_COMBAT);\n\n            Script* scr;\n            if (scr_ptr(a1->sid, &scr) != -1) {\n                scriptOverrides = scr->scriptOverrides;\n            }\n\n            if (game_user_wants_to_quit == 1) {\n                return -1;\n            }\n        }\n\n        if (!scriptOverrides) {\n            if (!a2 && critter_is_prone(a1)) {\n                combat_standup(a1);\n            }\n\n            if (a1 == obj_dude) {\n                game_ui_enable();\n                gmouse_3d_refresh();\n\n                if (gcsd != NULL) {\n                    combat_attack_this(gcsd->defender);\n                }\n\n                if (!a2) {\n                    combat_state |= 0x02;\n                }\n\n                intface_end_buttons_enable();\n\n                // NOTE: Uninline.\n                combat_update_critters_in_los(0);\n\n                if (combat_highlight != 0) {\n                    combat_outline_on();\n                }\n\n                if (combat_input() == -1) {\n                    game_ui_disable(1);\n                    gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH);\n                    a1->data.critter.combat.damageLastTurn = 0;\n                    intface_end_buttons_disable();\n                    combat_outline_off();\n                    intface_update_move_points(-1, -1);\n                    intface_update_ac(true);\n                    combat_free_move = 0;\n                    return -1;\n                }\n            } else {\n                Rect rect;\n                if (obj_turn_on_outline(a1, &rect) == 0) {\n                    tile_refresh_rect(&rect, a1->elevation);\n                }\n\n                combat_ai(a1, gcsd != NULL ? gcsd->defender : NULL);\n            }\n        }\n\n        while (combat_turn_running > 0) {\n            process_bk();\n        }\n\n        if (a1 == obj_dude) {\n            game_ui_disable(1);\n            gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH);\n            intface_end_buttons_disable();\n            combat_outline_off();\n            intface_update_move_points(-1, -1);\n            combat_turn_obj = NULL;\n            intface_update_ac(true);\n            combat_turn_obj = obj_dude;\n        } else {\n            Rect rect;\n            if (obj_turn_off_outline(a1, &rect) == 0) {\n                tile_refresh_rect(&rect, a1->elevation);\n            }\n        }\n    }\n\n    if ((obj_dude->data.critter.combat.results & DAM_DEAD) != 0) {\n        return -1;\n    }\n\n    if (a1 != obj_dude || combat_elev == obj_dude->elevation) {\n        combat_free_move = 0;\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x422C60\nstatic bool combat_should_end()\n{\n    if (list_com <= 1) {\n        return true;\n    }\n\n    int index;\n    for (index = 0; index < list_com; index++) {\n        if (combat_list[index] == obj_dude) {\n            break;\n        }\n    }\n\n    if (index == list_com) {\n        return true;\n    }\n\n    int team = obj_dude->data.critter.combat.team;\n\n    for (index = 0; index < list_com; index++) {\n        Object* critter = combat_list[index];\n        if (critter->data.critter.combat.team != team) {\n            break;\n        }\n\n        Object* critterWhoHitMe = critter->data.critter.combat.whoHitMe;\n        if (critterWhoHitMe != NULL && critterWhoHitMe->data.critter.combat.team == team) {\n            break;\n        }\n    }\n\n    if (index == list_com) {\n        return true;\n    }\n\n    return false;\n}\n\n// 0x422D2C\nvoid combat(STRUCT_664980* attack)\n{\n    if (attack == NULL\n        || (attack->attacker == NULL || attack->attacker->elevation == map_elevation)\n        || (attack->defender == NULL || attack->defender->elevation == map_elevation)) {\n        int v3 = combat_state & 0x01;\n\n        combat_begin(NULL);\n\n        int v6;\n\n        // TODO: Not sure.\n        if (v3 != 0) {\n            if (combat_turn(obj_dude, true) == -1) {\n                v6 = -1;\n            } else {\n                int index;\n                for (index = 0; index < list_com; index++) {\n                    if (combat_list[index] == obj_dude) {\n                        break;\n                    }\n                }\n                v6 = index + 1;\n            }\n            gcsd = NULL;\n        } else {\n            Object* v3;\n            Object* v9;\n            if (attack != NULL) {\n                v3 = attack->defender;\n                v9 = attack->attacker;\n            } else {\n                v3 = NULL;\n                v9 = NULL;\n            }\n            combat_sequence_init(v9, v3);\n            gcsd = attack;\n            v6 = 0;\n        }\n\n        do {\n            if (v6 == -1) {\n                break;\n            }\n\n            combat_set_move_all();\n\n            for (; v6 < list_com; v6++) {\n                if (combat_turn(combat_list[v6], false) == -1) {\n                    break;\n                }\n\n                if (combat_ending_guy != NULL) {\n                    break;\n                }\n\n                gcsd = NULL;\n            }\n\n            if (v6 < list_com) {\n                break;\n            }\n\n            combat_sequence();\n            v6 = 0;\n            combatNumTurns += 1;\n        } while (!combat_should_end());\n\n        if (combat_end_due_to_load) {\n            game_ui_enable();\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE);\n        } else {\n            gmouse_disable_scrolling();\n            intface_end_window_close(true);\n            gmouse_enable_scrolling();\n            combat_over();\n            scr_exec_map_update_scripts();\n        }\n\n        combat_end_due_to_load = 0;\n\n        if (game_user_wants_to_quit == 1) {\n            game_user_wants_to_quit = 0;\n        }\n    }\n}\n\n// 0x422EC4\nvoid combat_ctd_init(Attack* attack, Object* attacker, Object* defender, int hitMode, int hitLocation)\n{\n    attack->attacker = attacker;\n    attack->hitMode = hitMode;\n    attack->weapon = item_hit_with(attacker, hitMode);\n    attack->attackHitLocation = HIT_LOCATION_TORSO;\n    attack->attackerDamage = 0;\n    attack->attackerFlags = 0;\n    attack->ammoQuantity = 0;\n    attack->criticalMessageId = -1;\n    attack->defender = defender;\n    attack->tile = defender != NULL ? defender->tile : -1;\n    attack->defenderHitLocation = hitLocation;\n    attack->defenderDamage = 0;\n    attack->defenderFlags = 0;\n    attack->defenderKnockback = 0;\n    attack->extrasLength = 0;\n    attack->oops = defender;\n}\n\n// 0x422F3C\nint combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation)\n{\n    if (a1 != obj_dude && hitMode == HIT_MODE_PUNCH && roll_random(1, 4) == 1) {\n        int fid = art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_KICK_LEG, (a1->fid & 0xF000) >> 12, (a1->fid & 0x70000000) >> 28);\n        if (art_exists(fid)) {\n            hitMode = HIT_MODE_KICK;\n        }\n    }\n\n    combat_ctd_init(&main_ctd, a1, a2, hitMode, hitLocation);\n    debug_printf(\"computing attack...\\n\");\n\n    if (compute_attack(&main_ctd) == -1) {\n        return -1;\n    }\n\n    if (gcsd != NULL) {\n        main_ctd.defenderDamage += gcsd->damageBonus;\n\n        if (main_ctd.defenderDamage < gcsd->minDamage) {\n            main_ctd.defenderDamage = gcsd->minDamage;\n        }\n\n        if (main_ctd.defenderDamage > gcsd->maxDamage) {\n            main_ctd.defenderDamage = gcsd->maxDamage;\n        }\n\n        if (gcsd->field_1C) {\n            // FIXME: looks like a bug, two different fields are used to set\n            // one field.\n            main_ctd.defenderFlags = gcsd->field_20;\n            main_ctd.defenderFlags = gcsd->field_24;\n        }\n    }\n\n    bool aiming;\n    if (main_ctd.defenderHitLocation == HIT_LOCATION_TORSO || main_ctd.defenderHitLocation == HIT_LOCATION_UNCALLED) {\n        if (a1 == obj_dude) {\n            intface_get_attack(&hitMode, &aiming);\n        } else {\n            aiming = false;\n        }\n    } else {\n        aiming = true;\n    }\n\n    int actionPoints = item_w_mp_cost(a1, main_ctd.hitMode, aiming);\n    debug_printf(\"sequencing attack...\\n\");\n\n    if (action_attack(&main_ctd) == -1) {\n        return -1;\n    }\n\n    if (actionPoints > a1->data.critter.combat.ap) {\n        a1->data.critter.combat.ap = 0;\n    } else {\n        a1->data.critter.combat.ap -= actionPoints;\n    }\n\n    if (a1 == obj_dude) {\n        intface_update_move_points(a1->data.critter.combat.ap, combat_free_move);\n        critter_set_who_hit_me(a1, a2);\n    }\n\n    combat_call_display = 1;\n    combat_cleanup_enabled = 1;\n    combatAIInfoSetLastTarget(a1, a2);\n    debug_printf(\"running attack...\\n\");\n\n    return 0;\n}\n\n// Returns tile one step closer from [a1] to [a2]\n//\n// 0x423104\nint combat_bullet_start(const Object* a1, const Object* a2)\n{\n    int rotation = tile_dir(a1->tile, a2->tile);\n    return tile_num_in_direction(a1->tile, rotation, 1);\n}\n\n// 0x423128\nstatic bool check_ranged_miss(Attack* attack)\n{\n    int range = item_w_range(attack->attacker, attack->hitMode);\n    int to = tile_num_beyond(attack->attacker->tile, attack->defender->tile, range);\n\n    int roll = ROLL_FAILURE;\n    Object* critter = attack->attacker;\n    if (critter != NULL) {\n        int curr = attack->attacker->tile;\n        while (curr != to) {\n            make_straight_path_func(attack->attacker, curr, to, NULL, &critter, 32, obj_shoot_blocking_at);\n            if (critter != NULL) {\n                if ((critter->flags & OBJECT_SHOOT_THRU) == 0) {\n                    if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) {\n                        roll = ROLL_SUCCESS;\n                        break;\n                    }\n\n                    if (critter != attack->defender) {\n                        int v6 = determine_to_hit_func(attack->attacker, attack->attacker->tile, critter, attack->defenderHitLocation, attack->hitMode, 1) / 3;\n                        if (critter_is_dead(critter)) {\n                            v6 = 5;\n                        }\n\n                        if (roll_random(1, 100) <= v6) {\n                            roll = ROLL_SUCCESS;\n                            break;\n                        }\n                    }\n\n                    curr = critter->tile;\n                }\n            }\n\n            if (critter == NULL) {\n                break;\n            }\n        }\n    }\n\n    attack->defenderHitLocation = HIT_LOCATION_TORSO;\n\n    if (roll < ROLL_SUCCESS || critter == NULL || (critter->flags & OBJECT_SHOOT_THRU) == 0) {\n        return false;\n    }\n\n    attack->defender = critter;\n    attack->tile = critter->tile;\n    attack->attackerFlags |= DAM_HIT;\n    attack->defenderHitLocation = HIT_LOCATION_TORSO;\n    compute_damage(attack, 1, 2);\n    return true;\n}\n\n// 0x423284\nstatic int shoot_along_path(Attack* attack, int endTile, int rounds, int anim)\n{\n    // 0x56D3A0\n    static Attack temp_ctd;\n\n    int remainingRounds = rounds;\n    int roundsHitMainTarget = 0;\n    int currentTile = attack->attacker->tile;\n\n    Object* critter = attack->attacker;\n    while (critter != NULL) {\n        if ((remainingRounds <= 0 && anim != ANIM_FIRE_CONTINUOUS) || currentTile == endTile || attack->extrasLength >= 6) {\n            break;\n        }\n\n        make_straight_path_func(attack->attacker, currentTile, endTile, NULL, &critter, 32, obj_shoot_blocking_at);\n\n        if (critter != NULL) {\n            if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) {\n                break;\n            }\n\n            int accuracy = determine_to_hit_func(attack->attacker, attack->attacker->tile, critter, HIT_LOCATION_TORSO, attack->hitMode, 1);\n            if (anim == ANIM_FIRE_CONTINUOUS) {\n                remainingRounds = 1;\n            }\n\n            int roundsHit = 0;\n            while (roll_random(1, 100) <= accuracy && remainingRounds > 0) {\n                remainingRounds -= 1;\n                roundsHit += 1;\n            }\n\n            if (roundsHit != 0) {\n                if (critter == attack->defender) {\n                    roundsHitMainTarget += roundsHit;\n                } else {\n                    int index;\n                    for (index = 0; index < attack->extrasLength; index += 1) {\n                        if (critter == attack->extras[index]) {\n                            break;\n                        }\n                    }\n\n                    attack->extrasHitLocation[index] = HIT_LOCATION_TORSO;\n                    attack->extras[index] = critter;\n                    combat_ctd_init(&temp_ctd, attack->attacker, critter, attack->hitMode, HIT_LOCATION_TORSO);\n                    temp_ctd.attackerFlags |= DAM_HIT;\n                    compute_damage(&temp_ctd, roundsHit, 2);\n\n                    if (index == attack->extrasLength) {\n                        attack->extrasDamage[index] = temp_ctd.defenderDamage;\n                        attack->extrasFlags[index] = temp_ctd.defenderFlags;\n                        attack->extrasKnockback[index] = temp_ctd.defenderKnockback;\n                        attack->extrasLength++;\n                    } else {\n                        if (anim == ANIM_FIRE_BURST) {\n                            attack->extrasDamage[index] += temp_ctd.defenderDamage;\n                            attack->extrasFlags[index] |= temp_ctd.defenderFlags;\n                            attack->extrasKnockback[index] += temp_ctd.defenderKnockback;\n                        }\n                    }\n                }\n            }\n\n            currentTile = critter->tile;\n        }\n    }\n\n    if (anim == ANIM_FIRE_CONTINUOUS) {\n        roundsHitMainTarget = 0;\n    }\n\n    return roundsHitMainTarget;\n}\n\n// 0x423488\nstatic int compute_spray(Attack* attack, int accuracy, int* roundsHitMainTargetPtr, int* roundsSpentPtr, int anim)\n{\n    *roundsHitMainTargetPtr = 0;\n\n    int ammoQuantity = item_w_curr_ammo(attack->weapon);\n    int burstRounds = item_w_rounds(attack->weapon);\n    if (burstRounds < ammoQuantity) {\n        ammoQuantity = burstRounds;\n    }\n\n    *roundsSpentPtr = ammoQuantity;\n\n    int criticalChance = critterGetStat(attack->attacker, STAT_CRITICAL_CHANCE);\n    int roll = roll_check(accuracy, criticalChance, NULL);\n\n    if (roll == ROLL_CRITICAL_FAILURE) {\n        return roll;\n    }\n\n    if (roll == ROLL_CRITICAL_SUCCESS) {\n        accuracy += 20;\n    }\n\n    int leftRounds;\n    int mainTargetRounds;\n    int centerRounds;\n    int rightRounds;\n    if (anim == ANIM_FIRE_BURST) {\n        centerRounds = ammoQuantity / 3;\n        if (centerRounds == 0) {\n            centerRounds = 1;\n        }\n\n        leftRounds = ammoQuantity / 3;\n        rightRounds = ammoQuantity - centerRounds - leftRounds;\n        mainTargetRounds = centerRounds / 2;\n        if (mainTargetRounds == 0) {\n            mainTargetRounds = 1;\n            centerRounds -= 1;\n        }\n    } else {\n        leftRounds = 1;\n        mainTargetRounds = 1;\n        centerRounds = 1;\n        rightRounds = 1;\n    }\n\n    for (int index = 0; index < mainTargetRounds; index += 1) {\n        if (roll_check(accuracy, 0, NULL) >= ROLL_SUCCESS) {\n            *roundsHitMainTargetPtr += 1;\n        }\n    }\n\n    if (*roundsHitMainTargetPtr == 0 && check_ranged_miss(attack)) {\n        *roundsHitMainTargetPtr = 1;\n    }\n\n    int range = item_w_range(attack->attacker, attack->hitMode);\n    int mainTargetEndTile = tile_num_beyond(attack->attacker->tile, attack->defender->tile, range);\n    *roundsHitMainTargetPtr += shoot_along_path(attack, mainTargetEndTile, centerRounds - *roundsHitMainTargetPtr, anim);\n\n    int centerTile;\n    if (obj_dist(attack->attacker, attack->defender) <= 3) {\n        centerTile = tile_num_beyond(attack->attacker->tile, attack->defender->tile, 3);\n    } else {\n        centerTile = attack->defender->tile;\n    }\n\n    int rotation = tile_dir(centerTile, attack->attacker->tile);\n\n    int leftTile = tile_num_in_direction(centerTile, (rotation + 1) % ROTATION_COUNT, 1);\n    int leftEndTile = tile_num_beyond(attack->attacker->tile, leftTile, range);\n    *roundsHitMainTargetPtr += shoot_along_path(attack, leftEndTile, leftRounds, anim);\n\n    int rightTile = tile_num_in_direction(centerTile, (rotation + 5) % ROTATION_COUNT, 1);\n    int rightEndTile = tile_num_beyond(attack->attacker->tile, rightTile, range);\n    *roundsHitMainTargetPtr += shoot_along_path(attack, rightEndTile, rightRounds, anim);\n\n    if (roll != ROLL_FAILURE || (*roundsHitMainTargetPtr <= 0 && attack->extrasLength <= 0)) {\n        if (roll >= ROLL_SUCCESS && *roundsHitMainTargetPtr == 0 && attack->extrasLength == 0) {\n            roll = ROLL_FAILURE;\n        }\n    } else {\n        roll = ROLL_SUCCESS;\n    }\n\n    return roll;\n}\n\n// 0x423714\nstatic int correctAttackForPerks(Attack* attack)\n{\n    if (item_w_perk(attack->weapon) == PERK_WEAPON_ENHANCED_KNOCKOUT) {\n        int difficulty = critterGetStat(attack->attacker, STAT_STRENGTH) - 8;\n        int chance = roll_random(1, 100);\n        if (chance <= difficulty) {\n            Object* weapon = NULL;\n            if (attack->defender != obj_dude) {\n                weapon = item_hit_with(attack->defender, HIT_MODE_RIGHT_WEAPON_PRIMARY);\n            }\n\n            if (!(attackFindInvalidFlags(attack->defender, weapon) & 1)) {\n                attack->defenderFlags |= DAM_KNOCKED_OUT;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x42378C\nstatic int compute_attack(Attack* attack)\n{\n    int range = item_w_range(attack->attacker, attack->hitMode);\n    int distance = obj_dist(attack->attacker, attack->defender);\n\n    if (range < distance) {\n        return -1;\n    }\n\n    int anim = item_w_anim(attack->attacker, attack->hitMode);\n    int accuracy = determine_to_hit_func(attack->attacker, attack->attacker->tile, attack->defender, attack->defenderHitLocation, attack->hitMode, 1);\n\n    bool isGrenade = false;\n    int damageType = item_w_damage_type(attack->attacker, attack->weapon);\n    if (anim == ANIM_THROW_ANIM && (damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP)) {\n        isGrenade = true;\n    }\n\n    if (attack->defenderHitLocation == HIT_LOCATION_UNCALLED) {\n        attack->defenderHitLocation = HIT_LOCATION_TORSO;\n    }\n\n    int attackType = item_w_subtype(attack->weapon, attack->hitMode);\n    int roundsHitMainTarget = 1;\n    int damageMultiplier = 2;\n    int roundsSpent = 1;\n\n    int roll;\n\n    if (anim == ANIM_FIRE_BURST || anim == ANIM_FIRE_CONTINUOUS) {\n        roll = compute_spray(attack, accuracy, &roundsHitMainTarget, &roundsSpent, anim);\n    } else {\n        int chance = critterGetStat(attack->attacker, STAT_CRITICAL_CHANCE);\n        roll = roll_check(accuracy, chance - hit_location_penalty[attack->defenderHitLocation], NULL);\n    }\n\n    if (roll == ROLL_FAILURE) {\n        if (trait_level(TRAIT_JINXED) || perkHasRank(obj_dude, PERK_JINXED)) {\n            if (roll_random(0, 1) == 1) {\n                roll = ROLL_CRITICAL_FAILURE;\n            }\n        }\n    }\n\n    if (roll == ROLL_SUCCESS) {\n        if ((attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) && attack->attacker == obj_dude) {\n            if (perkHasRank(attack->attacker, PERK_SLAYER)) {\n                roll = ROLL_CRITICAL_SUCCESS;\n            }\n\n            if (perkHasRank(obj_dude, PERK_SILENT_DEATH)\n                && !is_hit_from_front(obj_dude, attack->defender)\n                && is_pc_flag(DUDE_STATE_SNEAKING)\n                && obj_dude != attack->defender->data.critter.combat.whoHitMe) {\n                damageMultiplier = 4;\n            }\n\n            if (((attack->hitMode == HIT_MODE_HAMMER_PUNCH || attack->hitMode == HIT_MODE_POWER_KICK) && roll_random(1, 100) <= 5)\n                || ((attack->hitMode == HIT_MODE_JAB || attack->hitMode == HIT_MODE_HOOK_KICK) && roll_random(1, 100) <= 10)\n                || (attack->hitMode == HIT_MODE_HAYMAKER && roll_random(1, 100) <= 15)\n                || (attack->hitMode == HIT_MODE_PALM_STRIKE && roll_random(1, 100) <= 20)\n                || (attack->hitMode == HIT_MODE_PIERCING_STRIKE && roll_random(1, 100) <= 40)\n                || (attack->hitMode == HIT_MODE_PIERCING_KICK && roll_random(1, 100) <= 50)) {\n                roll = ROLL_CRITICAL_SUCCESS;\n            }\n        }\n    }\n\n    if (attackType == ATTACK_TYPE_RANGED) {\n        attack->ammoQuantity = roundsSpent;\n\n        if (roll == ROLL_SUCCESS && attack->attacker == obj_dude) {\n            if (perk_level(obj_dude, PERK_SNIPER) != 0) {\n                int d10 = roll_random(1, 10);\n                int luck = critterGetStat(obj_dude, STAT_LUCK);\n                if (d10 <= luck) {\n                    roll = ROLL_CRITICAL_SUCCESS;\n                }\n            }\n        }\n    } else {\n        if (item_w_max_ammo(attack->weapon) > 0) {\n            attack->ammoQuantity = 1;\n        }\n    }\n\n    if (item_w_compute_ammo_cost(attack->weapon, &(attack->ammoQuantity)) == -1) {\n        return -1;\n    }\n\n    switch (roll) {\n    case ROLL_CRITICAL_SUCCESS:\n        damageMultiplier = attack_crit_success(attack);\n        // FALLTHROUGH\n    case ROLL_SUCCESS:\n        attack->attackerFlags |= DAM_HIT;\n        correctAttackForPerks(attack);\n        compute_damage(attack, roundsHitMainTarget, damageMultiplier);\n        break;\n    case ROLL_FAILURE:\n        if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) {\n            check_ranged_miss(attack);\n        }\n        break;\n    case ROLL_CRITICAL_FAILURE:\n        attack_crit_failure(attack);\n        break;\n    }\n\n    if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) {\n        if ((attack->attackerFlags & (DAM_HIT | DAM_CRITICAL)) == 0) {\n            int tile;\n            if (isGrenade) {\n                int throwDistance = roll_random(1, distance / 2);\n                if (throwDistance == 0) {\n                    throwDistance = 1;\n                }\n\n                int rotation = roll_random(0, 5);\n                tile = tile_num_in_direction(attack->defender->tile, rotation, throwDistance);\n            } else {\n                tile = tile_num_beyond(attack->attacker->tile, attack->defender->tile, range);\n            }\n\n            attack->tile = tile;\n\n            Object* v25 = attack->defender;\n            make_straight_path_func(v25, attack->defender->tile, attack->tile, NULL, &v25, 32, obj_shoot_blocking_at);\n            if (v25 != NULL && v25 != attack->defender) {\n                attack->tile = v25->tile;\n            } else {\n                v25 = obj_blocking_at(NULL, attack->tile, attack->defender->elevation);\n            }\n\n            if (v25 != NULL && (v25->flags & OBJECT_SHOOT_THRU) == 0) {\n                attack->attackerFlags |= DAM_HIT;\n                attack->defender = v25;\n                compute_damage(attack, 1, 2);\n            }\n        }\n    }\n\n    if ((damageType == DAMAGE_TYPE_EXPLOSION || isGrenade) && ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0)) {\n        compute_explosion_on_extras(attack, 0, isGrenade, 0);\n    } else {\n        if ((attack->attackerFlags & DAM_EXPLODE) != 0) {\n            compute_explosion_on_extras(attack, 1, isGrenade, 0);\n        }\n    }\n\n    death_checks(attack);\n\n    return 0;\n}\n\n// compute_explosion_on_extras\n// 0x423C10\nvoid compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4)\n{\n    // 0x56D458\n    Attack temp_ctd;\n\n    Object* attacker;\n\n    if (a2) {\n        attacker = attack->attacker;\n    } else {\n        if ((attack->attackerFlags & DAM_HIT) != 0) {\n            attacker = attack->defender;\n        } else {\n            attacker = NULL;\n        }\n    }\n\n    int tile;\n    if (attacker != NULL) {\n        tile = attacker->tile;\n    } else {\n        tile = attack->tile;\n    }\n\n    if (tile == -1) {\n        debug_printf(\"\\nError: compute_explosion_on_extras: Called with bad target/tileNum\");\n        return;\n    }\n\n    // TODO: The math in this loop is rather complex and hard to understand.\n    int v20;\n    int v22 = 0;\n    int rotation = 0;\n    int v5 = -1;\n    int v19 = tile;\n    while (attack->extrasLength < 6) {\n        if (v22 != 0 && (v5 == -1 || (v5 = tile_num_in_direction(v5, rotation, 1)) != v19)) {\n            v20++;\n            if (v20 % v22 == 0) {\n                rotation += 1;\n                if (rotation == ROTATION_COUNT) {\n                    rotation = ROTATION_NE;\n                }\n            }\n        } else {\n            v22++;\n            if (isGrenade && item_w_grenade_dmg_radius(attack->weapon) < v22) {\n                v5 = -1;\n            } else if (isGrenade || item_w_rocket_dmg_radius(attack->weapon) >= v22) {\n                v5 = tile_num_in_direction(v19, ROTATION_NE, 1);\n            } else {\n                v5 = -1;\n            }\n\n            v19 = v5;\n            rotation = ROTATION_SE;\n            v20 = 0;\n        }\n\n        if (v5 == -1) {\n            break;\n        }\n\n        Object* obstacle = obj_blocking_at(attacker, v5, attack->attacker->elevation);\n        if (obstacle != NULL\n            && FID_TYPE(obstacle->fid) == OBJ_TYPE_CRITTER\n            && (obstacle->data.critter.combat.results & DAM_DEAD) == 0\n            && (obstacle->flags & OBJECT_SHOOT_THRU) == 0\n            && !combat_is_shot_blocked(obstacle, obstacle->tile, tile, NULL, NULL)) {\n            if (obstacle == attack->attacker) {\n                attack->attackerFlags &= ~DAM_HIT;\n                compute_damage(attack, 1, 2);\n                attack->attackerFlags |= DAM_HIT;\n                attack->attackerFlags |= DAM_BACKWASH;\n            } else {\n                int index;\n                for (index = 0; index < attack->extrasLength; index++) {\n                    if (attack->extras[index] == obstacle) {\n                        break;\n                    }\n                }\n\n                if (index == attack->extrasLength) {\n                    attack->extrasHitLocation[index] = HIT_LOCATION_TORSO;\n                    attack->extras[index] = obstacle;\n                    combat_ctd_init(&temp_ctd, attack->attacker, obstacle, attack->hitMode, HIT_LOCATION_TORSO);\n                    if (!a4) {\n                        temp_ctd.attackerFlags |= DAM_HIT;\n                        compute_damage(&temp_ctd, 1, 2);\n                    }\n\n                    attack->extrasDamage[index] = temp_ctd.defenderDamage;\n                    attack->extrasFlags[index] = temp_ctd.defenderFlags;\n                    attack->extrasKnockback[index] = temp_ctd.defenderKnockback;\n                    attack->extrasLength += 1;\n                }\n            }\n        }\n    }\n}\n\n// 0x423EB4\nstatic int attack_crit_success(Attack* attack)\n{\n    Object* defender = attack->defender;\n    if (defender != NULL && critter_flag_check(defender->pid, CRITTER_INVULNERABLE)) {\n        return 2;\n    }\n\n    if (defender != NULL && PID_TYPE(defender->pid) != OBJ_TYPE_CRITTER) {\n        return 2;\n    }\n\n    attack->attackerFlags |= DAM_CRITICAL;\n\n    int chance = roll_random(1, 100);\n\n    chance += critterGetStat(attack->attacker, STAT_BETTER_CRITICALS);\n\n    int effect;\n    if (chance <= 20)\n        effect = 0;\n    else if (chance <= 45)\n        effect = 1;\n    else if (chance <= 70)\n        effect = 2;\n    else if (chance <= 90)\n        effect = 3;\n    else if (chance <= 100)\n        effect = 4;\n    else\n        effect = 5;\n\n    CriticalHitDescription* criticalHitDescription;\n    if (defender == obj_dude) {\n        criticalHitDescription = &(pc_crit_succ_eff[attack->defenderHitLocation][effect]);\n    } else {\n        int killType = critterGetKillType(defender);\n        criticalHitDescription = &(crit_succ_eff[killType][attack->defenderHitLocation][effect]);\n    }\n\n    attack->defenderFlags |= criticalHitDescription->flags;\n\n    // NOTE: Original code is slightly different, it does not set message in\n    // advance, instead using \"else\" statement.\n    attack->criticalMessageId = criticalHitDescription->messageId;\n\n    if (criticalHitDescription->massiveCriticalStat != -1) {\n        if (stat_result(defender, criticalHitDescription->massiveCriticalStat, criticalHitDescription->massiveCriticalStatModifier, NULL) <= ROLL_FAILURE) {\n            attack->defenderFlags |= criticalHitDescription->massiveCriticalFlags;\n            attack->criticalMessageId = criticalHitDescription->massiveCriticalMessageId;\n        }\n    }\n\n    if ((attack->defenderFlags & DAM_CRIP_RANDOM) != 0) {\n        // NOTE: Uninline.\n        do_random_cripple(&(attack->defenderFlags));\n    }\n\n    if (item_w_perk(attack->weapon) == PERK_WEAPON_ENHANCED_KNOCKOUT) {\n        attack->defenderFlags |= DAM_KNOCKED_OUT;\n    }\n\n    Object* weapon = NULL;\n    if (defender != obj_dude) {\n        weapon = item_hit_with(defender, HIT_MODE_RIGHT_WEAPON_PRIMARY);\n    }\n\n    int flags = attackFindInvalidFlags(defender, weapon);\n    attack->defenderFlags &= ~flags;\n\n    return criticalHitDescription->damageMultiplier;\n}\n\n// 0x424088\nstatic int attackFindInvalidFlags(Object* critter, Object* item)\n{\n    int flags = 0;\n\n    if (critter != NULL && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER && critter_flag_check(critter->pid, CRITTER_NO_DROP)) {\n        flags |= DAM_DROP;\n    }\n\n    if (item != NULL && item_is_hidden(item)) {\n        flags |= DAM_DROP;\n    }\n\n    return flags;\n}\n\n// 0x4240DC\nstatic int attack_crit_failure(Attack* attack)\n{\n    attack->attackerFlags |= DAM_HIT;\n\n    if (attack->attacker != NULL && critter_flag_check(attack->attacker->pid, CRITTER_INVULNERABLE)) {\n        return 0;\n    }\n\n    if (attack->attacker == obj_dude) {\n        unsigned int gameTime = game_time();\n        if (gameTime / GAME_TIME_TICKS_PER_DAY < 6) {\n            return 0;\n        }\n    }\n\n    int attackType = item_w_subtype(attack->weapon, attack->hitMode);\n    int criticalFailureTableIndex = item_w_crit_fail(attack->weapon);\n    if (criticalFailureTableIndex == -1) {\n        criticalFailureTableIndex = 0;\n    }\n\n    int chance = roll_random(1, 100) - 5 * (critterGetStat(attack->attacker, STAT_LUCK) - 5);\n\n    int effect;\n    if (chance <= 20)\n        effect = 0;\n    else if (chance <= 50)\n        effect = 1;\n    else if (chance <= 75)\n        effect = 2;\n    else if (chance <= 95)\n        effect = 3;\n    else\n        effect = 4;\n\n    int flags = cf_table[criticalFailureTableIndex][effect];\n    if (flags == 0) {\n        return 0;\n    }\n\n    attack->attackerFlags |= DAM_CRITICAL;\n    attack->attackerFlags |= flags;\n\n    int v17 = attackFindInvalidFlags(attack->attacker, attack->weapon);\n    attack->attackerFlags &= ~v17;\n\n    if ((attack->attackerFlags & DAM_HIT_SELF) != 0) {\n        int ammoQuantity = attackType == ATTACK_TYPE_RANGED ? attack->ammoQuantity : 1;\n        compute_damage(attack, ammoQuantity, 2);\n    } else if ((attack->attackerFlags & DAM_EXPLODE) != 0) {\n        compute_damage(attack, 1, 2);\n    }\n\n    if ((attack->attackerFlags & DAM_LOSE_TURN) != 0) {\n        attack->attacker->data.critter.combat.ap = 0;\n    }\n\n    if ((attack->attackerFlags & DAM_LOSE_AMMO) != 0) {\n        if (attackType == ATTACK_TYPE_RANGED) {\n            attack->ammoQuantity = item_w_curr_ammo(attack->weapon);\n        } else {\n            attack->attackerFlags &= ~DAM_LOSE_AMMO;\n        }\n    }\n\n    if ((attack->attackerFlags & DAM_CRIP_RANDOM) != 0) {\n        // NOTE: Uninline.\n        do_random_cripple(&(attack->attackerFlags));\n    }\n\n    if ((attack->attackerFlags & DAM_RANDOM_HIT) != 0) {\n        attack->defender = combat_ai_random_target(attack);\n        if (attack->defender != NULL) {\n            attack->attackerFlags |= DAM_HIT;\n            attack->defenderHitLocation = HIT_LOCATION_TORSO;\n            attack->attackerFlags &= ~DAM_CRITICAL;\n\n            int ammoQuantity = attackType == ATTACK_TYPE_RANGED ? attack->ammoQuantity : 1;\n            compute_damage(attack, ammoQuantity, 2);\n        } else {\n            attack->defender = attack->oops;\n        }\n\n        if (attack->defender != NULL) {\n            attack->tile = attack->defender->tile;\n        }\n    }\n\n    return 0;\n}\n\n// 0x42432C\nstatic void do_random_cripple(int* flagsPtr)\n{\n    *flagsPtr &= ~DAM_CRIP_RANDOM;\n\n    switch (roll_random(0, 3)) {\n    case 0:\n        *flagsPtr |= DAM_CRIP_LEG_LEFT;\n        break;\n    case 1:\n        *flagsPtr |= DAM_CRIP_LEG_RIGHT;\n        break;\n    case 2:\n        *flagsPtr |= DAM_CRIP_ARM_LEFT;\n        break;\n    case 3:\n        *flagsPtr |= DAM_CRIP_ARM_RIGHT;\n        break;\n    }\n}\n\n// 0x42436C\nint determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode)\n{\n    return determine_to_hit_func(a1, a1->tile, a2, hitLocation, hitMode, 1);\n}\n\n// 0x424380\nint determine_to_hit_no_range(Object* a1, Object* a2, int hitLocation, int hitMode, unsigned char* a5)\n{\n    return determine_to_hit_func(a1, a1->tile, a2, hitLocation, hitMode, 0);\n}\n\n// 0x424394\nint determine_to_hit_from_tile(Object* a1, int tile, Object* a3, int hitLocation, int hitMode)\n{\n    return determine_to_hit_func(a1, tile, a3, hitLocation, hitMode, 1);\n}\n\n// determine_to_hit\n// 0x4243A8\nstatic int determine_to_hit_func(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, int a6)\n{\n    Object* weapon = item_hit_with(attacker, hitMode);\n\n    bool targetIsCritter = defender != NULL\n        ? FID_TYPE(defender->fid) == OBJ_TYPE_CRITTER\n        : false;\n\n    bool isRangedWeapon = false;\n\n    int accuracy;\n    if (weapon == NULL || hitMode == HIT_MODE_PUNCH || hitMode == HIT_MODE_KICK || (hitMode >= FIRST_ADVANCED_UNARMED_HIT_MODE && hitMode <= LAST_ADVANCED_UNARMED_HIT_MODE)) {\n        accuracy = skill_level(attacker, SKILL_UNARMED);\n    } else {\n        accuracy = item_w_skill_level(attacker, hitMode);\n\n        int modifier = 0;\n\n        int attackType = item_w_subtype(weapon, hitMode);\n        if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) {\n            isRangedWeapon = true;\n\n            int v29 = 0;\n            int v25 = 0;\n\n            int weaponPerk = item_w_perk(weapon);\n            switch (weaponPerk) {\n            case PERK_WEAPON_LONG_RANGE:\n                v29 = 4;\n                break;\n            case PERK_WEAPON_SCOPE_RANGE:\n                v29 = 5;\n                v25 = 8;\n                break;\n            default:\n                v29 = 2;\n                break;\n            }\n\n            int perception = critterGetStat(attacker, STAT_PERCEPTION);\n\n            if (defender != NULL) {\n                modifier = obj_dist_with_tile(attacker, tile, defender, defender->tile);\n            } else {\n                modifier = 0;\n            }\n\n            if (modifier >= v25) {\n                int penalty = attacker == obj_dude\n                    ? v29 * (perception - 2)\n                    : v29 * perception;\n\n                modifier -= penalty;\n            } else {\n                modifier += v25;\n            }\n\n            if (-2 * perception > modifier) {\n                modifier = -2 * perception;\n            }\n\n            if (attacker == obj_dude) {\n                modifier -= 2 * perk_level(obj_dude, PERK_SHARPSHOOTER);\n            }\n\n            if (modifier >= 0) {\n                if ((attacker->data.critter.combat.results & DAM_BLIND) != 0) {\n                    modifier *= -12;\n                } else {\n                    modifier *= -4;\n                }\n            } else {\n                modifier *= -4;\n            }\n\n            if (a6 || modifier > 0) {\n                accuracy += modifier;\n            }\n\n            modifier = 0;\n\n            if (defender != NULL && a6) {\n                combat_is_shot_blocked(attacker, tile, defender->tile, defender, &modifier);\n            }\n\n            accuracy -= 10 * modifier;\n        }\n\n        if (attacker == obj_dude && trait_level(TRAIT_ONE_HANDER)) {\n            if (item_w_is_2handed(weapon)) {\n                accuracy -= 40;\n            } else {\n                accuracy += 20;\n            }\n        }\n\n        int minStrength = item_w_min_st(weapon);\n        modifier = minStrength - critterGetStat(attacker, STAT_STRENGTH);\n        if (attacker == obj_dude && perk_level(obj_dude, PERK_WEAPON_HANDLING) != 0) {\n            modifier -= 3;\n        }\n\n        if (modifier > 0) {\n            accuracy -= 20 * modifier;\n        }\n\n        if (item_w_perk(weapon) == PERK_WEAPON_ACCURATE) {\n            accuracy += 20;\n        }\n    }\n\n    if (targetIsCritter && defender != NULL) {\n        int armorClass = critterGetStat(defender, STAT_ARMOR_CLASS);\n        armorClass += item_w_ac_adjust(weapon);\n        if (armorClass < 0) {\n            armorClass = 0;\n        }\n\n        accuracy -= armorClass;\n    }\n\n    if (isRangedWeapon) {\n        accuracy += hit_location_penalty[hitLocation];\n    } else {\n        accuracy += hit_location_penalty[hitLocation] / 2;\n    }\n\n    if (defender != NULL && (defender->flags & OBJECT_MULTIHEX) != 0) {\n        accuracy += 15;\n    }\n\n    if (attacker == obj_dude) {\n        int lightIntensity;\n        if (defender != NULL) {\n            lightIntensity = obj_get_visible_light(defender);\n            if (item_w_perk(weapon) == PERK_WEAPON_NIGHT_SIGHT) {\n                lightIntensity = 65536;\n            }\n        } else {\n            lightIntensity = 0;\n        }\n\n        if (lightIntensity <= 26214)\n            accuracy -= 40;\n        else if (lightIntensity <= 39321)\n            accuracy -= 25;\n        else if (lightIntensity <= 52428)\n            accuracy -= 10;\n    }\n\n    if (gcsd != NULL) {\n        accuracy += gcsd->accuracyBonus;\n    }\n\n    if ((attacker->data.critter.combat.results & DAM_BLIND) != 0) {\n        accuracy -= 25;\n    }\n\n    if (targetIsCritter && defender != NULL && (defender->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) {\n        accuracy += 40;\n    }\n\n    if (attacker->data.critter.combat.team != obj_dude->data.critter.combat.team) {\n        int combatDifficuly = 1;\n        config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combatDifficuly);\n        switch (combatDifficuly) {\n        case 0:\n            accuracy -= 20;\n            break;\n        case 2:\n            accuracy += 20;\n            break;\n        }\n    }\n\n    if (accuracy > 95) {\n        accuracy = 95;\n    }\n\n    if (accuracy < -100) {\n        debug_printf(\"Whoa! Bad skill value in determine_to_hit!\\n\");\n    }\n\n    return accuracy;\n}\n\n// 0x4247B8\nstatic void compute_damage(Attack* attack, int ammoQuantity, int bonusDamageMultiplier)\n{\n    int* damagePtr;\n    Object* critter;\n    int* flagsPtr;\n    int* knockbackDistancePtr;\n\n    if ((attack->attackerFlags & DAM_HIT) != 0) {\n        damagePtr = &(attack->defenderDamage);\n        critter = attack->defender;\n        flagsPtr = &(attack->defenderFlags);\n        knockbackDistancePtr = &(attack->defenderKnockback);\n    } else {\n        damagePtr = &(attack->attackerDamage);\n        critter = attack->attacker;\n        flagsPtr = &(attack->attackerFlags);\n        knockbackDistancePtr = NULL;\n    }\n\n    *damagePtr = 0;\n\n    if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    int damageType = item_w_damage_type(attack->attacker, attack->weapon);\n    int damageThreshold = critterGetStat(critter, STAT_DAMAGE_THRESHOLD + damageType);\n    int damageResistance = critterGetStat(critter, STAT_DAMAGE_RESISTANCE + damageType);\n\n    if ((*flagsPtr & DAM_BYPASS) != 0 && damageType != DAMAGE_TYPE_EMP) {\n        damageThreshold = 20 * damageThreshold / 100;\n        damageResistance = 20 * damageResistance / 100;\n    } else {\n        if (item_w_perk(attack->weapon) == PERK_WEAPON_PENETRATE\n            || attack->hitMode == HIT_MODE_PALM_STRIKE\n            || attack->hitMode == HIT_MODE_PIERCING_STRIKE\n            || attack->hitMode == HIT_MODE_HOOK_KICK\n            || attack->hitMode == HIT_MODE_PIERCING_KICK) {\n            damageThreshold = 20 * damageThreshold / 100;\n        }\n\n        if (attack->attacker == obj_dude && trait_level(TRAIT_FINESSE)) {\n            damageResistance += 30;\n        }\n    }\n\n    int damageBonus;\n    if (attack->attacker == obj_dude && item_w_subtype(attack->weapon, attack->hitMode) == ATTACK_TYPE_RANGED) {\n        damageBonus = 2 * perk_level(obj_dude, PERK_BONUS_RANGED_DAMAGE);\n    } else {\n        damageBonus = 0;\n    }\n\n    int combatDifficultyDamageModifier = 100;\n    if (attack->attacker->data.critter.combat.team != obj_dude->data.critter.combat.team) {\n        int combatDifficulty = COMBAT_DIFFICULTY_NORMAL;\n        config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combatDifficulty);\n\n        switch (combatDifficulty) {\n        case COMBAT_DIFFICULTY_EASY:\n            combatDifficultyDamageModifier = 75;\n            break;\n        case COMBAT_DIFFICULTY_HARD:\n            combatDifficultyDamageModifier = 125;\n            break;\n        }\n    }\n\n    damageResistance += item_w_dr_adjust(attack->weapon);\n    if (damageResistance > 100) {\n        damageResistance = 100;\n    } else if (damageResistance < 0) {\n        damageResistance = 0;\n    }\n\n    int damageMultiplier = bonusDamageMultiplier * item_w_dam_mult(attack->weapon);\n    int damageDivisor = item_w_dam_div(attack->weapon);\n\n    for (int index = 0; index < ammoQuantity; index++) {\n        int damage = item_w_damage(attack->attacker, attack->hitMode);\n\n        damage += damageBonus;\n\n        damage *= damageMultiplier;\n\n        if (damageDivisor != 0) {\n            damage /= damageDivisor;\n        }\n\n        // TODO: Why we're halving it?\n        damage /= 2;\n\n        damage *= combatDifficultyDamageModifier;\n        damage /= 100;\n\n        damage -= damageThreshold;\n\n        if (damage > 0) {\n            damage -= damage * damageResistance / 100;\n        }\n\n        if (damage > 0) {\n            *damagePtr += damage;\n        }\n    }\n\n    if (attack->attacker == obj_dude) {\n        if (perk_level(attack->attacker, PERK_LIVING_ANATOMY) != 0) {\n            int kt = critterGetKillType(attack->defender);\n            if (kt != KILL_TYPE_ROBOT && kt != KILL_TYPE_ALIEN) {\n                *damagePtr += 5;\n            }\n        }\n\n        if (perk_level(attack->attacker, PERK_PYROMANIAC) != 0) {\n            if (item_w_damage_type(attack->attacker, attack->weapon) == DAMAGE_TYPE_FIRE) {\n                *damagePtr += 5;\n            }\n        }\n    }\n\n    if (knockbackDistancePtr != NULL\n        && (critter->flags & OBJECT_MULTIHEX) == 0\n        && (damageType == DAMAGE_TYPE_EXPLOSION || attack->weapon == NULL || item_w_subtype(attack->weapon, attack->hitMode) == ATTACK_TYPE_MELEE)\n        && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER\n        && critter_flag_check(critter->pid, CRITTER_NO_KNOCKBACK) == 0) {\n        bool shouldKnockback = true;\n        bool hasStonewall = false;\n        if (critter == obj_dude) {\n            if (perk_level(critter, PERK_STONEWALL) != 0) {\n                int chance = roll_random(0, 100);\n                hasStonewall = true;\n                if (chance < 50) {\n                    shouldKnockback = false;\n                }\n            }\n        }\n\n        if (shouldKnockback) {\n            int knockbackDistanceDivisor = item_w_perk(attack->weapon) == PERK_WEAPON_KNOCKBACK ? 5 : 10;\n\n            *knockbackDistancePtr = *damagePtr / knockbackDistanceDivisor;\n\n            if (hasStonewall) {\n                *knockbackDistancePtr /= 2;\n            }\n        }\n    }\n}\n\n// 0x424BAC\nvoid death_checks(Attack* attack)\n{\n    check_for_death(attack->attacker, attack->attackerDamage, &(attack->attackerFlags));\n    check_for_death(attack->defender, attack->defenderDamage, &(attack->defenderFlags));\n\n    for (int index = 0; index < attack->extrasLength; index++) {\n        check_for_death(attack->extras[index], attack->extrasDamage[index], &(attack->extrasFlags[index]));\n    }\n}\n\n// 0x424C04\nvoid apply_damage(Attack* attack, bool animated)\n{\n    Object* attacker = attack->attacker;\n    bool attackerIsCritter = attacker != NULL && FID_TYPE(attacker->fid) == OBJ_TYPE_CRITTER;\n    bool v5 = attack->defender != attack->oops;\n\n    if (attackerIsCritter && (attacker->data.critter.combat.results & DAM_DEAD) != 0) {\n        set_new_results(attacker, attack->attackerFlags);\n        // TODO: Not sure about \"attack->defender == attack->oops\".\n        damage_object(attacker, attack->attackerDamage, animated, attack->defender == attack->oops, attacker);\n    }\n\n    Object* v7 = attack->oops;\n    if (v7 != NULL && v7 != attack->defender) {\n        combatai_notify_onlookers(v7);\n    }\n\n    Object* defender = attack->defender;\n    bool defenderIsCritter = defender != NULL && FID_TYPE(defender->fid) == OBJ_TYPE_CRITTER;\n\n    if (!defenderIsCritter && !v5) {\n        bool v9 = isPartyMember(attack->defender) && isPartyMember(attack->attacker) ? false : true;\n        if (v9) {\n            if (defender != NULL) {\n                if (defender->sid != -1) {\n                    scr_set_ext_param(defender->sid, attack->attackerDamage);\n                    scr_set_objs(defender->sid, attack->attacker, attack->weapon);\n                    exec_script_proc(defender->sid, SCRIPT_PROC_DAMAGE);\n                }\n            }\n        }\n    }\n\n    if (defenderIsCritter && (defender->data.critter.combat.results & DAM_DEAD) == 0) {\n        set_new_results(defender, attack->defenderFlags);\n\n        if (defenderIsCritter) {\n            if (defenderIsCritter) {\n                if ((defender->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {\n                    if (!v5 || defender != obj_dude) {\n                        critter_set_who_hit_me(defender, attack->attacker);\n                    }\n                } else if (defender == attack->oops || defender->data.critter.combat.team != attack->attacker->data.critter.combat.team) {\n                    combatai_check_retaliation(defender, attack->attacker);\n                }\n            }\n        }\n\n        scr_set_objs(defender->sid, attack->attacker, attack->weapon);\n        damage_object(defender, attack->defenderDamage, animated, attack->defender != attack->oops, attacker);\n\n        if (defenderIsCritter) {\n            combatai_notify_onlookers(defender);\n        }\n\n        if (attack->defenderDamage >= 0 && (attack->attackerFlags & DAM_HIT) != 0) {\n            scr_set_objs(attack->attacker->sid, NULL, attack->defender);\n            scr_set_ext_param(attack->attacker->sid, 2);\n            exec_script_proc(attack->attacker->sid, SCRIPT_PROC_COMBAT);\n        }\n    }\n\n    for (int index = 0; index < attack->extrasLength; index++) {\n        Object* obj = attack->extras[index];\n        if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER && (obj->data.critter.combat.results & DAM_DEAD) == 0) {\n            set_new_results(obj, attack->extrasFlags[index]);\n\n            if (defenderIsCritter) {\n                if ((obj->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {\n                    critter_set_who_hit_me(obj, attack->attacker);\n                } else if (obj->data.critter.combat.team != attack->attacker->data.critter.combat.team) {\n                    combatai_check_retaliation(obj, attack->attacker);\n                }\n            }\n\n            scr_set_objs(obj->sid, attack->attacker, attack->weapon);\n            // TODO: Not sure about defender == oops.\n            damage_object(obj, attack->extrasDamage[index], animated, attack->defender == attack->oops, attack->attacker);\n            combatai_notify_onlookers(obj);\n\n            if (attack->extrasDamage[index] >= 0) {\n                if ((attack->attackerFlags & DAM_HIT) != 0) {\n                    scr_set_objs(attack->attacker->sid, NULL, obj);\n                    scr_set_ext_param(attack->attacker->sid, 2);\n                    exec_script_proc(attack->attacker->sid, SCRIPT_PROC_COMBAT);\n                }\n            }\n        }\n    }\n}\n\n// 0x424EE8\nstatic void check_for_death(Object* object, int damage, int* flags)\n{\n    if (object == NULL || !critter_flag_check(object->pid, CRITTER_INVULNERABLE)) {\n        if (object == NULL || PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n            if (damage > 0) {\n                if (critter_get_hits(object) - damage <= 0) {\n                    *flags |= DAM_DEAD;\n                }\n            }\n        }\n    }\n}\n\n// 0x424F2C\nstatic void set_new_results(Object* critter, int flags)\n{\n    if (critter == NULL) {\n        return;\n    }\n\n    if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    if (critter_flag_check(critter->pid, CRITTER_INVULNERABLE)) {\n        return;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    if ((flags & DAM_DEAD) != 0) {\n        queue_remove(critter);\n    } else if ((flags & DAM_KNOCKED_OUT) != 0) {\n        int endurance = critterGetStat(critter, STAT_ENDURANCE);\n        queue_add(10 * (35 - 3 * endurance), critter, NULL, EVENT_TYPE_KNOCKOUT);\n    }\n\n    if (critter == obj_dude && (flags & DAM_CRIP_ARM_ANY) != 0) {\n        critter->data.critter.combat.results |= flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_CRIP | DAM_DEAD | DAM_LOSE_TURN);\n\n        int leftItemAction;\n        int rightItemAction;\n        intface_get_item_states(&leftItemAction, &rightItemAction);\n        intface_update_items(true, leftItemAction, rightItemAction);\n    } else {\n        critter->data.critter.combat.results |= flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_CRIP | DAM_DEAD | DAM_LOSE_TURN);\n    }\n}\n\n// 0x425020\nstatic void damage_object(Object* a1, int damage, bool animated, int a4, Object* a5)\n{\n    if (a1 == NULL) {\n        return;\n    }\n\n    if (FID_TYPE(a1->fid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    if (critter_flag_check(a1->pid, CRITTER_INVULNERABLE)) {\n        return;\n    }\n\n    if (damage <= 0) {\n        return;\n    }\n\n    critter_adjust_hits(a1, -damage);\n\n    if (a1 == obj_dude) {\n        intface_update_hit_points(animated);\n    }\n\n    a1->data.critter.combat.damageLastTurn += damage;\n\n    if (!a4) {\n        // TODO: Not sure about this one.\n        if (!isPartyMember(a1) || !isPartyMember(a5)) {\n            scr_set_ext_param(a1->sid, damage);\n            exec_script_proc(a1->sid, SCRIPT_PROC_DAMAGE);\n        }\n    }\n\n    if ((a1->data.critter.combat.results & DAM_DEAD) != 0) {\n        scr_set_objs(a1->sid, a1->data.critter.combat.whoHitMe, NULL);\n        exec_script_proc(a1->sid, SCRIPT_PROC_DESTROY);\n        item_destroy_all_hidden(a1);\n\n        if (a1 != obj_dude) {\n            Object* whoHitMe = a1->data.critter.combat.whoHitMe;\n            if (whoHitMe == obj_dude || (whoHitMe != NULL && whoHitMe->data.critter.combat.team == obj_dude->data.critter.combat.team)) {\n                bool scriptOverrides = false;\n                Script* scr;\n                if (scr_ptr(a1->sid, &scr) != -1) {\n                    scriptOverrides = scr->scriptOverrides;\n                }\n\n                if (!scriptOverrides) {\n                    combat_exps += critter_kill_exps(a1);\n                    critter_kill_count_inc(critterGetKillType(a1));\n                }\n            }\n        }\n\n        if (a1->sid != -1) {\n            scr_remove(a1->sid);\n            a1->sid = -1;\n        }\n\n        partyMemberRemove(a1);\n    }\n}\n\n// Print attack description to monitor.\n//\n// 0x425170\nvoid combat_display(Attack* attack)\n{\n    MessageListItem messageListItem;\n\n    if (attack->attacker == obj_dude) {\n        Object* weapon = item_hit_with(attack->attacker, attack->hitMode);\n        int strengthRequired = item_w_min_st(weapon);\n\n        if (perk_level(attack->attacker, PERK_WEAPON_HANDLING) != 0) {\n            strengthRequired -= 3;\n        }\n\n        if (weapon != NULL) {\n            if (strengthRequired > critterGetStat(obj_dude, STAT_STRENGTH)) {\n                // You are not strong enough to use this weapon properly.\n                messageListItem.num = 107;\n                if (message_search(&combat_message_file, &messageListItem)) {\n                    display_print(messageListItem.text);\n                }\n            }\n        }\n    }\n\n    Object* mainCritter;\n    if ((attack->attackerFlags & DAM_HIT) != 0) {\n        mainCritter = attack->defender;\n    } else {\n        mainCritter = attack->attacker;\n    }\n\n    char* mainCritterName = _a_1;\n\n    char you[20];\n    you[0] = '\\0';\n    if (critterGetStat(obj_dude, STAT_GENDER) == GENDER_MALE) {\n        // You (male)\n        messageListItem.num = 506;\n    } else {\n        // You (female)\n        messageListItem.num = 556;\n    }\n\n    if (message_search(&combat_message_file, &messageListItem)) {\n        strcpy(you, messageListItem.text);\n    }\n\n    int baseMessageId;\n    if (mainCritter == obj_dude) {\n        mainCritterName = you;\n        if (critterGetStat(obj_dude, STAT_GENDER) == GENDER_MALE) {\n            baseMessageId = 500;\n        } else {\n            baseMessageId = 550;\n        }\n    } else if (mainCritter != NULL) {\n        mainCritterName = object_name(mainCritter);\n        if (critterGetStat(mainCritter, STAT_GENDER) == GENDER_MALE) {\n            baseMessageId = 600;\n        } else {\n            baseMessageId = 700;\n        }\n    }\n\n    char text[280];\n    if (attack->defender != NULL\n        && attack->oops != NULL\n        && attack->defender != attack->oops\n        && (attack->attackerFlags & DAM_HIT) != 0) {\n        if (FID_TYPE(attack->defender->fid) == OBJ_TYPE_CRITTER) {\n            if (attack->oops == obj_dude) {\n                // 608 (male) - Oops! %s was hit instead of you!\n                // 708 (female) - Oops! %s was hit instead of you!\n                messageListItem.num = baseMessageId + 8;\n                if (message_search(&combat_message_file, &messageListItem)) {\n                    sprintf(text, messageListItem.text, mainCritterName);\n                }\n            } else {\n                // 509 (male) - Oops! %s were hit instead of %s!\n                // 559 (female) - Oops! %s were hit instead of %s!\n                const char* name = object_name(attack->oops);\n                messageListItem.num = baseMessageId + 9;\n                if (message_search(&combat_message_file, &messageListItem)) {\n                    sprintf(text, messageListItem.text, mainCritterName, name);\n                }\n            }\n        } else {\n            if (attack->attacker == obj_dude) {\n                if (critterGetStat(attack->attacker, STAT_GENDER) == GENDER_MALE) {\n                    // (male) %s missed\n                    messageListItem.num = 515;\n                } else {\n                    // (female) %s missed\n                    messageListItem.num = 565;\n                }\n\n                if (message_search(&combat_message_file, &messageListItem)) {\n                    sprintf(text, messageListItem.text, you);\n                }\n            } else {\n                const char* name = object_name(attack->attacker);\n                if (critterGetStat(attack->attacker, STAT_GENDER) == GENDER_MALE) {\n                    // (male) %s missed\n                    messageListItem.num = 615;\n                } else {\n                    // (female) %s missed\n                    messageListItem.num = 715;\n                }\n\n                if (message_search(&combat_message_file, &messageListItem)) {\n                    sprintf(text, messageListItem.text, name);\n                }\n            }\n        }\n\n        strcat(text, \".\");\n\n        display_print(text);\n    }\n\n    if ((attack->attackerFlags & DAM_HIT) != 0) {\n        Object* v21 = attack->defender;\n        if (v21 != NULL && (v21->data.critter.combat.results & DAM_DEAD) == 0) {\n            text[0] = '\\0';\n\n            if (FID_TYPE(v21->fid) == OBJ_TYPE_CRITTER) {\n                if (attack->defenderHitLocation == HIT_LOCATION_TORSO) {\n                    if ((attack->attackerFlags & DAM_CRITICAL) != 0) {\n                        switch (attack->defenderDamage) {\n                        case 0:\n                            // 528 - %s were critically hit for no damage\n                            messageListItem.num = baseMessageId + 28;\n                            break;\n                        case 1:\n                            // 524 - %s were critically hit for 1 hit point\n                            messageListItem.num = baseMessageId + 24;\n                            break;\n                        default:\n                            // 520 - %s were critically hit for %d hit points\n                            messageListItem.num = baseMessageId + 20;\n                            break;\n                        }\n\n                        if (message_search(&combat_message_file, &messageListItem)) {\n                            if (attack->defenderDamage <= 1) {\n                                sprintf(text, messageListItem.text, mainCritterName);\n                            } else {\n                                sprintf(text, messageListItem.text, mainCritterName, attack->defenderDamage);\n                            }\n                        }\n                    } else {\n                        combat_display_hit(text, v21, attack->defenderDamage);\n                    }\n                } else {\n                    const char* hitLocationName = combat_get_loc_name(v21, attack->defenderHitLocation);\n                    if (hitLocationName != NULL) {\n                        if ((attack->attackerFlags & DAM_CRITICAL) != 0) {\n                            switch (attack->defenderDamage) {\n                            case 0:\n                                // 525 - %s were critically hit in %s for no damage\n                                messageListItem.num = baseMessageId + 25;\n                                break;\n                            case 1:\n                                // 521 - %s were critically hit in %s for 1 damage\n                                messageListItem.num = baseMessageId + 21;\n                                break;\n                            default:\n                                // 511 - %s were critically hit in %s for %d hit points\n                                messageListItem.num = baseMessageId + 11;\n                                break;\n                            }\n                        } else {\n                            switch (attack->defenderDamage) {\n                            case 0:\n                                // 526 - %s were hit in %s for no damage\n                                messageListItem.num = baseMessageId + 26;\n                                break;\n                            case 1:\n                                // 522 - %s were hit in %s for 1 damage\n                                messageListItem.num = baseMessageId + 22;\n                                break;\n                            default:\n                                // 512 - %s were hit in %s for %d hit points\n                                messageListItem.num = baseMessageId + 12;\n                                break;\n                            }\n                        }\n\n                        if (message_search(&combat_message_file, &messageListItem)) {\n                            if (attack->defenderDamage <= 1) {\n                                sprintf(text, messageListItem.text, mainCritterName, hitLocationName);\n                            } else {\n                                sprintf(text, messageListItem.text, mainCritterName, hitLocationName, attack->defenderDamage);\n                            }\n                        }\n                    }\n                }\n\n                int combatMessages = 1;\n                config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, &combatMessages);\n\n                if (combatMessages == 1 && (attack->attackerFlags & DAM_CRITICAL) != 0 && attack->criticalMessageId != -1) {\n                    messageListItem.num = attack->criticalMessageId;\n                    if (message_search(&combat_message_file, &messageListItem)) {\n                        strcat(text, messageListItem.text);\n                    }\n\n                    if ((attack->defenderFlags & DAM_DEAD) != 0) {\n                        strcat(text, \".\");\n                        display_print(text);\n\n                        if (attack->defender == obj_dude) {\n                            if (critterGetStat(attack->defender, STAT_GENDER) == GENDER_MALE) {\n                                // were killed\n                                messageListItem.num = 207;\n                            } else {\n                                // were killed\n                                messageListItem.num = 257;\n                            }\n                        } else {\n                            if (critterGetStat(attack->defender, STAT_GENDER) == GENDER_MALE) {\n                                // was killed\n                                messageListItem.num = 307;\n                            } else {\n                                // was killed\n                                messageListItem.num = 407;\n                            }\n                        }\n\n                        if (message_search(&combat_message_file, &messageListItem)) {\n                            sprintf(text, \"%s %s\", mainCritterName, messageListItem.text);\n                        }\n                    }\n                } else {\n                    combat_display_flags(text, attack->defenderFlags, attack->defender);\n                }\n\n                strcat(text, \".\");\n\n                display_print(text);\n            }\n        }\n    }\n\n    if (attack->attacker != NULL && (attack->attacker->data.critter.combat.results & DAM_DEAD) == 0) {\n        if ((attack->attackerFlags & DAM_HIT) == 0) {\n            if ((attack->attackerFlags & DAM_CRITICAL) != 0) {\n                switch (attack->attackerDamage) {\n                case 0:\n                    // 514 - %s critically missed\n                    messageListItem.num = baseMessageId + 14;\n                    break;\n                case 1:\n                    // 533 - %s critically missed and took 1 hit point\n                    messageListItem.num = baseMessageId + 33;\n                    break;\n                default:\n                    // 534 - %s critically missed and took %d hit points\n                    messageListItem.num = baseMessageId + 34;\n                    break;\n                }\n            } else {\n                // 515 - %s missed\n                messageListItem.num = baseMessageId + 15;\n            }\n\n            if (message_search(&combat_message_file, &messageListItem)) {\n                if (attack->attackerDamage <= 1) {\n                    sprintf(text, messageListItem.text, mainCritterName);\n                } else {\n                    sprintf(text, messageListItem.text, mainCritterName, attack->attackerDamage);\n                }\n            }\n\n            combat_display_flags(text, attack->attackerFlags, attack->attacker);\n\n            strcat(text, \".\");\n\n            display_print(text);\n        }\n\n        if ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0) {\n            if (attack->attackerDamage > 0) {\n                combat_display_hit(text, attack->attacker, attack->attackerDamage);\n                combat_display_flags(text, attack->attackerFlags, attack->attacker);\n                strcat(text, \".\");\n                display_print(text);\n            }\n        }\n    }\n\n    for (int index = 0; index < attack->extrasLength; index++) {\n        Object* critter = attack->extras[index];\n        if ((critter->data.critter.combat.results & DAM_DEAD) == 0) {\n            combat_display_hit(text, critter, attack->extrasDamage[index]);\n            combat_display_flags(text, attack->extrasFlags[index], critter);\n            strcat(text, \".\");\n\n            display_print(text);\n        }\n    }\n}\n\n// 0x425A9C\nstatic void combat_display_hit(char* dest, Object* critter, int damage)\n{\n    MessageListItem messageListItem;\n    char text[40];\n    char* name;\n\n    int messageId;\n    if (critter == obj_dude) {\n        text[0] = '\\0';\n\n        if (critterGetStat(obj_dude, STAT_GENDER) == GENDER_MALE) {\n            messageId = 500;\n        } else {\n            messageId = 550;\n        }\n\n        // 506 - You\n        messageListItem.num = messageId + 6;\n        if (message_search(&combat_message_file, &messageListItem)) {\n            strcpy(text, messageListItem.text);\n        }\n\n        name = text;\n    } else {\n        name = object_name(critter);\n\n        if (critterGetStat(critter, STAT_GENDER) == GENDER_MALE) {\n            messageId = 600;\n        } else {\n            messageId = 700;\n        }\n    }\n\n    switch (damage) {\n    case 0:\n        // 627 - %s was hit for no damage\n        messageId += 27;\n        break;\n    case 1:\n        // 623 - %s was hit for 1 hit point\n        messageId += 23;\n        break;\n    default:\n        // 613 - %s was hit for %d hit points\n        messageId += 13;\n        break;\n    }\n\n    messageListItem.num = messageId;\n    if (message_search(&combat_message_file, &messageListItem)) {\n        if (damage <= 1) {\n            sprintf(dest, messageListItem.text, name);\n        } else {\n            sprintf(dest, messageListItem.text, name, damage);\n        }\n    }\n}\n\n// 0x425BA4\nstatic void combat_display_flags(char* dest, int flags, Object* critter)\n{\n    MessageListItem messageListItem;\n\n    int num;\n    if (critter == obj_dude) {\n        if (critterGetStat(critter, STAT_GENDER) == GENDER_MALE) {\n            num = 200;\n        } else {\n            num = 250;\n        }\n    } else {\n        if (critterGetStat(critter, STAT_GENDER) == GENDER_MALE) {\n            num = 300;\n        } else {\n            num = 400;\n        }\n    }\n\n    if (flags == 0) {\n        return;\n    }\n\n    if ((flags & DAM_DEAD) != 0) {\n        // \" and \"\n        messageListItem.num = 108;\n        if (message_search(&combat_message_file, &messageListItem)) {\n            strcat(dest, messageListItem.text);\n        }\n\n        // were killed\n        messageListItem.num = num + 7;\n        if (message_search(&combat_message_file, &messageListItem)) {\n            strcat(dest, messageListItem.text);\n        }\n\n        return;\n    }\n\n    int bit = 1;\n    int flagsListLength = 0;\n    int flagsList[32];\n    for (int index = 0; index < 32; index++) {\n        if (bit != DAM_CRITICAL && bit != DAM_HIT && (bit & flags) != 0) {\n            flagsList[flagsListLength++] = index;\n        }\n        bit <<= 1;\n    }\n\n    if (flagsListLength != 0) {\n        for (int index = 0; index < flagsListLength - 1; index++) {\n            strcat(dest, \", \");\n\n            messageListItem.num = num + flagsList[index];\n            if (message_search(&combat_message_file, &messageListItem)) {\n                strcat(dest, messageListItem.text);\n            }\n        }\n\n        // \" and \"\n        messageListItem.num = 108;\n        if (message_search(&combat_message_file, &messageListItem)) {\n            strcat(dest, messageListItem.text);\n        }\n\n        messageListItem.num = flagsList[flagsListLength - 1];\n        if (message_search(&combat_message_file, &messageListItem)) {\n            strcat(dest, messageListItem.text);\n        }\n    }\n}\n\n// 0x425E3C\nvoid combat_anim_begin()\n{\n    if (++combat_turn_running == 1 && obj_dude == main_ctd.attacker) {\n        game_ui_disable(1);\n        gmouse_set_cursor(26);\n        if (combat_highlight == 2) {\n            combat_outline_off();\n        }\n    }\n}\n\n// 0x425E80\nvoid combat_anim_finished()\n{\n    combat_turn_running -= 1;\n    if (combat_turn_running != 0) {\n        return;\n    }\n\n    if (obj_dude == main_ctd.attacker) {\n        game_ui_enable();\n    }\n\n    if (combat_cleanup_enabled) {\n        combat_cleanup_enabled = false;\n\n        Object* weapon = item_hit_with(main_ctd.attacker, main_ctd.hitMode);\n        if (weapon != NULL) {\n            if (item_w_max_ammo(weapon) > 0) {\n                int ammoQuantity = item_w_curr_ammo(weapon);\n                item_w_set_curr_ammo(weapon, ammoQuantity - main_ctd.ammoQuantity);\n\n                if (main_ctd.attacker == obj_dude) {\n                    intface_update_ammo_lights();\n                }\n            }\n        }\n\n        if (combat_call_display) {\n            combat_display(&main_ctd);\n            combat_call_display = false;\n        }\n\n        apply_damage(&main_ctd, true);\n\n        Object* attacker = main_ctd.attacker;\n        if (attacker == obj_dude && combat_highlight == 2) {\n            combat_outline_on();\n        }\n\n        if (scr_end_combat()) {\n            if ((obj_dude->data.critter.combat.results & DAM_KNOCKED_OUT) != 0) {\n                if (attacker->data.critter.combat.team == obj_dude->data.critter.combat.team) {\n                    combat_ending_guy = obj_dude->data.critter.combat.whoHitMe;\n                } else {\n                    combat_ending_guy = attacker;\n                }\n            }\n        }\n\n        combat_ctd_init(&main_ctd, main_ctd.attacker, NULL, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);\n\n        if ((attacker->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) {\n            if ((attacker->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) == 0) {\n                combat_standup(attacker);\n            }\n        }\n    }\n}\n\n// 0x425FBC\nstatic void combat_standup(Object* a1)\n{\n    int v2;\n\n    v2 = 3;\n    if (a1 == obj_dude && perk_level(a1, PERK_QUICK_RECOVERY)) {\n        v2 = 1;\n    }\n\n    if (v2 > a1->data.critter.combat.ap) {\n        a1->data.critter.combat.ap = 0;\n    } else {\n        a1->data.critter.combat.ap -= v2;\n    }\n\n    if (a1 == obj_dude) {\n        intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n    }\n\n    dude_standup(a1);\n\n    // NOTE: Uninline.\n    combat_turn_run();\n}\n\n// Render two digits.\n//\n// 0x42603C\nstatic void print_tohit(unsigned char* dest, int destPitch, int accuracy)\n{\n    CacheEntry* numbersFrmHandle;\n    int numbersFrmFid = art_id(OBJ_TYPE_INTERFACE, 82, 0, 0, 0);\n    unsigned char* numbersFrmData = art_ptr_lock_data(numbersFrmFid, 0, 0, &numbersFrmHandle);\n    if (numbersFrmData == NULL) {\n        return;\n    }\n\n    if (accuracy >= 0) {\n        buf_to_buf(numbersFrmData + 9 * (accuracy % 10), 9, 17, 360, dest + 9, destPitch);\n        buf_to_buf(numbersFrmData + 9 * (accuracy / 10), 9, 17, 360, dest, destPitch);\n    } else {\n        buf_to_buf(numbersFrmData + 108, 6, 17, 360, dest + 9, destPitch);\n        buf_to_buf(numbersFrmData + 108, 6, 17, 360, dest, destPitch);\n    }\n\n    art_ptr_unlock(numbersFrmHandle);\n}\n\n// 0x42612C\nstatic char* combat_get_loc_name(Object* critter, int hitLocation)\n{\n    MessageListItem messageListItem;\n    messageListItem.num = 1000 + 10 * art_alias_num(critter->fid & 0xFFF) + hitLocation;\n    if (message_search(&combat_message_file, &messageListItem)) {\n        return messageListItem.text;\n    }\n\n    return NULL;\n}\n\n// 0x4261B4\nstatic void draw_loc_off(int a1, int a2)\n{\n    draw_loc(a2, colorTable[992]);\n}\n\n// 0x4261C0\nstatic void draw_loc_on(int a1, int a2)\n{\n    draw_loc(a2, colorTable[31744]);\n}\n\n// 0x4261CC\nstatic void draw_loc(int eventCode, int color)\n{\n    color |= 0x3000000;\n\n    if (eventCode >= 4) {\n        char* name = combat_get_loc_name(call_target, hit_loc_right[eventCode - 4]);\n        int width = text_width(name);\n        win_print(call_win, name, 0, 431 - width, call_ty[eventCode - 4] - 86, color);\n    } else {\n        char* name = combat_get_loc_name(call_target, hit_loc_left[eventCode]);\n        win_print(call_win, name, 0, 74, call_ty[eventCode] - 86, color);\n    }\n}\n\n// 0x426218\nstatic int get_called_shot_location(Object* critter, int* hitLocation, int hitMode)\n{\n    if (critter == NULL) {\n        return 0;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    call_target = critter;\n\n    int calledShotWindowX = CALLED_SHOT_WINDOW_X;\n    int calledShotWindowY = CALLED_SHOT_WINDOW_Y;\n    call_win = win_add(calledShotWindowX,\n        calledShotWindowY,\n        CALLED_SHOT_WINDOW_WIDTH,\n        CALLED_SHOT_WINDOW_HEIGHT,\n        colorTable[0],\n        WINDOW_FLAG_0x10);\n    if (call_win == -1) {\n        return -1;\n    }\n\n    int fid;\n    CacheEntry* handle;\n    unsigned char* data;\n\n    unsigned char* windowBuffer = win_get_buf(call_win);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 118, 0, 0, 0);\n    data = art_ptr_lock_data(fid, 0, 0, &handle);\n    if (data == NULL) {\n        win_delete(call_win);\n        return -1;\n    }\n\n    buf_to_buf(data, CALLED_SHOT_WINDOW_WIDTH, CALLED_SHOT_WINDOW_HEIGHT, CALLED_SHOT_WINDOW_WIDTH, windowBuffer, CALLED_SHOT_WINDOW_WIDTH);\n    art_ptr_unlock(handle);\n\n    fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_CALLED_SHOT_PIC, 0, 0);\n    data = art_ptr_lock_data(fid, 0, 0, &handle);\n    if (data != NULL) {\n        buf_to_buf(data, 170, 225, 170, windowBuffer + CALLED_SHOT_WINDOW_WIDTH * 31 + 168, CALLED_SHOT_WINDOW_WIDTH);\n        art_ptr_unlock(handle);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n\n    CacheEntry* upHandle;\n    unsigned char* up = art_ptr_lock_data(fid, 0, 0, &upHandle);\n    if (up == NULL) {\n        win_delete(call_win);\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n\n    CacheEntry* downHandle;\n    unsigned char* down = art_ptr_lock_data(fid, 0, 0, &downHandle);\n    if (down == NULL) {\n        art_ptr_unlock(upHandle);\n        win_delete(call_win);\n        return -1;\n    }\n\n    // Cancel button\n    int btn = win_register_button(call_win, 210, 268, 15, 16, -1, -1, -1, KEY_ESCAPE, up, down, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int oldFont = text_curr();\n    text_font(101);\n\n    for (int index = 0; index < 4; index++) {\n        int probability;\n        int btn;\n\n        probability = determine_to_hit(obj_dude, critter, hit_loc_left[index], hitMode);\n        print_tohit(windowBuffer + CALLED_SHOT_WINDOW_WIDTH * (call_ty[index] - 86) + 33, CALLED_SHOT_WINDOW_WIDTH, probability);\n\n        btn = win_register_button(call_win, 33, call_ty[index] - 90, 128, 20, index, index, -1, index, NULL, NULL, NULL, 0);\n        win_register_button_func(btn, draw_loc_on, draw_loc_off, NULL, NULL);\n        draw_loc(index, colorTable[992]);\n\n        probability = determine_to_hit(obj_dude, critter, hit_loc_right[index], hitMode);\n        print_tohit(windowBuffer + CALLED_SHOT_WINDOW_WIDTH * (call_ty[index] - 86) + 453, CALLED_SHOT_WINDOW_WIDTH, probability);\n\n        btn = win_register_button(call_win, 341, call_ty[index] - 90, 128, 20, index + 4, index + 4, -1, index + 4, NULL, NULL, NULL, 0);\n        win_register_button_func(btn, draw_loc_on, draw_loc_off, NULL, NULL);\n        draw_loc(index + 4, colorTable[992]);\n    }\n\n    win_draw(call_win);\n\n    bool gameUiWasDisabled = game_ui_is_disabled();\n    if (gameUiWasDisabled) {\n        game_ui_enable();\n    }\n\n    gmouse_disable(0);\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    int eventCode;\n    while (true) {\n        eventCode = get_input();\n\n        if (eventCode == KEY_ESCAPE) {\n            break;\n        }\n\n        if (eventCode >= 0 && eventCode < HIT_LOCATION_COUNT) {\n            break;\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n    }\n\n    gmouse_enable();\n\n    if (gameUiWasDisabled) {\n        game_ui_disable(0);\n    }\n\n    text_font(oldFont);\n\n    art_ptr_unlock(downHandle);\n    art_ptr_unlock(upHandle);\n    win_delete(call_win);\n\n    if (eventCode == KEY_ESCAPE) {\n        return -1;\n    }\n\n    *hitLocation = eventCode < 4 ? hit_loc_left[eventCode] : hit_loc_right[eventCode - 4];\n\n    gsound_play_sfx_file(\"icsxxxx1\");\n\n    return 0;\n}\n\n// check for possibility of performing attacking\n// 0x426614\nint combat_check_bad_shot(Object* attacker, Object* defender, int hitMode, bool aiming)\n{\n    int range = 1;\n    int tile = -1;\n    if (defender != NULL) {\n        tile = defender->tile;\n        range = obj_dist(attacker, defender);\n        if ((defender->data.critter.combat.results & DAM_DEAD) != 0) {\n            return COMBAT_BAD_SHOT_ALREADY_DEAD;\n        }\n    }\n\n    Object* weapon = item_hit_with(attacker, hitMode);\n    if (weapon != NULL) {\n        if ((attacker->data.critter.combat.results & DAM_CRIP_ARM_LEFT) != 0\n            && (attacker->data.critter.combat.results & DAM_CRIP_ARM_RIGHT) != 0) {\n            return COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED;\n        }\n\n        if ((attacker->data.critter.combat.results & DAM_CRIP_ARM_ANY) != 0) {\n            if (item_w_is_2handed(weapon)) {\n                return COMBAT_BAD_SHOT_ARM_CRIPPLED;\n            }\n        }\n    }\n\n    if (item_w_mp_cost(attacker, hitMode, aiming) > attacker->data.critter.combat.ap) {\n        return COMBAT_BAD_SHOT_NOT_ENOUGH_AP;\n    }\n\n    if (item_w_range(attacker, hitMode) < range) {\n        return COMBAT_BAD_SHOT_OUT_OF_RANGE;\n    }\n\n    int attackType = item_w_subtype(weapon, hitMode);\n\n    if (item_w_max_ammo(weapon) > 0) {\n        if (item_w_curr_ammo(weapon) == 0) {\n            return COMBAT_BAD_SHOT_NO_AMMO;\n        }\n    }\n\n    if (attackType == ATTACK_TYPE_RANGED\n        || attackType == ATTACK_TYPE_THROW\n        || item_w_range(attacker, hitMode) > 1) {\n        if (combat_is_shot_blocked(attacker, attacker->tile, tile, defender, NULL)) {\n            return COMBAT_BAD_SHOT_AIM_BLOCKED;\n        }\n    }\n\n    return COMBAT_BAD_SHOT_OK;\n}\n\n// 0x426744\nbool combat_to_hit(Object* target, int* accuracy)\n{\n    int hitMode;\n    bool aiming;\n    if (intface_get_attack(&hitMode, &aiming) == -1) {\n        return false;\n    }\n\n    if (combat_check_bad_shot(obj_dude, target, hitMode, aiming) != COMBAT_BAD_SHOT_OK) {\n        return false;\n    }\n\n    *accuracy = determine_to_hit_func(obj_dude, obj_dude->tile, target, HIT_LOCATION_UNCALLED, hitMode, 1);\n\n    return true;\n}\n\n// 0x4267CC\nvoid combat_attack_this(Object* a1)\n{\n    if (a1 == NULL) {\n        return;\n    }\n\n    if ((combat_state & 0x02) == 0) {\n        return;\n    }\n\n    int hitMode;\n    bool aiming;\n    if (intface_get_attack(&hitMode, &aiming) == -1) {\n        return;\n    }\n\n    MessageListItem messageListItem;\n    Object* item;\n    char formattedText[80];\n    const char* sfx;\n\n    int rc = combat_check_bad_shot(obj_dude, a1, hitMode, aiming);\n    switch (rc) {\n    case COMBAT_BAD_SHOT_NO_AMMO:\n        item = item_hit_with(obj_dude, hitMode);\n        messageListItem.num = 101; // Out of ammo.\n        if (message_search(&combat_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n\n        sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_OUT_OF_AMMO, item, hitMode, NULL);\n        gsound_play_sfx_file(sfx);\n        return;\n    case COMBAT_BAD_SHOT_OUT_OF_RANGE:\n        messageListItem.num = 102; // Target out of range.\n        if (message_search(&combat_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n        return;\n    case COMBAT_BAD_SHOT_NOT_ENOUGH_AP:\n        item = item_hit_with(obj_dude, hitMode);\n        messageListItem.num = 100; // You need %d action points.\n        if (message_search(&combat_message_file, &messageListItem)) {\n            int actionPointsRequired = item_w_mp_cost(obj_dude, hitMode, aiming);\n            sprintf(formattedText, messageListItem.text, actionPointsRequired);\n            display_print(formattedText);\n        }\n        return;\n    case COMBAT_BAD_SHOT_ALREADY_DEAD:\n        return;\n    case COMBAT_BAD_SHOT_AIM_BLOCKED:\n        messageListItem.num = 104; // Your aim is blocked.\n        if (message_search(&combat_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n        return;\n    case COMBAT_BAD_SHOT_ARM_CRIPPLED:\n        messageListItem.num = 106; // You cannot use two-handed weapons with a crippled arm.\n        if (message_search(&combat_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n        return;\n    case COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED:\n        messageListItem.num = 105; // You cannot use weapons with both arms crippled.\n        if (message_search(&combat_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n        return;\n    }\n\n    if (!isInCombat()) {\n        STRUCT_664980 stru;\n        stru.attacker = obj_dude;\n        stru.defender = a1;\n        stru.actionPointsBonus = 0;\n        stru.accuracyBonus = 0;\n        stru.damageBonus = 0;\n        stru.minDamage = 0;\n        stru.maxDamage = INT_MAX;\n        stru.field_1C = 0;\n        combat(&stru);\n        return;\n    }\n\n    if (!aiming) {\n        combat_attack(obj_dude, a1, hitMode, HIT_LOCATION_UNCALLED);\n        return;\n    }\n\n    if (aiming != 1) {\n        debug_printf(\"Bad called shot value %d\\n\", aiming);\n    }\n\n    int hitLocation;\n    if (get_called_shot_location(a1, &hitLocation, hitMode) != -1) {\n        combat_attack(obj_dude, a1, hitMode, hitLocation);\n    }\n}\n\n// Highlights critters.\n//\n// 0x426AA8\nvoid combat_outline_on()\n{\n    int targetHighlight = TARGET_HIGHLIGHT_TARGETING_ONLY;\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &targetHighlight);\n    if (targetHighlight == TARGET_HIGHLIGHT_OFF) {\n        return;\n    }\n\n    if (gmouse_3d_get_mode() != GAME_MOUSE_MODE_CROSSHAIR) {\n        return;\n    }\n\n    if (isInCombat()) {\n        for (int index = 0; index < list_total; index++) {\n            combat_update_critter_outline_for_los(combat_list[index], 1);\n        }\n    } else {\n        Object** critterList;\n        int critterListLength = obj_create_list(-1, map_elevation, OBJ_TYPE_CRITTER, &critterList);\n        for (int index = 0; index < critterListLength; index++) {\n            Object* critter = critterList[index];\n            if (critter != obj_dude && (critter->data.critter.combat.results & DAM_DEAD) == 0) {\n                combat_update_critter_outline_for_los(critter, 1);\n            }\n        }\n\n        if (critterListLength != 0) {\n            obj_delete_list(critterList);\n        }\n    }\n\n    // NOTE: Uninline.\n    combat_update_critters_in_los(1);\n\n    tile_refresh_display();\n}\n\n// 0x426BC0\nvoid combat_outline_off()\n{\n    int i;\n    int v5;\n    Object** v9;\n\n    if (combat_state & 1) {\n        for (i = 0; i < list_total; i++) {\n            obj_turn_off_outline(combat_list[i], NULL);\n        }\n    } else {\n        v5 = obj_create_list(-1, map_elevation, 1, &v9);\n        for (i = 0; i < v5; i++) {\n            obj_turn_off_outline(v9[i], NULL);\n            obj_remove_outline(v9[i], NULL);\n        }\n        if (v5) {\n            obj_delete_list(v9);\n        }\n    }\n\n    tile_refresh_display();\n}\n\n// 0x426C64\nvoid combat_highlight_change()\n{\n    int targetHighlight = 2;\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &targetHighlight);\n    if (targetHighlight != combat_highlight && isInCombat()) {\n        if (targetHighlight != 0) {\n            if (combat_highlight == 0) {\n                combat_outline_on();\n            }\n        } else {\n            combat_outline_off();\n        }\n    }\n\n    combat_highlight = targetHighlight;\n}\n\n// Probably calculates line of sight or determines if object can see other object.\n//\n// 0x426CC4\nbool combat_is_shot_blocked(Object* a1, int from, int to, Object* a4, int* a5)\n{\n    if (a5 != NULL) {\n        *a5 = 0;\n    }\n\n    Object* obstacle = a1;\n    int current = from;\n    while (obstacle != NULL && current != to) {\n        make_straight_path_func(a1, current, to, 0, &obstacle, 32, obj_shoot_blocking_at);\n        if (obstacle != NULL) {\n            if (FID_TYPE(obstacle->fid) != OBJ_TYPE_CRITTER && obstacle != a4) {\n                return true;\n            }\n\n            if (a5 != NULL) {\n                if (obstacle != a4) {\n                    if (a4 != NULL) {\n                        if ((a4->data.critter.combat.results & DAM_DEAD) == 0) {\n                            *a5 += 1;\n\n                            if ((a4->flags & OBJECT_MULTIHEX) != 0) {\n                                *a5 += 1;\n                            }\n                        }\n                    }\n                }\n            }\n\n            if ((obstacle->flags & OBJECT_MULTIHEX) != 0) {\n                int rotation = tile_dir(current, to);\n                current = tile_num_in_direction(current, rotation, 1);\n            } else {\n                current = obstacle->tile;\n            }\n        }\n    }\n\n    return false;\n}\n\n// 0x426D94\nint combat_player_knocked_out_by()\n{\n    if ((obj_dude->data.critter.combat.results & DAM_DEAD) != 0) {\n        return -1;\n    }\n\n    if (combat_ending_guy == NULL) {\n        return -1;\n    }\n\n    return combat_ending_guy->data.critter.combat.team;\n}\n\n// 0x426DB8\nint combat_explode_scenery(Object* a1, Object* a2)\n{\n    scr_explode_scenery(a1, a1->tile, item_w_rocket_dmg_radius(NULL), a1->elevation);\n    return 0;\n}\n\n// 0x426DDC\nvoid combat_delete_critter(Object* obj)\n{\n    // TODO: Check entire function.\n    if (!isInCombat()) {\n        return;\n    }\n\n    if (list_total == 0) {\n        return;\n    }\n\n    int i;\n    for (i = 0; i < list_total; i++) {\n        if (obj == combat_list[i]) {\n            break;\n        }\n    }\n\n    if (i == list_total) {\n        return;\n    }\n\n    while (i < (list_total - 1)) {\n        combat_list[i] = combat_list[i + 1];\n        combatCopyAIInfo(i + 1, i);\n        i++;\n    }\n\n    list_total--;\n\n    combat_list[list_total] = obj;\n\n    if (i >= list_com) {\n        if (i < (list_noncom + list_com)) {\n            list_noncom--;\n        }\n    } else {\n        list_com--;\n    }\n\n    obj->data.critter.combat.ap = 0;\n    obj_remove_outline(obj, NULL);\n\n    obj->data.critter.combat.whoHitMe = NULL;\n    combatai_delete_critter(obj);\n}\n\n// 0x426EC4\nvoid combatKillCritterOutsideCombat(Object* critter_obj, char* msg)\n{\n    if (critter_obj != obj_dude) {\n        display_print(msg);\n        exec_script_proc(critter_obj->sid, SCRIPT_PROC_DESTROY);\n        critter_kill(critter_obj, -1, 1);\n    }\n}\n"
  },
  {
    "path": "src/game/combat.h",
    "content": "#ifndef FALLOUT_GAME_COMBAT_H_\n#define FALLOUT_GAME_COMBAT_H_\n\n#include \"game/anim.h\"\n#include \"game/combat_defs.h\"\n#include \"plib/db/db.h\"\n#include \"game/message.h\"\n#include \"game/object_types.h\"\n#include \"game/party.h\"\n#include \"game/proto_types.h\"\n\nextern int combatNumTurns;\nextern unsigned int combat_state;\nextern STRUCT_664980* gcsd;\nextern bool combat_call_display;\nextern int cf_table[WEAPON_CRITICAL_FAILURE_TYPE_COUNT][WEAPON_CRITICAL_FAILURE_EFFECT_COUNT];\n\nextern MessageList combat_message_file;\nextern Object* combat_turn_obj;\nextern int combat_exps;\nextern int combat_free_move;\n\nint combat_init();\nvoid combat_reset();\nvoid combat_exit();\nint find_cid(int a1, int a2, Object** a3, int a4);\nint combat_load(File* stream);\nint combat_save(File* stream);\nbool combat_safety_invalidate_weapon(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr);\nbool combat_safety_invalidate_weapon_func(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr, Object* attackerFriend);\nbool combatTestIncidentalHit(Object* attacker, Object* defender, Object* attackerFriend, Object* weapon);\nObject* combat_whose_turn();\nvoid combat_data_init(Object* obj);\nObject* combatAIInfoGetFriendlyDead(Object* obj);\nint combatAIInfoSetFriendlyDead(Object* a1, Object* a2);\nObject* combatAIInfoGetLastTarget(Object* obj);\nint combatAIInfoSetLastTarget(Object* a1, Object* a2);\nObject* combatAIInfoGetLastItem(Object* obj);\nint combatAIInfoSetLastItem(Object* obj, Object* a2);\nint combatAIInfoGetLastMove(Object* object);\nint combatAIInfoSetLastMove(Object* object, int move);\nvoid combat_update_critter_outline_for_los(Object* critter, bool a2);\nvoid combat_over_from_load();\nvoid combat_give_exps(int exp_points);\nint combat_in_range(Object* critter);\nvoid combat_end();\nvoid combat_turn_run();\nvoid combat_end_turn();\nvoid combat(STRUCT_664980* attack);\nvoid combat_ctd_init(Attack* attack, Object* a2, Object* a3, int a4, int a5);\nint combat_attack(Object* a1, Object* a2, int a3, int a4);\nint combat_bullet_start(const Object* a1, const Object* a2);\nvoid compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4);\nint determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode);\nint determine_to_hit_no_range(Object* a1, Object* a2, int a3, int a4, unsigned char* a5);\nint determine_to_hit_from_tile(Object* a1, int a2, Object* a3, int a4, int a5);\nvoid death_checks(Attack* attack);\nvoid apply_damage(Attack* attack, bool animated);\nvoid combat_display(Attack* attack);\nvoid combat_anim_begin();\nvoid combat_anim_finished();\nint combat_check_bad_shot(Object* attacker, Object* defender, int hitMode, bool aiming);\nbool combat_to_hit(Object* target, int* accuracy);\nvoid combat_attack_this(Object* a1);\nvoid combat_outline_on();\nvoid combat_outline_off();\nvoid combat_highlight_change();\nbool combat_is_shot_blocked(Object* a1, int from, int to, Object* a4, int* a5);\nint combat_player_knocked_out_by();\nint combat_explode_scenery(Object* a1, Object* a2);\nvoid combat_delete_critter(Object* obj);\nvoid combatKillCritterOutsideCombat(Object* critter_obj, char* msg);\n\nstatic inline bool isInCombat()\n{\n    return (combat_state & COMBAT_STATE_0x01) != 0;\n}\n\n#endif /* FALLOUT_GAME_COMBAT_H_ */\n"
  },
  {
    "path": "src/game/combat_defs.h",
    "content": "#ifndef FALLOUT_GAME_COMBAT_DEFS_H_\n#define FALLOUT_GAME_COMBAT_DEFS_H_\n\n#include \"game/object_types.h\"\n\n#define EXPLOSION_TARGET_COUNT (6)\n\n#define CRTICIAL_EFFECT_COUNT (6)\n\n#define WEAPON_CRITICAL_FAILURE_TYPE_COUNT (7)\n#define WEAPON_CRITICAL_FAILURE_EFFECT_COUNT (5)\n\ntypedef enum CombatState {\n    COMBAT_STATE_0x01 = 0x01,\n    COMBAT_STATE_0x02 = 0x02,\n    COMBAT_STATE_0x08 = 0x08,\n} CombatState;\n\ntypedef enum HitMode {\n    HIT_MODE_LEFT_WEAPON_PRIMARY = 0,\n    HIT_MODE_LEFT_WEAPON_SECONDARY = 1,\n    HIT_MODE_RIGHT_WEAPON_PRIMARY = 2,\n    HIT_MODE_RIGHT_WEAPON_SECONDARY = 3,\n    HIT_MODE_PUNCH = 4,\n    HIT_MODE_KICK = 5,\n    HIT_MODE_LEFT_WEAPON_RELOAD = 6,\n    HIT_MODE_RIGHT_WEAPON_RELOAD = 7,\n\n    // Punch Level 2\n    HIT_MODE_STRONG_PUNCH = 8,\n\n    // Punch Level 3\n    HIT_MODE_HAMMER_PUNCH = 9,\n\n    // Punch Level 4 aka 'Lightning Punch'\n    HIT_MODE_HAYMAKER = 10,\n\n    // Punch Level 5 aka 'Chop Punch'\n    HIT_MODE_JAB = 11,\n\n    // Punch Level 6 aka 'Dragon Punch'\n    HIT_MODE_PALM_STRIKE = 12,\n\n    // Punch Level 7 aka 'Force Punch'\n    HIT_MODE_PIERCING_STRIKE = 13,\n\n    // Kick Level 2\n    HIT_MODE_STRONG_KICK = 14,\n\n    // Kick Level 3\n    HIT_MODE_SNAP_KICK = 15,\n\n    // Kick Level 4 aka 'Roundhouse Kick'\n    HIT_MODE_POWER_KICK = 16,\n\n    // Kick Level 5\n    HIT_MODE_HIP_KICK = 17,\n\n    // Kick Level 6 aka 'Jump Kick'\n    HIT_MODE_HOOK_KICK = 18,\n\n    // Kick Level 7 aka 'Death Blossom Kick'\n    HIT_MODE_PIERCING_KICK = 19,\n    HIT_MODE_COUNT,\n    FIRST_ADVANCED_PUNCH_HIT_MODE = HIT_MODE_STRONG_PUNCH,\n    LAST_ADVANCED_PUNCH_HIT_MODE = HIT_MODE_PIERCING_STRIKE,\n    FIRST_ADVANCED_KICK_HIT_MODE = HIT_MODE_STRONG_KICK,\n    LAST_ADVANCED_KICK_HIT_MODE = HIT_MODE_PIERCING_KICK,\n    FIRST_ADVANCED_UNARMED_HIT_MODE = FIRST_ADVANCED_PUNCH_HIT_MODE,\n    LAST_ADVANCED_UNARMED_HIT_MODE = LAST_ADVANCED_KICK_HIT_MODE,\n} HitMode;\n\ntypedef enum HitLocation {\n    HIT_LOCATION_HEAD,\n    HIT_LOCATION_LEFT_ARM,\n    HIT_LOCATION_RIGHT_ARM,\n    HIT_LOCATION_TORSO,\n    HIT_LOCATION_RIGHT_LEG,\n    HIT_LOCATION_LEFT_LEG,\n    HIT_LOCATION_EYES,\n    HIT_LOCATION_GROIN,\n    HIT_LOCATION_UNCALLED,\n    HIT_LOCATION_COUNT,\n    HIT_LOCATION_SPECIFIC_COUNT = HIT_LOCATION_COUNT - 1,\n} HitLocation;\n\ntypedef struct CombatAiInfo {\n    Object* friendlyDead;\n    Object* lastTarget;\n    Object* lastItem;\n    int lastMove;\n} CombatAiInfo;\n\ntypedef struct STRUCT_664980 {\n    Object* attacker;\n    Object* defender;\n    int actionPointsBonus;\n    int accuracyBonus;\n    int damageBonus;\n    int minDamage;\n    int maxDamage;\n    int field_1C; // probably bool, indicating field_20 and field_24 used\n    int field_20; // flags on attacker\n    int field_24; // flags on defender\n} STRUCT_664980;\n\nstatic_assert(sizeof(STRUCT_664980) == 40, \"wrong size\");\n\ntypedef struct Attack {\n    Object* attacker;\n    int hitMode;\n    Object* weapon;\n    int attackHitLocation;\n    int attackerDamage;\n    int attackerFlags;\n    int ammoQuantity;\n    int criticalMessageId;\n    Object* defender;\n    int tile;\n    int defenderHitLocation;\n    int defenderDamage;\n    int defenderFlags;\n    int defenderKnockback;\n    Object* oops;\n    int extrasLength;\n    Object* extras[EXPLOSION_TARGET_COUNT];\n    int extrasHitLocation[EXPLOSION_TARGET_COUNT];\n    int extrasDamage[EXPLOSION_TARGET_COUNT];\n    int extrasFlags[EXPLOSION_TARGET_COUNT];\n    int extrasKnockback[EXPLOSION_TARGET_COUNT];\n} Attack;\n\nstatic_assert(sizeof(Attack) == 184, \"wrong size\");\n\n// Provides metadata about critical hit effect.\ntypedef struct CriticalHitDescription {\n    int damageMultiplier;\n\n    // Damage flags that will be applied to defender.\n    int flags;\n\n    // Stat to check to upgrade this critical hit to massive critical hit or\n    // -1 if there is no massive critical hit.\n    int massiveCriticalStat;\n\n    // Bonus/penalty to massive critical stat.\n    int massiveCriticalStatModifier;\n\n    // Additional damage flags if this critical hit become massive critical.\n    int massiveCriticalFlags;\n\n    int messageId;\n    int massiveCriticalMessageId;\n} CriticalHitDescription;\n\ntypedef enum CombatBadShot {\n    COMBAT_BAD_SHOT_OK = 0,\n    COMBAT_BAD_SHOT_NO_AMMO = 1,\n    COMBAT_BAD_SHOT_OUT_OF_RANGE = 2,\n    COMBAT_BAD_SHOT_NOT_ENOUGH_AP = 3,\n    COMBAT_BAD_SHOT_ALREADY_DEAD = 4,\n    COMBAT_BAD_SHOT_AIM_BLOCKED = 5,\n    COMBAT_BAD_SHOT_ARM_CRIPPLED = 6,\n    COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED = 7,\n} CombatBadShot;\n\n#endif /* FALLOUT_GAME_COMBAT_DEFS_H_ */\n"
  },
  {
    "path": "src/game/combatai.c",
    "content": "#include \"game/combatai.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"game/actions.h\"\n#include \"game/anim.h\"\n#include \"game/combat.h\"\n#include \"game/config.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/light.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n\nstatic void parse_hurt_str(char* str, int* out_value);\nstatic int cai_match_str_to_list(const char* str, const char** list, int count, int* out_value);\nstatic void cai_init_cap(AiPacket* ai);\nstatic int cai_cap_load(File* stream, AiPacket* ai);\nstatic int cai_cap_save(File* stream, AiPacket* ai);\nstatic AiPacket* ai_cap(Object* obj);\nstatic AiPacket* ai_cap_from_packet(int aiPacketNum);\nstatic int ai_magic_hands(Object* a1, Object* a2, int num);\nstatic int ai_check_drugs(Object* critter);\nstatic void ai_run_away(Object* a1, Object* a2);\nstatic int ai_move_away(Object* a1, Object* a2, int a3);\nstatic bool ai_find_friend(Object* a1, int a2, int a3);\nstatic int compare_nearer(const void* a1, const void* a2);\nstatic void ai_sort_list_distance(Object** critterList, int length, Object* origin);\nstatic void ai_sort_list_strength(Object** critterList, int length);\nstatic void ai_sort_list_weakness(Object** critterList, int length);\nstatic Object* ai_find_nearest_team(Object* a1, Object* a2, int a3);\nstatic Object* ai_find_nearest_team_in_combat(Object* a1, Object* a2, int a3);\nstatic int ai_find_attackers(Object* a1, Object** a2, Object** a3, Object** a4);\nstatic int ai_have_ammo(Object* critter_obj, Object* weapon_obj, Object** out_ammo_obj);\nstatic bool caiHasWeapPrefType(AiPacket* ai, int attackType);\nstatic Object* ai_best_weapon(Object* a1, Object* a2, Object* a3, Object* a4);\nstatic bool ai_can_use_weapon(Object* critter, Object* weapon, int hitMode);\nstatic bool ai_can_use_drug(Object* obj, Object* a2);\nstatic Object* ai_search_environ(Object* critter, int itemType);\nstatic Object* ai_retrieve_object(Object* a1, Object* a2);\nstatic int ai_pick_hit_mode(Object* a1, Object* a2, Object* a3);\nstatic int ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, int a4);\nstatic int ai_move_closer(Object* a1, Object* a2, int a3);\nstatic int cai_retargetTileFromFriendlyFire(Object* source, Object* target, int* tilePtr);\nstatic int cai_retargetTileFromFriendlyFireSubFunc(AiRetargetData* aiRetargetData, int tile);\nstatic bool cai_attackWouldIntersect(Object* attacker, Object* defender, Object* attackerFriend, int tile, int* distance);\nstatic int ai_switch_weapons(Object* a1, int* hitMode, Object** weapon, Object* a4);\nstatic int ai_called_shot(Object* a1, Object* a2, int a3);\nstatic int ai_attack(Object* a1, Object* a2, int a3);\nstatic int ai_try_attack(Object* a1, Object* a2);\nstatic int cai_perform_distance_prefs(Object* a1, Object* a2);\nstatic int cai_get_min_hp(AiPacket* ai);\nstatic int ai_print_msg(Object* critter, int type);\nstatic int combatai_rating(Object* obj);\nstatic int combatai_load_messages();\nstatic int combatai_unload_messages();\n\n// 0x51805C\nstatic Object* combat_obj = NULL;\n\n// 0x518060\nstatic int num_caps = 0;\n\n// 0x518064\nstatic AiPacket* cap = NULL;\n\n// 0x518068\nstatic bool combatai_is_initialized = false;\n\n// 0x51806C\nconst char* area_attack_mode_strs[AREA_ATTACK_MODE_COUNT] = {\n    \"always\",\n    \"sometimes\",\n    \"be_sure\",\n    \"be_careful\",\n    \"be_absolutely_sure\",\n};\n\n// 0x5180D0\nconst char* attack_who_mode_strs[ATTACK_WHO_COUNT] = {\n    \"whomever_attacking_me\",\n    \"strongest\",\n    \"weakest\",\n    \"whomever\",\n    \"closest\",\n};\n\n// 0x51809C\nconst char* weapon_pref_strs[BEST_WEAPON_COUNT] = {\n    \"no_pref\",\n    \"melee\",\n    \"melee_over_ranged\",\n    \"ranged_over_melee\",\n    \"ranged\",\n    \"unarmed\",\n    \"unarmed_over_thrown\",\n    \"random\",\n};\n\n// 0x5180E4\nconst char* chem_use_mode_strs[CHEM_USE_COUNT] = {\n    \"clean\",\n    \"stims_when_hurt_little\",\n    \"stims_when_hurt_lots\",\n    \"sometimes\",\n    \"anytime\",\n    \"always\",\n};\n\n// 0x5180BC\nconst char* distance_pref_strs[DISTANCE_COUNT] = {\n    \"stay_close\",\n    \"charge\",\n    \"snipe\",\n    \"on_your_own\",\n    \"stay\",\n};\n\n// 0x518080\nconst char* run_away_mode_strs[RUN_AWAY_MODE_COUNT] = {\n    \"none\",\n    \"coward\",\n    \"finger_hurts\",\n    \"bleeding\",\n    \"not_feeling_good\",\n    \"tourniquet\",\n    \"never\",\n};\n\n// 0x5180FC\nconst char* disposition_strs[DISPOSITION_COUNT] = {\n    \"none\",\n    \"custom\",\n    \"coward\",\n    \"defensive\",\n    \"aggressive\",\n    \"berserk\",\n};\n\n// 0x518114\nstatic const char* matchHurtStrs[HURT_COUNT] = {\n    \"blind\",\n    \"crippled\",\n    \"crippled_legs\",\n    \"crippled_arms\",\n};\n\n// hurt_too_much\n//\n// 0x518124\nstatic int rmatchHurtVals[5] = {\n    DAM_BLIND,\n    DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT | DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT,\n    DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT,\n    DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT,\n    0,\n};\n\n// Hit points in percent to choose run away mode.\n//\n// 0x518138\nstatic int runModeValues[6] = {\n    0,\n    25,\n    40,\n    60,\n    75,\n    100,\n};\n\n// 0x518150\nstatic Object* attackerTeamObj = NULL;\n\n// 0x518154\nstatic Object* targetTeamObj = NULL;\n\n// 0x518158\nstatic int weapPrefOrderings[BEST_WEAPON_COUNT + 1][5] = {\n    { ATTACK_TYPE_RANGED, ATTACK_TYPE_THROW, ATTACK_TYPE_MELEE, ATTACK_TYPE_UNARMED, 0 },\n    { ATTACK_TYPE_RANGED, ATTACK_TYPE_THROW, ATTACK_TYPE_MELEE, ATTACK_TYPE_UNARMED, 0 }, // BEST_WEAPON_NO_PREF\n    { ATTACK_TYPE_MELEE, 0, 0, 0, 0 }, // BEST_WEAPON_MELEE\n    { ATTACK_TYPE_MELEE, ATTACK_TYPE_RANGED, 0, 0, 0 }, // BEST_WEAPON_MELEE_OVER_RANGED\n    { ATTACK_TYPE_RANGED, ATTACK_TYPE_MELEE, 0, 0, 0 }, // BEST_WEAPON_RANGED_OVER_MELEE\n    { ATTACK_TYPE_RANGED, 0, 0, 0, 0 }, // BEST_WEAPON_RANGED\n    { ATTACK_TYPE_UNARMED, 0, 0, 0, 0 }, // BEST_WEAPON_UNARMED\n    { ATTACK_TYPE_UNARMED, ATTACK_TYPE_THROW, 0, 0, 0 }, // BEST_WEAPON_UNARMED_OVER_THROW\n    { 0, 0, 0, 0, 0 }, // BEST_WEAPON_RANDOM\n};\n\n// ai.msg\n//\n// 0x56D510\nstatic MessageList ai_message_file;\n\n// 0x56D518\nstatic char target_str[260];\n\n// 0x56D61C\nstatic int curr_crit_num;\n\n// 0x56D620\nstatic Object** curr_crit_list;\n\n// 0x56D624\nstatic char attack_str[268];\n\n// parse hurt_too_much\nstatic void parse_hurt_str(char* str, int* valuePtr)\n{\n    int v5, v10;\n    char tmp;\n    int i;\n\n    *valuePtr = 0;\n\n    str = strlwr(str);\n    while (*str) {\n        v5 = strspn(str, \" \");\n        str += v5;\n\n        v10 = strcspn(str, \",\");\n        tmp = str[v10];\n        str[v10] = '\\0';\n\n        for (i = 0; i < 4; i++) {\n            if (strcmp(str, matchHurtStrs[i]) == 0) {\n                *valuePtr |= rmatchHurtVals[i];\n                break;\n            }\n        }\n\n        if (i == 4) {\n            debug_printf(\"Unrecognized flag: %s\\n\", str);\n        }\n\n        str[v10] = tmp;\n\n        if (tmp == '\\0') {\n            break;\n        }\n\n        str += v10 + 1;\n    }\n}\n\n// parse behaviour entry\nstatic int cai_match_str_to_list(const char* str, const char** list, int count, int* valuePtr)\n{\n    *valuePtr = -1;\n    for (int index = 0; index < count; index++) {\n        if (stricmp(str, list[index]) == 0) {\n            *valuePtr = index;\n        }\n    }\n\n    return 0;\n}\n\n// 0x426FE0\nstatic void cai_init_cap(AiPacket* ai)\n{\n    ai->name = NULL;\n\n    ai->area_attack_mode = -1;\n    ai->run_away_mode = -1;\n    ai->best_weapon = -1;\n    ai->distance = -1;\n    ai->attack_who = -1;\n    ai->chem_use = -1;\n\n    for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) {\n        ai->chem_primary_desire[index] = -1;\n    }\n\n    ai->disposition = -1;\n}\n\n// ai_init\n// 0x42703C\nint combat_ai_init()\n{\n    int index;\n\n    if (combatai_load_messages() == -1) {\n        return -1;\n    }\n\n    num_caps = 0;\n\n    Config config;\n    if (!config_init(&config)) {\n        return -1;\n    }\n\n    if (!config_load(&config, \"data\\\\ai.txt\", true)) {\n        return -1;\n    }\n\n    cap = (AiPacket*)mem_malloc(sizeof(*cap) * config.size);\n    if (cap == NULL) {\n        goto err;\n    }\n\n    for (index = 0; index < config.size; index++) {\n        cai_init_cap(&(cap[index]));\n    }\n\n    for (index = 0; index < config.size; index++) {\n        assoc_pair* sectionEntry = &(config.list[index]);\n        AiPacket* ai = &(cap[index]);\n        char* stringValue;\n\n        ai->name = mem_strdup(sectionEntry->name);\n\n        if (!config_get_value(&config, sectionEntry->name, \"packet_num\", &(ai->packet_num))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"max_dist\", &(ai->max_dist))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"min_to_hit\", &(ai->min_to_hit))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"min_hp\", &(ai->min_hp))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"aggression\", &(ai->aggression))) goto err;\n\n        if (config_get_string(&config, sectionEntry->name, \"hurt_too_much\", &stringValue)) {\n            parse_hurt_str(stringValue, &(ai->hurt_too_much));\n        }\n\n        if (!config_get_value(&config, sectionEntry->name, \"secondary_freq\", &(ai->secondary_freq))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"called_freq\", &(ai->called_freq))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"font\", &(ai->font))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"color\", &(ai->color))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"outline_color\", &(ai->outline_color))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"chance\", &(ai->chance))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"run_start\", &(ai->run.start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"run_end\", &(ai->run.end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"move_start\", &(ai->move.start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"move_end\", &(ai->move.end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"attack_start\", &(ai->attack.start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"attack_end\", &(ai->attack.end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"miss_start\", &(ai->miss.start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"miss_end\", &(ai->miss.end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_head_start\", &(ai->hit[HIT_LOCATION_HEAD].start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_head_end\", &(ai->hit[HIT_LOCATION_HEAD].end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_left_arm_start\", &(ai->hit[HIT_LOCATION_LEFT_ARM].start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_left_arm_end\", &(ai->hit[HIT_LOCATION_LEFT_ARM].end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_right_arm_start\", &(ai->hit[HIT_LOCATION_RIGHT_ARM].start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_right_arm_end\", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_torso_start\", &(ai->hit[HIT_LOCATION_TORSO].start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_torso_end\", &(ai->hit[HIT_LOCATION_TORSO].end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_right_leg_start\", &(ai->hit[HIT_LOCATION_RIGHT_LEG].start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_right_leg_end\", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_left_leg_start\", &(ai->hit[HIT_LOCATION_LEFT_LEG].start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_left_leg_end\", &(ai->hit[HIT_LOCATION_LEFT_LEG].end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_eyes_start\", &(ai->hit[HIT_LOCATION_EYES].start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_eyes_end\", &(ai->hit[HIT_LOCATION_EYES].end))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_groin_start\", &(ai->hit[HIT_LOCATION_GROIN].start))) goto err;\n        if (!config_get_value(&config, sectionEntry->name, \"hit_groin_end\", &(ai->hit[HIT_LOCATION_GROIN].end))) goto err;\n\n        ai->hit[HIT_LOCATION_GROIN].end++;\n\n        if (config_get_string(&config, sectionEntry->name, \"area_attack_mode\", &stringValue)) {\n            cai_match_str_to_list(stringValue, area_attack_mode_strs, AREA_ATTACK_MODE_COUNT, &(ai->area_attack_mode));\n        } else {\n            ai->run_away_mode = -1;\n        }\n\n        if (config_get_string(&config, sectionEntry->name, \"run_away_mode\", &stringValue)) {\n            cai_match_str_to_list(stringValue, run_away_mode_strs, RUN_AWAY_MODE_COUNT, &(ai->run_away_mode));\n\n            if (ai->run_away_mode >= 0) {\n                ai->run_away_mode--;\n            }\n        }\n\n        if (config_get_string(&config, sectionEntry->name, \"best_weapon\", &stringValue)) {\n            cai_match_str_to_list(stringValue, weapon_pref_strs, BEST_WEAPON_COUNT, &(ai->best_weapon));\n        }\n\n        if (config_get_string(&config, sectionEntry->name, \"distance\", &stringValue)) {\n            cai_match_str_to_list(stringValue, distance_pref_strs, DISTANCE_COUNT, &(ai->distance));\n        }\n\n        if (config_get_string(&config, sectionEntry->name, \"attack_who\", &stringValue)) {\n            cai_match_str_to_list(stringValue, attack_who_mode_strs, ATTACK_WHO_COUNT, &(ai->attack_who));\n        }\n\n        if (config_get_string(&config, sectionEntry->name, \"chem_use\", &stringValue)) {\n            cai_match_str_to_list(stringValue, chem_use_mode_strs, CHEM_USE_COUNT, &(ai->chem_use));\n        }\n\n        config_get_values(&config, sectionEntry->name, \"chem_primary_desire\", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT);\n\n        if (config_get_string(&config, sectionEntry->name, \"disposition\", &stringValue)) {\n            cai_match_str_to_list(stringValue, disposition_strs, DISPOSITION_COUNT, &(ai->disposition));\n            ai->disposition--;\n        }\n\n        if (config_get_string(&config, sectionEntry->name, \"body_type\", &stringValue)) {\n            ai->body_type = mem_strdup(stringValue);\n        } else {\n            ai->body_type = NULL;\n        }\n\n        if (config_get_string(&config, sectionEntry->name, \"general_type\", &stringValue)) {\n            ai->general_type = mem_strdup(stringValue);\n        } else {\n            ai->general_type = NULL;\n        }\n    }\n\n    if (index < config.size) {\n        goto err;\n    }\n\n    num_caps = config.size;\n\n    config_exit(&config);\n\n    combatai_is_initialized = true;\n\n    return 0;\n\nerr:\n\n    if (cap != NULL) {\n        for (index = 0; index < config.size; index++) {\n            AiPacket* ai = &(cap[index]);\n            if (ai->name != NULL) {\n                mem_free(ai->name);\n            }\n\n            // FIXME: leaking ai->body_type and ai->general_type, does not matter\n            // because it halts further processing\n        }\n        mem_free(cap);\n    }\n\n    debug_printf(\"Error processing ai.txt\");\n\n    config_exit(&config);\n\n    return -1;\n}\n\n// 0x4279F8\nvoid combat_ai_reset()\n{\n}\n\n// 0x4279FC\nint combat_ai_exit()\n{\n    for (int index = 0; index < num_caps; index++) {\n        AiPacket* ai = &(cap[index]);\n\n        if (ai->name != NULL) {\n            mem_free(ai->name);\n            ai->name = NULL;\n        }\n\n        if (ai->general_type != NULL) {\n            mem_free(ai->general_type);\n            ai->general_type = NULL;\n        }\n\n        if (ai->body_type != NULL) {\n            mem_free(ai->body_type);\n            ai->body_type = NULL;\n        }\n    }\n\n    mem_free(cap);\n    num_caps = 0;\n\n    combatai_is_initialized = false;\n\n    // NOTE: Uninline.\n    if (combatai_unload_messages() != 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x427AD8\nint combat_ai_load(File* stream)\n{\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        int pid = partyMemberPidList[index];\n        if (pid != -1 && PID_TYPE(pid) == OBJ_TYPE_CRITTER) {\n            Proto* proto;\n            if (proto_ptr(pid, &proto) == -1) {\n                return -1;\n            }\n\n            AiPacket* ai = ai_cap_from_packet(proto->critter.aiPacket);\n            if (ai->disposition == 0) {\n                cai_cap_load(stream, ai);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x427B50\nint combat_ai_save(File* stream)\n{\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        int pid = partyMemberPidList[index];\n        if (pid != -1 && PID_TYPE(pid) == OBJ_TYPE_CRITTER) {\n            Proto* proto;\n            if (proto_ptr(pid, &proto) == -1) {\n                return -1;\n            }\n\n            AiPacket* ai = ai_cap_from_packet(proto->critter.aiPacket);\n            if (ai->disposition == 0) {\n                cai_cap_save(stream, ai);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x427BC8\nstatic int cai_cap_load(File* stream, AiPacket* ai)\n{\n    if (db_freadInt(stream, &(ai->packet_num)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->max_dist)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->min_to_hit)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->min_hp)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->aggression)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->hurt_too_much)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->secondary_freq)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->called_freq)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->font)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->color)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->outline_color)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->chance)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->run.start)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->run.end)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->move.start)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->move.end)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->attack.start)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->attack.end)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->miss.start)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->miss.end)) == -1) return -1;\n\n    for (int index = 0; index < HIT_LOCATION_SPECIFIC_COUNT; index++) {\n        AiMessageRange* range = &(ai->hit[index]);\n        if (db_freadInt(stream, &(range->start)) == -1) return -1;\n        if (db_freadInt(stream, &(range->end)) == -1) return -1;\n    }\n\n    if (db_freadInt(stream, &(ai->area_attack_mode)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->best_weapon)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->distance)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->attack_who)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->chem_use)) == -1) return -1;\n    if (db_freadInt(stream, &(ai->run_away_mode)) == -1) return -1;\n\n    for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) {\n        if (db_freadInt(stream, &(ai->chem_primary_desire[index])) == -1) return -1;\n    }\n\n    return 0;\n}\n\n// 0x427E1C\nstatic int cai_cap_save(File* stream, AiPacket* ai)\n{\n    if (db_fwriteInt(stream, ai->packet_num) == -1) return -1;\n    if (db_fwriteInt(stream, ai->max_dist) == -1) return -1;\n    if (db_fwriteInt(stream, ai->min_to_hit) == -1) return -1;\n    if (db_fwriteInt(stream, ai->min_hp) == -1) return -1;\n    if (db_fwriteInt(stream, ai->aggression) == -1) return -1;\n    if (db_fwriteInt(stream, ai->hurt_too_much) == -1) return -1;\n    if (db_fwriteInt(stream, ai->secondary_freq) == -1) return -1;\n    if (db_fwriteInt(stream, ai->called_freq) == -1) return -1;\n    if (db_fwriteInt(stream, ai->font) == -1) return -1;\n    if (db_fwriteInt(stream, ai->color) == -1) return -1;\n    if (db_fwriteInt(stream, ai->outline_color) == -1) return -1;\n    if (db_fwriteInt(stream, ai->chance) == -1) return -1;\n    if (db_fwriteInt(stream, ai->run.start) == -1) return -1;\n    if (db_fwriteInt(stream, ai->run.end) == -1) return -1;\n    if (db_fwriteInt(stream, ai->move.start) == -1) return -1;\n    if (db_fwriteInt(stream, ai->move.end) == -1) return -1;\n    if (db_fwriteInt(stream, ai->attack.start) == -1) return -1;\n    if (db_fwriteInt(stream, ai->attack.end) == -1) return -1;\n    if (db_fwriteInt(stream, ai->miss.start) == -1) return -1;\n    if (db_fwriteInt(stream, ai->miss.end) == -1) return -1;\n\n    for (int index = 0; index < HIT_LOCATION_SPECIFIC_COUNT; index++) {\n        AiMessageRange* range = &(ai->hit[index]);\n        if (db_fwriteInt(stream, range->start) == -1) return -1;\n        if (db_fwriteInt(stream, range->end) == -1) return -1;\n    }\n\n    if (db_fwriteInt(stream, ai->area_attack_mode) == -1) return -1;\n    if (db_fwriteInt(stream, ai->best_weapon) == -1) return -1;\n    if (db_fwriteInt(stream, ai->distance) == -1) return -1;\n    if (db_fwriteInt(stream, ai->attack_who) == -1) return -1;\n    if (db_fwriteInt(stream, ai->chem_use) == -1) return -1;\n    if (db_fwriteInt(stream, ai->run_away_mode) == -1) return -1;\n\n    for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) {\n        // TODO: Check, probably writes chem_primary_desire[0] three times,\n        // might be a bug in original source code.\n        if (db_fwriteInt(stream, ai->chem_primary_desire[index]) == -1) return -1;\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x428058\nint combat_ai_num()\n{\n    return num_caps;\n}\n\n// 0x428060\nchar* combat_ai_name(int packetNum)\n{\n    int index;\n\n    if (packetNum < 0 || packetNum >= num_caps) {\n        return NULL;\n    }\n\n    for (index = 0; index < num_caps; index++) {\n        if (cap[index].packet_num == packetNum) {\n            return cap[index].name;\n        }\n    }\n\n    return NULL;\n}\n\n// Get ai from object\n//\n// 0x4280B4\nstatic AiPacket* ai_cap(Object* obj)\n{\n    // NOTE: Uninline.\n    AiPacket* ai = ai_cap_from_packet(obj->data.critter.combat.aiPacket);\n    return ai;\n}\n\n// get ai packet by num\n//\n// 0x42811C\nstatic AiPacket* ai_cap_from_packet(int aiPacketId)\n{\n    for (int index = 0; index < num_caps; index++) {\n        AiPacket* ai = &(cap[index]);\n        if (aiPacketId == ai->packet_num) {\n            return ai;\n        }\n    }\n\n    debug_printf(\"Missing AI Packet\\n\");\n\n    return cap;\n}\n\n// 0x428184\nint ai_get_burst_value(Object* obj)\n{\n    AiPacket* ai = ai_cap(obj);\n    return ai->area_attack_mode;\n}\n\n// 0x428190\nint ai_get_run_away_value(Object* obj)\n{\n    AiPacket* ai;\n    int v3;\n    int v5;\n    int v6;\n    int i;\n\n    ai = ai_cap(obj);\n    v3 = -1;\n\n    if (ai->run_away_mode != -1) {\n        return ai->run_away_mode;\n    }\n\n    v5 = 100 * ai->min_hp;\n    v6 = v5 / critterGetStat(obj, STAT_MAXIMUM_HIT_POINTS);\n\n    for (i = 0; i < 6; i++) {\n        if (v6 >= runModeValues[i]) {\n            v3 = i;\n        }\n    }\n\n    return v3;\n}\n\n// 0x4281FC\nint ai_get_weapon_pref_value(Object* obj)\n{\n    AiPacket* ai = ai_cap(obj);\n    return ai->best_weapon;\n}\n\n// 0x428208\nint ai_get_distance_pref_value(Object* obj)\n{\n    AiPacket* ai = ai_cap(obj);\n    return ai->distance;\n}\n\n// 0x428214\nint ai_get_attack_who_value(Object* obj)\n{\n    AiPacket* ai = ai_cap(obj);\n    return ai->attack_who;\n}\n\n// 0x428220\nint ai_get_chem_use_value(Object* obj)\n{\n    AiPacket* ai = ai_cap(obj);\n    return ai->chem_use;\n}\n\n// 0x42822C\nint ai_set_burst_value(Object* critter, int areaAttackMode)\n{\n    if (areaAttackMode >= AREA_ATTACK_MODE_COUNT) {\n        return -1;\n    }\n\n    AiPacket* ai = ai_cap(critter);\n    ai->area_attack_mode = areaAttackMode;\n    return 0;\n}\n\n// 0x428248\nint ai_set_run_away_value(Object* obj, int runAwayMode)\n{\n    if (runAwayMode >= 6) {\n        return -1;\n    }\n\n    AiPacket* ai = ai_cap(obj);\n    ai->run_away_mode = runAwayMode;\n\n    int maximumHp = critterGetStat(obj, STAT_MAXIMUM_HIT_POINTS);\n    ai->min_hp = maximumHp - maximumHp * runModeValues[runAwayMode] / 100;\n\n    int currentHp = critterGetStat(obj, STAT_CURRENT_HIT_POINTS);\n    const char* name = critter_name(obj);\n\n    debug_printf(\"\\n%s minHp = %d; curHp = %d\", name, ai->min_hp, currentHp);\n\n    return 0;\n}\n\n// 0x4282D0\nint ai_set_weapon_pref_value(Object* critter, int bestWeapon)\n{\n    if (bestWeapon >= BEST_WEAPON_COUNT) {\n        return -1;\n    }\n\n    AiPacket* ai = ai_cap(critter);\n    ai->best_weapon = bestWeapon;\n    return 0;\n}\n\n// 0x4282EC\nint ai_set_distance_pref_value(Object* critter, int distance)\n{\n    if (distance >= DISTANCE_COUNT) {\n        return -1;\n    }\n\n    AiPacket* ai = ai_cap(critter);\n    ai->distance = distance;\n    return 0;\n}\n\n// 0x428308\nint ai_set_attack_who_value(Object* critter, int attackWho)\n{\n    if (attackWho >= ATTACK_WHO_COUNT) {\n        return -1;\n    }\n\n    AiPacket* ai = ai_cap(critter);\n    ai->attack_who = attackWho;\n    return 0;\n}\n\n// 0x428324\nint ai_set_chem_use_value(Object* critter, int chemUse)\n{\n    if (chemUse >= CHEM_USE_COUNT) {\n        return -1;\n    }\n\n    AiPacket* ai = ai_cap(critter);\n    ai->chem_use = chemUse;\n    return 0;\n}\n\n// 0x428340\nint ai_get_disposition(Object* obj)\n{\n    if (obj == NULL) {\n        return 0;\n    }\n\n    AiPacket* ai = ai_cap(obj);\n    return ai->disposition;\n}\n\n// 0x428354\nint ai_set_disposition(Object* obj, int disposition)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (disposition == -1 || disposition >= 5) {\n        return -1;\n    }\n\n    AiPacket* ai = ai_cap(obj);\n    obj->data.critter.combat.aiPacket = ai->packet_num - (disposition - ai->disposition);\n\n    return 0;\n}\n\n// 0x428398\nstatic int ai_magic_hands(Object* critter, Object* item, int num)\n{\n    register_begin(ANIMATION_REQUEST_RESERVED);\n\n    register_object_animate(critter, ANIM_MAGIC_HANDS_MIDDLE, 0);\n\n    if (register_end() == 0) {\n        if (isInCombat()) {\n            combat_turn_run();\n        }\n    }\n\n    if (num != -1) {\n        MessageListItem messageListItem;\n        messageListItem.num = num;\n        if (message_search(&misc_message_file, &messageListItem)) {\n            const char* critterName = object_name(critter);\n\n            char text[200];\n            if (item != NULL) {\n                const char* itemName = object_name(item);\n                sprintf(text, \"%s %s %s.\", critterName, messageListItem.text, itemName);\n            } else {\n                sprintf(text, \"%s %s.\", critterName, messageListItem.text);\n            }\n\n            display_print(text);\n        }\n    }\n\n    return 0;\n}\n\n// ai using drugs\n// 0x428480\nstatic int ai_check_drugs(Object* critter)\n{\n    if (critter_body_type(critter) != BODY_TYPE_BIPED) {\n        return 0;\n    }\n\n    int v25 = 0;\n    int v28 = 0;\n    int v29 = 0;\n    Object* v3 = combatAIInfoGetLastItem(critter);\n    if (v3 == NULL) {\n        AiPacket* ai = ai_cap(critter);\n        if (ai == NULL) {\n            return 0;\n        }\n\n        int v2 = 50;\n        int v26 = 0;\n        switch (ai->chem_use + 1) {\n        case 1:\n            return 0;\n        case 2:\n            v2 = 60;\n            break;\n        case 3:\n            v2 = 30;\n            break;\n        case 4:\n            if ((combatNumTurns % 3) == 0) {\n                v26 = 25;\n            }\n            v2 = 50;\n            break;\n        case 5:\n            if ((combatNumTurns % 3) == 0) {\n                v26 = 75;\n            }\n            v2 = 50;\n            break;\n        case 6:\n            v26 = 100;\n            break;\n        }\n\n        int v27 = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS) * v2 / 100;\n        int token = -1;\n        while (true) {\n            if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) >= v27 || critter->data.critter.combat.ap < 2) {\n                break;\n            }\n\n            Object* drug = inven_find_type(critter, ITEM_TYPE_DRUG, &token);\n            if (drug == NULL) {\n                v25 = true;\n                break;\n            }\n\n            int drugPid = drug->pid;\n            if ((drugPid == PROTO_ID_STIMPACK || drugPid == PROTO_ID_SUPER_STIMPACK || drugPid == PROTO_ID_HEALING_POWDER)\n                && item_remove_mult(critter, drug, 1) == 0) {\n                if (item_d_take_drug(critter, drug) == -1) {\n                    item_add_force(critter, drug, 1);\n                } else {\n                    ai_magic_hands(critter, drug, 5000);\n                    obj_connect(drug, critter->tile, critter->elevation, NULL);\n                    obj_destroy(drug);\n                    v28 = 1;\n                }\n\n                if (critter->data.critter.combat.ap < 2) {\n                    critter->data.critter.combat.ap = 0;\n                } else {\n                    critter->data.critter.combat.ap -= 2;\n                }\n\n                token = -1;\n            }\n        }\n\n        if (!v28 && v26 > 0 && roll_random(0, 100) < v26) {\n            while (critter->data.critter.combat.ap >= 2) {\n                Object* drug = inven_find_type(critter, ITEM_TYPE_DRUG, &token);\n                if (drug == NULL) {\n                    v25 = 1;\n                    break;\n                }\n\n                int drugPid = drug->pid;\n                int index;\n                for (index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) {\n                    // TODO: Find out why it checks for inequality at 0x4286B1.\n                    if (ai->chem_primary_desire[index] != drugPid) {\n                        break;\n                    }\n                }\n\n                if (index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT) {\n                    if (drugPid != PROTO_ID_STIMPACK && drugPid != PROTO_ID_SUPER_STIMPACK && drugPid != 273\n                        && item_remove_mult(critter, drug, 1) == 0) {\n                        if (item_d_take_drug(critter, drug) == -1) {\n                            item_add_force(critter, drug, 1);\n                        } else {\n                            ai_magic_hands(critter, drug, 5000);\n                            obj_connect(drug, critter->tile, critter->elevation, NULL);\n                            obj_destroy(drug);\n                            v28 = 1;\n                            v29 += 1;\n                        }\n\n                        if (critter->data.critter.combat.ap < 2) {\n                            critter->data.critter.combat.ap = 0;\n                        } else {\n                            critter->data.critter.combat.ap -= 2;\n                        }\n\n                        if (ai->chem_use == CHEM_USE_SOMETIMES || (ai->chem_use == CHEM_USE_ANYTIME && v29 >= 2)) {\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    if (v3 != NULL || (!v28 && v25 == 1)) {\n        do {\n            if (v3 == NULL) {\n                v3 = ai_search_environ(critter, ITEM_TYPE_DRUG);\n            }\n\n            if (v3 != NULL) {\n                v3 = ai_retrieve_object(critter, v3);\n            } else {\n                Object* v22 = ai_search_environ(critter, ITEM_TYPE_MISC);\n                if (v22 != NULL) {\n                    v3 = ai_retrieve_object(critter, v22);\n                }\n            }\n\n            if (v3 != NULL && item_remove_mult(critter, v3, 1) == 0) {\n                if (item_d_take_drug(critter, v3) == -1) {\n                    item_add_force(critter, v3, 1);\n                } else {\n                    ai_magic_hands(critter, v3, 5000);\n                    obj_connect(v3, critter->tile, critter->elevation, NULL);\n                    obj_destroy(v3);\n                    v3 = NULL;\n                }\n\n                if (critter->data.critter.combat.ap < 2) {\n                    critter->data.critter.combat.ap = 0;\n                } else {\n                    critter->data.critter.combat.ap -= 2;\n                }\n            }\n\n        } while (v3 != NULL && critter->data.critter.combat.ap >= 2);\n    }\n\n    return 0;\n}\n\n// 0x428868\nstatic void ai_run_away(Object* a1, Object* a2)\n{\n    if (a2 == NULL) {\n        a2 = obj_dude;\n    }\n\n    CritterCombatData* combatData = &(a1->data.critter.combat);\n\n    AiPacket* ai = ai_cap(a1);\n    int distance = obj_dist(a1, a2);\n    if (distance < ai->max_dist) {\n        combatData->maneuver |= CRITTER_MANUEVER_FLEEING;\n\n        int rotation = tile_dir(a2->tile, a1->tile);\n\n        int destination;\n        int actionPoints = combatData->ap;\n        for (; actionPoints > 0; actionPoints -= 1) {\n            destination = tile_num_in_direction(a1->tile, rotation, actionPoints);\n            if (make_path(a1, a1->tile, destination, NULL, 1) > 0) {\n                break;\n            }\n\n            destination = tile_num_in_direction(a1->tile, (rotation + 1) % ROTATION_COUNT, actionPoints);\n            if (make_path(a1, a1->tile, destination, NULL, 1) > 0) {\n                break;\n            }\n\n            destination = tile_num_in_direction(a1->tile, (rotation + 5) % ROTATION_COUNT, actionPoints);\n            if (make_path(a1, a1->tile, destination, NULL, 1) > 0) {\n                break;\n            }\n        }\n\n        if (actionPoints > 0) {\n            register_begin(ANIMATION_REQUEST_RESERVED);\n            combatai_msg(a1, NULL, AI_MESSAGE_TYPE_RUN, 0);\n            register_object_run_to_tile(a1, destination, a1->elevation, combatData->ap, 0);\n            if (register_end() == 0) {\n                combat_turn_run();\n            }\n        }\n    } else {\n        combatData->maneuver |= CRITTER_MANEUVER_STOP_ATTACKING;\n    }\n}\n\n// 0x42899C\nstatic int ai_move_away(Object* a1, Object* a2, int a3)\n{\n    if (ai_cap(a1)->distance == DISTANCE_STAY) {\n        return -1;\n    }\n\n    if (obj_dist(a1, a2) <= a3) {\n        int actionPoints = a1->data.critter.combat.ap;\n        if (a3 < actionPoints) {\n            actionPoints = a3;\n        }\n\n        int rotation = tile_dir(a2->tile, a1->tile);\n\n        int destination;\n        int actionPointsLeft = actionPoints;\n        for (; actionPointsLeft > 0; actionPointsLeft -= 1) {\n            destination = tile_num_in_direction(a1->tile, rotation, actionPointsLeft);\n            if (make_path(a1, a1->tile, destination, NULL, 1) > 0) {\n                break;\n            }\n\n            destination = tile_num_in_direction(a1->tile, (rotation + 1) % ROTATION_COUNT, actionPointsLeft);\n            if (make_path(a1, a1->tile, destination, NULL, 1) > 0) {\n                break;\n            }\n\n            destination = tile_num_in_direction(a1->tile, (rotation + 5) % ROTATION_COUNT, actionPointsLeft);\n            if (make_path(a1, a1->tile, destination, NULL, 1) > 0) {\n                break;\n            }\n        }\n\n        if (actionPoints > 0) {\n            register_begin(ANIMATION_REQUEST_RESERVED);\n            register_object_move_to_tile(a1, destination, a1->elevation, actionPoints, 0);\n            if (register_end() == 0) {\n                combat_turn_run();\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x428AC4\nstatic bool ai_find_friend(Object* a1, int a2, int a3)\n{\n    Object* v1 = ai_find_nearest_team(a1, a1, 1);\n    if (v1 == NULL) {\n        return false;\n    }\n\n    int distance = obj_dist(a1, v1);\n    if (distance > a2) {\n        return false;\n    }\n\n    if (a3 > distance) {\n        int v2 = obj_dist(a1, v1) - a3;\n        ai_move_steps_closer(a1, v1, v2, 0);\n    }\n\n    return true;\n}\n\n// Compare objects by distance to origin.\n//\n// 0x428B1C\nstatic int compare_nearer(const void* a1, const void* a2)\n{\n    Object* v1 = *(Object**)a1;\n    Object* v2 = *(Object**)a2;\n\n    if (v1 == NULL) {\n        if (v2 == NULL) {\n            return 0;\n        }\n        return 1;\n    } else {\n        if (v2 == NULL) {\n            return -1;\n        }\n    }\n\n    int distance1 = obj_dist(v1, combat_obj);\n    int distance2 = obj_dist(v2, combat_obj);\n\n    if (distance1 < distance2) {\n        return -1;\n    } else if (distance1 > distance2) {\n        return 1;\n    } else {\n        return 0;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x428B74\nstatic void ai_sort_list_distance(Object** critterList, int length, Object* origin)\n{\n    combat_obj = origin;\n    qsort(critterList, length, sizeof(*critterList), compare_nearer);\n}\n\n// qsort compare function - melee then ranged.\n//\n// 0x428B8C\nint compare_strength(const void* p1, const void* p2)\n{\n    Object* a1 = *(Object**)p1;\n    Object* a2 = *(Object**)p2;\n\n    if (a1 == NULL) {\n        if (a2 == NULL) {\n            return 0;\n        }\n\n        return 1;\n    }\n\n    if (a2 == NULL) {\n        return -1;\n    }\n\n    int v3 = combatai_rating(a1);\n    int v5 = combatai_rating(a2);\n\n    if (v3 < v5) {\n        return -1;\n    }\n\n    if (v3 > v5) {\n        return 1;\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x428BD0\nstatic void ai_sort_list_strength(Object** critterList, int length)\n{\n    qsort(critterList, length, sizeof(*critterList), compare_strength);\n}\n\n// qsort compare unction - ranged then melee\n//\n// 0x428BE4\nint compare_weakness(const void* p1, const void* p2)\n{\n    Object* a1 = *(Object**)p1;\n    Object* a2 = *(Object**)p2;\n\n    if (a1 == NULL) {\n        if (a2 == NULL) {\n            return 0;\n        }\n\n        return 1;\n    }\n\n    if (a2 == NULL) {\n        return -1;\n    }\n\n    int v3 = combatai_rating(a1);\n    int v5 = combatai_rating(a2);\n\n    if (v3 < v5) {\n        return 1;\n    }\n\n    if (v3 > v5) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x428C28\nstatic void ai_sort_list_weakness(Object** critterList, int length)\n{\n    qsort(critterList, length, sizeof(*critterList), compare_weakness);\n}\n\n// 0x428C3C\nstatic Object* ai_find_nearest_team(Object* a1, Object* a2, int a3)\n{\n    int i;\n    Object* obj;\n\n    if (a2 == NULL) {\n        return NULL;\n    }\n\n    if (curr_crit_num == 0) {\n        return NULL;\n    }\n\n    // NOTE: Uninline.\n    ai_sort_list_distance(curr_crit_list, curr_crit_num, a1);\n\n    for (i = 0; i < curr_crit_num; i++) {\n        obj = curr_crit_list[i];\n        if (a1 != obj && !(obj->data.critter.combat.results & 0x80) && (((a3 & 0x02) && a2->data.critter.combat.team != obj->data.critter.combat.team) || ((a3 & 0x01) && a2->data.critter.combat.team == obj->data.critter.combat.team))) {\n            return obj;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x428CF4\nstatic Object* ai_find_nearest_team_in_combat(Object* a1, Object* a2, int a3)\n{\n    if (a2 == NULL) {\n        return NULL;\n    }\n\n    if (curr_crit_num == 0) {\n        return NULL;\n    }\n\n    int team = a2->data.critter.combat.team;\n\n    // NOTE: Uninline.\n    ai_sort_list_distance(curr_crit_list, curr_crit_num, a1);\n\n    for (int index = 0; index < curr_crit_num; index++) {\n        Object* obj = curr_crit_list[index];\n        if (obj != a1\n            && (obj->data.critter.combat.results & DAM_DEAD) == 0\n            && (((a3 & 0x02) != 0 && team != obj->data.critter.combat.team)\n                || ((a3 & 0x01) != 0 && team == obj->data.critter.combat.team))) {\n            if (obj->data.critter.combat.whoHitMe != NULL) {\n                return obj;\n            }\n        }\n    }\n\n    return NULL;\n}\n\n// 0x428DB0\nstatic int ai_find_attackers(Object* a1, Object** a2, Object** a3, Object** a4)\n{\n    if (a2 != NULL) {\n        *a2 = NULL;\n    }\n\n    if (a3 != NULL) {\n        *a3 = NULL;\n    }\n\n    if (*a4 != NULL) {\n        *a4 = NULL;\n    }\n\n    if (curr_crit_num == 0) {\n        return 0;\n    }\n\n    // NOTE: Uninline.\n    ai_sort_list_distance(curr_crit_list, curr_crit_num, a1);\n\n    int foundTargetCount = 0;\n    int team = a1->data.critter.combat.team;\n\n    for (int index = 0; foundTargetCount < 3 && index < curr_crit_num; index++) {\n        Object* candidate = curr_crit_list[index];\n        if (candidate != a1) {\n            if (a2 != NULL && *a2 == NULL) {\n                if ((candidate->data.critter.combat.results & DAM_DEAD) == 0\n                    && candidate->data.critter.combat.whoHitMe == a1) {\n                    foundTargetCount++;\n                    *a2 = candidate;\n                }\n            }\n\n            if (a3 != NULL && *a3 == NULL) {\n                if (team == candidate->data.critter.combat.team) {\n                    Object* whoHitCandidate = candidate->data.critter.combat.whoHitMe;\n                    if (whoHitCandidate != NULL\n                        && whoHitCandidate != a1\n                        && team != whoHitCandidate->data.critter.combat.team\n                        && (whoHitCandidate->data.critter.combat.results & DAM_DEAD) == 0) {\n                        foundTargetCount++;\n                        *a3 = whoHitCandidate;\n                    }\n                }\n            }\n\n            if (a4 != NULL && *a4 == NULL) {\n                if (candidate->data.critter.combat.team != team\n                    && (candidate->data.critter.combat.results & DAM_DEAD) == 0) {\n                    Object* whoHitCandidate = candidate->data.critter.combat.whoHitMe;\n                    if (whoHitCandidate != NULL\n                        && whoHitCandidate->data.critter.combat.team == team) {\n                        foundTargetCount++;\n                        *a4 = candidate;\n                    }\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\n// ai_danger_source\n// 0x428F4C\nObject* ai_danger_source(Object* a1)\n{\n    if (a1 == NULL) {\n        return NULL;\n    }\n\n    bool v2 = false;\n    int attackWho;\n\n    Object* targets[4];\n    targets[0] = NULL;\n\n    if (isPartyMember(a1)) {\n        int disposition = a1 != NULL ? ai_cap(a1)->disposition : 0;\n\n        switch (disposition + 1) {\n        case 1:\n        case 2:\n        case 3:\n        case 4:\n            v2 = true;\n            break;\n        case 0:\n        case 5:\n            v2 = false;\n            break;\n        }\n\n        if (v2 && ai_cap(a1)->distance == 1) {\n            v2 = false;\n        }\n\n        attackWho = ai_cap(a1)->attack_who;\n        switch (attackWho) {\n        case ATTACK_WHO_WHOMEVER_ATTACKING_ME: {\n            Object* candidate = combatAIInfoGetLastTarget(obj_dude);\n            if (candidate == NULL || a1->data.critter.combat.team == candidate->data.critter.combat.team) {\n                break;\n            }\n\n            if (make_path_func(a1, a1->tile, obj_dude->data.critter.combat.whoHitMe->tile, NULL, 0, obj_blocking_at) == 0\n                && combat_check_bad_shot(a1, candidate, HIT_MODE_RIGHT_WEAPON_PRIMARY, false) != COMBAT_BAD_SHOT_OK) {\n                debug_printf(\"\\nai_danger_source: %s couldn't attack at target!  Picking alternate!\", critter_name(a1));\n                break;\n            }\n\n            if (v2 && critter_is_fleeing(a1)) {\n                break;\n            }\n\n            return candidate;\n        }\n        case ATTACK_WHO_STRONGEST:\n        case ATTACK_WHO_WEAKEST:\n        case ATTACK_WHO_CLOSEST:\n            a1->data.critter.combat.whoHitMe = NULL;\n            break;\n        default:\n            break;\n        }\n    } else {\n        attackWho = -1;\n    }\n\n    Object* whoHitMe = a1->data.critter.combat.whoHitMe;\n    if (whoHitMe == NULL || a1 == whoHitMe) {\n        targets[0] = NULL;\n    } else {\n        if ((whoHitMe->data.critter.combat.results & DAM_DEAD) == 0) {\n            if (attackWho == ATTACK_WHO_WHOMEVER || attackWho == -1) {\n                return whoHitMe;\n            }\n        } else {\n            if (whoHitMe->data.critter.combat.team != a1->data.critter.combat.team) {\n                targets[0] = ai_find_nearest_team(a1, whoHitMe, 1);\n            } else {\n                targets[0] = NULL;\n            }\n        }\n    }\n\n    ai_find_attackers(a1, &(targets[1]), &(targets[2]), &(targets[3]));\n\n    if (v2) {\n        for (int index = 0; index < 4; index++) {\n            if (targets[index] != NULL && critter_is_fleeing(targets[index])) {\n                targets[index] = NULL;\n            }\n        }\n    }\n\n    switch (attackWho) {\n    case ATTACK_WHO_STRONGEST:\n        // NOTE: Uninline.\n        ai_sort_list_strength(targets, 4);\n        break;\n    case ATTACK_WHO_WEAKEST:\n        // NOTE: Uninline.\n        ai_sort_list_weakness(targets, 4);\n        break;\n    default:\n        // NOTE: Uninline.\n        ai_sort_list_distance(targets, 4, a1);\n        break;\n    }\n\n    for (int index = 0; index < 4; index++) {\n        Object* candidate = targets[index];\n        if (candidate != NULL && is_within_perception(a1, candidate)) {\n            if (make_path_func(a1, a1->tile, candidate->tile, NULL, 0, obj_blocking_at) != 0\n                || combat_check_bad_shot(a1, candidate, HIT_MODE_RIGHT_WEAPON_PRIMARY, false) == COMBAT_BAD_SHOT_OK) {\n                return candidate;\n            }\n            debug_printf(\"\\nai_danger_source: I couldn't get at my target!  Picking alternate!\");\n        }\n    }\n\n    return NULL;\n}\n\n// 0x4291C4\nint caiSetupTeamCombat(Object* a1, Object* a2)\n{\n    Object* obj;\n\n    obj = obj_find_first_at(a1->elevation);\n    while (obj != NULL) {\n        if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER && obj != obj_dude) {\n            obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01;\n        }\n        obj = obj_find_next_at();\n    }\n\n    attackerTeamObj = a1;\n    targetTeamObj = a2;\n\n    return 0;\n}\n\n// 0x_429210\nint caiTeamCombatInit(Object** a1, int a2)\n{\n    int v9;\n    int v10;\n    int i;\n    Object* v8;\n\n    if (a1 == NULL) {\n        return -1;\n    }\n\n    if (a2 == 0) {\n        return 0;\n    }\n\n    if (attackerTeamObj == NULL) {\n        return 0;\n    }\n\n    v9 = attackerTeamObj->data.critter.combat.team;\n    v10 = targetTeamObj->data.critter.combat.team;\n\n    for (i = 0; i < a2; i++) {\n        if (a1[i]->data.critter.combat.team == v9) {\n            v8 = targetTeamObj;\n        } else if (a1[i]->data.critter.combat.team == v10) {\n            v8 = attackerTeamObj;\n        } else {\n            continue;\n        }\n\n        a1[i]->data.critter.combat.whoHitMe = ai_find_nearest_team(a1[i], v8, 1);\n    }\n\n    attackerTeamObj = NULL;\n    targetTeamObj = NULL;\n\n    return 0;\n}\n\n// 0x4292C0\nvoid caiTeamCombatExit()\n{\n    targetTeamObj = 0;\n    attackerTeamObj = 0;\n}\n\n// 0x4292D4\nstatic int ai_have_ammo(Object* critter_obj, Object* weapon_obj, Object** out_ammo_obj)\n{\n    int v9;\n    Object* ammo_obj;\n\n    if (out_ammo_obj) {\n        *out_ammo_obj = NULL;\n    }\n\n    if (weapon_obj->pid == PROTO_ID_SOLAR_SCORCHER) {\n        return light_get_ambient() > 62259;\n    }\n\n    v9 = -1;\n\n    while (1) {\n        ammo_obj = inven_find_type(critter_obj, 4, &v9);\n        if (ammo_obj == NULL) {\n            break;\n        }\n\n        if (item_w_can_reload(weapon_obj, ammo_obj)) {\n            if (out_ammo_obj) {\n                *out_ammo_obj = ammo_obj;\n            }\n            return 1;\n        }\n\n        if (item_w_anim_code(weapon_obj)) {\n            if (item_w_range(critter_obj, 2) < 3) {\n                inven_unwield(critter_obj, 1);\n            }\n        } else {\n            inven_unwield(critter_obj, 1);\n        }\n    }\n\n    return 0;\n}\n\n// 0x42938C\nstatic bool caiHasWeapPrefType(AiPacket* ai, int attackType)\n{\n    int bestWeapon = ai->best_weapon + 1;\n\n    for (int index = 0; index < 5; index++) {\n        if (attackType == weapPrefOrderings[bestWeapon][index]) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x4293BC\nstatic Object* ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon2, Object* defender)\n{\n    if (attacker == NULL) {\n        return NULL;\n    }\n\n    AiPacket* ai = ai_cap(attacker);\n    if (ai->best_weapon == BEST_WEAPON_RANDOM) {\n        return roll_random(1, 100) <= 50 ? weapon1 : weapon2;\n    }\n    int minDamage;\n    int maxDamage;\n\n    int v24 = 0;\n    int v25 = 999;\n    int v26 = 999;\n    int avgDamage1 = 0;\n\n    Attack attack;\n    combat_ctd_init(&attack, attacker, defender, HIT_MODE_RIGHT_WEAPON_PRIMARY, HIT_LOCATION_TORSO);\n\n    int attackType1;\n    int distance;\n    int attackType2;\n    int avgDamage2 = 0;\n\n    int v23 = 0;\n\n    // NOTE: weaponClass1 and weaponClass2 both use ESI but they are not\n    // initialized. I'm not sure if this is right, but at least it doesn't\n    // crash.\n    attackType1 = -1;\n    attackType2 = -1;\n\n    if (weapon1 != NULL) {\n        attackType1 = item_w_subtype(weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY);\n        if (item_w_damage_min_max(weapon1, &minDamage, &maxDamage) == -1) {\n            return NULL;\n        }\n\n        avgDamage1 = (maxDamage - minDamage) / 2;\n        if (item_w_area_damage_radius(weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY) > 0 && defender != NULL) {\n            attack.weapon = weapon1;\n            compute_explosion_on_extras(&attack, 0, item_w_is_grenade(weapon1), 1);\n            avgDamage1 *= attack.extrasLength + 1;\n        }\n\n        // TODO: Probably an error, why it takes [weapon2], should likely use\n        // [weapon1].\n        if (item_w_perk(weapon2) != -1) {\n            avgDamage1 *= 5;\n        }\n\n        if (defender != NULL) {\n            if (combat_safety_invalidate_weapon(attacker, weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender, NULL)) {\n                v24 = 1;\n            }\n        }\n\n        if (item_is_hidden(weapon1)) {\n            return weapon1;\n        }\n    } else {\n        distance = obj_dist(attacker, defender);\n        if (item_w_range(attacker, HIT_MODE_PUNCH) >= distance) {\n            attackType1 = ATTACK_TYPE_UNARMED;\n        }\n    }\n\n    if (!v24) {\n        for (int index = 0; index < ATTACK_TYPE_COUNT; index++) {\n            if (weapPrefOrderings[ai->best_weapon + 1][index] == attackType1) {\n                v26 = index;\n                break;\n            }\n        }\n    }\n\n    if (weapon2 != NULL) {\n        attackType2 = item_w_subtype(weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY);\n        if (item_w_damage_min_max(weapon2, &minDamage, &maxDamage) == -1) {\n            return NULL;\n        }\n\n        avgDamage2 = (maxDamage - minDamage) / 2;\n        if (item_w_area_damage_radius(weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY) > 0 && defender != NULL) {\n            attack.weapon = weapon2;\n            compute_explosion_on_extras(&attack, 0, item_w_is_grenade(weapon2), 1);\n            avgDamage2 *= attack.extrasLength + 1;\n        }\n\n        if (item_w_perk(weapon2) != -1) {\n            avgDamage2 *= 5;\n        }\n\n        if (defender != NULL) {\n            if (combat_safety_invalidate_weapon(attacker, weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender, NULL)) {\n                v23 = 1;\n            }\n        }\n\n        if (item_is_hidden(weapon2)) {\n            return weapon2;\n        }\n    } else {\n        if (distance == 0) {\n            distance = obj_dist(attacker, weapon1);\n        }\n\n        if (item_w_range(attacker, HIT_MODE_PUNCH) >= distance) {\n            attackType2 = ATTACK_TYPE_UNARMED;\n        }\n    }\n\n    if (!v23) {\n        for (int index = 0; index < ATTACK_TYPE_COUNT; index++) {\n            if (weapPrefOrderings[ai->best_weapon + 1][index] == attackType2) {\n                v25 = index;\n                break;\n            }\n        }\n    }\n\n    if (v26 == v25) {\n        if (v26 == 999) {\n            return NULL;\n        }\n\n        if (abs(avgDamage2 - avgDamage1) <= 5) {\n            return item_cost(weapon2) > item_cost(weapon1) ? weapon2 : weapon1;\n        }\n\n        return avgDamage2 > avgDamage1 ? weapon2 : weapon1;\n    }\n\n    if (weapon1 != NULL && weapon1->pid == PROTO_ID_FLARE && weapon2 != NULL) {\n        return weapon2;\n    }\n\n    if (weapon2 != NULL && weapon2->pid == PROTO_ID_FLARE && weapon1 != NULL) {\n        return weapon1;\n    }\n\n    if ((ai->best_weapon == -1 || ai->best_weapon >= BEST_WEAPON_UNARMED_OVER_THROW)\n        && abs(avgDamage2 - avgDamage1) > 5) {\n        return avgDamage2 > avgDamage1 ? weapon2 : weapon1;\n    }\n\n    return v26 > v25 ? weapon2 : weapon1;\n}\n\n// 0x4298EC\nstatic bool ai_can_use_weapon(Object* critter, Object* weapon, int hitMode)\n{\n    int damageFlags = critter->data.critter.combat.results;\n    if ((damageFlags & DAM_CRIP_ARM_LEFT) != 0 && (damageFlags & DAM_CRIP_ARM_RIGHT) != 0) {\n        return false;\n    }\n\n    if ((damageFlags & DAM_CRIP_ARM_ANY) != 0 && item_w_is_2handed(weapon)) {\n        return false;\n    }\n\n    int rotation = critter->rotation + 1;\n    int animationCode = item_w_anim_code(weapon);\n    int v9 = item_w_anim_weap(weapon, hitMode);\n    int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, v9, animationCode, rotation);\n    if (!art_exists(fid)) {\n        return false;\n    }\n\n    int skill = item_w_skill(weapon, hitMode);\n    AiPacket* ai = ai_cap(critter);\n    if (skill_level(critter, skill) < ai->min_to_hit) {\n        return false;\n    }\n\n    int attackType = item_w_subtype(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY);\n    return caiHasWeapPrefType(ai, attackType) != 0;\n}\n\n// 0x4299A0\nObject* ai_search_inven_weap(Object* critter, int a2, Object* a3)\n{\n    int bodyType = critter_body_type(critter);\n    if (bodyType != BODY_TYPE_BIPED\n        && bodyType != BODY_TYPE_ROBOTIC\n        && critter->pid != PROTO_ID_0x1000098) {\n        return NULL;\n    }\n\n    int token = -1;\n    Object* bestWeapon = NULL;\n    Object* rightHandWeapon = inven_right_hand(critter);\n    while (true) {\n        Object* weapon = inven_find_type(critter, ITEM_TYPE_WEAPON, &token);\n        if (weapon == NULL) {\n            break;\n        }\n\n        if (weapon == rightHandWeapon) {\n            continue;\n        }\n\n        if (a2) {\n            if (item_w_primary_mp_cost(weapon) > critter->data.critter.combat.ap) {\n                continue;\n            }\n        }\n\n        if (!ai_can_use_weapon(critter, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY)) {\n            continue;\n        }\n\n        if (item_w_subtype(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY) == ATTACK_TYPE_RANGED) {\n            if (item_w_curr_ammo(weapon) == 0) {\n                if (!ai_have_ammo(critter, weapon, NULL)) {\n                    continue;\n                }\n            }\n        }\n\n        bestWeapon = ai_best_weapon(critter, bestWeapon, weapon, a3);\n    }\n\n    return bestWeapon;\n}\n\n// Finds new best armor (other than what's already equipped) based on the armor score.\n//\n// 0x429A6C\nObject* ai_search_inven_armor(Object* critter)\n{\n    if (!isPartyMember(critter)) {\n        return NULL;\n    }\n\n    // Calculate armor score - it's a unitless combination of armor class and bonuses across\n    // all damage types.\n    int armorScore = 0;\n    Object* armor = inven_worn(critter);\n    if (armor != NULL) {\n        armorScore = item_ar_ac(armor);\n\n        for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType++) {\n            armorScore += item_ar_dr(armor, damageType);\n            armorScore += item_ar_dt(armor, damageType);\n        }\n    } else {\n        armorScore = 0;\n    }\n\n    Object* bestArmor = NULL;\n\n    int v15 = -1;\n    while (true) {\n        Object* candidate = inven_find_type(critter, ITEM_TYPE_ARMOR, &v15);\n        if (candidate == NULL) {\n            break;\n        }\n\n        if (armor != candidate) {\n            int candidateScore = item_ar_ac(candidate);\n            for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType++) {\n                candidateScore += item_ar_dr(candidate, damageType);\n                candidateScore += item_ar_dt(candidate, damageType);\n            }\n\n            if (candidateScore > armorScore) {\n                armorScore = candidateScore;\n                bestArmor = candidate;\n            }\n        }\n    }\n\n    return bestArmor;\n}\n\n// Returns true if critter can use given item.\n//\n// That means the item is one of it's primary desires,\n// or it's a humanoid being with intelligence at least 3,\n// and the iteam is a something healing.\n//\n// 0x429B44\nstatic bool ai_can_use_drug(Object* critter, Object* item)\n{\n    if (critter == NULL) {\n        return false;\n    }\n\n    if (item == NULL) {\n        return false;\n    }\n\n    AiPacket* ai = ai_cap_from_packet(critter->data.critter.combat.aiPacket);\n    if (ai == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) {\n        if (item->pid == ai->chem_primary_desire[index]) {\n            return true;\n        }\n    }\n\n    if (critter_body_type(critter) != BODY_TYPE_BIPED) {\n        return false;\n    }\n\n    int killType = critterGetKillType(critter);\n    if (killType != KILL_TYPE_MAN\n        && killType != KILL_TYPE_WOMAN\n        && killType != KILL_TYPE_SUPER_MUTANT\n        && killType != KILL_TYPE_GHOUL\n        && killType != KILL_TYPE_CHILD) {\n        return false;\n    }\n\n    if (critterGetStat(critter, STAT_INTELLIGENCE) < 3) {\n        return false;\n    }\n\n    int itemPid = item->pid;\n    if (itemPid != PROTO_ID_STIMPACK\n        && itemPid != PROTO_ID_SUPER_STIMPACK\n        && itemPid != PROTO_ID_HEALING_POWDER) {\n        return false;\n    }\n\n    return true;\n}\n\n// Find best item type to use?\n//\n// 0x429C18\nstatic Object* ai_search_environ(Object* critter, int itemType)\n{\n    if (critter_body_type(critter) != BODY_TYPE_BIPED) {\n        return NULL;\n    }\n\n    Object** objects;\n    int count = obj_create_list(-1, map_elevation, OBJ_TYPE_ITEM, &objects);\n    if (count == 0) {\n        return NULL;\n    }\n\n    // NOTE: Uninline.\n    ai_sort_list_distance(objects, count, critter);\n\n    int perception = critterGetStat(critter, STAT_PERCEPTION) + 5;\n    Object* item2 = inven_right_hand(critter);\n\n    Object* foundItem = NULL;\n\n    for (int index = 0; index < count; index++) {\n        Object* item = objects[index];\n        int distance = obj_dist(critter, item);\n        if (distance > perception) {\n            break;\n        }\n\n        if (item_get_type(item) == itemType) {\n            switch (itemType) {\n            case ITEM_TYPE_WEAPON:\n                if (ai_can_use_weapon(critter, item, HIT_MODE_RIGHT_WEAPON_PRIMARY)) {\n                    foundItem = item;\n                }\n                break;\n            case ITEM_TYPE_AMMO:\n                if (item_w_can_reload(item2, item)) {\n                    foundItem = item;\n                }\n                break;\n            case ITEM_TYPE_DRUG:\n            case ITEM_TYPE_MISC:\n                if (ai_can_use_drug(critter, item)) {\n                    foundItem = item;\n                }\n                break;\n            }\n\n            if (foundItem != NULL) {\n                break;\n            }\n        }\n    }\n\n    obj_delete_list(objects);\n\n    return foundItem;\n}\n\n// 0x429D60\nstatic Object* ai_retrieve_object(Object* a1, Object* a2)\n{\n    if (action_get_an_object(a1, a2) != 0) {\n        return NULL;\n    }\n\n    combat_turn_run();\n\n    Object* v3 = inven_find_id(a1, a2->id);\n\n    // TODO: Not sure about this one.\n    if (v3 != NULL || a2->owner != NULL) {\n        a2 = NULL;\n    }\n\n    combatAIInfoSetLastItem(v3, a2);\n\n    return v3;\n}\n\n// 0x429DB4\nstatic int ai_pick_hit_mode(Object* a1, Object* a2, Object* a3)\n{\n    if (a2 == NULL) {\n        return HIT_MODE_PUNCH;\n    }\n\n    if (item_get_type(a2) != ITEM_TYPE_WEAPON) {\n        return HIT_MODE_PUNCH;\n    }\n\n    int attackType = item_w_subtype(a2, HIT_MODE_RIGHT_WEAPON_SECONDARY);\n    int intelligence = critterGetStat(a1, STAT_INTELLIGENCE);\n    if (attackType == ATTACK_TYPE_NONE || !ai_can_use_weapon(a1, a2, HIT_MODE_RIGHT_WEAPON_SECONDARY)) {\n        return HIT_MODE_RIGHT_WEAPON_PRIMARY;\n    }\n\n    bool useSecondaryMode = false;\n\n    AiPacket* ai = ai_cap(a1);\n    if (ai == NULL) {\n        return HIT_MODE_PUNCH;\n    }\n\n    if (ai->area_attack_mode != -1) {\n        switch (ai->area_attack_mode) {\n        case AREA_ATTACK_MODE_ALWAYS:\n            useSecondaryMode = true;\n            break;\n        case AREA_ATTACK_MODE_SOMETIMES:\n            if (roll_random(1, ai->secondary_freq) == 1) {\n                useSecondaryMode = true;\n            }\n            break;\n        case AREA_ATTACK_MODE_BE_SURE:\n            if (determine_to_hit(a1, a3, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 85\n                && !combat_safety_invalidate_weapon(a1, a2, 3, a3, 0)) {\n                useSecondaryMode = true;\n            }\n            break;\n        case AREA_ATTACK_MODE_BE_CAREFUL:\n            if (determine_to_hit(a1, a3, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 50\n                && !combat_safety_invalidate_weapon(a1, a2, 3, a3, 0)) {\n                useSecondaryMode = true;\n            }\n            break;\n        case AREA_ATTACK_MODE_BE_ABSOLUTELY_SURE:\n            if (determine_to_hit(a1, a3, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 95\n                && !combat_safety_invalidate_weapon(a1, a2, 3, a3, 0)) {\n                useSecondaryMode = true;\n            }\n            break;\n        }\n    } else {\n        if (intelligence < 6 || obj_dist(a1, a3) < 10) {\n            if (roll_random(1, ai->secondary_freq) == 1) {\n                useSecondaryMode = true;\n            }\n        }\n    }\n\n    if (useSecondaryMode) {\n        if (!caiHasWeapPrefType(ai, attackType)) {\n            useSecondaryMode = false;\n        }\n    }\n\n    if (useSecondaryMode) {\n        if (attackType != ATTACK_TYPE_THROW\n            || ai_search_inven_weap(a1, 0, a3) != NULL\n            || stat_result(a1, STAT_INTELLIGENCE, 0, NULL) <= 1) {\n            return HIT_MODE_RIGHT_WEAPON_SECONDARY;\n        }\n    }\n\n    return HIT_MODE_RIGHT_WEAPON_PRIMARY;\n}\n\n// 0x429FC8\nstatic int ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, int a4)\n{\n    if (actionPoints <= 0) {\n        return -1;\n    }\n\n    int distance = ai_cap(a1)->distance;\n    if (distance == DISTANCE_STAY) {\n        return -1;\n    }\n\n    if (distance == DISTANCE_STAY_CLOSE) {\n        if (a2 != obj_dude) {\n            int v10 = obj_dist(a1, obj_dude);\n            if (v10 > 5 && obj_dist(a2, obj_dude) > 5 && v10 + actionPoints > 5) {\n                return -1;\n            }\n        }\n    }\n\n    if (obj_dist(a1, a2) <= 1) {\n        return -1;\n    }\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n\n    if (a4) {\n        combatai_msg(a1, NULL, AI_MESSAGE_TYPE_MOVE, 0);\n    }\n\n    Object* v18 = a2;\n\n    bool shouldUnhide;\n    if ((a2->flags & 0x800) != 0) {\n        shouldUnhide = true;\n        a2->flags |= OBJECT_HIDDEN;\n    } else {\n        shouldUnhide = false;\n    }\n\n    if (make_path_func(a1, a1->tile, a2->tile, NULL, 0, obj_blocking_at) == 0) {\n        moveBlockObj = NULL;\n        if (make_path_func(a1, a1->tile, a2->tile, NULL, 0, obj_ai_blocking_at) == 0\n            && moveBlockObj != NULL\n            && PID_TYPE(moveBlockObj->pid) == OBJ_TYPE_CRITTER) {\n            if (shouldUnhide) {\n                a2->flags &= ~OBJECT_HIDDEN;\n            }\n\n            a2 = moveBlockObj;\n            if ((a2->flags & 0x800) != 0) {\n                shouldUnhide = true;\n                a2->flags |= OBJECT_HIDDEN;\n            } else {\n                shouldUnhide = false;\n            }\n        }\n    }\n\n    if (shouldUnhide) {\n        a2->flags &= ~OBJECT_HIDDEN;\n    }\n\n    int tile = a2->tile;\n    if (a2 == v18) {\n        cai_retargetTileFromFriendlyFire(a1, a2, &tile);\n    }\n\n    if (actionPoints >= critterGetStat(a1, STAT_MAXIMUM_ACTION_POINTS) / 2 && artCritterFidShouldRun(a1->fid)) {\n        if ((a2->flags & OBJECT_MULTIHEX) != 0) {\n            register_object_run_to_object(a1, a2, actionPoints, 0);\n        } else {\n            register_object_run_to_tile(a1, tile, a1->elevation, actionPoints, 0);\n        }\n    } else {\n        if ((a2->flags & OBJECT_MULTIHEX) != 0) {\n            register_object_move_to_object(a1, a2, actionPoints, 0);\n        } else {\n            register_object_move_to_tile(a1, tile, a1->elevation, actionPoints, 0);\n        }\n    }\n\n    if (register_end() != 0) {\n        return -1;\n    }\n\n    combat_turn_run();\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x42A1C0\nstatic int ai_move_closer(Object* a1, Object* a2, int a3)\n{\n    return ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, a3);\n}\n\n// 0x42A1D4\nstatic int cai_retargetTileFromFriendlyFire(Object* source, Object* target, int* tilePtr)\n{\n    if (source == NULL) {\n        return -1;\n    }\n\n    if (target == NULL) {\n        return -1;\n    }\n\n    if (tilePtr == NULL) {\n        return -1;\n    }\n\n    if (*tilePtr == -1) {\n        return -1;\n    }\n\n    if (curr_crit_num == 0) {\n        return -1;\n    }\n\n    int tiles[32];\n\n    AiRetargetData aiRetargetData;\n    aiRetargetData.source = source;\n    aiRetargetData.target = target;\n    aiRetargetData.sourceTeam = source->data.critter.combat.team;\n    aiRetargetData.sourceRating = combatai_rating(source);\n    aiRetargetData.critterCount = 0;\n    aiRetargetData.tiles = tiles;\n    aiRetargetData.notSameTile = *tilePtr != source->tile;\n    aiRetargetData.currentTileIndex = 0;\n    aiRetargetData.sourceIntelligence = critterGetStat(source, STAT_INTELLIGENCE);\n\n    for (int index = 0; index < 32; index++) {\n        tiles[index] = -1;\n    }\n\n    for (int index = 0; index < curr_crit_num; index++) {\n        Object* obj = curr_crit_list[index];\n        if ((obj->data.critter.combat.results & DAM_DEAD) == 0\n            && obj->data.critter.combat.team == aiRetargetData.sourceTeam\n            && combatAIInfoGetLastTarget(obj) == aiRetargetData.target\n            && obj != aiRetargetData.source) {\n            int rating = combatai_rating(obj);\n            if (rating >= aiRetargetData.sourceRating) {\n                aiRetargetData.critterList[aiRetargetData.critterCount] = obj;\n                aiRetargetData.ratingList[aiRetargetData.critterCount] = rating;\n                aiRetargetData.critterCount += 1;\n            }\n        }\n    }\n\n    // NOTE: Uninline.\n    ai_sort_list_distance(aiRetargetData.critterList, aiRetargetData.critterCount, source);\n\n    if (cai_retargetTileFromFriendlyFireSubFunc(&aiRetargetData, *tilePtr) == 0) {\n        int minDistance = 99999;\n        int minDistanceIndex = -1;\n\n        for (int index = 0; index < 32; index++) {\n            int tile = tiles[index];\n            if (tile == -1) {\n                break;\n            }\n\n            if (obj_blocking_at(NULL, tile, source->elevation) == 0) {\n                int distance = tile_dist(*tilePtr, tile);\n                if (distance < minDistance) {\n                    minDistance = distance;\n                    minDistanceIndex = index;\n                }\n            }\n        }\n\n        if (minDistanceIndex != -1) {\n            *tilePtr = tiles[minDistanceIndex];\n        }\n    }\n\n    return 0;\n}\n\n// 0x42A410\nstatic int cai_retargetTileFromFriendlyFireSubFunc(AiRetargetData* aiRetargetData, int tile)\n{\n    if (aiRetargetData->sourceIntelligence <= 0) {\n        return 0;\n    }\n\n    int distance = 1;\n\n    for (int index = 0; index < aiRetargetData->critterCount; index++) {\n        Object* critter = aiRetargetData->critterList[index];\n        if (cai_attackWouldIntersect(critter, aiRetargetData->target, aiRetargetData->source, tile, &distance)) {\n            debug_printf(\"In the way!\");\n\n            aiRetargetData->tiles[aiRetargetData->currentTileIndex] = tile_num_in_direction(tile, (critter->rotation + 1) % ROTATION_COUNT, distance);\n            aiRetargetData->tiles[aiRetargetData->currentTileIndex + 1] = tile_num_in_direction(tile, (critter->rotation + 5) % ROTATION_COUNT, distance);\n\n            aiRetargetData->sourceIntelligence -= 2;\n            aiRetargetData->currentTileIndex += 2;\n            break;\n        }\n    }\n\n    return 0;\n}\n\n// 0x42A518\nstatic bool cai_attackWouldIntersect(Object* attacker, Object* defender, Object* attackerFriend, int tile, int* distance)\n{\n    int hitMode = HIT_MODE_RIGHT_WEAPON_PRIMARY;\n    bool aiming = false;\n    if (attacker == obj_dude) {\n        intface_get_attack(&hitMode, &aiming);\n    }\n\n    Object* weapon = item_hit_with(attacker, hitMode);\n    if (weapon == NULL) {\n        return false;\n    }\n\n    if (item_w_range(attacker, hitMode) < 1) {\n        return false;\n    }\n\n    Object* object = NULL;\n    make_straight_path_func(attacker, attacker->tile, defender->tile, NULL, &object, 32, obj_shoot_blocking_at);\n    if (object != attackerFriend) {\n        if (!combatTestIncidentalHit(attacker, defender, attackerFriend, weapon)) {\n            return false;\n        }\n    }\n\n    return true;\n}\n\n// 0x42A5B8\nstatic int ai_switch_weapons(Object* a1, int* hitMode, Object** weapon, Object* a4)\n{\n    *weapon = NULL;\n    *hitMode = HIT_MODE_PUNCH;\n\n    Object* bestWeapon = ai_search_inven_weap(a1, 1, a4);\n    if (bestWeapon != NULL) {\n        *weapon = bestWeapon;\n        *hitMode = ai_pick_hit_mode(a1, bestWeapon, a4);\n    } else {\n        Object* v8 = ai_search_environ(a1, ITEM_TYPE_WEAPON);\n        if (v8 == NULL) {\n            if (item_w_mp_cost(a1, *hitMode, 0) <= a1->data.critter.combat.ap) {\n                return 0;\n            }\n\n            return -1;\n        }\n\n        Object* v9 = ai_retrieve_object(a1, v8);\n        if (v9 != NULL) {\n            *weapon = v9;\n            *hitMode = ai_pick_hit_mode(a1, v9, a4);\n        }\n    }\n\n    if (*weapon != NULL) {\n        inven_wield(a1, *weapon, 1);\n        combat_turn_run();\n        if (item_w_mp_cost(a1, *hitMode, 0) <= a1->data.critter.combat.ap) {\n            return 0;\n        }\n    }\n\n    return -1;\n}\n\n// 0x42A670\nstatic int ai_called_shot(Object* a1, Object* a2, int a3)\n{\n    AiPacket* ai;\n    int v5;\n    int v6;\n    int v7;\n    int combat_difficulty;\n\n    v5 = 3;\n\n    if (item_w_mp_cost(a1, a3, 1) <= a1->data.critter.combat.ap) {\n        if (item_w_called_shot(a1, a3)) {\n            ai = ai_cap(a1);\n            if (roll_random(1, ai->called_freq) == 1) {\n                combat_difficulty = 1;\n                config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combat_difficulty);\n                if (combat_difficulty) {\n                    if (combat_difficulty == 2) {\n                        v6 = 3;\n                    } else {\n                        v6 = 5;\n                    }\n                } else {\n                    v6 = 7;\n                }\n\n                if (critterGetStat(a1, STAT_INTELLIGENCE) >= v6) {\n                    v5 = roll_random(0, 8);\n                    v7 = determine_to_hit(a1, a2, a3, v5);\n                    if (v7 < ai->min_to_hit) {\n                        v5 = 3;\n                    }\n                }\n            }\n        }\n    }\n\n    return v5;\n}\n\n// 0x42A748\nstatic int ai_attack(Object* a1, Object* a2, int a3)\n{\n    int v6;\n\n    if (a1->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) {\n        return -1;\n    }\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n    register_object_turn_towards(a1, a2->tile);\n    register_end();\n    combat_turn_run();\n\n    v6 = ai_called_shot(a1, a2, a3);\n    if (combat_attack(a1, a2, a3, v6)) {\n        return -1;\n    }\n\n    combat_turn_run();\n\n    return 0;\n}\n\n// 0x42A7D8\nstatic int ai_try_attack(Object* a1, Object* a2)\n{\n    critter_set_who_hit_me(a1, a2);\n\n    CritterCombatData* combatData = &(a1->data.critter.combat);\n    int v38 = 1;\n\n    Object* weapon = inven_right_hand(a1);\n    if (weapon != NULL && item_get_type(weapon) != ITEM_TYPE_WEAPON) {\n        weapon = NULL;\n    }\n\n    int hitMode = ai_pick_hit_mode(a1, weapon, a2);\n    int minToHit = ai_cap(a1)->min_to_hit;\n\n    int actionPoints = a1->data.critter.combat.ap;\n    int v31 = 0;\n    int v42 = 0;\n    if (weapon != NULL\n        || (critter_body_type(a2) == BODY_TYPE_BIPED\n            && ((a2->fid & 0xF000) >> 12 == 0)\n            && art_exists(art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_THROW_PUNCH, 0, a1->rotation + 1)))) {\n        if (combat_safety_invalidate_weapon(a1, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, a2, &v31)) {\n            ai_switch_weapons(a1, &hitMode, &weapon, a2);\n        }\n    } else {\n        ai_switch_weapons(a1, &hitMode, &weapon, a2);\n    }\n\n    unsigned char v30[800];\n\n    Object* ammo = NULL;\n    for (int attempt = 0; attempt < 10; attempt++) {\n        if ((combatData->results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) {\n            break;\n        }\n\n        int reason = combat_check_bad_shot(a1, a2, hitMode, false);\n        if (reason == COMBAT_BAD_SHOT_NO_AMMO) {\n            // out of ammo\n            if (ai_have_ammo(a1, weapon, &ammo)) {\n                int v9 = item_w_reload(weapon, ammo);\n                if (v9 == 0 && ammo != NULL) {\n                    obj_destroy(ammo);\n                }\n\n                if (v9 != -1) {\n                    int volume = gsound_compute_relative_volume(a1);\n                    const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, weapon, hitMode, NULL);\n                    gsound_play_sfx_file_volume(sfx, volume);\n                    ai_magic_hands(a1, weapon, 5002);\n\n                    int actionPoints = a1->data.critter.combat.ap;\n                    if (actionPoints >= 2) {\n                        a1->data.critter.combat.ap = actionPoints - 2;\n                    } else {\n                        a1->data.critter.combat.ap = 0;\n                    }\n                }\n            } else {\n                ammo = ai_search_environ(a1, ITEM_TYPE_AMMO);\n                if (ammo != NULL) {\n                    ammo = ai_retrieve_object(a1, ammo);\n                    if (ammo != NULL) {\n                        int v15 = item_w_reload(weapon, ammo);\n                        if (v15 == 0) {\n                            obj_destroy(ammo);\n                        }\n\n                        if (v15 != -1) {\n                            int volume = gsound_compute_relative_volume(a1);\n                            const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, weapon, hitMode, NULL);\n                            gsound_play_sfx_file_volume(sfx, volume);\n                            ai_magic_hands(a1, weapon, 5002);\n\n                            int actionPoints = a1->data.critter.combat.ap;\n                            if (actionPoints >= 2) {\n                                a1->data.critter.combat.ap = actionPoints - 2;\n                            } else {\n                                a1->data.critter.combat.ap = 0;\n                            }\n                        }\n                    }\n                } else {\n                    int volume = gsound_compute_relative_volume(a1);\n                    const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_OUT_OF_AMMO, weapon, hitMode, NULL);\n                    gsound_play_sfx_file_volume(sfx, volume);\n                    ai_magic_hands(a1, weapon, 5001);\n\n                    if (inven_unwield(a1, 1) == 0) {\n                        combat_turn_run();\n                    }\n\n                    ai_switch_weapons(a1, &hitMode, &weapon, a2);\n                }\n            }\n        } else if (reason == COMBAT_BAD_SHOT_NOT_ENOUGH_AP || reason == COMBAT_BAD_SHOT_ARM_CRIPPLED || reason == COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED) {\n            // 3 - not enough action points\n            // 6 - crippled one arm for two-handed weapon\n            // 7 - both hands crippled\n            if (ai_switch_weapons(a1, &hitMode, &weapon, a2) == -1) {\n                return -1;\n            }\n        } else if (reason == COMBAT_BAD_SHOT_OUT_OF_RANGE) {\n            // target out of range\n            int accuracy = determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, v30);\n            if (accuracy < minToHit) {\n                const char* name = critter_name(a1);\n                debug_printf(\"%s: FLEEING: Can't possibly Hit Target!\", name);\n                ai_run_away(a1, a2);\n                return 0;\n            }\n\n            if (weapon != NULL) {\n                if (ai_move_steps_closer(a1, a2, actionPoints, v38) == -1) {\n                    return -1;\n                }\n                v38 = 0;\n            } else {\n                if (ai_switch_weapons(a1, &hitMode, &weapon, a2) == -1 || weapon == NULL) {\n                    // NOTE: Uninline.\n                    if (ai_move_closer(a1, a2, v38) == -1) {\n                        return -1;\n                    }\n                }\n                v38 = 0;\n            }\n        } else if (reason == COMBAT_BAD_SHOT_AIM_BLOCKED) {\n            // aim is blocked\n            if (ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, v38) == -1) {\n                return -1;\n            }\n            v38 = 0;\n        } else if (reason == COMBAT_BAD_SHOT_OK) {\n            int accuracy = determine_to_hit(a1, a2, HIT_LOCATION_UNCALLED, hitMode);\n            if (v31) {\n                if (ai_move_away(a1, a2, v31) == -1) {\n                    return -1;\n                }\n            }\n\n            if (accuracy < minToHit) {\n                int v22 = determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, v30);\n                if (v22 < minToHit) {\n                    const char* name = critter_name(a1);\n                    debug_printf(\"%s: FLEEING: Can't possibly Hit Target!\", name);\n                    ai_run_away(a1, a2);\n                    return 0;\n                }\n\n                if (actionPoints > 0) {\n                    int v24 = make_path_func(a1, a1->tile, a2->tile, v30, 0, obj_blocking_at);\n                    if (v24 == 0) {\n                        v42 = actionPoints;\n                    } else {\n                        if (v24 < actionPoints) {\n                            actionPoints = v24;\n                        }\n\n                        int tile = a1->tile;\n                        int index;\n                        for (index = 0; index < actionPoints; index++) {\n                            tile = tile_num_in_direction(tile, v30[index], 1);\n\n                            v42++;\n\n                            int v27 = determine_to_hit_from_tile(a1, tile, a2, HIT_LOCATION_UNCALLED, hitMode);\n                            if (v27 >= minToHit) {\n                                break;\n                            }\n                        }\n\n                        if (index == actionPoints) {\n                            v42 = actionPoints;\n                        }\n                    }\n                }\n\n                if (ai_move_steps_closer(a1, a2, v42, v38) == -1) {\n                    const char* name = critter_name(a1);\n                    debug_printf(\"%s: FLEEING: Can't possibly get closer to Target!\", name);\n                    ai_run_away(a1, a2);\n                    return 0;\n                }\n\n                v38 = 0;\n                if (ai_attack(a1, a2, hitMode) == -1 || item_w_mp_cost(a1, hitMode, 0) > a1->data.critter.combat.ap) {\n                    return -1;\n                }\n            } else {\n                if (ai_attack(a1, a2, hitMode) == -1 || item_w_mp_cost(a1, hitMode, 0) > a1->data.critter.combat.ap) {\n                    return -1;\n                }\n            }\n        }\n    }\n\n    return -1;\n}\n\n// Something with using flare\n//\n// 0x42AE90\nint cAIPrepWeaponItem(Object* critter, Object* item)\n{\n    if (item != NULL && critterGetStat(critter, STAT_INTELLIGENCE) >= 3 && item->pid == PROTO_ID_FLARE && light_get_ambient() < 55705) {\n        protinst_use_item(critter, item);\n    }\n    return 0;\n}\n\n// 0x42AECC\nvoid cai_attempt_w_reload(Object* critter_obj, int a2)\n{\n    Object* weapon_obj;\n    Object* ammo_obj;\n    int v5;\n    int v9;\n    const char* sfx;\n    int v10;\n\n    weapon_obj = inven_right_hand(critter_obj);\n    if (weapon_obj == NULL) {\n        return;\n    }\n\n    v5 = item_w_curr_ammo(weapon_obj);\n    if (v5 < item_w_max_ammo(weapon_obj) && ai_have_ammo(critter_obj, weapon_obj, &ammo_obj)) {\n        v9 = item_w_reload(weapon_obj, ammo_obj);\n        if (v9 == 0) {\n            obj_destroy(ammo_obj);\n        }\n\n        if (v9 != -1 && isPartyMember(critter_obj)) {\n            v10 = gsound_compute_relative_volume(critter_obj);\n            sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, weapon_obj, HIT_MODE_RIGHT_WEAPON_PRIMARY, NULL);\n            gsound_play_sfx_file_volume(sfx, v10);\n            if (a2) {\n                ai_magic_hands(critter_obj, weapon_obj, 5002);\n            }\n        }\n    }\n}\n\n// 0x42AF78\nvoid combat_ai_begin(int a1, void* a2)\n{\n    curr_crit_num = a1;\n\n    if (a1 != 0) {\n        curr_crit_list = (Object**)mem_malloc(sizeof(Object*) * a1);\n        if (curr_crit_list) {\n            memcpy(curr_crit_list, a2, sizeof(Object*) * a1);\n        } else {\n            curr_crit_num = 0;\n        }\n    }\n}\n\n// 0x42AFBC\nvoid combat_ai_over()\n{\n    if (curr_crit_num) {\n        mem_free(curr_crit_list);\n    }\n\n    curr_crit_num = 0;\n}\n\n// 0x42AFDC\nstatic int cai_perform_distance_prefs(Object* a1, Object* a2)\n{\n    if (a1->data.critter.combat.ap <= 0) {\n        return -1;\n    }\n\n    int distance = ai_cap(a1)->distance;\n\n    if (a2 != NULL) {\n        if ((a2->data.critter.combat.ap & DAM_DEAD) != 0) {\n            a2 = NULL;\n        }\n    }\n\n    switch (distance) {\n    case DISTANCE_STAY_CLOSE:\n        if (a1->data.critter.combat.whoHitMe != obj_dude) {\n            int distance = obj_dist(a1, obj_dude);\n            if (distance > 5) {\n                ai_move_steps_closer(a1, obj_dude, distance - 5, 0);\n            }\n        }\n        break;\n    case DISTANCE_CHARGE:\n        if (a2 != NULL) {\n            // NOTE: Uninline.\n            ai_move_closer(a1, a2, 1);\n        }\n        break;\n    case DISTANCE_SNIPE:\n        if (a2 != NULL) {\n            if (obj_dist(a1, a2) < 10) {\n                // NOTE: some odd code omitted\n                ai_move_away(a1, a2, 10);\n            }\n        }\n        break;\n    }\n\n    int tile = a1->tile;\n    if (cai_retargetTileFromFriendlyFire(a1, a2, &tile) == 0 && tile != a1->tile) {\n        register_begin(ANIMATION_REQUEST_RESERVED);\n        register_object_move_to_tile(a1, tile, a1->elevation, a1->data.critter.combat.ap, 0);\n        if (register_end() != 0) {\n            return -1;\n        }\n        combat_turn_run();\n    }\n\n    return 0;\n}\n\n// 0x42B100\nstatic int cai_get_min_hp(AiPacket* ai)\n{\n    if (ai == NULL) {\n        return 0;\n    }\n\n    int run_away_mode = ai->run_away_mode;\n    if (run_away_mode >= 0 && run_away_mode < RUN_AWAY_MODE_COUNT) {\n        return runModeValues[run_away_mode];\n    } else if (run_away_mode == -1) {\n        return ai->min_hp;\n    }\n\n    return 0;\n}\n\n// 0x42B130\nvoid combat_ai(Object* a1, Object* a2)\n{\n    // 0x51820C\n    static int aiPartyMemberDistances[DISTANCE_COUNT] = {\n        5,\n        7,\n        7,\n        7,\n        50000,\n    };\n\n    AiPacket* ai = ai_cap(a1);\n    int hpRatio = cai_get_min_hp(ai);\n    if (ai->run_away_mode != -1) {\n        int v7 = critterGetStat(a1, STAT_MAXIMUM_HIT_POINTS) * hpRatio / 100;\n        int minimumHitPoints = critterGetStat(a1, STAT_MAXIMUM_HIT_POINTS) - v7;\n        int currentHitPoints = critterGetStat(a1, STAT_CURRENT_HIT_POINTS);\n        const char* name = critter_name(a1);\n        debug_printf(\"\\n%s minHp = %d; curHp = %d\", name, minimumHitPoints, currentHitPoints);\n    }\n\n    CritterCombatData* combatData = &(a1->data.critter.combat);\n    if ((combatData->maneuver & CRITTER_MANUEVER_FLEEING) != 0\n        || (combatData->results & ai->hurt_too_much) != 0\n        || critterGetStat(a1, STAT_CURRENT_HIT_POINTS) < ai->min_hp) {\n        const char* name = critter_name(a1);\n        debug_printf(\"%s: FLEEING: I'm Hurt!\", name);\n        ai_run_away(a1, a2);\n        return;\n    }\n\n    if (ai_check_drugs(a1)) {\n        const char* name = critter_name(a1);\n        debug_printf(\"%s: FLEEING: I need DRUGS!\", name);\n        ai_run_away(a1, a2);\n    } else {\n        if (a2 == NULL) {\n            a2 = ai_danger_source(a1);\n        }\n\n        cai_perform_distance_prefs(a1, a2);\n\n        if (a2 != NULL) {\n            ai_try_attack(a1, a2);\n        }\n    }\n\n    if (a2 != NULL\n        && (a1->data.critter.combat.results & DAM_DEAD) == 0\n        && a1->data.critter.combat.ap != 0\n        && obj_dist(a1, a2) > ai->max_dist) {\n        Object* v13 = combatAIInfoGetFriendlyDead(a1);\n        if (v13 != NULL) {\n            ai_move_away(a1, v13, 10);\n            combatAIInfoSetFriendlyDead(a1, NULL);\n        } else {\n            int perception = critterGetStat(a1, STAT_PERCEPTION);\n            if (!ai_find_friend(a1, perception * 2, 5)) {\n                combatData->maneuver |= CRITTER_MANEUVER_STOP_ATTACKING;\n            }\n        }\n    }\n\n    if (a2 == NULL && !isPartyMember(a1)) {\n        Object* whoHitMe = combatData->whoHitMe;\n        if (whoHitMe != NULL) {\n            if ((whoHitMe->data.critter.combat.results & DAM_DEAD) == 0 && combatData->damageLastTurn > 0) {\n                Object* v16 = combatAIInfoGetFriendlyDead(a1);\n                if (v16 != NULL) {\n                    ai_move_away(a1, v16, 10);\n                    combatAIInfoSetFriendlyDead(a1, NULL);\n                } else {\n                    const char* name = critter_name(a1);\n                    debug_printf(\"%s: FLEEING: Somebody is shooting at me that I can't see!\");\n                    ai_run_away(a1, NULL);\n                }\n            }\n        }\n    }\n\n    Object* v18 = combatAIInfoGetFriendlyDead(a1);\n    if (v18 != NULL) {\n        ai_move_away(a1, v18, 10);\n        if (obj_dist(a1, v18) >= 10) {\n            combatAIInfoSetFriendlyDead(a1, NULL);\n        }\n    }\n\n    Object* v20;\n    int v21 = 5; // 0x42B156\n    if (a1->data.critter.combat.team != 0) {\n        v20 = ai_find_nearest_team_in_combat(a1, a1, 1);\n    } else {\n        v20 = obj_dude;\n        if (isPartyMember(a1)) {\n            // NOTE: Uninline\n            int distance = ai_get_distance_pref_value(a1);\n            if (distance != -1) {\n                v21 = aiPartyMemberDistances[distance];\n            }\n        }\n    }\n\n    if (a2 == NULL && v20 != NULL && obj_dist(a1, v20) > v21) {\n        int v23 = obj_dist(a1, v20);\n        ai_move_steps_closer(a1, v20, v23 - v21, 0);\n    } else {\n        if (a1->data.critter.combat.ap > 0) {\n            debug_printf(\"\\n>>>NOTE: %s had extra AP's to use!<<<\", critter_name(a1));\n            cai_perform_distance_prefs(a1, a2);\n        }\n    }\n}\n\n// 0x42B3FC\nbool combatai_want_to_join(Object* a1)\n{\n    process_bk();\n\n    if ((a1->flags & OBJECT_HIDDEN) != 0) {\n        return false;\n    }\n\n    if (a1->elevation != obj_dude->elevation) {\n        return false;\n    }\n\n    if ((a1->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {\n        return false;\n    }\n\n    if (a1->data.critter.combat.damageLastTurn > 0) {\n        return true;\n    }\n\n    if (a1->sid != -1) {\n        scr_set_objs(a1->sid, NULL, NULL);\n        scr_set_ext_param(a1->sid, 5);\n        exec_script_proc(a1->sid, SCRIPT_PROC_COMBAT);\n    }\n\n    if ((a1->data.critter.combat.maneuver & CRITTER_MANEUVER_0x01) != 0) {\n        return true;\n    }\n\n    if ((a1->data.critter.combat.maneuver & CRITTER_MANEUVER_STOP_ATTACKING) == 0) {\n        return false;\n    }\n\n    if ((a1->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) == 0) {\n        return false;\n    }\n\n    if (ai_danger_source(a1) == NULL) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x42B4A8\nbool combatai_want_to_stop(Object* a1)\n{\n    process_bk();\n\n    if ((a1->data.critter.combat.maneuver & CRITTER_MANEUVER_STOP_ATTACKING) != 0) {\n        return true;\n    }\n\n    if ((a1->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD)) != 0) {\n        return true;\n    }\n\n    if ((a1->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) {\n        return true;\n    }\n\n    Object* v4 = ai_danger_source(a1);\n    return v4 == NULL || !is_within_perception(a1, v4);\n}\n\n// 0x42B504\nint combatai_switch_team(Object* obj, int team)\n{\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    obj->data.critter.combat.team = team;\n\n    if (obj->data.critter.combat.whoHitMeCid == -1) {\n        critter_set_who_hit_me(obj, NULL);\n        debug_printf(\"\\nError: CombatData found with invalid who_hit_me!\");\n        return -1;\n    }\n\n    Object* whoHitMe = obj->data.critter.combat.whoHitMe;\n    if (whoHitMe != NULL) {\n        if (whoHitMe->data.critter.combat.team == team) {\n            critter_set_who_hit_me(obj, NULL);\n        }\n    }\n\n    combatAIInfoSetLastTarget(obj, NULL);\n\n    if (isInCombat()) {\n        bool outlineWasEnabled = obj->outline != 0 && (obj->outline & OUTLINE_DISABLED) == 0;\n\n        obj_remove_outline(obj, NULL);\n\n        int outlineType;\n        if (obj->data.critter.combat.team == obj_dude->data.critter.combat.team) {\n            outlineType = OUTLINE_TYPE_2;\n        } else {\n            outlineType = OUTLINE_TYPE_HOSTILE;\n        }\n\n        obj_outline_object(obj, outlineType, NULL);\n\n        if (outlineWasEnabled) {\n            Rect rect;\n            obj_turn_on_outline(obj, &rect);\n            tile_refresh_rect(&rect, obj->elevation);\n        }\n    }\n\n    return 0;\n}\n\n// 0x42B5D4\nint combat_ai_set_ai_packet(Object* object, int aiPacket)\n{\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        return -1;\n    }\n\n    object->data.critter.combat.aiPacket = aiPacket;\n\n    if (isPotentialPartyMember(object)) {\n        Proto* proto;\n        if (proto_ptr(object->pid, &proto) == -1) {\n            return -1;\n        }\n\n        proto->critter.aiPacket = aiPacket;\n    }\n\n    return 0;\n}\n\n// combatai_msg\n// 0x42B634\nint combatai_msg(Object* a1, Attack* attack, int type, int delay)\n{\n    if (PID_TYPE(a1->pid) != OBJ_TYPE_CRITTER) {\n        return -1;\n    }\n\n    bool combatTaunts = true;\n    configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, &combatTaunts);\n    if (!combatTaunts) {\n        return -1;\n    }\n\n    if (a1 == obj_dude) {\n        return -1;\n    }\n\n    if (a1->data.critter.combat.results & 0x81) {\n        return -1;\n    }\n\n    AiPacket* ai = ai_cap(a1);\n\n    debug_printf(\"%s is using %s packet with a %d%% chance to taunt\\n\", object_name(a1), ai->name, ai->chance);\n\n    if (roll_random(1, 100) > ai->chance) {\n        return -1;\n    }\n\n    int start;\n    int end;\n    char* string;\n\n    switch (type) {\n    case AI_MESSAGE_TYPE_RUN:\n        start = ai->run.start;\n        end = ai->run.end;\n        string = attack_str;\n        break;\n    case AI_MESSAGE_TYPE_MOVE:\n        start = ai->move.start;\n        end = ai->move.end;\n        string = attack_str;\n        break;\n    case AI_MESSAGE_TYPE_ATTACK:\n        start = ai->attack.start;\n        end = ai->attack.end;\n        string = attack_str;\n        break;\n    case AI_MESSAGE_TYPE_MISS:\n        start = ai->miss.start;\n        end = ai->miss.end;\n        string = target_str;\n        break;\n    case AI_MESSAGE_TYPE_HIT:\n        start = ai->hit[attack->defenderHitLocation].start;\n        end = ai->hit[attack->defenderHitLocation].end;\n        string = target_str;\n        break;\n    default:\n        return -1;\n    }\n\n    if (end < start) {\n        return -1;\n    }\n\n    MessageListItem messageListItem;\n    messageListItem.num = roll_random(start, end);\n    if (!message_search(&ai_message_file, &messageListItem)) {\n        debug_printf(\"\\nERROR: combatai_msg: Couldn't find message # %d for %s\", messageListItem.num, critter_name(a1));\n        return -1;\n    }\n\n    debug_printf(\"%s said message %d\\n\", object_name(a1), messageListItem.num);\n    strncpy(string, messageListItem.text, 259);\n\n    // TODO: Get rid of casts.\n    return register_object_call(a1, (void*)type, (AnimationCallback*)ai_print_msg, delay);\n}\n\n// 0x42B80C\nstatic int ai_print_msg(Object* critter, int type)\n{\n    if (text_object_count() > 0) {\n        return 0;\n    }\n\n    char* string;\n    switch (type) {\n    case AI_MESSAGE_TYPE_HIT:\n    case AI_MESSAGE_TYPE_MISS:\n        string = target_str;\n        break;\n    default:\n        string = attack_str;\n        break;\n    }\n\n    AiPacket* ai = ai_cap(critter);\n\n    Rect rect;\n    if (text_object_create(critter, string, ai->font, ai->color, ai->outline_color, &rect) == 0) {\n        tile_refresh_rect(&rect, critter->elevation);\n    }\n\n    return 0;\n}\n\n// Returns random critter for attacking as a result of critical weapon failure.\n//\n// 0x42B868\nObject* combat_ai_random_target(Attack* attack)\n{\n    // Looks like this function does nothing because it's result is not used. I\n    // suppose it was planned to use range as a condition below, but it was\n    // later moved into 0x426614, but remained here.\n    item_w_range(attack->attacker, attack->hitMode);\n\n    Object* critter = NULL;\n\n    if (curr_crit_num != 0) {\n        // Randomize starting critter.\n        int start = roll_random(0, curr_crit_num - 1);\n        int index = start;\n        while (true) {\n            Object* obj = curr_crit_list[index];\n            if (obj != attack->attacker\n                && obj != attack->defender\n                && can_see(attack->attacker, obj)\n                && combat_check_bad_shot(attack->attacker, obj, attack->hitMode, false) == COMBAT_BAD_SHOT_OK) {\n                critter = obj;\n                break;\n            }\n\n            index += 1;\n            if (index == curr_crit_num) {\n                index = 0;\n            }\n\n            if (index == start) {\n                break;\n            }\n        }\n    }\n\n    return critter;\n}\n\n// 0x42B90C\nstatic int combatai_rating(Object* obj)\n{\n    int melee_damage;\n    Object* item;\n    int weapon_damage_min;\n    int weapon_damage_max;\n\n    if (obj == NULL) {\n        return 0;\n    }\n\n    if (FID_TYPE(obj->fid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    if ((obj->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {\n        return 0;\n    }\n\n    melee_damage = critterGetStat(obj, STAT_MELEE_DAMAGE);\n\n    item = inven_right_hand(obj);\n    if (item != NULL && item_get_type(item) == ITEM_TYPE_WEAPON && item_w_damage_min_max(item, &weapon_damage_min, &weapon_damage_max) != -1 && melee_damage < weapon_damage_max) {\n        melee_damage = weapon_damage_max;\n    }\n\n    item = inven_left_hand(obj);\n    if (item != NULL && item_get_type(item) == ITEM_TYPE_WEAPON && item_w_damage_min_max(item, &weapon_damage_min, &weapon_damage_max) != -1 && melee_damage < weapon_damage_max) {\n        melee_damage = weapon_damage_max;\n    }\n\n    return melee_damage + critterGetStat(obj, STAT_ARMOR_CLASS);\n}\n\n// 0x42B9D4\nint combatai_check_retaliation(Object* a1, Object* a2)\n{\n    Object* whoHitMe = a1->data.critter.combat.whoHitMe;\n    if (whoHitMe != NULL) {\n        int v3 = combatai_rating(a2);\n        int result = combatai_rating(whoHitMe);\n        if (v3 <= result) {\n            return result;\n        }\n    }\n    return critter_set_who_hit_me(a1, a2);\n}\n\n// 0x42BA04\nbool is_within_perception(Object* a1, Object* a2)\n{\n    if (a2 == NULL) {\n        return false;\n    }\n\n    int distance = obj_dist(a2, a1);\n    int perception = critterGetStat(a1, STAT_PERCEPTION);\n    int sneak = skill_level(a2, SKILL_SNEAK);\n    if (can_see(a1, a2)) {\n        int maxDistance = perception * 5;\n        if ((a2->flags & OBJECT_TRANS_GLASS) != 0) {\n            maxDistance /= 2;\n        }\n\n        if (a2 == obj_dude) {\n            if (is_pc_sneak_working()) {\n                maxDistance /= 4;\n                if (sneak > 120) {\n                    maxDistance -= 1;\n                }\n            } else if (is_pc_flag(DUDE_STATE_SNEAKING)) {\n                maxDistance = maxDistance * 2 / 3;\n            }\n        }\n\n        if (distance <= maxDistance) {\n            return true;\n        }\n    }\n\n    int maxDistance;\n    if (isInCombat()) {\n        maxDistance = perception * 2;\n    } else {\n        maxDistance = perception;\n    }\n\n    if (a2 == obj_dude) {\n        if (is_pc_sneak_working()) {\n            maxDistance /= 4;\n            if (sneak > 120) {\n                maxDistance -= 1;\n            }\n        } else if (is_pc_flag(DUDE_STATE_SNEAKING)) {\n            maxDistance = maxDistance * 2 / 3;\n        }\n    }\n\n    if (distance <= maxDistance) {\n        return true;\n    }\n\n    return false;\n}\n\n// Load combatai.msg and apply language filter.\n//\n// 0x42BB34\nstatic int combatai_load_messages()\n{\n    if (!message_init(&ai_message_file)) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"combatai.msg\");\n\n    if (!message_load(&ai_message_file, path)) {\n        return -1;\n    }\n\n    bool languageFilter;\n    configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter);\n\n    if (languageFilter) {\n        message_filter(&ai_message_file);\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x42BBD8\nstatic int combatai_unload_messages()\n{\n    if (!message_exit(&ai_message_file)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x42BBF0\nvoid combatai_refresh_messages()\n{\n    // 0x518220\n    static int old_state = -1;\n\n    int languageFilter = 0;\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter);\n\n    if (languageFilter != old_state) {\n        old_state = languageFilter;\n\n        if (languageFilter == 1) {\n            message_filter(&ai_message_file);\n        } else {\n            // NOTE: Uninline.\n            combatai_unload_messages();\n\n            combatai_load_messages();\n        }\n    }\n}\n\n// 0x42BC60\nvoid combatai_notify_onlookers(Object* a1)\n{\n    for (int index = 0; index < curr_crit_num; index++) {\n        Object* obj = curr_crit_list[index];\n        if ((obj->data.critter.combat.maneuver & CRITTER_MANEUVER_0x01) == 0) {\n            if (is_within_perception(obj, a1)) {\n                obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01;\n                if ((a1->data.critter.combat.results & DAM_DEAD) != 0) {\n                    if (!is_within_perception(obj, obj->data.critter.combat.whoHitMe)) {\n                        debug_printf(\"\\nSomebody Died and I don't know why!  Run!!!\");\n                        combatAIInfoSetFriendlyDead(obj, a1);\n                    }\n                }\n            }\n        }\n    }\n}\n\n// 0x42BCD4\nvoid combatai_notify_friends(Object* a1)\n{\n    int team = a1->data.critter.combat.team;\n\n    for (int index = 0; index < curr_crit_num; index++) {\n        Object* obj = curr_crit_list[index];\n        if ((obj->data.critter.combat.maneuver & CRITTER_MANEUVER_0x01) == 0 && team == obj->data.critter.combat.team) {\n            if (is_within_perception(obj, a1)) {\n                obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01;\n            }\n        }\n    }\n}\n\n// 0x42BD28\nvoid combatai_delete_critter(Object* obj)\n{\n    // TODO: Check entire function.\n    for (int i = 0; i < curr_crit_num; i++) {\n        if (obj == curr_crit_list[i]) {\n            curr_crit_num--;\n            curr_crit_list[i] = curr_crit_list[curr_crit_num];\n            curr_crit_list[curr_crit_num] = obj;\n            break;\n        }\n    }\n}\n"
  },
  {
    "path": "src/game/combatai.h",
    "content": "#ifndef FALLOUT_GAME_COMBATAI_H_\n#define FALLOUT_GAME_COMBATAI_H_\n\n#include <stdbool.h>\n\n#include \"game/combatai_defs.h\"\n#include \"game/combat_defs.h\"\n#include \"plib/db/db.h\"\n#include \"game/message.h\"\n#include \"game/object_types.h\"\n#include \"game/party.h\"\n\n#define AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT (3)\n\ntypedef enum AiMessageType {\n    AI_MESSAGE_TYPE_RUN,\n    AI_MESSAGE_TYPE_MOVE,\n    AI_MESSAGE_TYPE_ATTACK,\n    AI_MESSAGE_TYPE_MISS,\n    AI_MESSAGE_TYPE_HIT,\n} AiMessageType;\n\ntypedef struct AiMessageRange {\n    int start;\n    int end;\n} AiMessageRange;\n\ntypedef struct AiPacket {\n    char* name;\n    int packet_num;\n    int max_dist;\n    int min_to_hit;\n    int min_hp;\n    int aggression;\n    int hurt_too_much;\n    int secondary_freq;\n    int called_freq;\n    int font;\n    int color;\n    int outline_color;\n    int chance;\n    AiMessageRange run;\n    AiMessageRange move;\n    AiMessageRange attack;\n    AiMessageRange miss;\n    AiMessageRange hit[HIT_LOCATION_SPECIFIC_COUNT];\n    int area_attack_mode;\n    int run_away_mode;\n    int best_weapon;\n    int distance;\n    int attack_who;\n    int chem_use;\n    int chem_primary_desire[AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT];\n    int disposition;\n    char* body_type;\n    char* general_type;\n} AiPacket;\n\ntypedef struct AiRetargetData {\n    Object* source;\n    Object* target;\n    Object* critterList[100];\n    int ratingList[100];\n    int critterCount;\n    int sourceTeam;\n    int sourceRating;\n    bool notSameTile;\n    int* tiles;\n    int currentTileIndex;\n    int sourceIntelligence;\n} AiRetargetData;\n\nextern const char* area_attack_mode_strs[AREA_ATTACK_MODE_COUNT];\nextern const char* attack_who_mode_strs[ATTACK_WHO_COUNT];\nextern const char* weapon_pref_strs[BEST_WEAPON_COUNT];\nextern const char* chem_use_mode_strs[CHEM_USE_COUNT];\nextern const char* distance_pref_strs[DISTANCE_COUNT];\nextern const char* run_away_mode_strs[RUN_AWAY_MODE_COUNT];\nextern const char* disposition_strs[DISPOSITION_COUNT];\n\nint combat_ai_init();\nvoid combat_ai_reset();\nint combat_ai_exit();\nint combat_ai_load(File* stream);\nint combat_ai_save(File* stream);\nint combat_ai_num();\nchar* combat_ai_name(int packetNum);\nint ai_get_burst_value(Object* obj);\nint ai_get_run_away_value(Object* obj);\nint ai_get_weapon_pref_value(Object* obj);\nint ai_get_distance_pref_value(Object* obj);\nint ai_get_attack_who_value(Object* obj);\nint ai_get_chem_use_value(Object* obj);\nint ai_set_burst_value(Object* critter, int areaAttackMode);\nint ai_set_run_away_value(Object* obj, int run_away_mode);\nint ai_set_weapon_pref_value(Object* critter, int bestWeapon);\nint ai_set_distance_pref_value(Object* critter, int distance);\nint ai_set_attack_who_value(Object* critter, int attackWho);\nint ai_set_chem_use_value(Object* critter, int chemUse);\nint ai_get_disposition(Object* obj);\nint ai_set_disposition(Object* obj, int a2);\nint compare_strength(const void* p1, const void* p2);\nint compare_weakness(const void* p1, const void* p2);\nObject* ai_danger_source(Object* a1);\nint caiSetupTeamCombat(Object* a1, Object* a2);\nint caiTeamCombatInit(Object** a1, int a2);\nvoid caiTeamCombatExit();\nObject* ai_search_inven_weap(Object* critter, int a2, Object* a3);\nObject* ai_search_inven_armor(Object* critter);\nint cAIPrepWeaponItem(Object* critter, Object* item);\nvoid cai_attempt_w_reload(Object* critter_obj, int a2);\nvoid combat_ai_begin(int a1, void* a2);\nvoid combat_ai_over();\nvoid combat_ai(Object* a1, Object* a2);\nbool combatai_want_to_join(Object* a1);\nbool combatai_want_to_stop(Object* a1);\nint combatai_switch_team(Object* obj, int team);\nint combat_ai_set_ai_packet(Object* object, int aiPacket);\nint combatai_msg(Object* a1, Attack* attack, int a3, int a4);\nObject* combat_ai_random_target(Attack* attack);\nint combatai_check_retaliation(Object* a1, Object* a2);\nbool is_within_perception(Object* a1, Object* a2);\nvoid combatai_refresh_messages();\nvoid combatai_notify_onlookers(Object* a1);\nvoid combatai_notify_friends(Object* a1);\nvoid combatai_delete_critter(Object* obj);\n\n#endif /* FALLOUT_GAME_COMBATAI_H_ */\n"
  },
  {
    "path": "src/game/combatai_defs.h",
    "content": "#ifndef FALLOUT_GAME_COMBATAI_DEFS_H_\n#define FALLOUT_GAME_COMBATAI_DEFS_H_\n\ntypedef enum AreaAttackMode {\n    AREA_ATTACK_MODE_ALWAYS,\n    AREA_ATTACK_MODE_SOMETIMES,\n    AREA_ATTACK_MODE_BE_SURE,\n    AREA_ATTACK_MODE_BE_CAREFUL,\n    AREA_ATTACK_MODE_BE_ABSOLUTELY_SURE,\n    AREA_ATTACK_MODE_COUNT,\n} AreaAttackMode;\n\ntypedef enum RunAwayMode {\n    RUN_AWAY_MODE_NONE,\n    RUN_AWAY_MODE_COWARD,\n    RUN_AWAY_MODE_FINGER_HURTS,\n    RUN_AWAY_MODE_BLEEDING,\n    RUN_AWAY_MODE_NOT_FEELING_GOOD,\n    RUN_AWAY_MODE_TOURNIQUET,\n    RUN_AWAY_MODE_NEVER,\n    RUN_AWAY_MODE_COUNT,\n} RunAwayMode;\n\ntypedef enum BestWeapon {\n    BEST_WEAPON_NO_PREF,\n    BEST_WEAPON_MELEE,\n    BEST_WEAPON_MELEE_OVER_RANGED,\n    BEST_WEAPON_RANGED_OVER_MELEE,\n    BEST_WEAPON_RANGED,\n    BEST_WEAPON_UNARMED,\n    BEST_WEAPON_UNARMED_OVER_THROW,\n    BEST_WEAPON_RANDOM,\n    BEST_WEAPON_COUNT,\n} BestWeapon;\n\ntypedef enum DistanceMode {\n    DISTANCE_STAY_CLOSE,\n    DISTANCE_CHARGE,\n    DISTANCE_SNIPE,\n    DISTANCE_ON_YOUR_OWN,\n    DISTANCE_STAY,\n    DISTANCE_COUNT,\n} DistanceMode;\n\ntypedef enum AttackWho {\n    ATTACK_WHO_WHOMEVER_ATTACKING_ME,\n    ATTACK_WHO_STRONGEST,\n    ATTACK_WHO_WEAKEST,\n    ATTACK_WHO_WHOMEVER,\n    ATTACK_WHO_CLOSEST,\n    ATTACK_WHO_COUNT,\n} AttackWho;\n\ntypedef enum ChemUse {\n    CHEM_USE_CLEAN,\n    CHEM_USE_STIMS_WHEN_HURT_LITTLE,\n    CHEM_USE_STIMS_WHEN_HURT_LOTS,\n    CHEM_USE_SOMETIMES,\n    CHEM_USE_ANYTIME,\n    CHEM_USE_ALWAYS,\n    CHEM_USE_COUNT,\n} ChemUse;\n\ntypedef enum Disposition {\n    DISPOSITION_NONE,\n    DISPOSITION_CUSTOM,\n    DISPOSITION_COWARD,\n    DISPOSITION_DEFENSIVE,\n    DISPOSITION_AGGRESSIVE,\n    DISPOSITION_BERKSERK,\n    DISPOSITION_COUNT,\n} Disposition;\n\ntypedef enum HurtTooMuch {\n    HURT_BLIND,\n    HURT_CRIPPLED,\n    HURT_CRIPPLED_LEGS,\n    HURT_CRIPPLED_ARMS,\n    HURT_COUNT,\n} HurtTooMuch;\n\n#endif /* FALLOUT_GAME_COMBATAI_DEFS_H_ */\n"
  },
  {
    "path": "src/game/config.c",
    "content": "#include \"game/config.h\"\n\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/db/db.h\"\n#include \"plib/gnw/memory.h\"\n\n#define CONFIG_FILE_MAX_LINE_LENGTH 256\n\n// The initial number of sections (or key-value) pairs in the config.\n#define CONFIG_INITIAL_CAPACITY 10\n\nstatic bool config_parse_line(Config* config, char* string);\nstatic bool config_split_line(char* string, char* key, char* value);\nstatic bool config_add_section(Config* config, const char* sectionKey);\nstatic bool config_strip_white_space(char* string);\n\n// 0x42BD90\nbool config_init(Config* config)\n{\n    if (config == NULL) {\n        return false;\n    }\n\n    if (assoc_init(config, CONFIG_INITIAL_CAPACITY, sizeof(ConfigSection), NULL) != 0) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x42BDBC\nvoid config_exit(Config* config)\n{\n    if (config == NULL) {\n        return;\n    }\n\n    for (int sectionIndex = 0; sectionIndex < config->size; sectionIndex++) {\n        assoc_pair* sectionEntry = &(config->list[sectionIndex]);\n\n        ConfigSection* section = (ConfigSection*)sectionEntry->data;\n        for (int keyValueIndex = 0; keyValueIndex < section->size; keyValueIndex++) {\n            assoc_pair* keyValueEntry = &(section->list[keyValueIndex]);\n\n            char** value = (char**)keyValueEntry->data;\n            mem_free(*value);\n            *value = NULL;\n        }\n\n        assoc_free(section);\n    }\n\n    assoc_free(config);\n}\n\n// Parses command line argments and adds them into the config.\n//\n// The expected format of [argv] elements are \"[section]key=value\", otherwise\n// the element is silently ignored.\n//\n// NOTE: This function trims whitespace in key-value pair, but not in section.\n// I don't know if this is intentional or it's bug.\n//\n// 0x42BE38\nbool config_cmd_line_parse(Config* config, int argc, char** argv)\n{\n    if (config == NULL) {\n        return false;\n    }\n\n    for (int arg = 0; arg < argc; arg++) {\n        char* pch;\n        char* string = argv[arg];\n\n        // Find opening bracket.\n        pch = strchr(string, '[');\n        if (pch == NULL) {\n            continue;\n        }\n\n        char* sectionKey = pch + 1;\n\n        // Find closing bracket.\n        pch = strchr(sectionKey, ']');\n        if (pch == NULL) {\n            continue;\n        }\n\n        *pch = '\\0';\n\n        char key[260];\n        char value[260];\n        if (config_split_line(pch + 1, key, value)) {\n            if (!config_set_string(config, sectionKey, key, value)) {\n                *pch = ']';\n                return false;\n            }\n        }\n\n        *pch = ']';\n    }\n\n    return true;\n}\n\n// 0x42BF48\nbool config_get_string(Config* config, const char* sectionKey, const char* key, char** valuePtr)\n{\n    if (config == NULL || sectionKey == NULL || key == NULL || valuePtr == NULL) {\n        return false;\n    }\n\n    int sectionIndex = assoc_search(config, sectionKey);\n    if (sectionIndex == -1) {\n        return false;\n    }\n\n    assoc_pair* sectionEntry = &(config->list[sectionIndex]);\n    ConfigSection* section = (ConfigSection*)sectionEntry->data;\n\n    int index = assoc_search(section, key);\n    if (index == -1) {\n        return false;\n    }\n\n    assoc_pair* keyValueEntry = &(section->list[index]);\n    *valuePtr = *(char**)keyValueEntry->data;\n\n    return true;\n}\n\n// 0x42BF90\nbool config_set_string(Config* config, const char* sectionKey, const char* key, const char* value)\n{\n    if (config == NULL || sectionKey == NULL || key == NULL || value == NULL) {\n        return false;\n    }\n\n    int sectionIndex = assoc_search(config, sectionKey);\n    if (sectionIndex == -1) {\n        // FIXME: Looks like a bug, this function never returns -1, which will\n        // eventually lead to crash.\n        if (config_add_section(config, sectionKey) == -1) {\n            return false;\n        }\n        sectionIndex = assoc_search(config, sectionKey);\n    }\n\n    assoc_pair* sectionEntry = &(config->list[sectionIndex]);\n    ConfigSection* section = (ConfigSection*)sectionEntry->data;\n\n    int index = assoc_search(section, key);\n    if (index != -1) {\n        assoc_pair* keyValueEntry = &(section->list[index]);\n\n        char** existingValue = (char**)keyValueEntry->data;\n        mem_free(*existingValue);\n        *existingValue = NULL;\n\n        assoc_delete(section, key);\n    }\n\n    char* valueCopy = mem_strdup(value);\n    if (valueCopy == NULL) {\n        return false;\n    }\n\n    if (assoc_insert(section, key, &valueCopy) == -1) {\n        mem_free(valueCopy);\n        return false;\n    }\n\n    return true;\n}\n\n// 0x42C05C\nbool config_get_value(Config* config, const char* sectionKey, const char* key, int* valuePtr)\n{\n    if (valuePtr == NULL) {\n        return false;\n    }\n\n    char* stringValue;\n    if (!config_get_string(config, sectionKey, key, &stringValue)) {\n        return false;\n    }\n\n    *valuePtr = atoi(stringValue);\n\n    return true;\n}\n\n// 0x42C090\nbool config_get_values(Config* config, const char* sectionKey, const char* key, int* arr, int count)\n{\n    if (arr == NULL || count < 2) {\n        return false;\n    }\n\n    char* string;\n    if (!config_get_string(config, sectionKey, key, &string)) {\n        return false;\n    }\n\n    char temp[CONFIG_FILE_MAX_LINE_LENGTH];\n    string = strncpy(temp, string, CONFIG_FILE_MAX_LINE_LENGTH - 1);\n\n    while (1) {\n        char* pch = strchr(string, ',');\n        if (pch == NULL) {\n            break;\n        }\n\n        count--;\n        if (count == 0) {\n            break;\n        }\n\n        *pch = '\\0';\n        *arr++ = atoi(string);\n        string = pch + 1;\n    }\n\n    if (count <= 1) {\n        *arr = atoi(string);\n        return true;\n    }\n\n    return false;\n}\n\n// 0x42C160\nbool config_set_value(Config* config, const char* sectionKey, const char* key, int value)\n{\n    char stringValue[20];\n    itoa(value, stringValue, 10);\n\n    return config_set_string(config, sectionKey, key, stringValue);\n}\n\n// Reads .INI file into config.\n//\n// 0x42C280\nbool config_load(Config* config, const char* filePath, bool isDb)\n{\n    if (config == NULL || filePath == NULL) {\n        return false;\n    }\n\n    char string[CONFIG_FILE_MAX_LINE_LENGTH];\n\n    if (isDb) {\n        File* stream = db_fopen(filePath, \"rb\");\n        if (stream != NULL) {\n            while (db_fgets(string, sizeof(string), stream) != NULL) {\n                config_parse_line(config, string);\n            }\n            db_fclose(stream);\n        }\n    } else {\n        FILE* stream = fopen(filePath, \"rt\");\n        if (stream != NULL) {\n            while (fgets(string, sizeof(string), stream) != NULL) {\n                config_parse_line(config, string);\n            }\n\n            fclose(stream);\n        }\n\n        // FIXME: This function returns `true` even if the file was not actually\n        // read. I'm pretty sure it's bug.\n    }\n\n    return true;\n}\n\n// Writes config into .INI file.\n//\n// 0x42C324\nbool config_save(Config* config, const char* filePath, bool isDb)\n{\n    if (config == NULL || filePath == NULL) {\n        return false;\n    }\n\n    if (isDb) {\n        File* stream = db_fopen(filePath, \"wt\");\n        if (stream == NULL) {\n            return false;\n        }\n\n        for (int sectionIndex = 0; sectionIndex < config->size; sectionIndex++) {\n            assoc_pair* sectionEntry = &(config->list[sectionIndex]);\n            db_fprintf(stream, \"[%s]\\n\", sectionEntry->name);\n\n            ConfigSection* section = (ConfigSection*)sectionEntry->data;\n            for (int index = 0; index < section->size; index++) {\n                assoc_pair* keyValueEntry = &(section->list[index]);\n                db_fprintf(stream, \"%s=%s\\n\", keyValueEntry->name, *(char**)keyValueEntry->data);\n            }\n\n            db_fprintf(stream, \"\\n\");\n        }\n\n        db_fclose(stream);\n    } else {\n        FILE* stream = fopen(filePath, \"wt\");\n        if (stream == NULL) {\n            return false;\n        }\n\n        for (int sectionIndex = 0; sectionIndex < config->size; sectionIndex++) {\n            assoc_pair* sectionEntry = &(config->list[sectionIndex]);\n            fprintf(stream, \"[%s]\\n\", sectionEntry->name);\n\n            ConfigSection* section = (ConfigSection*)sectionEntry->data;\n            for (int index = 0; index < section->size; index++) {\n                assoc_pair* keyValueEntry = &(section->list[index]);\n                fprintf(stream, \"%s=%s\\n\", keyValueEntry->name, *(char**)keyValueEntry->data);\n            }\n\n            fprintf(stream, \"\\n\");\n        }\n\n        fclose(stream);\n    }\n\n    return true;\n}\n\n// Parses a line from .INI file into config.\n//\n// A line either contains a \"[section]\" section key or \"key=value\" pair. In the\n// first case section key is not added to config immediately, instead it is\n// stored in |section| for later usage. This prevents empty\n// sections in the config.\n//\n// In case of key-value pair it pretty straight forward - it adds key-value\n// pair into previously read section key stored in |section|.\n//\n// Returns `true` when a section was parsed or key-value pair was parsed and\n// added to the config, or `false` otherwise.\n//\n// 0x42C4BC\nstatic bool config_parse_line(Config* config, char* string)\n{\n    // 0x518224\n    static char section[CONFIG_FILE_MAX_LINE_LENGTH] = \"unknown\";\n\n    char* pch;\n\n    // Find comment marker and truncate the string.\n    pch = strchr(string, ';');\n    if (pch != NULL) {\n        *pch = '\\0';\n    }\n\n    // Find opening bracket.\n    pch = strchr(string, '[');\n    if (pch != NULL) {\n        char* sectionKey = pch + 1;\n\n        // Find closing bracket.\n        pch = strchr(sectionKey, ']');\n        if (pch != NULL) {\n            *pch = '\\0';\n            strcpy(section, sectionKey);\n            return config_strip_white_space(section);\n        }\n    }\n\n    char key[260];\n    char value[260];\n    if (!config_split_line(string, key, value)) {\n        return false;\n    }\n\n    return config_set_string(config, section, key, value);\n}\n\n// Splits \"key=value\" pair from [string] and copy appropriate parts into [key]\n// and [value] respectively.\n//\n// Both key and value are trimmed.\n//\n// 0x42C594\nstatic bool config_split_line(char* string, char* key, char* value)\n{\n    if (string == NULL || key == NULL || value == NULL) {\n        return false;\n    }\n\n    // Find equals character.\n    char* pch = strchr(string, '=');\n    if (pch == NULL) {\n        return false;\n    }\n\n    *pch = '\\0';\n\n    strcpy(key, string);\n    strcpy(value, pch + 1);\n\n    *pch = '=';\n\n    config_strip_white_space(key);\n    config_strip_white_space(value);\n\n    return true;\n}\n\n// Ensures the config has a section with specified key.\n//\n// Return `true` if section exists or it was successfully added, or `false`\n// otherwise.\n//\n// 0x42C638\nstatic bool config_add_section(Config* config, const char* sectionKey)\n{\n    if (config == NULL || sectionKey == NULL) {\n        return false;\n    }\n\n    if (assoc_search(config, sectionKey) != -1) {\n        // Section already exists, no need to do anything.\n        return true;\n    }\n\n    ConfigSection section;\n    if (assoc_init(&section, CONFIG_INITIAL_CAPACITY, sizeof(char**), NULL) == -1) {\n        return false;\n    }\n\n    if (assoc_insert(config, sectionKey, &section) == -1) {\n        return false;\n    }\n\n    return true;\n}\n\n// Removes leading and trailing whitespace from the specified string.\n//\n// 0x42C698\nstatic bool config_strip_white_space(char* string)\n{\n    if (string == NULL) {\n        return false;\n    }\n\n    int length = strlen(string);\n    if (length == 0) {\n        return true;\n    }\n\n    // Starting from the end of the string, loop while it's a whitespace and\n    // decrement string length.\n    char* pch = string + length - 1;\n    while (length != 0 && isspace(*pch)) {\n        length--;\n        pch--;\n    }\n\n    // pch now points to the last non-whitespace character.\n    pch[1] = '\\0';\n\n    // Starting from the beginning of the string loop while it's a whitespace\n    // and decrement string length.\n    pch = string;\n    while (isspace(*pch)) {\n        pch++;\n        length--;\n    }\n\n    // pch now points for to the first non-whitespace character.\n    memmove(string, pch, length + 1);\n\n    return true;\n}\n\n// 0x42C718\nbool config_get_double(Config* config, const char* sectionKey, const char* key, double* valuePtr)\n{\n    if (valuePtr == NULL) {\n        return false;\n    }\n\n    char* stringValue;\n    if (!config_get_string(config, sectionKey, key, &stringValue)) {\n        return false;\n    }\n\n    *valuePtr = strtod(stringValue, NULL);\n\n    return true;\n}\n\n// 0x42C74C\nbool config_set_double(Config* config, const char* sectionKey, const char* key, double value)\n{\n    char stringValue[32];\n    sprintf(stringValue, \"%.6f\", value);\n\n    return config_set_string(config, sectionKey, key, stringValue);\n}\n\n// NOTE: Boolean-typed variant of [config_get_value].\nbool configGetBool(Config* config, const char* sectionKey, const char* key, bool* valuePtr)\n{\n    if (valuePtr == NULL) {\n        return false;\n    }\n\n    int integerValue;\n    if (!config_get_value(config, sectionKey, key, &integerValue)) {\n        return false;\n    }\n\n    *valuePtr = integerValue != 0;\n\n    return true;\n}\n\n// NOTE: Boolean-typed variant of [configGetInt].\nbool configSetBool(Config* config, const char* sectionKey, const char* key, bool value)\n{\n    return config_set_value(config, sectionKey, key, value ? 1 : 0);\n}\n"
  },
  {
    "path": "src/game/config.h",
    "content": "#ifndef FALLOUT_GAME_CONFIG_H_\n#define FALLOUT_GAME_CONFIG_H_\n\n#include <stdbool.h>\n\n#include \"plib/assoc/assoc.h\"\n\n// A representation of .INI file.\n//\n// It's implemented as a [assoc_array] whos keys are section names of .INI file,\n// and it's values are [ConfigSection] structs.\ntypedef assoc_array Config;\n\n// Representation of .INI section.\n//\n// It's implemented as a [assoc_array] whos keys are names of .INI file\n// key-pair values, and it's values are pointers to strings (char**).\ntypedef assoc_array ConfigSection;\n\nbool config_init(Config* config);\nvoid config_exit(Config* config);\nbool config_cmd_line_parse(Config* config, int argc, char** argv);\nbool config_get_string(Config* config, const char* sectionKey, const char* key, char** valuePtr);\nbool config_set_string(Config* config, const char* sectionKey, const char* key, const char* value);\nbool config_get_value(Config* config, const char* sectionKey, const char* key, int* valuePtr);\nbool config_get_values(Config* config, const char* section, const char* key, int* arr, int count);\nbool config_set_value(Config* config, const char* sectionKey, const char* key, int value);\nbool config_load(Config* config, const char* filePath, bool isDb);\nbool config_save(Config* config, const char* filePath, bool isDb);\nbool config_get_double(Config* config, const char* sectionKey, const char* key, double* valuePtr);\nbool config_set_double(Config* config, const char* sectionKey, const char* key, double value);\n\n// TODO: Remove.\nbool configGetBool(Config* config, const char* sectionKey, const char* key, bool* valuePtr);\nbool configSetBool(Config* config, const char* sectionKey, const char* key, bool value);\n\n#endif /* FALLOUT_GAME_CONFIG_H_ */\n"
  },
  {
    "path": "src/game/counter.c",
    "content": "#include \"game/counter.h\"\n\n#include <time.h>\n\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/debug.h\"\n\nstatic void counter();\n\n// 0x518324\nstatic int counter_is_on = 0;\n\n// 0x518328\nstatic unsigned char count = 0;\n\n// 0x51832C\nstatic clock_t last_time = 0;\n\n// 0x518330\nstatic CounterOutputFunc* counter_output_func;\n\n// 0x42C790\nvoid counter_on(CounterOutputFunc* outputFunc)\n{\n    if (!counter_is_on) {\n        debug_printf(\"Turning on counter...\\n\");\n        add_bk_process(counter);\n        counter_output_func = outputFunc;\n        counter_is_on = 1;\n        last_time = clock();\n    }\n}\n\n// 0x42C7D4\nvoid counter_off()\n{\n    if (counter_is_on) {\n        remove_bk_process(counter);\n        counter_is_on = 0;\n    }\n}\n\n// 0x42C7F4\nstatic void counter()\n{\n    // 0x56D730\n    static clock_t this_time;\n\n    count++;\n    if (count == 0) {\n        this_time = clock();\n        if (counter_output_func != NULL) {\n            counter_output_func(256.0 / (this_time - last_time) / 100.0);\n        }\n        last_time = this_time;\n    }\n}\n"
  },
  {
    "path": "src/game/counter.h",
    "content": "#ifndef FALLOUT_GAME_COUNTER_H_\n#define FALLOUT_GAME_COUNTER_H_\n\ntypedef void(CounterOutputFunc)(double a1);\n\nvoid counter_on(CounterOutputFunc* outputFunc);\nvoid counter_off();\n\n#endif /* FALLOUT_GAME_COUNTER_H_ */\n"
  },
  {
    "path": "src/game/credits.c",
    "content": "#include \"game/credits.h\"\n\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/cycle.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/gmouse.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/palette.h\"\n#include \"int/sound.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define CREDITS_WINDOW_WIDTH 640\n#define CREDITS_WINDOW_HEIGHT 480\n#define CREDITS_WINDOW_SCROLLING_DELAY 38\n\nstatic bool credits_get_next_line(char* dest, int* font, int* color);\n\n// 0x56D740\nstatic File* credits_file;\n\n// 0x56D744\nstatic int name_color;\n\n// 0x56D748\nstatic int title_font;\n\n// 0x56D74C\nstatic int name_font;\n\n// 0x56D750\nstatic int title_color;\n\n// 0x42C860\nvoid credits(const char* filePath, int backgroundFid, bool useReversedStyle)\n{\n    int oldFont = text_curr();\n\n    loadColorTable(\"color.pal\");\n\n    if (useReversedStyle) {\n        title_color = colorTable[18917];\n        name_font = 103;\n        title_font = 104;\n        name_color = colorTable[13673];\n    } else {\n        title_color = colorTable[13673];\n        name_font = 104;\n        title_font = 103;\n        name_color = colorTable[18917];\n    }\n\n    soundContinueAll();\n\n    char localizedPath[MAX_PATH];\n    if (message_make_path(localizedPath, filePath)) {\n        credits_file = db_fopen(localizedPath, \"rt\");\n        if (credits_file != NULL) {\n            soundContinueAll();\n\n            cycle_disable();\n            gmouse_set_cursor(MOUSE_CURSOR_NONE);\n\n            bool cursorWasHidden = mouse_hidden();\n            if (cursorWasHidden) {\n                mouse_show();\n            }\n\n            int creditsWindowX = 0;\n            int creditsWindowY = 0;\n            int window = win_add(creditsWindowX, creditsWindowY, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_HEIGHT, colorTable[0], 20);\n            soundContinueAll();\n            if (window != -1) {\n                unsigned char* windowBuffer = win_get_buf(window);\n                if (windowBuffer != NULL) {\n                    unsigned char* backgroundBuffer = (unsigned char*)mem_malloc(CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT);\n                    if (backgroundBuffer) {\n                        soundContinueAll();\n\n                        memset(backgroundBuffer, colorTable[0], CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT);\n\n                        if (backgroundFid != -1) {\n                            CacheEntry* backgroundFrmHandle;\n                            Art* frm = art_ptr_lock(backgroundFid, &backgroundFrmHandle);\n                            if (frm != NULL) {\n                                int width = art_frame_width(frm, 0, 0);\n                                int height = art_frame_length(frm, 0, 0);\n                                unsigned char* backgroundFrmData = art_frame_data(frm, 0, 0);\n                                buf_to_buf(backgroundFrmData,\n                                    width,\n                                    height,\n                                    width,\n                                    backgroundBuffer + CREDITS_WINDOW_WIDTH * ((CREDITS_WINDOW_HEIGHT - height) / 2) + (CREDITS_WINDOW_WIDTH - width) / 2,\n                                    CREDITS_WINDOW_WIDTH);\n                                art_ptr_unlock(backgroundFrmHandle);\n                            }\n                        }\n\n                        unsigned char* intermediateBuffer = (unsigned char*)mem_malloc(CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT);\n                        if (intermediateBuffer != NULL) {\n                            memset(intermediateBuffer, 0, CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT);\n\n                            text_font(title_font);\n                            int titleFontLineHeight = text_height();\n\n                            text_font(name_font);\n                            int nameFontLineHeight = text_height();\n\n                            int lineHeight = nameFontLineHeight + (titleFontLineHeight >= nameFontLineHeight ? titleFontLineHeight - nameFontLineHeight : 0);\n                            int stringBufferSize = CREDITS_WINDOW_WIDTH * lineHeight;\n                            unsigned char* stringBuffer = (unsigned char*)mem_malloc(stringBufferSize);\n                            if (stringBuffer != NULL) {\n                                buf_to_buf(backgroundBuffer,\n                                    CREDITS_WINDOW_WIDTH,\n                                    CREDITS_WINDOW_HEIGHT,\n                                    CREDITS_WINDOW_WIDTH,\n                                    windowBuffer,\n                                    CREDITS_WINDOW_WIDTH);\n\n                                win_draw(window);\n\n                                palette_fade_to(cmap);\n\n                                unsigned char* v40 = intermediateBuffer + CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH;\n                                char str[260];\n                                int font;\n                                int color;\n                                unsigned int tick = 0;\n                                bool stop = false;\n                                while (credits_get_next_line(str, &font, &color)) {\n                                    text_font(font);\n\n                                    int v19 = text_width(str);\n                                    if (v19 >= CREDITS_WINDOW_WIDTH) {\n                                        continue;\n                                    }\n\n                                    memset(stringBuffer, 0, stringBufferSize);\n                                    text_to_buf(stringBuffer, str, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_WIDTH, color);\n\n                                    unsigned char* dest = intermediateBuffer + CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH + (CREDITS_WINDOW_WIDTH - v19) / 2;\n                                    unsigned char* src = stringBuffer;\n                                    for (int index = 0; index < lineHeight; index++) {\n                                        if (get_input() != -1) {\n                                            stop = true;\n                                            break;\n                                        }\n\n                                        memmove(intermediateBuffer, intermediateBuffer + CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH);\n                                        memcpy(dest, src, v19);\n\n                                        buf_to_buf(backgroundBuffer,\n                                            CREDITS_WINDOW_WIDTH,\n                                            CREDITS_WINDOW_HEIGHT,\n                                            CREDITS_WINDOW_WIDTH,\n                                            windowBuffer,\n                                            CREDITS_WINDOW_WIDTH);\n\n                                        trans_buf_to_buf(intermediateBuffer,\n                                            CREDITS_WINDOW_WIDTH,\n                                            CREDITS_WINDOW_HEIGHT,\n                                            CREDITS_WINDOW_WIDTH,\n                                            windowBuffer,\n                                            CREDITS_WINDOW_WIDTH);\n\n                                        while (elapsed_time(tick) < CREDITS_WINDOW_SCROLLING_DELAY) {\n                                        }\n\n                                        tick = get_time();\n\n                                        win_draw(window);\n\n                                        src += CREDITS_WINDOW_WIDTH;\n                                    }\n\n                                    if (stop) {\n                                        break;\n                                    }\n                                }\n\n                                if (!stop) {\n                                    for (int index = 0; index < CREDITS_WINDOW_HEIGHT; index++) {\n                                        if (get_input() != -1) {\n                                            break;\n                                        }\n\n                                        memmove(intermediateBuffer, intermediateBuffer + CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH);\n                                        memset(intermediateBuffer + CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH, 0, CREDITS_WINDOW_WIDTH);\n\n                                        buf_to_buf(backgroundBuffer,\n                                            CREDITS_WINDOW_WIDTH,\n                                            CREDITS_WINDOW_HEIGHT,\n                                            CREDITS_WINDOW_WIDTH,\n                                            windowBuffer,\n                                            CREDITS_WINDOW_WIDTH);\n\n                                        trans_buf_to_buf(intermediateBuffer,\n                                            CREDITS_WINDOW_WIDTH,\n                                            CREDITS_WINDOW_HEIGHT,\n                                            CREDITS_WINDOW_WIDTH,\n                                            windowBuffer,\n                                            CREDITS_WINDOW_WIDTH);\n\n                                        while (elapsed_time(tick) < CREDITS_WINDOW_SCROLLING_DELAY) {\n                                        }\n\n                                        tick = get_time();\n\n                                        win_draw(window);\n                                    }\n                                }\n\n                                mem_free(stringBuffer);\n                            }\n                            mem_free(intermediateBuffer);\n                        }\n                        mem_free(backgroundBuffer);\n                    }\n                }\n\n                soundContinueAll();\n                palette_fade_to(black_palette);\n                soundContinueAll();\n                win_delete(window);\n            }\n\n            if (cursorWasHidden) {\n                mouse_hide();\n            }\n\n            gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n            cycle_enable();\n            db_fclose(credits_file);\n        }\n    }\n\n    text_font(oldFont);\n}\n\n// 0x42CE6C\nstatic bool credits_get_next_line(char* dest, int* font, int* color)\n{\n    char string[256];\n    while (db_fgets(string, 256, credits_file)) {\n        char* pch;\n        if (string[0] == ';') {\n            continue;\n        } else if (string[0] == '@') {\n            *font = title_font;\n            *color = title_color;\n            pch = string + 1;\n        } else if (string[0] == '#') {\n            *font = name_font;\n            *color = colorTable[17969];\n            pch = string + 1;\n        } else {\n            *font = name_font;\n            *color = name_color;\n            pch = string;\n        }\n\n        strcpy(dest, pch);\n\n        return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/game/credits.h",
    "content": "#ifndef CREDITS_H\n#define CREDITS_H\n\n#include <stdbool.h>\n\nvoid credits(const char* path, int fid, bool useReversedStyle);\n\n#endif /* CREDITS_H */\n"
  },
  {
    "path": "src/game/critter.c",
    "content": "#include \"game/critter.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"game/editor.h\"\n#include \"game/combat.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"game/endgame.h\"\n#include \"game/game.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/party.h\"\n#include \"game/proto.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/reaction.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n#include \"game/worldmap.h\"\n\nstatic int get_rad_damage_level(Object* obj, void* data);\nstatic int clear_rad_damage(Object* obj, void* data);\nstatic void process_rads(Object* obj, int radiationLevel, bool direction);\nstatic int critter_kill_count_clear();\nstatic int critterClearObjDrugs(Object* obj, void* data);\n\n// TODO: Remove.\n// 0x50141C\nchar _aCorpse[] = \"corpse\";\n\n// TODO: Remove.\n// 0x501494\nchar byte_501494[] = \"\";\n\n// List of stats affected by radiation.\n//\n// The values of this list specify stats that can be affected by radiation.\n// The amount of penalty to every stat (identified by index) is stored\n// separately in [rad_bonus] per radiation level.\n//\n// The order of stats is important - primary stats must be at the top. See\n// [RADIATION_EFFECT_PRIMARY_STAT_COUNT] for more info.\n//\n// 0x518358\nint rad_stat[RADIATION_EFFECT_COUNT] = {\n    STAT_STRENGTH,\n    STAT_PERCEPTION,\n    STAT_ENDURANCE,\n    STAT_CHARISMA,\n    STAT_INTELLIGENCE,\n    STAT_AGILITY,\n    STAT_CURRENT_HIT_POINTS,\n    STAT_HEALING_RATE,\n};\n\n// Denotes how many primary stats at the top of [rad_stat] array.\n// These stats are used to determine if critter is alive after applying\n// radiation effects.\n#define RADIATION_EFFECT_PRIMARY_STAT_COUNT 6\n\n// List of stat modifiers caused by radiation at different radiation levels.\n//\n// 0x518378\nint rad_bonus[RADIATION_LEVEL_COUNT][RADIATION_EFFECT_COUNT] = {\n    // clang-format off\n    {   0,   0,   0,   0,   0,   0,   0,   0 },\n    {  -1,   0,   0,   0,   0,   0,   0,   0 },\n    {  -1,   0,   0,   0,   0,  -1,   0,  -3 },\n    {  -2,   0,  -1,   0,   0,  -2,  -5,  -5 },\n    {  -4,  -3,  -3,  -3,  -1,  -5, -15, -10 },\n    {  -6,  -5,  -5,  -5,  -3,  -6, -20, -10 },\n    // clang-format on\n};\n\n// 0x518438\nstatic Object* critterClearObj = NULL;\n\n// scrname.msg\n//\n// 0x56D754\nstatic MessageList critter_scrmsg_file;\n\n// 0x56D75C\nstatic char pc_name[DUDE_NAME_MAX_LENGTH];\n\n// 0x56D77C\nstatic int sneak_working;\n\n// 0x56D780\nstatic int pc_kill_counts[KILL_TYPE_COUNT];\n\n// Something with radiation.\n//\n// 0x56D7CC\nstatic int old_rad_level;\n\n// scrname_init\n// 0x42CF50\nint critter_init()\n{\n    critter_pc_reset_name();\n\n    // NOTE: Uninline.\n    critter_kill_count_clear();\n\n    if (!message_init(&critter_scrmsg_file)) {\n        debug_printf(\"\\nError: Initing critter name message file!\");\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%sscrname.msg\", msg_path);\n\n    if (!message_load(&critter_scrmsg_file, path)) {\n        debug_printf(\"\\nError: Loading critter name message file!\");\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x42CFE4\nvoid critter_reset()\n{\n    critter_pc_reset_name();\n\n    // NOTE: Uninline;\n    critter_kill_count_clear();\n}\n\n// 0x42D004\nvoid critter_exit()\n{\n    message_exit(&critter_scrmsg_file);\n}\n\n// 0x42D01C\nint critter_load(File* stream)\n{\n    if (db_freadInt(stream, &sneak_working) == -1) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n\n    return critter_read_data(stream, &(proto->critter.data));\n}\n\n// 0x42D058\nint critter_save(File* stream)\n{\n    if (db_fwriteInt(stream, sneak_working) == -1) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n\n    return critter_write_data(stream, &(proto->critter.data));\n}\n\n// 0x42D094\nvoid critter_copy(CritterProtoData* dest, CritterProtoData* src)\n{\n    memcpy(dest, src, sizeof(CritterProtoData));\n}\n\n// 0x42D0A8\nchar* critter_name(Object* obj)\n{\n    // TODO: Rename.\n    // 0x51833C\n    static char* _name_critter = _aCorpse;\n\n    if (obj == obj_dude) {\n        return pc_name;\n    }\n\n    if (obj->field_80 == -1) {\n        if (obj->sid != -1) {\n            Script* script;\n            if (scr_ptr(obj->sid, &script) != -1) {\n                obj->field_80 = script->field_14;\n            }\n        }\n    }\n\n    char* name = NULL;\n    if (obj->field_80 != -1) {\n        MessageListItem messageListItem;\n        messageListItem.num = 101 + obj->field_80;\n        if (message_search(&critter_scrmsg_file, &messageListItem)) {\n            name = messageListItem.text;\n        }\n    }\n\n    if (name == NULL || *name == '\\0') {\n        name = proto_name(obj->pid);\n    }\n\n    _name_critter = name;\n\n    return name;\n}\n\n// 0x42D138\nint critter_pc_set_name(const char* name)\n{\n    if (strlen(name) <= DUDE_NAME_MAX_LENGTH) {\n        strncpy(pc_name, name, DUDE_NAME_MAX_LENGTH);\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x42D170\nvoid critter_pc_reset_name()\n{\n    strncpy(pc_name, \"None\", DUDE_NAME_MAX_LENGTH);\n}\n\n// 0x42D18C\nint critter_get_hits(Object* critter)\n{\n    return PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER ? critter->data.critter.hp : 0;\n}\n\n// 0x42D1A4\nint critter_adjust_hits(Object* critter, int hp)\n{\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    int maximumHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS);\n    int newHp = critter->data.critter.hp + hp;\n\n    critter->data.critter.hp = newHp;\n    if (maximumHp >= newHp) {\n        if (newHp <= 0 && (critter->data.critter.combat.results & DAM_DEAD) == 0) {\n            critter_kill(critter, -1, true);\n        }\n    } else {\n        critter->data.critter.hp = maximumHp;\n    }\n\n    return 0;\n}\n\n// 0x42D1F8\nint critter_get_poison(Object* critter)\n{\n    return PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER ? critter->data.critter.poison : 0;\n}\n\n// Adjust critter's current poison by specified amount.\n//\n// For unknown reason this function only works on dude.\n//\n// The [amount] can either be positive (adds poison) or negative (removes\n// poison).\n//\n// 0x42D210\nint critter_adjust_poison(Object* critter, int amount)\n{\n    MessageListItem messageListItem;\n\n    if (critter != obj_dude) {\n        return -1;\n    }\n\n    if (amount > 0) {\n        // Take poison resistance into account.\n        amount -= amount * critterGetStat(critter, STAT_POISON_RESISTANCE) / 100;\n    } else {\n        if (obj_dude->data.critter.poison <= 0) {\n            // Critter is not poisoned and we're want to decrease it even\n            // further, which makes no sense.\n            return 0;\n        }\n    }\n\n    int newPoison = critter->data.critter.poison + amount;\n    if (newPoison > 0) {\n        critter->data.critter.poison = newPoison;\n\n        queue_clear_type(EVENT_TYPE_POISON, NULL);\n        queue_add(10 * (505 - 5 * newPoison), obj_dude, NULL, EVENT_TYPE_POISON);\n\n        // You have been poisoned!\n        messageListItem.num = 3000;\n        if (amount < 0) {\n            // You feel a little better.\n            messageListItem.num = 3002;\n        }\n    } else {\n        critter->data.critter.poison = 0;\n\n        // You feel better.\n        messageListItem.num = 3003;\n    }\n\n    if (message_search(&misc_message_file, &messageListItem)) {\n        display_print(messageListItem.text);\n    }\n\n    if (critter == obj_dude) {\n        refresh_box_bar_win();\n    }\n\n    return 0;\n}\n\n// 0x42D318\nint critter_check_poison(Object* obj, void* data)\n{\n    if (obj != obj_dude) {\n        return 0;\n    }\n\n    critter_adjust_poison(obj, -2);\n    critter_adjust_hits(obj, -1);\n\n    intface_update_hit_points(false);\n\n    MessageListItem messageListItem;\n    // You take damage from poison.\n    messageListItem.num = 3001;\n    if (message_search(&misc_message_file, &messageListItem)) {\n        display_print(messageListItem.text);\n    }\n\n    // NOTE: Uninline.\n    int hitPoints = critter_get_hits(obj);\n    if (hitPoints > 5) {\n        return 0;\n    }\n\n    return 1;\n}\n\n// 0x42D38C\nint critter_get_rads(Object* obj)\n{\n    return PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER ? obj->data.critter.radiation : 0;\n}\n\n// 0x42D3A4\nint critter_adjust_rads(Object* obj, int amount)\n{\n    MessageListItem messageListItem;\n\n    if (obj != obj_dude) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n\n    if (amount > 0) {\n        amount -= critterGetStat(obj, STAT_RADIATION_RESISTANCE) * amount / 100;\n    }\n\n    if (amount > 0) {\n        proto->critter.data.flags |= CRITTER_BARTER;\n    }\n\n    if (amount > 0) {\n        Object* geigerCounter = NULL;\n\n        Object* item1 = inven_left_hand(obj_dude);\n        if (item1 != NULL) {\n            if (item1->pid == PROTO_ID_GEIGER_COUNTER_I || item1->pid == PROTO_ID_GEIGER_COUNTER_II) {\n                geigerCounter = item1;\n            }\n        }\n\n        Object* item2 = inven_right_hand(obj_dude);\n        if (item2 != NULL) {\n            if (item2->pid == PROTO_ID_GEIGER_COUNTER_I || item2->pid == PROTO_ID_GEIGER_COUNTER_II) {\n                geigerCounter = item2;\n            }\n        }\n\n        if (geigerCounter != NULL) {\n            if (item_m_on(geigerCounter)) {\n                if (amount > 5) {\n                    // The geiger counter is clicking wildly.\n                    messageListItem.num = 1009;\n                } else {\n                    // The geiger counter is clicking.\n                    messageListItem.num = 1008;\n                }\n\n                if (message_search(&misc_message_file, &messageListItem)) {\n                    display_print(messageListItem.text);\n                }\n            }\n        }\n    }\n\n    if (amount >= 10) {\n        // You have received a large dose of radiation.\n        messageListItem.num = 1007;\n\n        if (message_search(&misc_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n    }\n\n    obj->data.critter.radiation += amount;\n    if (obj->data.critter.radiation < 0) {\n        obj->data.critter.radiation = 0;\n    }\n\n    if (obj == obj_dude) {\n        refresh_box_bar_win();\n    }\n\n    return 0;\n}\n\n// 0x42D4F4\nint critter_check_rads(Object* obj)\n{\n    // Modifiers to endurance for performing radiation damage check.\n    //\n    // 0x518340\n    static int bonus[RADIATION_LEVEL_COUNT] = {\n        2,\n        0,\n        -2,\n        -4,\n        -6,\n        -8,\n    };\n\n    if (obj != obj_dude) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(obj->pid, &proto);\n    if ((proto->critter.data.flags & CRITTER_BARTER) == 0) {\n        return 0;\n    }\n\n    old_rad_level = 0;\n\n    queue_clear_type(EVENT_TYPE_RADIATION, get_rad_damage_level);\n\n    // NOTE: Uninline\n    int radiation = critter_get_rads(obj);\n\n    int radiationLevel;\n    if (radiation > 999)\n        radiationLevel = RADIATION_LEVEL_FATAL;\n    else if (radiation > 599)\n        radiationLevel = RADIATION_LEVEL_DEADLY;\n    else if (radiation > 399)\n        radiationLevel = RADIATION_LEVEL_CRITICAL;\n    else if (radiation > 199)\n        radiationLevel = RADIATION_LEVEL_ADVANCED;\n    else if (radiation > 99)\n        radiationLevel = RADIATION_LEVEL_MINOR;\n    else\n        radiationLevel = RADIATION_LEVEL_NONE;\n\n    if (stat_result(obj, STAT_ENDURANCE, bonus[radiationLevel], NULL) <= ROLL_FAILURE) {\n        radiationLevel++;\n    }\n\n    if (radiationLevel > old_rad_level) {\n        // Create timer event for applying radiation damage.\n        RadiationEvent* radiationEvent = (RadiationEvent*)mem_malloc(sizeof(*radiationEvent));\n        if (radiationEvent == NULL) {\n            return 0;\n        }\n\n        radiationEvent->radiationLevel = radiationLevel;\n        radiationEvent->isHealing = 0;\n        queue_add(GAME_TIME_TICKS_PER_HOUR * roll_random(4, 18), obj, radiationEvent, EVENT_TYPE_RADIATION);\n    }\n\n    proto->critter.data.flags &= ~(CRITTER_BARTER);\n\n    return 0;\n}\n\n// 0x42D618\nstatic int get_rad_damage_level(Object* obj, void* data)\n{\n    RadiationEvent* radiationEvent = (RadiationEvent*)data;\n\n    old_rad_level = radiationEvent->radiationLevel;\n\n    return 0;\n}\n\n// 0x42D624\nstatic int clear_rad_damage(Object* obj, void* data)\n{\n    RadiationEvent* radiationEvent = (RadiationEvent*)data;\n\n    if (radiationEvent->isHealing) {\n        process_rads(obj, radiationEvent->radiationLevel, true);\n    }\n\n    return 1;\n}\n\n// Applies radiation.\n//\n// 0x42D63C\nstatic void process_rads(Object* obj, int radiationLevel, bool isHealing)\n{\n    MessageListItem messageListItem;\n\n    if (radiationLevel == RADIATION_LEVEL_NONE) {\n        return;\n    }\n\n    int radiationLevelIndex = radiationLevel - 1;\n    int modifier = isHealing ? -1 : 1;\n\n    if (obj == obj_dude) {\n        // Radiation level message, higher is worse.\n        messageListItem.num = 1000 + radiationLevelIndex;\n        if (message_search(&misc_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n    }\n\n    for (int effect = 0; effect < RADIATION_EFFECT_COUNT; effect++) {\n        int value = stat_get_bonus(obj, rad_stat[effect]);\n        value += modifier * rad_bonus[radiationLevelIndex][effect];\n        stat_set_bonus(obj, rad_stat[effect], value);\n    }\n\n    if ((obj->data.critter.combat.results & DAM_DEAD) == 0) {\n        // Loop thru effects affecting primary stats. If any of the primary stat\n        // dropped below minimal value, kill it.\n        for (int effect = 0; effect < RADIATION_EFFECT_PRIMARY_STAT_COUNT; effect++) {\n            int base = stat_get_base(obj, rad_stat[effect]);\n            int bonus = stat_get_bonus(obj, rad_stat[effect]);\n            if (base + bonus < PRIMARY_STAT_MIN) {\n                critter_kill(obj, -1, 1);\n                break;\n            }\n        }\n    }\n\n    if ((obj->data.critter.combat.results & DAM_DEAD) != 0) {\n        if (obj == obj_dude) {\n            // You have died from radiation sickness.\n            messageListItem.num = 1006;\n            if (message_search(&misc_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n    }\n}\n\n// 0x42D740\nint critter_process_rads(Object* obj, void* data)\n{\n    RadiationEvent* radiationEvent = (RadiationEvent*)data;\n    if (!radiationEvent->isHealing) {\n        // Schedule healing stats event in 7 days.\n        RadiationEvent* newRadiationEvent = (RadiationEvent*)mem_malloc(sizeof(*newRadiationEvent));\n        if (newRadiationEvent != NULL) {\n            queue_clear_type(EVENT_TYPE_RADIATION, clear_rad_damage);\n            newRadiationEvent->radiationLevel = radiationEvent->radiationLevel;\n            newRadiationEvent->isHealing = 1;\n            queue_add(GAME_TIME_TICKS_PER_DAY * 7, obj, newRadiationEvent, EVENT_TYPE_RADIATION);\n        }\n    }\n\n    process_rads(obj, radiationEvent->radiationLevel, radiationEvent->isHealing);\n\n    return 1;\n}\n\n// 0x42D7A0\nint critter_load_rads(File* stream, void** dataPtr)\n{\n    RadiationEvent* radiationEvent = (RadiationEvent*)mem_malloc(sizeof(*radiationEvent));\n    if (radiationEvent == NULL) {\n        return -1;\n    }\n\n    if (db_freadInt(stream, &(radiationEvent->radiationLevel)) == -1) goto err;\n    if (db_freadInt(stream, &(radiationEvent->isHealing)) == -1) goto err;\n\n    *dataPtr = radiationEvent;\n    return 0;\n\nerr:\n\n    mem_free(radiationEvent);\n    return -1;\n}\n\n// 0x42D7FC\nint critter_save_rads(File* stream, void* data)\n{\n    RadiationEvent* radiationEvent = (RadiationEvent*)data;\n\n    if (db_fwriteInt(stream, radiationEvent->radiationLevel) == -1) return -1;\n    if (db_fwriteInt(stream, radiationEvent->isHealing) == -1) return -1;\n\n    return 0;\n}\n\n// 0x42D82C\nint critter_get_base_damage_type(Object* obj)\n{\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    Proto* proto;\n    if (proto_ptr(obj->pid, &proto) == -1) {\n        return 0;\n    }\n\n    return proto->critter.data.damageType;\n}\n\n// NOTE: Inlined.\n//\n// 0x42D860\nstatic int critter_kill_count_clear()\n{\n    memset(pc_kill_counts, 0, sizeof(pc_kill_counts));\n    return 0;\n}\n\n// 0x42D878\nint critter_kill_count_inc(int killType)\n{\n    if (killType != -1 && killType < KILL_TYPE_COUNT) {\n        pc_kill_counts[killType]++;\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x42D8A8\nint critter_kill_count(int killType)\n{\n    if (killType != -1 && killType < KILL_TYPE_COUNT) {\n        return pc_kill_counts[killType];\n    }\n\n    return 0;\n}\n\n// 0x42D8C0\nint critter_kill_count_load(File* stream)\n{\n    if (db_freadIntCount(stream, pc_kill_counts, KILL_TYPE_COUNT) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x42D8F0\nint critter_kill_count_save(File* stream)\n{\n    if (db_fwriteIntCount(stream, pc_kill_counts, KILL_TYPE_COUNT) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x42D920\nint critterGetKillType(Object* obj)\n{\n    if (obj == obj_dude) {\n        int gender = critterGetStat(obj, STAT_GENDER);\n        if (gender == GENDER_FEMALE) {\n            return KILL_TYPE_WOMAN;\n        }\n        return KILL_TYPE_MAN;\n    }\n\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(obj->pid, &proto);\n\n    return proto->critter.data.killType;\n}\n\n// 0x42D974\nchar* critter_kill_name(int killType)\n{\n    if (killType != -1 && killType < KILL_TYPE_COUNT) {\n        if (killType >= 0 && killType < KILL_TYPE_COUNT) {\n            MessageListItem messageListItem;\n            return getmsg(&proto_main_msg_file, &messageListItem, 1450 + killType);\n        } else {\n            return NULL;\n        }\n    } else {\n        return byte_501494;\n    }\n}\n\n// 0x42D9B4\nchar* critter_kill_info(int killType)\n{\n    if (killType != -1 && killType < KILL_TYPE_COUNT) {\n        if (killType >= 0 && killType < KILL_TYPE_COUNT) {\n            MessageListItem messageListItem;\n            return getmsg(&proto_main_msg_file, &messageListItem, 1469 + killType);\n        } else {\n            return NULL;\n        }\n    } else {\n        return byte_501494;\n    }\n}\n\n// 0x42D9F4\nint critter_heal_hours(Object* critter, int a2)\n{\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return -1;\n    }\n\n    if (critter->data.critter.hp < critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS)) {\n        critter_adjust_hits(critter, 14 * (a2 / 3));\n    }\n\n    critter->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n\n    return 0;\n}\n\n// 0x42DA54\nstatic int critterClearObjDrugs(Object* obj, void* data)\n{\n    return obj == critterClearObj;\n}\n\n// 0x42DA64\nvoid critter_kill(Object* critter, int anim, bool a3)\n{\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    int elevation = critter->elevation;\n\n    partyMemberRemove(critter);\n\n    // NOTE: Original code uses goto to jump out from nested conditions below.\n    bool shouldChangeFid = false;\n    int fid;\n    if (critter_is_prone(critter)) {\n        int current = FID_ANIM_TYPE(critter->fid);\n        if (current == ANIM_FALL_BACK || current == ANIM_FALL_FRONT) {\n            bool back = false;\n            if (current == ANIM_FALL_BACK) {\n                back = true;\n            } else {\n                fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_FRONT_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);\n                if (!art_exists(fid)) {\n                    back = true;\n                }\n            }\n\n            if (back) {\n                fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_BACK_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);\n            }\n\n            shouldChangeFid = true;\n        }\n    } else {\n        if (anim < 0) {\n            anim = LAST_SF_DEATH_ANIM;\n        }\n\n        if (anim > LAST_SF_DEATH_ANIM) {\n            debug_printf(\"\\nError: Critter Kill: death_frame out of range!\");\n            anim = LAST_SF_DEATH_ANIM;\n        }\n\n        fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1);\n        obj_fix_violence_settings(&fid);\n        if (!art_exists(fid)) {\n            debug_printf(\"\\nError: Critter Kill: Can't match fid!\");\n\n            fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_BACK_BLOOD_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1);\n            obj_fix_violence_settings(&fid);\n        }\n\n        shouldChangeFid = true;\n    }\n\n    Rect updatedRect;\n    Rect tempRect;\n\n    if (shouldChangeFid) {\n        obj_set_frame(critter, 0, &updatedRect);\n\n        obj_change_fid(critter, fid, &tempRect);\n        rect_min_bound(&updatedRect, &tempRect, &updatedRect);\n    }\n\n    if (!critter_flag_check(critter->pid, CRITTER_FLAT)) {\n        critter->flags |= OBJECT_NO_BLOCK;\n        obj_toggle_flat(critter, &tempRect);\n    }\n\n    // NOTE: using uninitialized updatedRect/tempRect if fid was not set.\n\n    rect_min_bound(&updatedRect, &tempRect, &updatedRect);\n\n    obj_turn_off_light(critter, &tempRect);\n    rect_min_bound(&updatedRect, &tempRect, &updatedRect);\n\n    critter->data.critter.hp = 0;\n    critter->data.critter.combat.results |= DAM_DEAD;\n\n    if (critter->sid != -1) {\n        scr_remove(critter->sid);\n        critter->sid = -1;\n    }\n\n    critterClearObj = critter;\n    queue_clear_type(EVENT_TYPE_DRUG, critterClearObjDrugs);\n\n    item_destroy_all_hidden(critter);\n\n    if (a3) {\n        tile_refresh_rect(&updatedRect, elevation);\n    }\n\n    if (critter == obj_dude) {\n        endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH);\n        game_user_wants_to_quit = 2;\n    }\n}\n\n// Returns experience for killing [critter].\n//\n// 0x42DCB8\nint critter_kill_exps(Object* critter)\n{\n    Proto* proto;\n    proto_ptr(critter->pid, &proto);\n    return proto->critter.data.experience;\n}\n\n// 0x42DCDC\nbool critter_is_active(Object* critter)\n{\n    if (critter == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if ((critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD)) != 0) {\n        return false;\n    }\n\n    if ((critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) {\n        return false;\n    }\n\n    return (critter->data.critter.combat.results & DAM_DEAD) == 0;\n}\n\n// 0x42DD18\nbool critter_is_dead(Object* critter)\n{\n    if (critter == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) <= 0) {\n        return true;\n    }\n\n    if ((critter->data.critter.combat.results & DAM_DEAD) != 0) {\n        return true;\n    }\n\n    return false;\n}\n\n// 0x42DD58\nbool critter_is_crippled(Object* critter)\n{\n    if (critter == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    return (critter->data.critter.combat.results & DAM_CRIP) != 0;\n}\n\n// 0x42DD80\nbool critter_is_prone(Object* critter)\n{\n    if (critter == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    int anim = FID_ANIM_TYPE(critter->fid);\n\n    return (critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0\n        || (anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM)\n        || (anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM);\n}\n\n// critter_body_type\n// 0x42DDC4\nint critter_body_type(Object* critter)\n{\n    if (critter == NULL) {\n        debug_printf(\"\\nError: critter_body_type: pobj was NULL!\");\n        return 0;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(critter->pid, &proto);\n    return proto->critter.data.bodyType;\n}\n\n// NOTE: Unused.\n//\n// 0x42DE10\nint critter_load_data(CritterProtoData* critterData, const char* path)\n{\n    File* stream;\n\n    stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    if (critter_read_data(stream, critterData) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fclose(stream);\n    return 0;\n}\n\n// 0x42DE58\nint pc_load_data(const char* path)\n{\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n\n    if (critter_read_data(stream, &(proto->critter.data)) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fread(pc_name, DUDE_NAME_MAX_LENGTH, 1, stream);\n\n    if (skill_load(stream) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    if (trait_load(stream) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    if (db_freadInt(stream, &character_points) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    proto->critter.data.baseStats[STAT_DAMAGE_RESISTANCE_EMP] = 100;\n    proto->critter.data.bodyType = 0;\n    proto->critter.data.experience = 0;\n    proto->critter.data.killType = 0;\n\n    db_fclose(stream);\n    return 0;\n}\n\n// 0x42DF70\nint critter_read_data(File* stream, CritterProtoData* critterData)\n{\n    if (db_freadInt(stream, &(critterData->flags)) == -1) return -1;\n    if (db_freadIntCount(stream, critterData->baseStats, SAVEABLE_STAT_COUNT) == -1) return -1;\n    if (db_freadIntCount(stream, critterData->bonusStats, SAVEABLE_STAT_COUNT) == -1) return -1;\n    if (db_freadIntCount(stream, critterData->skills, SKILL_COUNT) == -1) return -1;\n    if (db_freadInt(stream, &(critterData->bodyType)) == -1) return -1;\n    if (db_freadInt(stream, &(critterData->experience)) == -1) return -1;\n    if (db_freadInt(stream, &(critterData->killType)) == -1) return -1;\n\n    // NOTE: For unknown reason damage type is not present in two protos: Sentry\n    // Bot and Weak Brahmin. These two protos are 412 bytes, not 416.\n    //\n    // Given that only Floating Eye Bot, Floater, and Nasty Floater have\n    // natural damage type other than normal, I think addition of natural\n    // damage type as a feature was a last minute design decision. Most protos\n    // were updated, but not all. Another suggestion is that some team member\n    // used outdated toolset to build those two protos (mapper or whatever\n    // they used to create protos in the first place).\n    //\n    // Regardless of the reason, damage type is considered optional by original\n    // code as seen at 0x42E01B.\n    if (db_freadInt(stream, &(critterData->damageType)) == -1) {\n        critterData->damageType = DAMAGE_TYPE_NORMAL;\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x42E044\nint critter_save_data(CritterProtoData* critterData, const char* path)\n{\n    File* stream;\n\n    stream = db_fopen(path, \"wb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    if (critter_write_data(stream, critterData) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fclose(stream);\n    return 0;\n}\n\n// 0x42E08C\nint pc_save_data(const char* path)\n{\n    File* stream = db_fopen(path, \"wb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n\n    if (critter_write_data(stream, &(proto->critter.data)) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fwrite(pc_name, DUDE_NAME_MAX_LENGTH, 1, stream);\n\n    if (skill_save(stream) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    if (trait_save(stream) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    if (db_fwriteInt(stream, character_points) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fclose(stream);\n    return 0;\n}\n\n// 0x42E174\nint critter_write_data(File* stream, CritterProtoData* critterData)\n{\n    if (db_fwriteInt(stream, critterData->flags) == -1) return -1;\n    if (db_fwriteIntCount(stream, critterData->baseStats, SAVEABLE_STAT_COUNT) == -1) return -1;\n    if (db_fwriteIntCount(stream, critterData->bonusStats, SAVEABLE_STAT_COUNT) == -1) return -1;\n    if (db_fwriteIntCount(stream, critterData->skills, SKILL_COUNT) == -1) return -1;\n    if (db_fwriteInt(stream, critterData->bodyType) == -1) return -1;\n    if (db_fwriteInt(stream, critterData->experience) == -1) return -1;\n    if (db_fwriteInt(stream, critterData->killType) == -1) return -1;\n    if (db_fwriteInt(stream, critterData->damageType) == -1) return -1;\n\n    return 0;\n}\n\n// 0x42E220\nvoid pc_flag_off(int state)\n{\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n\n    proto->critter.data.flags &= ~(1 << state);\n\n    if (state == DUDE_STATE_SNEAKING) {\n        queue_remove_this(obj_dude, EVENT_TYPE_SNEAK);\n    }\n\n    refresh_box_bar_win();\n}\n\n// 0x42E26C\nvoid pc_flag_on(int state)\n{\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n\n    proto->critter.data.flags |= (1 << state);\n\n    if (state == DUDE_STATE_SNEAKING) {\n        critter_sneak_check(NULL, NULL);\n    }\n\n    refresh_box_bar_win();\n}\n\n// 0x42E2B0\nvoid pc_flag_toggle(int state)\n{\n    // NOTE: Uninline.\n    if (is_pc_flag(state)) {\n        pc_flag_off(state);\n    } else {\n        pc_flag_on(state);\n    }\n}\n\n// 0x42E2F8\nbool is_pc_flag(int state)\n{\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n    return (proto->critter.data.flags & (1 << state)) != 0;\n}\n\n// 0x42E32C\nint critter_sneak_check(Object* obj, void* data)\n{\n    int time;\n\n    int sneak = skill_level(obj_dude, SKILL_SNEAK);\n    if (skill_result(obj_dude, SKILL_SNEAK, 0, NULL) < ROLL_SUCCESS) {\n        time = 600;\n        sneak_working = false;\n\n        if (sneak > 250)\n            time = 100;\n        else if (sneak > 200)\n            time = 120;\n        else if (sneak > 170)\n            time = 150;\n        else if (sneak > 135)\n            time = 200;\n        else if (sneak > 100)\n            time = 300;\n        else if (sneak > 80)\n            time = 400;\n    } else {\n        time = 600;\n        sneak_working = true;\n    }\n\n    queue_add(time, obj_dude, NULL, EVENT_TYPE_SNEAK);\n\n    return 0;\n}\n\n// 0x42E3E4\nint critter_sneak_clear(Object* obj, void* data)\n{\n    pc_flag_off(DUDE_STATE_SNEAKING);\n    return 1;\n}\n\n// Returns true if dude is really sneaking.\n//\n// 0x42E3F4\nbool is_pc_sneak_working()\n{\n    // NOTE: Uninline.\n    if (is_pc_flag(DUDE_STATE_SNEAKING)) {\n        return sneak_working;\n    }\n\n    return false;\n}\n\n// 0x42E424\nint critter_wake_up(Object* obj, void* data)\n{\n    if ((obj->data.critter.combat.results & DAM_DEAD) != 0) {\n        return 0;\n    }\n\n    obj->data.critter.combat.results &= ~(DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN);\n    obj->data.critter.combat.results |= DAM_KNOCKED_DOWN;\n\n    if (isInCombat()) {\n        obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01;\n    } else {\n        dude_standup(obj);\n    }\n\n    return 0;\n}\n\n// 0x42E460\nint critter_wake_clear(Object* obj, void* data)\n{\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    if ((obj->data.critter.combat.results & DAM_DEAD) != 0) {\n        return 0;\n    }\n\n    obj->data.critter.combat.results &= ~(DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN);\n\n    int fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_STAND, (obj->fid & 0xF000) >> 12, obj->rotation + 1);\n    obj_change_fid(obj, fid, 0);\n\n    return 0;\n}\n\n// 0x42E4C0\nint critter_set_who_hit_me(Object* a1, Object* a2)\n{\n    if (a1 == NULL) {\n        return -1;\n    }\n\n    if (a2 != NULL && FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) {\n        return -1;\n    }\n\n    if (PID_TYPE(a1->pid) == OBJ_TYPE_CRITTER) {\n        if (a2 == NULL || a1->data.critter.combat.team != a2->data.critter.combat.team || (stat_result(a1, STAT_INTELLIGENCE, -1, NULL) < 2 && (!isPartyMember(a1) || !isPartyMember(a2)))) {\n            a1->data.critter.combat.whoHitMe = a2;\n            if (a2 == obj_dude) {\n                reaction_set(a1, -3);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x42E564\nbool critter_can_obj_dude_rest()\n{\n    bool v1 = false;\n    if (!wmMapCanRestHere(map_elevation)) {\n        v1 = true;\n    }\n\n    bool result = true;\n\n    Object** critterList;\n    int critterListLength = obj_create_list(-1, map_elevation, OBJ_TYPE_CRITTER, &critterList);\n\n    // TODO: Check conditions in this loop.\n    for (int index = 0; index < critterListLength; index++) {\n        Object* critter = critterList[index];\n        if ((critter->data.critter.combat.results & DAM_DEAD) != 0) {\n            continue;\n        }\n\n        if (critter == obj_dude) {\n            continue;\n        }\n\n        if (critter->data.critter.combat.whoHitMe != obj_dude) {\n            if (!v1 || critter->data.critter.combat.team == obj_dude->data.critter.combat.team) {\n                continue;\n            }\n        }\n\n        result = false;\n        break;\n    }\n\n    if (critterListLength != 0) {\n        obj_delete_list(critterList);\n    }\n\n    return result;\n}\n\n// 0x42E62C\nint critter_compute_ap_from_distance(Object* critter, int actionPoints)\n{\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    int flags = critter->data.critter.combat.results;\n    if ((flags & DAM_CRIP_LEG_LEFT) != 0 && (flags & DAM_CRIP_LEG_RIGHT) != 0) {\n        return 8 * actionPoints;\n    } else if ((flags & DAM_CRIP_LEG_ANY) != 0) {\n        return 4 * actionPoints;\n    } else {\n        return actionPoints;\n    }\n}\n\n// 0x42E66C\nbool critterIsOverloaded(Object* critter)\n{\n    int maxWeight = critterGetStat(critter, STAT_CARRY_WEIGHT);\n    int currentWeight = item_total_weight(critter);\n    return maxWeight < currentWeight;\n}\n\n// 0x42E690\nbool critter_is_fleeing(Object* critter)\n{\n    return critter != NULL\n        ? (critter->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0\n        : false;\n}\n\n// Checks proto critter flag.\n//\n// 0x42E6AC\nbool critter_flag_check(int pid, int flag)\n{\n    if (pid == -1) {\n        return false;\n    }\n\n    if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    Proto* proto;\n    proto_ptr(pid, &proto);\n    return (proto->critter.data.flags & flag) != 0;\n}\n\n// NOTE: Unused.\n//\n// 0x42E6F0\nvoid critter_flag_set(int pid, int flag)\n{\n    Proto* proto;\n\n    if (pid == -1) {\n        return;\n    }\n\n    if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    proto_ptr(pid, &proto);\n\n    proto->critter.data.flags |= flag;\n}\n\n// NOTE: Unused.\n//\n// 0x42E71C\nvoid critter_flag_unset(int pid, int flag)\n{\n    Proto* proto;\n\n    if (pid == -1) {\n        return;\n    }\n\n    if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    proto_ptr(pid, &proto);\n\n    proto->critter.data.flags &= ~flag;\n}\n\n// NOTE: Unused.\n//\n// 0x42E74C\nvoid critter_flag_toggle(int pid, int flag)\n{\n    Proto* proto;\n\n    if (pid == -1) {\n        return;\n    }\n\n    if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    proto_ptr(pid, &proto);\n\n    proto->critter.data.flags ^= flag;\n}\n"
  },
  {
    "path": "src/game/critter.h",
    "content": "#ifndef FALLOUT_GAME_CRITTER_H_\n#define FALLOUT_GAME_CRITTER_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/object_types.h\"\n#include \"game/proto_types.h\"\n\n// Maximum length of dude's name length.\n#define DUDE_NAME_MAX_LENGTH 32\n\n// The number of effects caused by radiation.\n//\n// A radiation effect is an identifier and does not have it's own name. It's\n// stat is specified in [rad_stat], and it's amount is specified\n// in [rad_bonus] for every [RadiationLevel].\n#define RADIATION_EFFECT_COUNT 8\n\n// Radiation levels.\n//\n// The names of levels are taken from Fallout 3, comments from Fallout 2.\ntypedef enum RadiationLevel {\n    // Very nauseous.\n    RADIATION_LEVEL_NONE,\n\n    // Slightly fatigued.\n    RADIATION_LEVEL_MINOR,\n\n    // Vomiting does not stop.\n    RADIATION_LEVEL_ADVANCED,\n\n    // Hair is falling out.\n    RADIATION_LEVEL_CRITICAL,\n\n    // Skin is falling off.\n    RADIATION_LEVEL_DEADLY,\n\n    // Intense agony.\n    RADIATION_LEVEL_FATAL,\n\n    // The number of radiation levels.\n    RADIATION_LEVEL_COUNT,\n} RadiationLevel;\n\ntypedef enum DudeState {\n    DUDE_STATE_SNEAKING = 0,\n    DUDE_STATE_LEVEL_UP_AVAILABLE = 3,\n    DUDE_STATE_ADDICTED = 4,\n} DudeState;\n\nextern int rad_stat[RADIATION_EFFECT_COUNT];\nextern int rad_bonus[RADIATION_LEVEL_COUNT][RADIATION_EFFECT_COUNT];\n\nint critter_init();\nvoid critter_reset();\nvoid critter_exit();\nint critter_load(File* stream);\nint critter_save(File* stream);\nchar* critter_name(Object* obj);\nvoid critter_copy(CritterProtoData* dest, CritterProtoData* src);\nint critter_pc_set_name(const char* name);\nvoid critter_pc_reset_name();\nint critter_get_hits(Object* critter);\nint critter_adjust_hits(Object* critter, int hp);\nint critter_get_poison(Object* critter);\nint critter_adjust_poison(Object* obj, int amount);\nint critter_check_poison(Object* obj, void* data);\nint critter_get_rads(Object* critter);\nint critter_adjust_rads(Object* obj, int amount);\nint critter_check_rads(Object* critter);\nint critter_process_rads(Object* obj, void* data);\nint critter_load_rads(File* stream, void** dataPtr);\nint critter_save_rads(File* stream, void* data);\nint critter_get_base_damage_type(Object* critter);\nint critter_kill_count_inc(int killType);\nint critter_kill_count(int killType);\nint critter_kill_count_load(File* stream);\nint critter_kill_count_save(File* stream);\nint critterGetKillType(Object* critter);\nchar* critter_kill_name(int killType);\nchar* critter_kill_info(int killType);\nint critter_heal_hours(Object* obj, int a2);\nvoid critter_kill(Object* critter, int anim, bool a3);\nint critter_kill_exps(Object* critter);\nbool critter_is_active(Object* critter);\nbool critter_is_dead(Object* critter);\nbool critter_is_crippled(Object* critter);\nbool critter_is_prone(Object* critter);\nint critter_body_type(Object* critter);\nint critter_load_data(CritterProtoData* critterData, const char* path);\nint pc_load_data(const char* path);\nint critter_read_data(File* stream, CritterProtoData* critterData);\nint critter_save_data(CritterProtoData* critterData, const char* path);\nint pc_save_data(const char* path);\nint critter_write_data(File* stream, CritterProtoData* critterData);\nvoid pc_flag_off(int state);\nvoid pc_flag_on(int state);\nvoid pc_flag_toggle(int state);\nbool is_pc_flag(int state);\nint critter_sneak_check(Object* obj, void* data);\nint critter_sneak_clear(Object* obj, void* data);\nbool is_pc_sneak_working();\nint critter_wake_up(Object* obj, void* data);\nint critter_wake_clear(Object* obj, void* data);\nint critter_set_who_hit_me(Object* a1, Object* a2);\nbool critter_can_obj_dude_rest();\nint critter_compute_ap_from_distance(Object* critter, int a2);\nbool critterIsOverloaded(Object* critter);\nbool critter_is_fleeing(Object* a1);\nbool critter_flag_check(int pid, int flag);\nvoid critter_flag_set(int pid, int flag);\nvoid critter_flag_unset(int pid, int flag);\nvoid critter_flag_toggle(int pid, int flag);\n\n#endif /* FALLOUT_GAME_CRITTER_H_ */\n"
  },
  {
    "path": "src/game/cycle.c",
    "content": "#include \"game/cycle.h\"\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/gconfig.h\"\n#include \"game/palette.h\"\n\n#define COLOR_CYCLE_PERIOD_SLOW 200U\n#define COLOR_CYCLE_PERIOD_MEDIUM 142U\n#define COLOR_CYCLE_PERIOD_FAST 100U\n#define COLOR_CYCLE_PERIOD_VERY_FAST 33U\n\nstatic void cycle_colors();\n\n// 0x51843C\nstatic int cycle_speed_factor = 1;\n\n// TODO: Convert colors to RGB.\n// clang-format off\n\n// Green.\n//\n// 0x518440\nunsigned char slime[12] = {\n    0, 108, 0,\n    11, 115, 7,\n    27, 123, 15,\n    43, 131, 27,\n};\n\n// Light gray?\n//\n// 0x51844C\nunsigned char shoreline[18] = {\n    83, 63, 43,\n    75, 59, 43,\n    67, 55, 39,\n    63, 51, 39,\n    55, 47, 35,\n    51, 43, 35,\n};\n\n// Orange.\n//\n// 0x51845E\nunsigned char fire_slow[15] = {\n    255, 0, 0,\n    215, 0, 0,\n    147, 43, 11,\n    255, 119, 0,\n    255, 59, 0,\n};\n\n// Red.\n//\n// 0x51846D\nunsigned char fire_fast[15] = {\n    71, 0, 0,\n    123, 0, 0,\n    179, 0, 0,\n    123, 0, 0,\n    71, 0, 0,\n};\n\n// Light blue.\n//\n// 0x51847C\nunsigned char monitors[15] = {\n    107, 107, 111,\n    99, 103, 127,\n    87, 107, 143,\n    0, 147, 163,\n    107, 187, 255,\n};\n\n// clang-format on\n\n// 0x51848C\nstatic bool cycle_initialized = false;\n\n// 0x518490\nstatic bool cycle_enabled = false;\n\n// 0x56D7D0\nstatic unsigned int last_cycle_fast;\n\n// 0x56D7D4\nstatic unsigned int last_cycle_slow;\n\n// 0x56D7D8\nstatic unsigned int last_cycle_medium;\n\n// 0x56D7DC\nstatic unsigned int last_cycle_very_fast;\n\n// 0x42E780\nvoid cycle_init()\n{\n    if (cycle_initialized) {\n        return;\n    }\n\n    bool colorCycling;\n    if (!configGetBool(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_COLOR_CYCLING_KEY, &colorCycling)) {\n        colorCycling = true;\n    }\n\n    if (!colorCycling) {\n        return;\n    }\n\n    for (int index = 0; index < 12; index++) {\n        slime[index] >>= 2;\n    }\n\n    for (int index = 0; index < 18; index++) {\n        shoreline[index] >>= 2;\n    }\n\n    for (int index = 0; index < 15; index++) {\n        fire_slow[index] >>= 2;\n    }\n\n    for (int index = 0; index < 15; index++) {\n        fire_fast[index] >>= 2;\n    }\n\n    for (int index = 0; index < 15; index++) {\n        monitors[index] >>= 2;\n    }\n\n    add_bk_process(cycle_colors);\n\n    cycle_initialized = true;\n    cycle_enabled = true;\n\n    int cycleSpeedFactor;\n    if (!config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CYCLE_SPEED_FACTOR_KEY, &cycleSpeedFactor)) {\n        cycleSpeedFactor = 1;\n    }\n\n    change_cycle_speed(cycleSpeedFactor);\n}\n\n// 0x42E8CC\nvoid cycle_reset()\n{\n    if (cycle_initialized) {\n        last_cycle_slow = 0;\n        last_cycle_medium = 0;\n        last_cycle_fast = 0;\n        last_cycle_very_fast = 0;\n        add_bk_process(cycle_colors);\n        cycle_enabled = true;\n    }\n}\n\n// 0x42E90C\nvoid cycle_exit()\n{\n    if (cycle_initialized) {\n        remove_bk_process(cycle_colors);\n        cycle_initialized = false;\n        cycle_enabled = false;\n    }\n}\n\n// 0x42E930\nvoid cycle_disable()\n{\n    cycle_enabled = false;\n}\n\n// 0x42E93C\nvoid cycle_enable()\n{\n    cycle_enabled = true;\n}\n\n// 0x42E948\nbool cycle_is_enabled()\n{\n    return cycle_enabled;\n}\n\n// 0x42E97C\nstatic void cycle_colors()\n{\n    // 0x518494\n    static int slime_start = 0;\n\n    // 0x518498\n    static int shoreline_start = 0;\n\n    // 0x51849C\n    static int fire_slow_start = 0;\n\n    // 0x5184A0\n    static int fire_fast_start = 0;\n\n    // 0x5184A4\n    static int monitors_start = 0;\n\n    // 0x5184A8\n    static unsigned char bobber_red = 0;\n\n    // 0x5184A9\n    static signed char bobber_diff = -4;\n\n    if (!cycle_enabled) {\n        return;\n    }\n\n    bool changed = false;\n\n    unsigned char* palette = getSystemPalette();\n    unsigned int time = get_time();\n\n    if (elapsed_tocks(time, last_cycle_slow) >= COLOR_CYCLE_PERIOD_SLOW * cycle_speed_factor) {\n        changed = true;\n        last_cycle_slow = time;\n\n        int paletteIndex = 229 * 3;\n\n        for (int index = slime_start; index < 12; index++) {\n            palette[paletteIndex++] = slime[index];\n        }\n\n        for (int index = 0; index < slime_start; index++) {\n            palette[paletteIndex++] = slime[index];\n        }\n\n        slime_start -= 3;\n        if (slime_start < 0) {\n            slime_start = 9;\n        }\n\n        paletteIndex = 248 * 3;\n\n        for (int index = shoreline_start; index < 18; index++) {\n            palette[paletteIndex++] = shoreline[index];\n        }\n\n        for (int index = 0; index < shoreline_start; index++) {\n            palette[paletteIndex++] = shoreline[index];\n        }\n\n        shoreline_start -= 3;\n        if (shoreline_start < 0) {\n            shoreline_start = 15;\n        }\n\n        paletteIndex = 238 * 3;\n\n        for (int index = fire_slow_start; index < 15; index++) {\n            palette[paletteIndex++] = fire_slow[index];\n        }\n\n        for (int index = 0; index < fire_slow_start; index++) {\n            palette[paletteIndex++] = fire_slow[index];\n        }\n\n        fire_slow_start -= 3;\n        if (fire_slow_start < 0) {\n            fire_slow_start = 12;\n        }\n    }\n\n    if (elapsed_tocks(time, last_cycle_medium) >= COLOR_CYCLE_PERIOD_MEDIUM * cycle_speed_factor) {\n        changed = true;\n        last_cycle_medium = time;\n\n        int paletteIndex = 243 * 3;\n\n        for (int index = fire_fast_start; index < 15; index++) {\n            palette[paletteIndex++] = fire_fast[index];\n        }\n\n        for (int index = 0; index < fire_fast_start; index++) {\n            palette[paletteIndex++] = fire_fast[index];\n        }\n\n        fire_fast_start -= 3;\n        if (fire_fast_start < 0) {\n            fire_fast_start = 12;\n        }\n    }\n\n    if (elapsed_tocks(time, last_cycle_fast) >= COLOR_CYCLE_PERIOD_FAST * cycle_speed_factor) {\n        changed = true;\n        last_cycle_fast = time;\n\n        int paletteIndex = 233 * 3;\n\n        for (int index = monitors_start; index < 15; index++) {\n            palette[paletteIndex++] = monitors[index];\n        }\n\n        for (int index = 0; index < monitors_start; index++) {\n            palette[paletteIndex++] = monitors[index];\n        }\n\n        monitors_start -= 3;\n\n        if (monitors_start < 0) {\n            monitors_start = 12;\n        }\n    }\n\n    if (elapsed_tocks(time, last_cycle_very_fast) >= COLOR_CYCLE_PERIOD_VERY_FAST * cycle_speed_factor) {\n        changed = true;\n        last_cycle_very_fast = time;\n\n        if (bobber_red == 0 || bobber_red == 60) {\n            bobber_diff = -bobber_diff;\n        }\n\n        bobber_red += bobber_diff;\n\n        int paletteIndex = 254 * 3;\n        palette[paletteIndex++] = bobber_red;\n        palette[paletteIndex++] = 0;\n        palette[paletteIndex++] = 0;\n    }\n\n    if (changed) {\n        palette_set_entries(palette + 229 * 3, 229, 255);\n    }\n}\n\n// 0x42E950\nvoid change_cycle_speed(int value)\n{\n    cycle_speed_factor = value;\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CYCLE_SPEED_FACTOR_KEY, value);\n}\n\n// NOTE: Unused.\n//\n// 0x42E974\nint get_cycle_speed()\n{\n    return cycle_speed_factor;\n}\n"
  },
  {
    "path": "src/game/cycle.h",
    "content": "#ifndef FALLOUT_GAME_CYCLE_H_\n#define FALLOUT_GAME_CYCLE_H_\n\n#include <stdbool.h>\n\nextern unsigned char slime[12];\nextern unsigned char shoreline[18];\nextern unsigned char fire_slow[15];\nextern unsigned char fire_fast[15];\nextern unsigned char monitors[15];\n\nvoid cycle_init();\nvoid cycle_reset();\nvoid cycle_exit();\nvoid cycle_disable();\nvoid cycle_enable();\nbool cycle_is_enabled();\nvoid change_cycle_speed(int value);\nint get_cycle_speed();\n\n#endif /* FALLOUT_GAME_CYCLE_H_ */\n"
  },
  {
    "path": "src/game/diskspce.c",
    "content": "#include \"game/diskspce.h\"\n\n#include <ctype.h>\n#include <direct.h>\n#include <stdlib.h>\n\n#include \"game/gconfig.h\"\n\n// 0x431560\nint GetFreeDiskSpace(long* diskSpacePtr)\n{\n    char* path;\n    char drive[_MAX_DRIVE];\n    int useGetDrive;\n    int driveNumber;\n    struct diskfree_t df;\n\n    *diskSpacePtr = 0;\n    useGetDrive = 1;\n\n    if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_PATCHES_KEY, &path) == 1) {\n        _splitpath(path, drive, NULL, NULL, NULL);\n\n        if (drive[0] != '\\0') {\n            useGetDrive = 0;\n            driveNumber = toupper(drive[0]) - 64;\n        }\n    }\n\n    if (useGetDrive) {\n        driveNumber = _getdrive();\n    }\n\n    if (_getdiskfree(driveNumber, &df) == 0) {\n        *diskSpacePtr = ((long)df.bytes_per_sector * (long)df.sectors_per_cluster * (long)df.avail_clusters) / 1024;\n        return 0;\n    }\n\n    return -1;\n}\n"
  },
  {
    "path": "src/game/diskspce.h",
    "content": "#ifndef FALLOUT_GAME_DISKSPCE_H_\n#define FALLOUT_GAME_DISKSPCE_H_\n\nint GetFreeDiskSpace(long* diskSpacePtr);\n\n#endif /* FALLOUT_GAME_DISKSPCE_H_ */\n"
  },
  {
    "path": "src/game/display.c",
    "content": "#include \"game/display.h\"\n\n#include <stdbool.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"plib/gnw/memory.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n\n// The maximum number of lines display monitor can hold. Once this value\n// is reached earlier messages are thrown away.\n#define DISPLAY_MONITOR_LINES_CAPACITY 100\n\n// The maximum length of a string in display monitor (in characters).\n#define DISPLAY_MONITOR_LINE_LENGTH 80\n\n#define DISPLAY_MONITOR_X 23\n#define DISPLAY_MONITOR_Y 24\n#define DISPLAY_MONITOR_WIDTH 167\n#define DISPLAY_MONITOR_HEIGHT 60\n\n#define DISPLAY_MONITOR_HALF_HEIGHT (DISPLAY_MONITOR_HEIGHT / 2)\n\n#define DISPLAY_MONITOR_FONT 101\n\n#define DISPLAY_MONITOR_BEEP_DELAY 500U\n\n// 0x51850C\nstatic bool disp_init = false;\n\n// The rectangle that display monitor occupies in the main interface window.\n//\n// 0x518510\nstatic Rect disp_rect = {\n    DISPLAY_MONITOR_X,\n    DISPLAY_MONITOR_Y,\n    DISPLAY_MONITOR_X + DISPLAY_MONITOR_WIDTH - 1,\n    DISPLAY_MONITOR_Y + DISPLAY_MONITOR_HEIGHT - 1,\n};\n\n// 0x518520\nstatic int dn_bid = -1;\n\n// 0x518524\nstatic int up_bid = -1;\n\n// 0x56DBFC\nstatic char disp_str[DISPLAY_MONITOR_LINES_CAPACITY][DISPLAY_MONITOR_LINE_LENGTH];\n\n// 0x56FB3C\nstatic unsigned char* disp_buf;\n\n// 0x56FB40\nstatic int max_disp_ptr;\n\n// 0x56FB44\nstatic bool display_enabled;\n\n// 0x56FB48\nstatic int disp_curr;\n\n// 0x56FB4C\nstatic int intface_full_wid;\n\n// 0x56FB50\nstatic int max_ptr;\n\n// 0x56FB54\nstatic int disp_start;\n\n// 0x431610\nint display_init()\n{\n    if (!disp_init) {\n        int oldFont = text_curr();\n        text_font(DISPLAY_MONITOR_FONT);\n\n        max_ptr = DISPLAY_MONITOR_LINES_CAPACITY;\n        max_disp_ptr = DISPLAY_MONITOR_HEIGHT / text_height();\n        disp_start = 0;\n        disp_curr = 0;\n        text_font(oldFont);\n\n        disp_buf = (unsigned char*)mem_malloc(DISPLAY_MONITOR_WIDTH * DISPLAY_MONITOR_HEIGHT);\n        if (disp_buf == NULL) {\n            return -1;\n        }\n\n        CacheEntry* backgroundFrmHandle;\n        int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 16, 0, 0, 0);\n        Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle);\n        if (backgroundFrm == NULL) {\n            mem_free(disp_buf);\n            return -1;\n        }\n\n        unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0);\n        intface_full_wid = art_frame_width(backgroundFrm, 0, 0);\n        buf_to_buf(backgroundFrmData + intface_full_wid * DISPLAY_MONITOR_Y + DISPLAY_MONITOR_X,\n            DISPLAY_MONITOR_WIDTH,\n            DISPLAY_MONITOR_HEIGHT,\n            intface_full_wid,\n            disp_buf,\n            DISPLAY_MONITOR_WIDTH);\n\n        art_ptr_unlock(backgroundFrmHandle);\n\n        up_bid = win_register_button(interfaceWindow,\n            DISPLAY_MONITOR_X,\n            DISPLAY_MONITOR_Y,\n            DISPLAY_MONITOR_WIDTH,\n            DISPLAY_MONITOR_HALF_HEIGHT,\n            -1,\n            -1,\n            -1,\n            -1,\n            NULL,\n            NULL,\n            NULL,\n            0);\n        if (up_bid != -1) {\n            win_register_button_func(up_bid,\n                display_arrow_up,\n                display_arrow_restore,\n                display_scroll_up,\n                NULL);\n        }\n\n        dn_bid = win_register_button(interfaceWindow,\n            DISPLAY_MONITOR_X,\n            DISPLAY_MONITOR_Y + DISPLAY_MONITOR_HALF_HEIGHT,\n            DISPLAY_MONITOR_WIDTH,\n            DISPLAY_MONITOR_HEIGHT - DISPLAY_MONITOR_HALF_HEIGHT,\n            -1,\n            -1,\n            -1,\n            -1,\n            NULL,\n            NULL,\n            NULL,\n            0);\n        if (dn_bid != -1) {\n            win_register_button_func(dn_bid,\n                display_arrow_down,\n                display_arrow_restore,\n                display_scroll_down,\n                NULL);\n        }\n\n        display_enabled = true;\n        disp_init = true;\n\n        // NOTE: Uninline.\n        display_clear();\n    }\n\n    return 0;\n}\n\n// 0x431800\nint display_reset()\n{\n    // NOTE: Uninline.\n    display_clear();\n\n    return 0;\n}\n\n// 0x43184C\nvoid display_exit()\n{\n    if (disp_init) {\n        mem_free(disp_buf);\n        disp_init = false;\n    }\n}\n\n// 0x43186C\nvoid display_print(char* str)\n{\n    // 0x56FB58\n    static unsigned int last_time;\n\n    if (!disp_init) {\n        return;\n    }\n\n    int oldFont = text_curr();\n    text_font(DISPLAY_MONITOR_FONT);\n\n    char knob = '\\x95';\n\n    char knobString[2];\n    knobString[0] = knob;\n    knobString[1] = '\\0';\n    int knobWidth = text_width(knobString);\n\n    if (!isInCombat()) {\n        unsigned int now = get_bk_time();\n        if (elapsed_tocks(now, last_time) >= DISPLAY_MONITOR_BEEP_DELAY) {\n            last_time = now;\n            gsound_play_sfx_file(\"monitor\");\n        }\n    }\n\n    // TODO: Refactor these two loops.\n    char* v1 = NULL;\n    while (true) {\n        while (text_width(str) < DISPLAY_MONITOR_WIDTH - max_disp_ptr - knobWidth) {\n            char* temp = disp_str[disp_start];\n            int length;\n            if (knob != '\\0') {\n                *temp++ = knob;\n                length = DISPLAY_MONITOR_LINE_LENGTH - 2;\n                knob = '\\0';\n                knobWidth = 0;\n            } else {\n                length = DISPLAY_MONITOR_LINE_LENGTH - 1;\n            }\n            strncpy(temp, str, length);\n            disp_str[disp_start][DISPLAY_MONITOR_LINE_LENGTH - 1] = '\\0';\n            disp_start = (disp_start + 1) % max_ptr;\n\n            if (v1 == NULL) {\n                text_font(oldFont);\n                disp_curr = disp_start;\n                display_redraw();\n                return;\n            }\n\n            str = v1 + 1;\n            *v1 = ' ';\n            v1 = NULL;\n        }\n\n        char* space = strrchr(str, ' ');\n        if (space == NULL) {\n            break;\n        }\n\n        if (v1 != NULL) {\n            *v1 = ' ';\n        }\n\n        v1 = space;\n        if (space != NULL) {\n            *space = '\\0';\n        }\n    }\n\n    char* temp = disp_str[disp_start];\n    int length;\n    if (knob != '\\0') {\n        temp++;\n        disp_str[disp_start][0] = knob;\n        length = DISPLAY_MONITOR_LINE_LENGTH - 2;\n        knob = '\\0';\n    } else {\n        length = DISPLAY_MONITOR_LINE_LENGTH - 1;\n    }\n    strncpy(temp, str, length);\n\n    disp_str[disp_start][DISPLAY_MONITOR_LINE_LENGTH - 1] = '\\0';\n    disp_start = (disp_start + 1) % max_ptr;\n\n    text_font(oldFont);\n    disp_curr = disp_start;\n    display_redraw();\n}\n\n// NOTE: Inlined.\n//\n// 0x431A2C\nvoid display_clear()\n{\n    int index;\n\n    if (disp_init) {\n        for (index = 0; index < max_ptr; index++) {\n            disp_str[index][0] = '\\0';\n        }\n\n        disp_start = 0;\n        disp_curr = 0;\n        display_redraw();\n    }\n}\n\n// 0x431A78\nvoid display_redraw()\n{\n    if (!disp_init) {\n        return;\n    }\n\n    unsigned char* buf = win_get_buf(interfaceWindow);\n    if (buf == NULL) {\n        return;\n    }\n\n    buf += intface_full_wid * DISPLAY_MONITOR_Y + DISPLAY_MONITOR_X;\n    buf_to_buf(disp_buf,\n        DISPLAY_MONITOR_WIDTH,\n        DISPLAY_MONITOR_HEIGHT,\n        DISPLAY_MONITOR_WIDTH,\n        buf,\n        intface_full_wid);\n\n    int oldFont = text_curr();\n    text_font(DISPLAY_MONITOR_FONT);\n\n    for (int index = 0; index < max_disp_ptr; index++) {\n        int stringIndex = (disp_curr + max_ptr + index - max_disp_ptr) % max_ptr;\n        text_to_buf(buf + index * intface_full_wid * text_height(), disp_str[stringIndex], DISPLAY_MONITOR_WIDTH, intface_full_wid, colorTable[992]);\n\n        // Even though the display monitor is rectangular, it's graphic is not.\n        // To give a feel of depth it's covered by some metal canopy and\n        // considered inclined outwards. This way earlier messages appear a\n        // little bit far from player's perspective. To implement this small\n        // detail the destination buffer is incremented by 1.\n        buf++;\n    }\n\n    win_draw_rect(interfaceWindow, &disp_rect);\n    text_font(oldFont);\n}\n\n// 0x431B70\nvoid display_scroll_up(int btn, int keyCode)\n{\n    if ((max_ptr + disp_curr - 1) % max_ptr != disp_start) {\n        disp_curr = (max_ptr + disp_curr - 1) % max_ptr;\n        display_redraw();\n    }\n}\n\n// 0x431B9C\nvoid display_scroll_down(int btn, int keyCode)\n{\n    if (disp_curr != disp_start) {\n        disp_curr = (disp_curr + 1) % max_ptr;\n        display_redraw();\n    }\n}\n\n// 0x431BC8\nvoid display_arrow_up(int btn, int keyCode)\n{\n    gmouse_set_cursor(MOUSE_CURSOR_SMALL_ARROW_UP);\n}\n\n// 0x431BD4\nvoid display_arrow_down(int btn, int keyCode)\n{\n    gmouse_set_cursor(MOUSE_CURSOR_SMALL_ARROW_DOWN);\n}\n\n// 0x431BE0\nvoid display_arrow_restore(int btn, int keyCode)\n{\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n}\n\n// 0x431BEC\nvoid display_disable()\n{\n    if (display_enabled) {\n        win_disable_button(dn_bid);\n        win_disable_button(up_bid);\n        display_enabled = false;\n    }\n}\n\n// 0x431C14\nvoid display_enable()\n{\n    if (!display_enabled) {\n        win_enable_button(dn_bid);\n        win_enable_button(up_bid);\n        display_enabled = true;\n    }\n}\n"
  },
  {
    "path": "src/game/display.h",
    "content": "#ifndef FALLOUT_GAME_DISPLAY_H_\n#define FALLOUT_GAME_DISPLAY_H_\n\nint display_init();\nint display_reset();\nvoid display_exit();\nvoid display_print(char* string);\nvoid display_clear();\nvoid display_redraw();\nvoid display_scroll_up(int btn, int keyCode);\nvoid display_scroll_down(int btn, int keyCode);\nvoid display_arrow_up(int btn, int keyCode);\nvoid display_arrow_down(int btn, int keyCode);\nvoid display_arrow_restore(int btn, int keyCode);\nvoid display_disable();\nvoid display_enable();\n\n#endif /* FALLOUT_GAME_DISPLAY_H_ */\n"
  },
  {
    "path": "src/game/editor.c",
    "content": "#include \"game/editor.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/cycle.h\"\n#include \"plib/db/db.h\"\n#include \"game/bmpdlog.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gmouse.h\"\n#include \"game/graphlib.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/palette.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/trait.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"game/wordwrap.h\"\n#include \"game/worldmap.h\"\n\n#define RENDER_ALL_STATS 7\n\n#define EDITOR_WINDOW_X 0\n#define EDITOR_WINDOW_Y 0\n#define EDITOR_WINDOW_WIDTH 640\n#define EDITOR_WINDOW_HEIGHT 480\n\n#define NAME_BUTTON_X 9\n#define NAME_BUTTON_Y 0\n\n#define TAG_SKILLS_BUTTON_X 347\n#define TAG_SKILLS_BUTTON_Y 26\n#define TAG_SKILLS_BUTTON_CODE 536\n\n#define PRINT_BTN_X 363\n#define PRINT_BTN_Y 454\n\n#define DONE_BTN_X 475\n#define DONE_BTN_Y 454\n\n#define CANCEL_BTN_X 571\n#define CANCEL_BTN_Y 454\n\n#define NAME_BTN_CODE 517\n#define AGE_BTN_CODE 519\n#define SEX_BTN_CODE 520\n\n#define OPTIONAL_TRAITS_LEFT_BTN_X 23\n#define OPTIONAL_TRAITS_RIGHT_BTN_X 298\n#define OPTIONAL_TRAITS_BTN_Y 352\n\n#define OPTIONAL_TRAITS_BTN_CODE 555\n\n#define OPTIONAL_TRAITS_BTN_SPACE 2\n\n#define SPECIAL_STATS_BTN_X 149\n\n#define PERK_WINDOW_X 33\n#define PERK_WINDOW_Y 91\n#define PERK_WINDOW_WIDTH 573\n#define PERK_WINDOW_HEIGHT 230\n\n#define PERK_WINDOW_LIST_X 45\n#define PERK_WINDOW_LIST_Y 43\n#define PERK_WINDOW_LIST_WIDTH 192\n#define PERK_WINDOW_LIST_HEIGHT 129\n\n#define ANIMATE 0x01\n#define RED_NUMBERS 0x02\n#define BIG_NUM_WIDTH 14\n#define BIG_NUM_HEIGHT 24\n#define BIG_NUM_ANIMATION_DELAY 123\n\n// TODO: Should be MAX(PERK_COUNT, TRAIT_COUNT).\n#define DIALOG_PICKER_NUM_OPTIONS PERK_COUNT\n\ntypedef enum EditorFolder {\n    EDITOR_FOLDER_PERKS,\n    EDITOR_FOLDER_KARMA,\n    EDITOR_FOLDER_KILLS,\n} EditorFolder;\n\nenum {\n    EDITOR_DERIVED_STAT_ARMOR_CLASS,\n    EDITOR_DERIVED_STAT_ACTION_POINTS,\n    EDITOR_DERIVED_STAT_CARRY_WEIGHT,\n    EDITOR_DERIVED_STAT_MELEE_DAMAGE,\n    EDITOR_DERIVED_STAT_DAMAGE_RESISTANCE,\n    EDITOR_DERIVED_STAT_POISON_RESISTANCE,\n    EDITOR_DERIVED_STAT_RADIATION_RESISTANCE,\n    EDITOR_DERIVED_STAT_SEQUENCE,\n    EDITOR_DERIVED_STAT_HEALING_RATE,\n    EDITOR_DERIVED_STAT_CRITICAL_CHANCE,\n    EDITOR_DERIVED_STAT_COUNT,\n};\n\nenum {\n    EDITOR_FIRST_PRIMARY_STAT,\n    EDITOR_HIT_POINTS = 43,\n    EDITOR_POISONED,\n    EDITOR_RADIATED,\n    EDITOR_EYE_DAMAGE,\n    EDITOR_CRIPPLED_RIGHT_ARM,\n    EDITOR_CRIPPLED_LEFT_ARM,\n    EDITOR_CRIPPLED_RIGHT_LEG,\n    EDITOR_CRIPPLED_LEFT_LEG,\n    EDITOR_FIRST_DERIVED_STAT,\n    EDITOR_FIRST_SKILL = EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_COUNT,\n    EDITOR_TAG_SKILL = EDITOR_FIRST_SKILL + SKILL_COUNT,\n    EDITOR_SKILLS,\n    EDITOR_OPTIONAL_TRAITS,\n    EDITOR_FIRST_TRAIT,\n    EDITOR_BUTTONS_COUNT = EDITOR_FIRST_TRAIT + TRAIT_COUNT,\n};\n\nenum {\n    EDITOR_GRAPHIC_BIG_NUMBERS,\n    EDITOR_GRAPHIC_AGE_MASK,\n    EDITOR_GRAPHIC_AGE_OFF,\n    EDITOR_GRAPHIC_DOWN_ARROW_OFF,\n    EDITOR_GRAPHIC_DOWN_ARROW_ON,\n    EDITOR_GRAPHIC_NAME_MASK,\n    EDITOR_GRAPHIC_NAME_ON,\n    EDITOR_GRAPHIC_NAME_OFF,\n    EDITOR_GRAPHIC_FOLDER_MASK, // mask for all three folders\n    EDITOR_GRAPHIC_SEX_MASK,\n    EDITOR_GRAPHIC_SEX_OFF,\n    EDITOR_GRAPHIC_SEX_ON,\n    EDITOR_GRAPHIC_SLIDER, // image containing small plus/minus buttons appeared near selected skill\n    EDITOR_GRAPHIC_SLIDER_MINUS_OFF,\n    EDITOR_GRAPHIC_SLIDER_MINUS_ON,\n    EDITOR_GRAPHIC_SLIDER_PLUS_OFF,\n    EDITOR_GRAPHIC_SLIDER_PLUS_ON,\n    EDITOR_GRAPHIC_SLIDER_TRANS_MINUS_OFF,\n    EDITOR_GRAPHIC_SLIDER_TRANS_MINUS_ON,\n    EDITOR_GRAPHIC_SLIDER_TRANS_PLUS_OFF,\n    EDITOR_GRAPHIC_SLIDER_TRANS_PLUS_ON,\n    EDITOR_GRAPHIC_UP_ARROW_OFF,\n    EDITOR_GRAPHIC_UP_ARROW_ON,\n    EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP,\n    EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN,\n    EDITOR_GRAPHIC_AGE_ON,\n    EDITOR_GRAPHIC_AGE_BOX, // image containing right and left buttons with age stepper in the middle\n    EDITOR_GRAPHIC_ATTRIBOX, // ??? black image with two little arrows (up and down) in the right-top corner\n    EDITOR_GRAPHIC_ATTRIBWN, // ??? not sure where and when it's used\n    EDITOR_GRAPHIC_CHARWIN, // ??? looks like metal plate\n    EDITOR_GRAPHIC_DONE_BOX, // metal plate holding DONE button\n    EDITOR_GRAPHIC_FEMALE_OFF,\n    EDITOR_GRAPHIC_FEMALE_ON,\n    EDITOR_GRAPHIC_MALE_OFF,\n    EDITOR_GRAPHIC_MALE_ON,\n    EDITOR_GRAPHIC_NAME_BOX, // placeholder for name\n    EDITOR_GRAPHIC_LEFT_ARROW_UP,\n    EDITOR_GRAPHIC_LEFT_ARROW_DOWN,\n    EDITOR_GRAPHIC_RIGHT_ARROW_UP,\n    EDITOR_GRAPHIC_RIGHT_ARROW_DOWN,\n    EDITOR_GRAPHIC_BARARRWS, // ??? two arrows up/down with some strange knob at the top, probably for scrollbar\n    EDITOR_GRAPHIC_OPTIONS_BASE, // options metal plate\n    EDITOR_GRAPHIC_OPTIONS_BUTTON_OFF,\n    EDITOR_GRAPHIC_OPTIONS_BUTTON_ON,\n    EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED, // all three folders with middle folder selected (karma)\n    EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED, // all theee folders with right folder selected (kills)\n    EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED, // all three folders with left folder selected (perks)\n    EDITOR_GRAPHIC_KARMAFDR_PLACEOLDER, // ??? placeholder for traits folder image <- this is comment from intrface.lst\n    EDITOR_GRAPHIC_TAG_SKILL_BUTTON_OFF,\n    EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON,\n    EDITOR_GRAPHIC_COUNT,\n};\n\ntypedef struct KarmaEntry {\n    int gvar;\n    int art_num;\n    int name;\n    int description;\n} KarmaEntry;\n\ntypedef struct GenericReputationEntry {\n    int threshold;\n    int name;\n} GenericReputationEntry;\n\ntypedef struct PerkDialogOption {\n    // Depending on the current mode this value is the id of either\n    // perk, trait (handling Mutate perk), or skill (handling Tag perk).\n    int value;\n    char* name;\n} PerkDialogOption;\n\n// TODO: Field order is probably wrong.\ntypedef struct KillInfo {\n    const char* name;\n    int killTypeId;\n    int kills;\n} KillInfo;\n\nstatic int CharEditStart();\nstatic void CharEditEnd();\nstatic void RstrBckgProc();\nstatic void DrawFolder();\nstatic void list_perks();\nstatic int kills_list_comp(const void* a1, const void* a2);\nstatic int ListKills();\nstatic void PrintBigNum(int x, int y, int flags, int value, int previousValue, int windowHandle);\nstatic void PrintLevelWin();\nstatic void PrintBasicStat(int stat, bool animate, int previousValue);\nstatic void PrintGender();\nstatic void PrintAgeBig();\nstatic void PrintBigname();\nstatic void ListDrvdStats();\nstatic void ListSkills(int a1);\nstatic void DrawInfoWin();\nstatic int NameWindow();\nstatic void PrintName(unsigned char* buf, int pitch);\nstatic int AgeWindow();\nstatic void SexWindow();\nstatic void StatButton(int eventCode);\nstatic int OptionWindow();\nstatic int Save_as_ASCII(const char* fileName);\nstatic char* AddDots(char* string, int length);\nstatic void ResetScreen();\nstatic void RegInfoAreas();\nstatic int CheckValidPlayer();\nstatic void SavePlayer();\nstatic void RestorePlayer();\nstatic int DrawCard(int graphicId, const char* name, const char* attributes, char* description);\nstatic void FldrButton();\nstatic void InfoButton(int eventCode);\nstatic void SliderBtn(int a1);\nstatic int tagskl_free();\nstatic void TagSkillSelect(int skill);\nstatic void ListTraits();\nstatic int get_trait_count();\nstatic void TraitSelect(int trait);\nstatic void list_karma();\nstatic int UpdateLevel();\nstatic void RedrwDPrks();\nstatic int perks_dialog();\nstatic int InputPDLoop(int count, void (*refreshProc)());\nstatic int ListDPerks();\nstatic bool GetMutateTrait();\nstatic void RedrwDMTagSkl();\nstatic bool Add4thTagSkill();\nstatic void ListNewTagSkills();\nstatic int ListMyTraits(int a1);\nstatic int name_sort_comp(const void* a1, const void* a2);\nstatic int DrawCard2(int frmId, const char* name, const char* rank, char* description);\nstatic void push_perks();\nstatic void pop_perks();\nstatic int PerkCount();\nstatic int is_supper_bonus();\nstatic int folder_init();\nstatic void folder_exit();\nstatic void folder_scroll(int direction);\nstatic void folder_clear();\nstatic int folder_print_seperator(const char* string);\nstatic bool folder_print_line(const char* string);\nstatic bool folder_print_kill(const char* name, int kills);\nstatic int karma_vars_init();\nstatic void karma_vars_exit();\nstatic int karma_vars_qsort_compare(const void* a1, const void* a2);\nstatic int general_reps_init();\nstatic void general_reps_exit();\nstatic int general_reps_qsort_compare(const void* a1, const void* a2);\n\n// 0x431C40\nstatic const int grph_id[EDITOR_GRAPHIC_COUNT] = {\n    170,\n    175,\n    176,\n    181,\n    182,\n    183,\n    184,\n    185,\n    186,\n    187,\n    188,\n    189,\n    190,\n    191,\n    192,\n    193,\n    194,\n    195,\n    196,\n    197,\n    198,\n    199,\n    200,\n    8,\n    9,\n    204,\n    205,\n    206,\n    207,\n    208,\n    209,\n    210,\n    211,\n    212,\n    213,\n    214,\n    122,\n    123,\n    124,\n    125,\n    219,\n    220,\n    221,\n    222,\n    178,\n    179,\n    180,\n    38,\n    215,\n    216,\n};\n\n// flags to preload fid\n//\n// 0x431D08\nstatic const unsigned char copyflag[EDITOR_GRAPHIC_COUNT] = {\n    0,\n    0,\n    1,\n    0,\n    0,\n    0,\n    1,\n    1,\n    0,\n    0,\n    1,\n    1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    1,\n    1,\n    1,\n    1,\n    0,\n    0,\n};\n\n// graphic ids for derived stats panel\n//\n// 0x431D3A\nstatic const short ndrvd[EDITOR_DERIVED_STAT_COUNT] = {\n    18,\n    19,\n    20,\n    21,\n    22,\n    23,\n    83,\n    24,\n    25,\n    26,\n};\n\n// y offsets for stats +/- buttons\n//\n// 0x431D50\nstatic const int StatYpos[7] = {\n    37,\n    70,\n    103,\n    136,\n    169,\n    202,\n    235,\n};\n\n// stat ids for derived stats panel\n//\n// 0x431D6\nstatic const short ndinfoxlt[EDITOR_DERIVED_STAT_COUNT] = {\n    STAT_ARMOR_CLASS,\n    STAT_MAXIMUM_ACTION_POINTS,\n    STAT_CARRY_WEIGHT,\n    STAT_MELEE_DAMAGE,\n    STAT_DAMAGE_RESISTANCE,\n    STAT_POISON_RESISTANCE,\n    STAT_RADIATION_RESISTANCE,\n    STAT_SEQUENCE,\n    STAT_HEALING_RATE,\n    STAT_CRITICAL_CHANCE,\n};\n\n// TODO: Remove.\n// 0x431D93\nchar byte_431D93[64];\n\n// TODO: Remove\n// 0x5016E4\nchar byte_5016E4[] = \"------\";\n\n// 0x518528\nstatic bool bk_enable = false;\n\n// 0x51852C\nstatic int skill_cursor = 0;\n\n// 0x518534\nstatic int slider_y = 27;\n\n// 0x518538\nint character_points = 0;\n\n// 0x51853C\nstatic KarmaEntry* karma_vars = NULL;\n\n// 0x518540\nstatic int karma_vars_count = 0;\n\n// 0x518544\nstatic GenericReputationEntry* general_reps = NULL;\n\n// 0x518548\nstatic int general_reps_count = 0;\n\n// 0x51854C\nTownReputationEntry town_rep_info[TOWN_REPUTATION_COUNT] = {\n    { GVAR_TOWN_REP_ARROYO, CITY_ARROYO },\n    { GVAR_TOWN_REP_KLAMATH, CITY_KLAMATH },\n    { GVAR_TOWN_REP_THE_DEN, CITY_DEN },\n    { GVAR_TOWN_REP_VAULT_CITY, CITY_VAULT_CITY },\n    { GVAR_TOWN_REP_GECKO, CITY_GECKO },\n    { GVAR_TOWN_REP_MODOC, CITY_MODOC },\n    { GVAR_TOWN_REP_SIERRA_BASE, CITY_SIERRA_ARMY_BASE },\n    { GVAR_TOWN_REP_BROKEN_HILLS, CITY_BROKEN_HILLS },\n    { GVAR_TOWN_REP_NEW_RENO, CITY_NEW_RENO },\n    { GVAR_TOWN_REP_REDDING, CITY_REDDING },\n    { GVAR_TOWN_REP_NCR, CITY_NEW_CALIFORNIA_REPUBLIC },\n    { GVAR_TOWN_REP_VAULT_13, CITY_VAULT_13 },\n    { GVAR_TOWN_REP_SAN_FRANCISCO, CITY_SAN_FRANCISCO },\n    { GVAR_TOWN_REP_ABBEY, CITY_ABBEY },\n    { GVAR_TOWN_REP_EPA, CITY_ENV_PROTECTION_AGENCY },\n    { GVAR_TOWN_REP_PRIMITIVE_TRIBE, CITY_PRIMITIVE_TRIBE },\n    { GVAR_TOWN_REP_RAIDERS, CITY_RAIDERS },\n    { GVAR_TOWN_REP_VAULT_15, CITY_VAULT_15 },\n    { GVAR_TOWN_REP_GHOST_FARM, CITY_MODOC_GHOST_TOWN },\n};\n\n// 0x5185E4\nint addiction_vars[ADDICTION_REPUTATION_COUNT] = {\n    GVAR_NUKA_COLA_ADDICT,\n    GVAR_BUFF_OUT_ADDICT,\n    GVAR_MENTATS_ADDICT,\n    GVAR_PSYCHO_ADDICT,\n    GVAR_RADAWAY_ADDICT,\n    GVAR_ALCOHOL_ADDICT,\n    GVAR_ADDICT_JET,\n    GVAR_ADDICT_TRAGIC,\n};\n\n// 0x518604\nint addiction_pics[ADDICTION_REPUTATION_COUNT] = {\n    142,\n    126,\n    140,\n    144,\n    145,\n    52,\n    136,\n    149,\n};\n\n// 0x518624\nstatic int folder_up_button = -1;\n\n// 0x518628\nstatic int folder_down_button = -1;\n\n// 0x56FB60\nstatic char folder_card_string[256];\n\n// 0x56FC60\nstatic int skillsav[SKILL_COUNT];\n\n// 0x56FCA8\nstatic MessageList editor_message_file;\n\n// 0x56FCB0\nstatic PerkDialogOption name_sort_list[DIALOG_PICKER_NUM_OPTIONS];\n\n// buttons for selecting traits\n//\n// 0x5700A8\nstatic int trait_bids[TRAIT_COUNT];\n\n// 0x5700E8\nstatic MessageListItem mesg;\n\n// 0x5700F8\nstatic char old_str1[48];\n\n// 0x570128\nstatic char old_str2[48];\n\n// buttons for tagging skills\n//\n// 0x570158\nstatic int tag_bids[SKILL_COUNT];\n\n// pc name\n//\n// 0x5701A0\nstatic char name_save[32];\n\n// 0x5701C0\nstatic Size GInfo[EDITOR_GRAPHIC_COUNT];\n\n// 0x570350\nstatic CacheEntry* grph_key[EDITOR_GRAPHIC_COUNT];\n\n// 0x570418\nstatic unsigned char* grphcpy[EDITOR_GRAPHIC_COUNT];\n\n// 0x5704E0\nstatic unsigned char* grphbmp[EDITOR_GRAPHIC_COUNT];\n\n// 0x5705A8\nstatic int folder_max_lines;\n\n// 0x5705AC\nstatic int folder_line;\n\n// 0x5705B0\nstatic int folder_card_fid;\n\n// 0x5705B4\nstatic int folder_top_line;\n\n// 0x5705B8\nstatic char* folder_card_title;\n\n// 0x5705BC\nstatic char* folder_card_title2;\n\n// 0x5705C0\nstatic int folder_yoffset;\n\n// 0x5705C4\nstatic int folder_karma_top_line;\n\n// 0x5705C8\nstatic int folder_highlight_line;\n\n// 0x5705CC\nstatic char* folder_card_desc;\n\n// 0x5705D0\nstatic int folder_ypos;\n\n// 0x5705D4\nstatic int folder_kills_top_line;\n\n// 0x5705D8\nstatic int folder_perk_top_line;\n\n// 0x5705DC\nstatic unsigned char* pbckgnd;\n\n// 0x5705E0\nstatic int pwin;\n\n// 0x5705E4\nstatic int SliderPlusID;\n\n// 0x5705E8\nstatic int SliderNegID;\n\n// - stats buttons\n//\n// 0x5705EC\nstatic int stat_bids_minus[7];\n\n// 0x570608\nstatic unsigned char* win_buf;\n\n// 0x57060C\nstatic int edit_win;\n\n// + stats buttons\n//\n// 0x570610\nstatic int stat_bids_plus[7];\n\n// 0x57062C\nstatic unsigned char* pwin_buf;\n\n// 0x570630\nstatic CritterProtoData dude_data;\n\n// 0x5707A4\nstatic unsigned char* bckgnd;\n\n// 0x5707A8\nstatic int cline;\n\n// 0x5707AC\nstatic int oldsline;\n\n// unspent skill points\n//\n// 0x5707B0\nstatic int upsent_points_back;\n\n// 0x5707B4\nstatic int last_level;\n\n// 0x5707B8\nstatic int fontsave;\n\n// 0x5707BC\nstatic int kills_count;\n\n// character editor background\n//\n// 0x5707C0\nstatic CacheEntry* bck_key;\n\n// current hit points\n//\n// 0x5707C4\nstatic int hp_back;\n\n// 0x5707C8\nstatic int mouse_ypos; // mouse y\n\n// 0x5707CC\nstatic int mouse_xpos; // mouse x\n\n// 0x5707D0\nstatic int info_line;\n\n// 0x5707D4\nstatic int folder;\n\n// 0x5707D8\nstatic bool frstc_draw1;\n\n// 0x5707DC\nstatic int crow;\n\n// 0x5707E0\nstatic bool frstc_draw2;\n\n// 0x5707E4\nstatic int perk_back[PERK_COUNT];\n\n// 0x5709C0\nstatic unsigned int _repFtime;\n\n// 0x5709C4\nstatic unsigned int _frame_time;\n\n// 0x5709C8\nstatic int old_tags;\n\n// 0x5709CC\nstatic int last_level_back;\n\n// 0x5709E8\nstatic int old_fid2;\n\n// 0x5709EC\nstatic int old_fid1;\n\n// 0x5709D0\nstatic bool glblmode;\n\n// 0x5709D4\nstatic int tag_skill_back[NUM_TAGGED_SKILLS];\n\n// 0x5709F0\nstatic int trait_back[3];\n\n// current index for selecting new trait\n//\n// 0x5709FC\nstatic int trait_count;\n\n// 0x570A00\nstatic int optrt_count;\n\n// 0x570A04\nstatic int temp_trait[3];\n\n// 0x570A10\nstatic int tagskill_count;\n\n// 0x570A14\nstatic int temp_tag_skill[NUM_TAGGED_SKILLS];\n\n// 0x570A28\nstatic char free_perk_back;\n\n// 0x570A29\nstatic unsigned char free_perk;\n\n// 0x570A2A\nstatic unsigned char first_skill_list;\n\n// 0x431DF8\nint editor_design(bool isCreationMode)\n{\n    char* messageListItemText;\n    char line1[128];\n    char line2[128];\n    const char* lines[] = { line2 };\n\n    glblmode = isCreationMode;\n\n    SavePlayer();\n\n    if (CharEditStart() == -1) {\n        debug_printf(\"\\n ** Error loading character editor data! **\\n\");\n        return -1;\n    }\n\n    if (!glblmode) {\n        if (UpdateLevel()) {\n            stat_recalc_derived(obj_dude);\n            ListTraits();\n            ListSkills(0);\n            PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n            ListDrvdStats();\n            DrawInfoWin();\n        }\n    }\n\n    int rc = -1;\n    while (rc == -1) {\n        _frame_time = get_time();\n        int keyCode = get_input();\n\n        bool done = false;\n        if (keyCode == 500) {\n            done = true;\n        }\n\n        if (keyCode == KEY_RETURN || keyCode == KEY_UPPERCASE_D || keyCode == KEY_LOWERCASE_D) {\n            done = true;\n            gsound_play_sfx_file(\"ib1p1xx1\");\n        }\n\n        if (done) {\n            if (glblmode) {\n                if (character_points != 0) {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    // You must use all character points\n                    messageListItemText = getmsg(&editor_message_file, &mesg, 118);\n                    strcpy(line1, messageListItemText);\n\n                    // before starting the game!\n                    messageListItemText = getmsg(&editor_message_file, &mesg, 119);\n                    strcpy(line2, messageListItemText);\n\n                    dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0);\n                    win_draw(edit_win);\n\n                    rc = -1;\n                    continue;\n                }\n\n                if (tagskill_count > 0) {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    // You must select all tag skills\n                    messageListItemText = getmsg(&editor_message_file, &mesg, 142);\n                    strcpy(line1, messageListItemText);\n\n                    // before starting the game!\n                    messageListItemText = getmsg(&editor_message_file, &mesg, 143);\n                    strcpy(line2, messageListItemText);\n\n                    dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0);\n                    win_draw(edit_win);\n\n                    rc = -1;\n                    continue;\n                }\n\n                if (is_supper_bonus()) {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    // All stats must be between 1 and 10\n                    messageListItemText = getmsg(&editor_message_file, &mesg, 157);\n                    strcpy(line1, messageListItemText);\n\n                    // before starting the game!\n                    messageListItemText = getmsg(&editor_message_file, &mesg, 158);\n                    strcpy(line2, messageListItemText);\n\n                    dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0);\n                    win_draw(edit_win);\n\n                    rc = -1;\n                    continue;\n                }\n\n                if (stricmp(critter_name(obj_dude), \"None\") == 0) {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    // Warning: You haven't changed your player\n                    messageListItemText = getmsg(&editor_message_file, &mesg, 160);\n                    strcpy(line1, messageListItemText);\n\n                    // name. Use this character any way?\n                    messageListItemText = getmsg(&editor_message_file, &mesg, 161);\n                    strcpy(line2, messageListItemText);\n\n                    if (dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_YES_NO) == 0) {\n                        win_draw(edit_win);\n\n                        rc = -1;\n                        continue;\n                    }\n                }\n            }\n\n            win_draw(edit_win);\n            rc = 0;\n        } else if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n            game_quit_with_confirm();\n            win_draw(edit_win);\n        } else if (keyCode == 502 || keyCode == KEY_ESCAPE || keyCode == KEY_UPPERCASE_C || keyCode == KEY_LOWERCASE_C || game_user_wants_to_quit != 0) {\n            win_draw(edit_win);\n            rc = 1;\n        } else if (glblmode && (keyCode == 517 || keyCode == KEY_UPPERCASE_N || keyCode == KEY_LOWERCASE_N)) {\n            NameWindow();\n            win_draw(edit_win);\n        } else if (glblmode && (keyCode == 519 || keyCode == KEY_UPPERCASE_A || keyCode == KEY_LOWERCASE_A)) {\n            AgeWindow();\n            win_draw(edit_win);\n        } else if (glblmode && (keyCode == 520 || keyCode == KEY_UPPERCASE_S || keyCode == KEY_LOWERCASE_S)) {\n            SexWindow();\n            win_draw(edit_win);\n        } else if (glblmode && (keyCode >= 503 && keyCode < 517)) {\n            StatButton(keyCode);\n            win_draw(edit_win);\n        } else if ((glblmode && (keyCode == 501 || keyCode == KEY_UPPERCASE_O || keyCode == KEY_LOWERCASE_O))\n            || (!glblmode && (keyCode == 501 || keyCode == KEY_UPPERCASE_P || keyCode == KEY_LOWERCASE_P))) {\n            OptionWindow();\n            win_draw(edit_win);\n        } else if (keyCode >= 525 && keyCode < 535) {\n            InfoButton(keyCode);\n            win_draw(edit_win);\n        } else {\n            switch (keyCode) {\n            case KEY_TAB:\n                if (info_line >= 0 && info_line < 7) {\n                    info_line = glblmode ? 82 : 7;\n                } else if (info_line >= 7 && info_line < 9) {\n                    if (glblmode) {\n                        info_line = 82;\n                    } else {\n                        info_line = 10;\n                        folder = 0;\n                    }\n                } else if (info_line >= 10 && info_line < 43) {\n                    switch (folder) {\n                    case EDITOR_FOLDER_PERKS:\n                        info_line = 10;\n                        folder = EDITOR_FOLDER_KARMA;\n                        break;\n                    case EDITOR_FOLDER_KARMA:\n                        info_line = 10;\n                        folder = EDITOR_FOLDER_KILLS;\n                        break;\n                    case EDITOR_FOLDER_KILLS:\n                        info_line = 43;\n                        break;\n                    }\n                } else if (info_line >= 43 && info_line < 51) {\n                    info_line = 51;\n                } else if (info_line >= 51 && info_line < 61) {\n                    info_line = 61;\n                } else if (info_line >= 61 && info_line < 82) {\n                    info_line = 0;\n                } else if (info_line >= 82 && info_line < 98) {\n                    info_line = 43;\n                }\n                PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n                ListTraits();\n                ListSkills(0);\n                PrintLevelWin();\n                DrawFolder();\n                ListDrvdStats();\n                DrawInfoWin();\n                win_draw(edit_win);\n                break;\n            case KEY_ARROW_LEFT:\n            case KEY_MINUS:\n            case KEY_UPPERCASE_J:\n                if (info_line >= 0 && info_line < 7) {\n                    if (glblmode) {\n                        win_button_press_and_release(stat_bids_minus[info_line]);\n                        win_draw(edit_win);\n                    }\n                } else if (info_line >= 61 && info_line < 79) {\n                    if (glblmode) {\n                        win_button_press_and_release(tag_bids[glblmode - 61]);\n                        win_draw(edit_win);\n                    } else {\n                        SliderBtn(keyCode);\n                        win_draw(edit_win);\n                    }\n                } else if (info_line >= 82 && info_line < 98) {\n                    if (glblmode) {\n                        win_button_press_and_release(trait_bids[glblmode - 82]);\n                        win_draw(edit_win);\n                    }\n                }\n                break;\n            case KEY_ARROW_RIGHT:\n            case KEY_PLUS:\n            case KEY_UPPERCASE_N:\n                if (info_line >= 0 && info_line < 7) {\n                    if (glblmode) {\n                        win_button_press_and_release(stat_bids_plus[info_line]);\n                        win_draw(edit_win);\n                    }\n                } else if (info_line >= 61 && info_line < 79) {\n                    if (glblmode) {\n                        win_button_press_and_release(tag_bids[glblmode - 61]);\n                        win_draw(edit_win);\n                    } else {\n                        SliderBtn(keyCode);\n                        win_draw(edit_win);\n                    }\n                } else if (info_line >= 82 && info_line < 98) {\n                    if (glblmode) {\n                        win_button_press_and_release(trait_bids[glblmode - 82]);\n                        win_draw(edit_win);\n                    }\n                }\n                break;\n            case KEY_ARROW_UP:\n                if (info_line >= 10 && info_line < 43) {\n                    if (info_line == 10) {\n                        if (folder_top_line > 0) {\n                            folder_scroll(-1);\n                            info_line--;\n                            DrawFolder();\n                            DrawInfoWin();\n                        }\n                    } else {\n                        info_line--;\n                        DrawFolder();\n                        DrawInfoWin();\n                    }\n\n                    win_draw(edit_win);\n                } else {\n                    switch (info_line) {\n                    case 0:\n                        info_line = 6;\n                        break;\n                    case 7:\n                        info_line = 9;\n                        break;\n                    case 43:\n                        info_line = 50;\n                        break;\n                    case 51:\n                        info_line = 60;\n                        break;\n                    case 61:\n                        info_line = 78;\n                        break;\n                    case 82:\n                        info_line = 97;\n                        break;\n                    default:\n                        info_line -= 1;\n                        break;\n                    }\n\n                    if (info_line >= 61 && info_line < 79) {\n                        skill_cursor = info_line - 61;\n                    }\n\n                    PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n                    ListTraits();\n                    ListSkills(0);\n                    PrintLevelWin();\n                    DrawFolder();\n                    ListDrvdStats();\n                    DrawInfoWin();\n                    win_draw(edit_win);\n                }\n                break;\n            case KEY_ARROW_DOWN:\n                if (info_line >= 10 && info_line < 43) {\n                    if (info_line - 10 < folder_line - folder_top_line) {\n                        if (info_line - 10 == folder_max_lines - 1) {\n                            folder_scroll(1);\n                        }\n\n                        info_line++;\n\n                        DrawFolder();\n                        DrawInfoWin();\n                    }\n\n                    win_draw(edit_win);\n                } else {\n                    switch (info_line) {\n                    case 6:\n                        info_line = 0;\n                        break;\n                    case 9:\n                        info_line = 7;\n                        break;\n                    case 50:\n                        info_line = 43;\n                        break;\n                    case 60:\n                        info_line = 51;\n                        break;\n                    case 78:\n                        info_line = 61;\n                        break;\n                    case 97:\n                        info_line = 82;\n                        break;\n                    default:\n                        info_line += 1;\n                        break;\n                    }\n\n                    if (info_line >= 61 && info_line < 79) {\n                        skill_cursor = info_line - 61;\n                    }\n\n                    PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n                    ListTraits();\n                    ListSkills(0);\n                    PrintLevelWin();\n                    DrawFolder();\n                    ListDrvdStats();\n                    DrawInfoWin();\n                    win_draw(edit_win);\n                }\n                break;\n            case 521:\n            case 523:\n                SliderBtn(keyCode);\n                win_draw(edit_win);\n                break;\n            case 535:\n                FldrButton();\n                win_draw(edit_win);\n                break;\n            case 17000:\n                folder_scroll(-1);\n                win_draw(edit_win);\n                break;\n            case 17001:\n                folder_scroll(1);\n                win_draw(edit_win);\n                break;\n            default:\n                if (glblmode && (keyCode >= 536 && keyCode < 554)) {\n                    TagSkillSelect(keyCode - 536);\n                    win_draw(edit_win);\n                } else if (glblmode && (keyCode >= 555 && keyCode < 571)) {\n                    TraitSelect(keyCode - 555);\n                    win_draw(edit_win);\n                } else {\n                    if (keyCode == 390) {\n                        dump_screen();\n                    }\n\n                    win_draw(edit_win);\n                }\n            }\n        }\n    }\n\n    if (rc == 0) {\n        if (isCreationMode) {\n            proto_dude_update_gender();\n            palette_fade_to(black_palette);\n        }\n    }\n\n    CharEditEnd();\n\n    if (rc == 1) {\n        RestorePlayer();\n    }\n\n    if (is_pc_flag(DUDE_STATE_LEVEL_UP_AVAILABLE)) {\n        pc_flag_off(DUDE_STATE_LEVEL_UP_AVAILABLE);\n    }\n\n    intface_update_hit_points(false);\n\n    return rc;\n}\n\n// 0x4329EC\nstatic int CharEditStart()\n{\n    int i;\n    char path[MAX_PATH];\n    int fid;\n    char* str;\n    int len;\n    int btn;\n    int x;\n    int y;\n    char perks[32];\n    char karma[32];\n    char kills[32];\n\n    fontsave = text_curr();\n    old_tags = 0;\n    bk_enable = 0;\n    old_fid2 = -1;\n    old_fid1 = -1;\n    frstc_draw2 = false;\n    frstc_draw1 = false;\n    first_skill_list = 1;\n    old_str2[0] = '\\0';\n    old_str1[0] = '\\0';\n\n    text_font(101);\n\n    slider_y = skill_cursor * (text_height() + 1) + 27;\n\n    // skills\n    skill_get_tags(temp_tag_skill, NUM_TAGGED_SKILLS);\n\n    // NOTE: Uninline.\n    tagskill_count = tagskl_free();\n\n    // traits\n    trait_get(&(temp_trait[0]), &(temp_trait[1]));\n\n    // NOTE: Uninline.\n    trait_count = get_trait_count();\n\n    if (!glblmode) {\n        bk_enable = map_disable_bk_processes();\n    }\n\n    cycle_disable();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    if (!message_init(&editor_message_file)) {\n        return -1;\n    }\n\n    sprintf(path, \"%s%s\", msg_path, \"editor.msg\");\n\n    if (!message_load(&editor_message_file, path)) {\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, (glblmode ? 169 : 177), 0, 0, 0);\n    bckgnd = art_lock(fid, &bck_key, &(GInfo[0].width), &(GInfo[0].height));\n    if (bckgnd == NULL) {\n        message_exit(&editor_message_file);\n        return -1;\n    }\n\n    if (karma_vars_init() == -1) {\n        return -1;\n    }\n\n    if (general_reps_init() == -1) {\n        return -1;\n    }\n\n    soundContinueAll();\n\n    for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) {\n        fid = art_id(OBJ_TYPE_INTERFACE, grph_id[i], 0, 0, 0);\n        grphbmp[i] = art_lock(fid, &(grph_key[i]), &(GInfo[i].width), &(GInfo[i].height));\n        if (grphbmp[i] == NULL) {\n            break;\n        }\n    }\n\n    if (i != EDITOR_GRAPHIC_COUNT) {\n        while (--i >= 0) {\n            art_ptr_unlock(grph_key[i]);\n        }\n        return -1;\n\n        art_ptr_unlock(bck_key);\n\n        message_exit(&editor_message_file);\n\n        // NOTE: Uninline.\n        RstrBckgProc();\n\n        return -1;\n    }\n\n    soundContinueAll();\n\n    for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) {\n        if (copyflag[i]) {\n            grphcpy[i] = (unsigned char*)mem_malloc(GInfo[i].width * GInfo[i].height);\n            if (grphcpy[i] == NULL) {\n                break;\n            }\n            memcpy(grphcpy[i], grphbmp[i], GInfo[i].width * GInfo[i].height);\n        } else {\n            grphcpy[i] = (unsigned char*)-1;\n        }\n    }\n\n    if (i != EDITOR_GRAPHIC_COUNT) {\n        while (--i >= 0) {\n            if (copyflag[i]) {\n                mem_free(grphcpy[i]);\n            }\n        }\n\n        for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) {\n            art_ptr_unlock(grph_key[i]);\n        }\n\n        art_ptr_unlock(bck_key);\n\n        message_exit(&editor_message_file);\n\n        // NOTE: Uninline.\n        RstrBckgProc();\n\n        return -1;\n    }\n\n    int editorWindowX = EDITOR_WINDOW_X;\n    int editorWindowY = EDITOR_WINDOW_Y;\n    edit_win = win_add(editorWindowX,\n        editorWindowY,\n        EDITOR_WINDOW_WIDTH,\n        EDITOR_WINDOW_HEIGHT,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n    if (edit_win == -1) {\n        for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) {\n            if (copyflag[i]) {\n                mem_free(grphcpy[i]);\n            }\n            art_ptr_unlock(grph_key[i]);\n        }\n\n        art_ptr_unlock(bck_key);\n\n        message_exit(&editor_message_file);\n\n        // NOTE: Uninline.\n        RstrBckgProc();\n\n        return -1;\n    }\n\n    win_buf = win_get_buf(edit_win);\n    memcpy(win_buf, bckgnd, 640 * 480);\n\n    if (glblmode) {\n        text_font(103);\n\n        // CHAR POINTS\n        str = getmsg(&editor_message_file, &mesg, 116);\n        text_to_buf(win_buf + (286 * 640) + 14, str, 640, 640, colorTable[18979]);\n        PrintBigNum(126, 282, 0, character_points, 0, edit_win);\n\n        // OPTIONS\n        str = getmsg(&editor_message_file, &mesg, 101);\n        text_to_buf(win_buf + (454 * 640) + 363, str, 640, 640, colorTable[18979]);\n\n        // OPTIONAL TRAITS\n        str = getmsg(&editor_message_file, &mesg, 139);\n        text_to_buf(win_buf + (326 * 640) + 52, str, 640, 640, colorTable[18979]);\n        PrintBigNum(522, 228, 0, optrt_count, 0, edit_win);\n\n        // TAG SKILLS\n        str = getmsg(&editor_message_file, &mesg, 138);\n        text_to_buf(win_buf + (233 * 640) + 422, str, 640, 640, colorTable[18979]);\n        PrintBigNum(522, 228, 0, tagskill_count, 0, edit_win);\n    } else {\n        text_font(103);\n\n        str = getmsg(&editor_message_file, &mesg, 109);\n        strcpy(perks, str);\n\n        str = getmsg(&editor_message_file, &mesg, 110);\n        strcpy(karma, str);\n\n        str = getmsg(&editor_message_file, &mesg, 111);\n        strcpy(kills, str);\n\n        // perks selected\n        len = text_width(perks);\n        text_to_buf(\n            grphcpy[46] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 61 - len / 2,\n            perks,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            colorTable[18979]);\n\n        len = text_width(karma);\n        text_to_buf(grphcpy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 159 - len / 2,\n            karma,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            colorTable[14723]);\n\n        len = text_width(kills);\n        text_to_buf(grphcpy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 257 - len / 2,\n            kills,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            colorTable[14723]);\n\n        // karma selected\n        len = text_width(perks);\n        text_to_buf(grphcpy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 61 - len / 2,\n            perks,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            colorTable[14723]);\n\n        len = text_width(karma);\n        text_to_buf(grphcpy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 159 - len / 2,\n            karma,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            colorTable[18979]);\n\n        len = text_width(kills);\n        text_to_buf(grphcpy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 257 - len / 2,\n            kills,\n            GInfo[46].width,\n            GInfo[46].width,\n            colorTable[14723]);\n\n        // kills selected\n        len = text_width(perks);\n        text_to_buf(grphcpy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 61 - len / 2,\n            perks,\n            GInfo[46].width,\n            GInfo[46].width,\n            colorTable[14723]);\n\n        len = text_width(karma);\n        text_to_buf(grphcpy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 159 - len / 2,\n            karma,\n            GInfo[46].width,\n            GInfo[46].width,\n            colorTable[14723]);\n\n        len = text_width(kills);\n        text_to_buf(grphcpy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 257 - len / 2,\n            kills,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            colorTable[18979]);\n\n        DrawFolder();\n\n        text_font(103);\n\n        // PRINT\n        str = getmsg(&editor_message_file, &mesg, 103);\n        text_to_buf(win_buf + (EDITOR_WINDOW_WIDTH * PRINT_BTN_Y) + PRINT_BTN_X, str, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_WIDTH, colorTable[18979]);\n\n        PrintLevelWin();\n        folder_init();\n    }\n\n    text_font(103);\n\n    // CANCEL\n    str = getmsg(&editor_message_file, &mesg, 102);\n    text_to_buf(win_buf + (EDITOR_WINDOW_WIDTH * CANCEL_BTN_Y) + CANCEL_BTN_X, str, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_WIDTH, colorTable[18979]);\n\n    // DONE\n    str = getmsg(&editor_message_file, &mesg, 100);\n    text_to_buf(win_buf + (EDITOR_WINDOW_WIDTH * DONE_BTN_Y) + DONE_BTN_X, str, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_WIDTH, colorTable[18979]);\n\n    PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n    ListDrvdStats();\n\n    if (!glblmode) {\n        SliderPlusID = win_register_button(\n            edit_win,\n            614,\n            20,\n            GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].width,\n            GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].height,\n            -1,\n            522,\n            521,\n            522,\n            grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_OFF],\n            grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_ON],\n            0,\n            96);\n        SliderNegID = win_register_button(\n            edit_win,\n            614,\n            20 + GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].height - 1,\n            GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].width,\n            GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_OFF].height,\n            -1,\n            524,\n            523,\n            524,\n            grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_OFF],\n            grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_ON],\n            0,\n            96);\n        win_register_button_sound_func(SliderPlusID, gsound_red_butt_press, NULL);\n        win_register_button_sound_func(SliderNegID, gsound_red_butt_press, NULL);\n    }\n\n    ListSkills(0);\n    DrawInfoWin();\n    soundContinueAll();\n    PrintBigname();\n    PrintAgeBig();\n    PrintGender();\n\n    if (glblmode) {\n        x = NAME_BUTTON_X;\n        btn = win_register_button(\n            edit_win,\n            x,\n            NAME_BUTTON_Y,\n            GInfo[EDITOR_GRAPHIC_NAME_ON].width,\n            GInfo[EDITOR_GRAPHIC_NAME_ON].height,\n            -1,\n            -1,\n            -1,\n            NAME_BTN_CODE,\n            grphcpy[EDITOR_GRAPHIC_NAME_OFF],\n            grphcpy[EDITOR_GRAPHIC_NAME_ON],\n            0,\n            32);\n        if (btn != -1) {\n            win_register_button_mask(btn, grphbmp[EDITOR_GRAPHIC_NAME_MASK]);\n            win_register_button_sound_func(btn, gsound_lrg_butt_press, NULL);\n        }\n\n        x += GInfo[EDITOR_GRAPHIC_NAME_ON].width;\n        btn = win_register_button(\n            edit_win,\n            x,\n            NAME_BUTTON_Y,\n            GInfo[EDITOR_GRAPHIC_AGE_ON].width,\n            GInfo[EDITOR_GRAPHIC_AGE_ON].height,\n            -1,\n            -1,\n            -1,\n            AGE_BTN_CODE,\n            grphcpy[EDITOR_GRAPHIC_AGE_OFF],\n            grphcpy[EDITOR_GRAPHIC_AGE_ON],\n            0,\n            32);\n        if (btn != -1) {\n            win_register_button_mask(btn, grphbmp[EDITOR_GRAPHIC_AGE_MASK]);\n            win_register_button_sound_func(btn, gsound_lrg_butt_press, NULL);\n        }\n\n        x += GInfo[EDITOR_GRAPHIC_AGE_ON].width;\n        btn = win_register_button(\n            edit_win,\n            x,\n            NAME_BUTTON_Y,\n            GInfo[EDITOR_GRAPHIC_SEX_ON].width,\n            GInfo[EDITOR_GRAPHIC_SEX_ON].height,\n            -1,\n            -1,\n            -1,\n            SEX_BTN_CODE,\n            grphcpy[EDITOR_GRAPHIC_SEX_OFF],\n            grphcpy[EDITOR_GRAPHIC_SEX_ON],\n            0,\n            32);\n        if (btn != -1) {\n            win_register_button_mask(btn, grphbmp[EDITOR_GRAPHIC_SEX_MASK]);\n            win_register_button_sound_func(btn, gsound_lrg_butt_press, NULL);\n        }\n\n        y = TAG_SKILLS_BUTTON_Y;\n        for (i = 0; i < SKILL_COUNT; i++) {\n            tag_bids[i] = win_register_button(\n                edit_win,\n                TAG_SKILLS_BUTTON_X,\n                y,\n                GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].width,\n                GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height,\n                -1,\n                -1,\n                -1,\n                TAG_SKILLS_BUTTON_CODE + i,\n                grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_OFF],\n                grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON],\n                NULL,\n                32);\n            y += GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height;\n        }\n\n        y = OPTIONAL_TRAITS_BTN_Y;\n        for (i = 0; i < TRAIT_COUNT / 2; i++) {\n            trait_bids[i] = win_register_button(\n                edit_win,\n                OPTIONAL_TRAITS_LEFT_BTN_X,\n                y,\n                GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].width,\n                GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height,\n                -1,\n                -1,\n                -1,\n                OPTIONAL_TRAITS_BTN_CODE + i,\n                grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_OFF],\n                grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON],\n                NULL,\n                32);\n            y += GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height + OPTIONAL_TRAITS_BTN_SPACE;\n        }\n\n        y = OPTIONAL_TRAITS_BTN_Y;\n        for (i = TRAIT_COUNT / 2; i < TRAIT_COUNT; i++) {\n            trait_bids[i] = win_register_button(\n                edit_win,\n                OPTIONAL_TRAITS_RIGHT_BTN_X,\n                y,\n                GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].width,\n                GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height,\n                -1,\n                -1,\n                -1,\n                OPTIONAL_TRAITS_BTN_CODE + i,\n                grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_OFF],\n                grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON],\n                NULL,\n                32);\n            y += GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height + OPTIONAL_TRAITS_BTN_SPACE;\n        }\n\n        ListTraits();\n    } else {\n        x = NAME_BUTTON_X;\n        trans_buf_to_buf(grphcpy[EDITOR_GRAPHIC_NAME_OFF],\n            GInfo[EDITOR_GRAPHIC_NAME_ON].width,\n            GInfo[EDITOR_GRAPHIC_NAME_ON].height,\n            GInfo[EDITOR_GRAPHIC_NAME_ON].width,\n            win_buf + (EDITOR_WINDOW_WIDTH * NAME_BUTTON_Y) + x,\n            EDITOR_WINDOW_WIDTH);\n\n        x += GInfo[EDITOR_GRAPHIC_NAME_ON].width;\n        trans_buf_to_buf(grphcpy[EDITOR_GRAPHIC_AGE_OFF],\n            GInfo[EDITOR_GRAPHIC_AGE_ON].width,\n            GInfo[EDITOR_GRAPHIC_AGE_ON].height,\n            GInfo[EDITOR_GRAPHIC_AGE_ON].width,\n            win_buf + (EDITOR_WINDOW_WIDTH * NAME_BUTTON_Y) + x,\n            EDITOR_WINDOW_WIDTH);\n\n        x += GInfo[EDITOR_GRAPHIC_AGE_ON].width;\n        trans_buf_to_buf(grphcpy[EDITOR_GRAPHIC_SEX_OFF],\n            GInfo[EDITOR_GRAPHIC_SEX_ON].width,\n            GInfo[EDITOR_GRAPHIC_SEX_ON].height,\n            GInfo[EDITOR_GRAPHIC_SEX_ON].width,\n            win_buf + (EDITOR_WINDOW_WIDTH * NAME_BUTTON_Y) + x,\n            EDITOR_WINDOW_WIDTH);\n\n        btn = win_register_button(edit_win,\n            11,\n            327,\n            GInfo[EDITOR_GRAPHIC_FOLDER_MASK].width,\n            GInfo[EDITOR_GRAPHIC_FOLDER_MASK].height,\n            -1,\n            -1,\n            -1,\n            535,\n            NULL,\n            NULL,\n            NULL,\n            BUTTON_FLAG_TRANSPARENT);\n        if (btn != -1) {\n            win_register_button_mask(btn, grphbmp[EDITOR_GRAPHIC_FOLDER_MASK]);\n        }\n    }\n\n    if (glblmode) {\n        // +/- buttons for stats\n        for (i = 0; i < 7; i++) {\n            stat_bids_plus[i] = win_register_button(edit_win,\n                SPECIAL_STATS_BTN_X,\n                StatYpos[i],\n                GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].width,\n                GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].height,\n                -1,\n                518,\n                503 + i,\n                518,\n                grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_OFF],\n                grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_ON],\n                NULL,\n                32);\n            if (stat_bids_plus[i] != -1) {\n                win_register_button_sound_func(stat_bids_plus[i], gsound_red_butt_press, NULL);\n            }\n\n            stat_bids_minus[i] = win_register_button(edit_win,\n                SPECIAL_STATS_BTN_X,\n                StatYpos[i] + GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].height - 1,\n                GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].width,\n                GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].height,\n                -1,\n                518,\n                510 + i,\n                518,\n                grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_OFF],\n                grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_ON],\n                NULL,\n                32);\n            if (stat_bids_minus[i] != -1) {\n                win_register_button_sound_func(stat_bids_minus[i], gsound_red_butt_press, NULL);\n            }\n        }\n    }\n\n    RegInfoAreas();\n    soundContinueAll();\n\n    btn = win_register_button(\n        edit_win,\n        343,\n        454,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        501,\n        grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP],\n        grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    btn = win_register_button(\n        edit_win,\n        552,\n        454,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        502,\n        grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP],\n        grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN],\n        0,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    btn = win_register_button(\n        edit_win,\n        455,\n        454,\n        GInfo[23].width,\n        GInfo[23].height,\n        -1,\n        -1,\n        -1,\n        500,\n        grphbmp[23],\n        grphbmp[24],\n        0,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    win_draw(edit_win);\n    disable_box_bar_win();\n\n    return 0;\n}\n\n// 0x433AA8\nstatic void CharEditEnd()\n{\n    // NOTE: Uninline.\n    folder_exit();\n\n    win_delete(edit_win);\n\n    for (int index = 0; index < EDITOR_GRAPHIC_COUNT; index++) {\n        art_ptr_unlock(grph_key[index]);\n\n        if (copyflag[index]) {\n            mem_free(grphcpy[index]);\n        }\n    }\n\n    art_ptr_unlock(bck_key);\n\n    // NOTE: Uninline.\n    general_reps_exit();\n\n    // NOTE: Uninline.\n    karma_vars_exit();\n\n    message_exit(&editor_message_file);\n\n    intface_redraw();\n\n    // NOTE: Uninline.\n    RstrBckgProc();\n\n    text_font(fontsave);\n\n    if (glblmode == 1) {\n        skill_set_tags(temp_tag_skill, 3);\n        trait_set(temp_trait[0], temp_trait[1]);\n        info_line = 0;\n        critter_adjust_hits(obj_dude, 1000);\n    }\n\n    enable_box_bar_win();\n}\n\n// NOTE: Inlined.\n//\n// 0x433BEC\nstatic void RstrBckgProc()\n{\n    if (bk_enable) {\n        map_enable_bk_processes();\n    }\n\n    cycle_enable();\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n}\n\n// CharEditInit\n// 0x433C0C\nvoid CharEditInit()\n{\n    int i;\n\n    info_line = 0;\n    skill_cursor = 0;\n    slider_y = 27;\n    free_perk = 0;\n    folder = EDITOR_FOLDER_PERKS;\n\n    for (i = 0; i < 2; i++) {\n        temp_trait[i] = -1;\n        trait_back[i] = -1;\n    }\n\n    character_points = 5;\n    last_level = 1;\n}\n\n// handle name input\nint get_input_str(int win, int cancelKeyCode, char* text, int maxLength, int x, int y, int textColor, int backgroundColor, int flags)\n{\n    int cursorWidth = text_width(\"_\") - 4;\n    int windowWidth = win_width(win);\n    int v60 = text_height();\n    unsigned char* windowBuffer = win_get_buf(win);\n    if (maxLength > 255) {\n        maxLength = 255;\n    }\n\n    char copy[257];\n    strcpy(copy, text);\n\n    int nameLength = strlen(text);\n    copy[nameLength] = ' ';\n    copy[nameLength + 1] = '\\0';\n\n    int nameWidth = text_width(copy);\n\n    buf_fill(windowBuffer + windowWidth * y + x, nameWidth, text_height(), windowWidth, backgroundColor);\n    text_to_buf(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor);\n\n    win_draw(win);\n\n    int blinkingCounter = 3;\n    bool blink = false;\n\n    int rc = 1;\n    while (rc == 1) {\n        _frame_time = get_time();\n\n        int keyCode = get_input();\n        if (keyCode == cancelKeyCode) {\n            rc = 0;\n        } else if (keyCode == KEY_RETURN) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            rc = 0;\n        } else if (keyCode == KEY_ESCAPE || game_user_wants_to_quit != 0) {\n            rc = -1;\n        } else {\n            if ((keyCode == KEY_DELETE || keyCode == KEY_BACKSPACE) && nameLength >= 1) {\n                buf_fill(windowBuffer + windowWidth * y + x, text_width(copy), v60, windowWidth, backgroundColor);\n                copy[nameLength - 1] = ' ';\n                copy[nameLength] = '\\0';\n                text_to_buf(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor);\n                nameLength--;\n\n                win_draw(win);\n            } else if ((keyCode >= KEY_FIRST_INPUT_CHARACTER && keyCode <= KEY_LAST_INPUT_CHARACTER) && nameLength < maxLength) {\n                if ((flags & 0x01) != 0) {\n                    if (!isdoschar(keyCode)) {\n                        break;\n                    }\n                }\n\n                buf_fill(windowBuffer + windowWidth * y + x, text_width(copy), v60, windowWidth, backgroundColor);\n\n                copy[nameLength] = keyCode & 0xFF;\n                copy[nameLength + 1] = ' ';\n                copy[nameLength + 2] = '\\0';\n                text_to_buf(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor);\n                nameLength++;\n\n                win_draw(win);\n            }\n        }\n\n        blinkingCounter -= 1;\n        if (blinkingCounter == 0) {\n            blinkingCounter = 3;\n\n            int color = blink ? backgroundColor : textColor;\n            blink = !blink;\n\n            buf_fill(windowBuffer + windowWidth * y + x + text_width(copy) - cursorWidth, cursorWidth, v60 - 2, windowWidth, color);\n        }\n\n        win_draw(win);\n\n        while (elapsed_time(_frame_time) < 1000 / 24) { }\n    }\n\n    if (rc == 0 || nameLength > 0) {\n        copy[nameLength] = '\\0';\n        strcpy(text, copy);\n    }\n\n    return rc;\n}\n\n// 0x434060\nbool isdoschar(int ch)\n{\n    const char* punctuations = \"#@!$`'~^&()-_=[]{}\";\n\n    if (isalnum(ch)) {\n        return true;\n    }\n\n    int length = strlen(punctuations);\n    for (int index = 0; index < length; index++) {\n        if (punctuations[index] == ch) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// copy filename replacing extension\n//\n// 0x4340D0\nchar* strmfe(char* dest, const char* name, const char* ext)\n{\n    char* save = dest;\n\n    while (*name != '\\0' && *name != '.') {\n        *dest++ = *name++;\n    }\n\n    *dest++ = '.';\n\n    strcpy(dest, ext);\n\n    return save;\n}\n\n// 0x43410C\nstatic void DrawFolder()\n{\n    if (glblmode) {\n        return;\n    }\n\n    buf_to_buf(bckgnd + (360 * 640) + 34, 280, 120, 640, win_buf + (360 * 640) + 34, 640);\n\n    text_font(101);\n\n    switch (folder) {\n    case EDITOR_FOLDER_PERKS:\n        buf_to_buf(grphcpy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED],\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].height,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            win_buf + (327 * 640) + 11,\n            640);\n        list_perks();\n        break;\n    case EDITOR_FOLDER_KARMA:\n        buf_to_buf(grphcpy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED],\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].height,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            win_buf + (327 * 640) + 11,\n            640);\n        list_karma();\n        break;\n    case EDITOR_FOLDER_KILLS:\n        buf_to_buf(grphcpy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED],\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].height,\n            GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width,\n            win_buf + (327 * 640) + 11,\n            640);\n        kills_count = ListKills();\n        break;\n    default:\n        debug_printf(\"\\n ** Unknown folder type! **\\n\");\n        break;\n    }\n}\n\n// 0x434238\nstatic void list_perks()\n{\n    const char* string;\n    char perkName[80];\n    int perk;\n    int perkLevel;\n    bool hasContent = false;\n\n    folder_clear();\n\n    if (temp_trait[0] != -1) {\n        // TRAITS\n        string = getmsg(&editor_message_file, &mesg, 156);\n        if (folder_print_seperator(string)) {\n            folder_card_fid = 54;\n            // Optional Traits\n            folder_card_title = getmsg(&editor_message_file, &mesg, 146);\n            folder_card_title2 = NULL;\n            // Optional traits describe your character in more detail. All traits will have positive and negative effects. You may choose up to two traits during creation.\n            folder_card_desc = getmsg(&editor_message_file, &mesg, 147);\n            hasContent = true;\n        }\n\n        if (temp_trait[0] != -1) {\n            string = trait_name(temp_trait[0]);\n            if (folder_print_line(string)) {\n                folder_card_fid = trait_pic(temp_trait[0]);\n                folder_card_title = trait_name(temp_trait[0]);\n                folder_card_title2 = NULL;\n                folder_card_desc = trait_description(temp_trait[0]);\n                hasContent = true;\n            }\n        }\n\n        if (temp_trait[1] != -1) {\n            string = trait_name(temp_trait[1]);\n            if (folder_print_line(string)) {\n                folder_card_fid = trait_pic(temp_trait[1]);\n                folder_card_title = trait_name(temp_trait[1]);\n                folder_card_title2 = NULL;\n                folder_card_desc = trait_description(temp_trait[1]);\n                hasContent = true;\n            }\n        }\n    }\n\n    for (perk = 0; perk < PERK_COUNT; perk++) {\n        if (perk_level(obj_dude, perk) != 0) {\n            break;\n        }\n    }\n\n    if (perk != PERK_COUNT) {\n        // PERKS\n        string = getmsg(&editor_message_file, &mesg, 109);\n        folder_print_seperator(string);\n\n        for (perk = 0; perk < PERK_COUNT; perk++) {\n            perkLevel = perk_level(obj_dude, perk);\n            if (perkLevel != 0) {\n                string = perk_name(perk);\n\n                if (perkLevel == 1) {\n                    strcpy(perkName, string);\n                } else {\n                    sprintf(perkName, \"%s (%d)\", string, perkLevel);\n                }\n\n                if (folder_print_line(perkName)) {\n                    folder_card_fid = perk_skilldex_fid(perk);\n                    folder_card_title = perk_name(perk);\n                    folder_card_title2 = NULL;\n                    folder_card_desc = perk_description(perk);\n                    hasContent = true;\n                }\n            }\n        }\n    }\n\n    if (!hasContent) {\n        folder_card_fid = 71;\n        // Perks\n        folder_card_title = getmsg(&editor_message_file, &mesg, 124);\n        folder_card_title2 = NULL;\n        // Perks add additional abilities. Every third experience level, you can choose one perk.\n        folder_card_desc = getmsg(&editor_message_file, &mesg, 127);\n    }\n}\n\n// 0x434498\nstatic int kills_list_comp(const void* a1, const void* a2)\n{\n    const KillInfo* v1 = (const KillInfo*)a1;\n    const KillInfo* v2 = (const KillInfo*)a2;\n    return stricmp(v1->name, v2->name);\n}\n\n// 0x4344A4\nstatic int ListKills()\n{\n    int i;\n    int killsCount;\n    KillInfo kills[19];\n    int usedKills = 0;\n    bool hasContent = false;\n\n    folder_clear();\n\n    for (i = 0; i < KILL_TYPE_COUNT; i++) {\n        killsCount = critter_kill_count(i);\n        if (killsCount != 0) {\n            KillInfo* killInfo = &(kills[usedKills]);\n            killInfo->name = critter_kill_name(i);\n            killInfo->killTypeId = i;\n            killInfo->kills = killsCount;\n            usedKills++;\n        }\n    }\n\n    if (usedKills != 0) {\n        qsort(kills, usedKills, sizeof(*kills), kills_list_comp);\n\n        for (i = 0; i < usedKills; i++) {\n            KillInfo* killInfo = &(kills[i]);\n            if (folder_print_kill(killInfo->name, killInfo->kills)) {\n                folder_card_fid = 46;\n                folder_card_title = folder_card_string;\n                folder_card_title2 = NULL;\n                folder_card_desc = critter_kill_info(kills[i].killTypeId);\n                sprintf(folder_card_string, \"%s %s\", killInfo->name, getmsg(&editor_message_file, &mesg, 126));\n                hasContent = true;\n            }\n        }\n    }\n\n    if (!hasContent) {\n        folder_card_fid = 46;\n        folder_card_title = getmsg(&editor_message_file, &mesg, 126);\n        folder_card_title2 = NULL;\n        folder_card_desc = getmsg(&editor_message_file, &mesg, 129);\n    }\n\n    return usedKills;\n}\n\n// 0x4345DC\nstatic void PrintBigNum(int x, int y, int flags, int value, int previousValue, int windowHandle)\n{\n    Rect rect;\n    int windowWidth;\n    unsigned char* windowBuf;\n    int tens;\n    int ones;\n    unsigned char* tensBufferPtr;\n    unsigned char* onesBufferPtr;\n    unsigned char* numbersGraphicBufferPtr;\n\n    windowWidth = win_width(windowHandle);\n    windowBuf = win_get_buf(windowHandle);\n\n    rect.ulx = x;\n    rect.uly = y;\n    rect.lrx = x + BIG_NUM_WIDTH * 2;\n    rect.lry = y + BIG_NUM_HEIGHT;\n\n    numbersGraphicBufferPtr = grphbmp[0];\n\n    if (flags & RED_NUMBERS) {\n        // First half of the bignum.frm is white,\n        // second half is red.\n        numbersGraphicBufferPtr += GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width / 2;\n    }\n\n    tensBufferPtr = windowBuf + windowWidth * y + x;\n    onesBufferPtr = tensBufferPtr + BIG_NUM_WIDTH;\n\n    if (value >= 0 && value <= 99 && previousValue >= 0 && previousValue <= 99) {\n        tens = value / 10;\n        ones = value % 10;\n\n        if (flags & ANIMATE) {\n            if (previousValue % 10 != ones) {\n                _frame_time = get_time();\n                buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 11,\n                    BIG_NUM_WIDTH,\n                    BIG_NUM_HEIGHT,\n                    GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width,\n                    onesBufferPtr,\n                    windowWidth);\n                win_draw_rect(windowHandle, &rect);\n                while (elapsed_time(_frame_time) < BIG_NUM_ANIMATION_DELAY)\n                    ;\n            }\n\n            buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * ones,\n                BIG_NUM_WIDTH,\n                BIG_NUM_HEIGHT,\n                GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width,\n                onesBufferPtr,\n                windowWidth);\n            win_draw_rect(windowHandle, &rect);\n\n            if (previousValue / 10 != tens) {\n                _frame_time = get_time();\n                buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 11,\n                    BIG_NUM_WIDTH,\n                    BIG_NUM_HEIGHT,\n                    GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width,\n                    tensBufferPtr,\n                    windowWidth);\n                win_draw_rect(windowHandle, &rect);\n                while (elapsed_time(_frame_time) < BIG_NUM_ANIMATION_DELAY)\n                    ;\n            }\n\n            buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * tens,\n                BIG_NUM_WIDTH,\n                BIG_NUM_HEIGHT,\n                GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width,\n                tensBufferPtr,\n                windowWidth);\n            win_draw_rect(windowHandle, &rect);\n        } else {\n            buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * tens,\n                BIG_NUM_WIDTH,\n                BIG_NUM_HEIGHT,\n                GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width,\n                tensBufferPtr,\n                windowWidth);\n            buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * ones,\n                BIG_NUM_WIDTH,\n                BIG_NUM_HEIGHT,\n                GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width,\n                onesBufferPtr,\n                windowWidth);\n        }\n    } else {\n\n        buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 9,\n            BIG_NUM_WIDTH,\n            BIG_NUM_HEIGHT,\n            GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width,\n            tensBufferPtr,\n            windowWidth);\n        buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 9,\n            BIG_NUM_WIDTH,\n            BIG_NUM_HEIGHT,\n            GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width,\n            onesBufferPtr,\n            windowWidth);\n    }\n}\n\n// 0x434920\nstatic void PrintLevelWin()\n{\n    int color;\n    int y;\n    char* formattedValue;\n    // NOTE: The length of this buffer is 8 bytes, which is enough to display\n    // 999,999 (7 bytes NULL-terminated) experience points. Usually a player\n    // will never gain that much during normal gameplay.\n    //\n    // However it's possible to use one of the F2 modding tools and savegame\n    // editors to receive rediculous amount of experience points. Vanilla is\n    // able to handle it, because `stringBuffer` acts as continuation of\n    // `formattedValueBuffer`. This is not the case with MSVC, where\n    // insufficient space for xp greater then 999,999 ruins the stack. In order\n    // to fix the `formattedValueBuffer` is expanded to 16 bytes, so it should\n    // be possible to store max 32-bit integer (4,294,967,295).\n    char formattedValueBuffer[16];\n    char stringBuffer[128];\n\n    if (glblmode == 1) {\n        return;\n    }\n\n    text_font(101);\n\n    buf_to_buf(bckgnd + 640 * 280 + 32, 124, 32, 640, win_buf + 640 * 280 + 32, 640);\n\n    // LEVEL\n    y = 280;\n    if (info_line != 7) {\n        color = colorTable[992];\n    } else {\n        color = colorTable[32747];\n    }\n\n    int level = stat_pc_get(PC_STAT_LEVEL);\n    sprintf(stringBuffer, \"%s %d\",\n        getmsg(&editor_message_file, &mesg, 113),\n        level);\n    text_to_buf(win_buf + 640 * y + 32, stringBuffer, 640, 640, color);\n\n    // EXPERIENCE\n    y += text_height() + 1;\n    if (info_line != 8) {\n        color = colorTable[992];\n    } else {\n        color = colorTable[32747];\n    }\n\n    int exp = stat_pc_get(PC_STAT_EXPERIENCE);\n    sprintf(stringBuffer, \"%s %s\",\n        getmsg(&editor_message_file, &mesg, 114),\n        itostndn(exp, formattedValueBuffer));\n    text_to_buf(win_buf + 640 * y + 32, stringBuffer, 640, 640, color);\n\n    // EXP NEEDED TO NEXT LEVEL\n    y += text_height() + 1;\n    if (info_line != 9) {\n        color = colorTable[992];\n    } else {\n        color = colorTable[32747];\n    }\n\n    int expToNextLevel = stat_pc_min_exp();\n    int expMsgId;\n    if (expToNextLevel == -1) {\n        expMsgId = 115;\n        formattedValue = byte_5016E4;\n    } else {\n        expMsgId = 115;\n        if (expToNextLevel > 999999) {\n            expMsgId = 175;\n        }\n        formattedValue = itostndn(expToNextLevel, formattedValueBuffer);\n    }\n\n    sprintf(stringBuffer, \"%s %s\",\n        getmsg(&editor_message_file, &mesg, expMsgId),\n        formattedValue);\n    text_to_buf(win_buf + 640 * y + 32, stringBuffer, 640, 640, color);\n}\n\n// 0x434B38\nstatic void PrintBasicStat(int stat, bool animate, int previousValue)\n{\n    int off;\n    int color;\n    const char* description;\n    int value;\n    int flags;\n    int messageListItemId;\n\n    text_font(101);\n\n    if (stat == RENDER_ALL_STATS) {\n        // NOTE: Original code is different, looks like tail recursion\n        // optimization.\n        for (stat = 0; stat < 7; stat++) {\n            PrintBasicStat(stat, 0, 0);\n        }\n        return;\n    }\n\n    if (info_line == stat) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    off = 640 * (StatYpos[stat] + 8) + 103;\n\n    // TODO: The original code is different.\n    if (glblmode) {\n        value = stat_get_base(obj_dude, stat) + stat_get_bonus(obj_dude, stat);\n\n        flags = 0;\n\n        if (animate) {\n            flags |= ANIMATE;\n        }\n\n        if (value > 10) {\n            flags |= RED_NUMBERS;\n        }\n\n        PrintBigNum(58, StatYpos[stat], flags, value, previousValue, edit_win);\n\n        buf_to_buf(bckgnd + off, 40, text_height(), 640, win_buf + off, 640);\n\n        messageListItemId = critterGetStat(obj_dude, stat) + 199;\n        if (messageListItemId > 210) {\n            messageListItemId = 210;\n        }\n\n        description = getmsg(&editor_message_file, &mesg, messageListItemId);\n        text_to_buf(win_buf + 640 * (StatYpos[stat] + 8) + 103, description, 640, 640, color);\n    } else {\n        value = critterGetStat(obj_dude, stat);\n        PrintBigNum(58, StatYpos[stat], 0, value, 0, edit_win);\n        buf_to_buf(bckgnd + off, 40, text_height(), 640, win_buf + off, 640);\n\n        value = critterGetStat(obj_dude, stat);\n        if (value > 10) {\n            value = 10;\n        }\n\n        description = stat_level_description(value);\n        text_to_buf(win_buf + off, description, 640, 640, color);\n    }\n}\n\n// 0x434F18\nstatic void PrintGender()\n{\n    int gender;\n    char* str;\n    char text[32];\n    int x, width;\n\n    text_font(103);\n\n    gender = critterGetStat(obj_dude, STAT_GENDER);\n    str = getmsg(&editor_message_file, &mesg, 107 + gender);\n\n    strcpy(text, str);\n\n    width = GInfo[EDITOR_GRAPHIC_SEX_ON].width;\n    x = (width / 2) - (text_width(text) / 2);\n\n    memcpy(grphcpy[11],\n        grphbmp[EDITOR_GRAPHIC_SEX_ON],\n        width * GInfo[EDITOR_GRAPHIC_SEX_ON].height);\n    memcpy(grphcpy[EDITOR_GRAPHIC_SEX_OFF],\n        grphbmp[10],\n        width * GInfo[EDITOR_GRAPHIC_SEX_OFF].height);\n\n    x += 6 * width;\n    text_to_buf(grphcpy[EDITOR_GRAPHIC_SEX_ON] + x, text, width, width, colorTable[14723]);\n    text_to_buf(grphcpy[EDITOR_GRAPHIC_SEX_OFF] + x, text, width, width, colorTable[18979]);\n}\n\n// 0x43501C\nstatic void PrintAgeBig()\n{\n    int age;\n    char* str;\n    char text[32];\n    int x, width;\n\n    text_font(103);\n\n    age = critterGetStat(obj_dude, STAT_AGE);\n    str = getmsg(&editor_message_file, &mesg, 104);\n\n    sprintf(text, \"%s %d\", str, age);\n\n    width = GInfo[EDITOR_GRAPHIC_AGE_ON].width;\n    x = (width / 2) + 1 - (text_width(text) / 2);\n\n    memcpy(grphcpy[EDITOR_GRAPHIC_AGE_ON],\n        grphbmp[EDITOR_GRAPHIC_AGE_ON],\n        width * GInfo[EDITOR_GRAPHIC_AGE_ON].height);\n    memcpy(grphcpy[EDITOR_GRAPHIC_AGE_OFF],\n        grphbmp[EDITOR_GRAPHIC_AGE_OFF],\n        width * GInfo[EDITOR_GRAPHIC_AGE_ON].height);\n\n    x += 6 * width;\n    text_to_buf(grphcpy[EDITOR_GRAPHIC_AGE_ON] + x, text, width, width, colorTable[14723]);\n    text_to_buf(grphcpy[EDITOR_GRAPHIC_AGE_OFF] + x, text, width, width, colorTable[18979]);\n}\n\n// 0x435118\nstatic void PrintBigname()\n{\n    char* str;\n    char text[32];\n    int x, width;\n    char *pch, tmp;\n    bool has_space;\n\n    text_font(103);\n\n    str = critter_name(obj_dude);\n    strcpy(text, str);\n\n    if (text_width(text) > 100) {\n        pch = text;\n        has_space = false;\n        while (*pch != '\\0') {\n            tmp = *pch;\n            *pch = '\\0';\n            if (tmp == ' ') {\n                has_space = true;\n            }\n\n            if (text_width(text) > 100) {\n                break;\n            }\n\n            *pch = tmp;\n            pch++;\n        }\n\n        if (has_space) {\n            pch = text + strlen(text);\n            while (pch != text && *pch != ' ') {\n                *pch = '\\0';\n                pch--;\n            }\n        }\n    }\n\n    width = GInfo[EDITOR_GRAPHIC_NAME_ON].width;\n    x = (width / 2) + 3 - (text_width(text) / 2);\n\n    memcpy(grphcpy[EDITOR_GRAPHIC_NAME_ON],\n        grphbmp[EDITOR_GRAPHIC_NAME_ON],\n        GInfo[EDITOR_GRAPHIC_NAME_ON].width * GInfo[EDITOR_GRAPHIC_NAME_ON].height);\n    memcpy(grphcpy[EDITOR_GRAPHIC_NAME_OFF],\n        grphbmp[EDITOR_GRAPHIC_NAME_OFF],\n        GInfo[EDITOR_GRAPHIC_NAME_OFF].width * GInfo[EDITOR_GRAPHIC_NAME_OFF].height);\n\n    x += 6 * width;\n    text_to_buf(grphcpy[EDITOR_GRAPHIC_NAME_ON] + x, text, width, width, colorTable[14723]);\n    text_to_buf(grphcpy[EDITOR_GRAPHIC_NAME_OFF] + x, text, width, width, colorTable[18979]);\n}\n\n// 0x43527C\nstatic void ListDrvdStats()\n{\n    int conditions;\n    int color;\n    const char* messageListItemText;\n    char t[420]; // TODO: Size is wrong.\n    int y;\n\n    conditions = obj_dude->data.critter.combat.results;\n\n    text_font(101);\n\n    y = 46;\n\n    buf_to_buf(bckgnd + 640 * y + 194, 118, 108, 640, win_buf + 640 * y + 194, 640);\n\n    // Hit Points\n    if (info_line == EDITOR_HIT_POINTS) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    int currHp;\n    int maxHp;\n    if (glblmode) {\n        maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n        currHp = maxHp;\n    } else {\n        maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n        currHp = critter_get_hits(obj_dude);\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 300);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    sprintf(t, \"%d/%d\", currHp, maxHp);\n    text_to_buf(win_buf + 640 * y + 263, t, 640, 640, color);\n\n    // Poisoned\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_POISONED) {\n        color = critter_get_poison(obj_dude) != 0 ? colorTable[32747] : colorTable[15845];\n    } else {\n        color = critter_get_poison(obj_dude) != 0 ? colorTable[992] : colorTable[1313];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 312);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    // Radiated\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_RADIATED) {\n        color = critter_get_rads(obj_dude) != 0 ? colorTable[32747] : colorTable[15845];\n    } else {\n        color = critter_get_rads(obj_dude) != 0 ? colorTable[992] : colorTable[1313];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 313);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    // Eye Damage\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_EYE_DAMAGE) {\n        color = (conditions & DAM_BLIND) ? colorTable[32747] : colorTable[15845];\n    } else {\n        color = (conditions & DAM_BLIND) ? colorTable[992] : colorTable[1313];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 314);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    // Crippled Right Arm\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_CRIPPLED_RIGHT_ARM) {\n        color = (conditions & DAM_CRIP_ARM_RIGHT) ? colorTable[32747] : colorTable[15845];\n    } else {\n        color = (conditions & DAM_CRIP_ARM_RIGHT) ? colorTable[992] : colorTable[1313];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 315);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    // Crippled Left Arm\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_CRIPPLED_LEFT_ARM) {\n        color = (conditions & DAM_CRIP_ARM_LEFT) ? colorTable[32747] : colorTable[15845];\n    } else {\n        color = (conditions & DAM_CRIP_ARM_LEFT) ? colorTable[992] : colorTable[1313];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 316);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    // Crippled Right Leg\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_CRIPPLED_RIGHT_LEG) {\n        color = (conditions & DAM_CRIP_LEG_RIGHT) ? colorTable[32747] : colorTable[15845];\n    } else {\n        color = (conditions & DAM_CRIP_LEG_RIGHT) ? colorTable[992] : colorTable[1313];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 317);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    // Crippled Left Leg\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_CRIPPLED_LEFT_LEG) {\n        color = (conditions & DAM_CRIP_LEG_LEFT) ? colorTable[32747] : colorTable[15845];\n    } else {\n        color = (conditions & DAM_CRIP_LEG_LEFT) ? colorTable[992] : colorTable[1313];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 318);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    y = 179;\n\n    buf_to_buf(bckgnd + 640 * y + 194, 116, 130, 640, win_buf + 640 * y + 194, 640);\n\n    // Armor Class\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_ARMOR_CLASS) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 302);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    itoa(critterGetStat(obj_dude, STAT_ARMOR_CLASS), t, 10);\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n\n    // Action Points\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_ACTION_POINTS) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 301);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    itoa(critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS), t, 10);\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n\n    // Carry Weight\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_CARRY_WEIGHT) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 311);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    itoa(critterGetStat(obj_dude, STAT_CARRY_WEIGHT), t, 10);\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, critterIsOverloaded(obj_dude) ? colorTable[31744] : color);\n\n    // Melee Damage\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_MELEE_DAMAGE) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 304);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    itoa(critterGetStat(obj_dude, STAT_MELEE_DAMAGE), t, 10);\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n\n    // Damage Resistance\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_DAMAGE_RESISTANCE) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 305);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    sprintf(t, \"%d%%\", critterGetStat(obj_dude, STAT_DAMAGE_RESISTANCE));\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n\n    // Poison Resistance\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_POISON_RESISTANCE) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 306);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    sprintf(t, \"%d%%\", critterGetStat(obj_dude, STAT_POISON_RESISTANCE));\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n\n    // Radiation Resistance\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_RADIATION_RESISTANCE) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 307);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    sprintf(t, \"%d%%\", critterGetStat(obj_dude, STAT_RADIATION_RESISTANCE));\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n\n    // Sequence\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_SEQUENCE) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 308);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    itoa(critterGetStat(obj_dude, STAT_SEQUENCE), t, 10);\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n\n    // Healing Rate\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_HEALING_RATE) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 309);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    itoa(critterGetStat(obj_dude, STAT_HEALING_RATE), t, 10);\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n\n    // Critical Chance\n    y += text_height() + 3;\n\n    if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_CRITICAL_CHANCE) {\n        color = colorTable[32747];\n    } else {\n        color = colorTable[992];\n    }\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 310);\n    sprintf(t, \"%s\", messageListItemText);\n    text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color);\n\n    sprintf(t, \"%d%%\", critterGetStat(obj_dude, STAT_CRITICAL_CHANCE));\n    text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color);\n}\n\n// 0x436154\nstatic void ListSkills(int a1)\n{\n    int selectedSkill = -1;\n    const char* str;\n    int i;\n    int color;\n    int y;\n    int value;\n    char valueString[32];\n\n    if (info_line >= EDITOR_FIRST_SKILL && info_line < 79) {\n        selectedSkill = info_line - EDITOR_FIRST_SKILL;\n    }\n\n    if (glblmode == 0 && a1 == 0) {\n        win_delete_button(SliderPlusID);\n        win_delete_button(SliderNegID);\n        SliderNegID = -1;\n        SliderPlusID = -1;\n    }\n\n    buf_to_buf(bckgnd + 370, 270, 252, 640, win_buf + 370, 640);\n\n    text_font(103);\n\n    // SKILLS\n    str = getmsg(&editor_message_file, &mesg, 117);\n    text_to_buf(win_buf + 640 * 5 + 380, str, 640, 640, colorTable[18979]);\n\n    if (!glblmode) {\n        // SKILL POINTS\n        str = getmsg(&editor_message_file, &mesg, 112);\n        text_to_buf(win_buf + 640 * 233 + 400, str, 640, 640, colorTable[18979]);\n\n        value = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS);\n        PrintBigNum(522, 228, 0, value, 0, edit_win);\n    } else {\n        // TAG SKILLS\n        str = getmsg(&editor_message_file, &mesg, 138);\n        text_to_buf(win_buf + 640 * 233 + 422, str, 640, 640, colorTable[18979]);\n\n        if (a1 == 2 && !first_skill_list) {\n            PrintBigNum(522, 228, ANIMATE, tagskill_count, old_tags, edit_win);\n        } else {\n            PrintBigNum(522, 228, 0, tagskill_count, 0, edit_win);\n            first_skill_list = 0;\n        }\n    }\n\n    skill_set_tags(temp_tag_skill, NUM_TAGGED_SKILLS);\n\n    text_font(101);\n\n    y = 27;\n    for (i = 0; i < SKILL_COUNT; i++) {\n        if (i == selectedSkill) {\n            if (i != temp_tag_skill[0] && i != temp_tag_skill[1] && i != temp_tag_skill[2] && i != temp_tag_skill[3]) {\n                color = colorTable[32747];\n            } else {\n                color = colorTable[32767];\n            }\n        } else {\n            if (i != temp_tag_skill[0] && i != temp_tag_skill[1] && i != temp_tag_skill[2] && i != temp_tag_skill[3]) {\n                color = colorTable[992];\n            } else {\n                color = colorTable[21140];\n            }\n        }\n\n        str = skill_name(i);\n        text_to_buf(win_buf + 640 * y + 380, str, 640, 640, color);\n\n        value = skill_level(obj_dude, i);\n        sprintf(valueString, \"%d%%\", value);\n\n        text_to_buf(win_buf + 640 * y + 573, valueString, 640, 640, color);\n\n        y += text_height() + 1;\n    }\n\n    if (!glblmode) {\n        y = skill_cursor * (text_height() + 1);\n        slider_y = y + 27;\n\n        trans_buf_to_buf(\n            grphbmp[EDITOR_GRAPHIC_SLIDER],\n            GInfo[EDITOR_GRAPHIC_SLIDER].width,\n            GInfo[EDITOR_GRAPHIC_SLIDER].height,\n            GInfo[EDITOR_GRAPHIC_SLIDER].width,\n            win_buf + 640 * (y + 16) + 592,\n            640);\n\n        if (a1 == 0) {\n            if (SliderPlusID == -1) {\n                SliderPlusID = win_register_button(\n                    edit_win,\n                    614,\n                    slider_y - 7,\n                    GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].width,\n                    GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].height,\n                    -1,\n                    522,\n                    521,\n                    522,\n                    grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_OFF],\n                    grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_ON],\n                    NULL,\n                    96);\n                win_register_button_sound_func(SliderPlusID, gsound_red_butt_press, NULL);\n            }\n\n            if (SliderNegID == -1) {\n                SliderNegID = win_register_button(\n                    edit_win,\n                    614,\n                    slider_y + 4 - 12 + GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].height,\n                    GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].width,\n                    GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_OFF].height,\n                    -1,\n                    524,\n                    523,\n                    524,\n                    grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_OFF],\n                    grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_ON],\n                    NULL,\n                    96);\n                win_register_button_sound_func(SliderNegID, gsound_red_butt_press, NULL);\n            }\n        }\n    }\n}\n\n// 0x4365AC\nstatic void DrawInfoWin()\n{\n    int graphicId;\n    char* title;\n    char* description;\n\n    if (info_line < 0 || info_line >= 98) {\n        return;\n    }\n\n    buf_to_buf(bckgnd + (640 * 267) + 345, 277, 170, 640, win_buf + (267 * 640) + 345, 640);\n\n    if (info_line >= 0 && info_line < 7) {\n        description = stat_description(info_line);\n        title = stat_name(info_line);\n        graphicId = stat_picture(info_line);\n        DrawCard(graphicId, title, NULL, description);\n    } else if (info_line >= 7 && info_line < 10) {\n        if (glblmode) {\n            switch (info_line) {\n            case 7:\n                // Character Points\n                description = getmsg(&editor_message_file, &mesg, 121);\n                title = getmsg(&editor_message_file, &mesg, 120);\n                DrawCard(7, title, NULL, description);\n                break;\n            }\n        } else {\n            switch (info_line) {\n            case 7:\n                description = stat_pc_description(PC_STAT_LEVEL);\n                title = stat_pc_name(PC_STAT_LEVEL);\n                DrawCard(7, title, NULL, description);\n                break;\n            case 8:\n                description = stat_pc_description(PC_STAT_EXPERIENCE);\n                title = stat_pc_name(PC_STAT_EXPERIENCE);\n                DrawCard(8, title, NULL, description);\n                break;\n            case 9:\n                // Next Level\n                description = getmsg(&editor_message_file, &mesg, 123);\n                title = getmsg(&editor_message_file, &mesg, 122);\n                DrawCard(9, title, NULL, description);\n                break;\n            }\n        }\n    } else if ((info_line >= 10 && info_line < 43) || (info_line >= 82 && info_line < 98)) {\n        DrawCard(folder_card_fid, folder_card_title, folder_card_title2, folder_card_desc);\n    } else if (info_line >= 43 && info_line < 51) {\n        switch (info_line) {\n        case EDITOR_HIT_POINTS:\n            description = stat_description(STAT_MAXIMUM_HIT_POINTS);\n            title = getmsg(&editor_message_file, &mesg, 300);\n            graphicId = stat_picture(STAT_MAXIMUM_HIT_POINTS);\n            DrawCard(graphicId, title, NULL, description);\n            break;\n        case EDITOR_POISONED:\n            description = getmsg(&editor_message_file, &mesg, 400);\n            title = getmsg(&editor_message_file, &mesg, 312);\n            DrawCard(11, title, NULL, description);\n            break;\n        case EDITOR_RADIATED:\n            description = getmsg(&editor_message_file, &mesg, 401);\n            title = getmsg(&editor_message_file, &mesg, 313);\n            DrawCard(12, title, NULL, description);\n            break;\n        case EDITOR_EYE_DAMAGE:\n            description = getmsg(&editor_message_file, &mesg, 402);\n            title = getmsg(&editor_message_file, &mesg, 314);\n            DrawCard(13, title, NULL, description);\n            break;\n        case EDITOR_CRIPPLED_RIGHT_ARM:\n            description = getmsg(&editor_message_file, &mesg, 403);\n            title = getmsg(&editor_message_file, &mesg, 315);\n            DrawCard(14, title, NULL, description);\n            break;\n        case EDITOR_CRIPPLED_LEFT_ARM:\n            description = getmsg(&editor_message_file, &mesg, 404);\n            title = getmsg(&editor_message_file, &mesg, 316);\n            DrawCard(15, title, NULL, description);\n            break;\n        case EDITOR_CRIPPLED_RIGHT_LEG:\n            description = getmsg(&editor_message_file, &mesg, 405);\n            title = getmsg(&editor_message_file, &mesg, 317);\n            DrawCard(16, title, NULL, description);\n            break;\n        case EDITOR_CRIPPLED_LEFT_LEG:\n            description = getmsg(&editor_message_file, &mesg, 406);\n            title = getmsg(&editor_message_file, &mesg, 318);\n            DrawCard(17, title, NULL, description);\n            break;\n        }\n    } else if (info_line >= EDITOR_FIRST_DERIVED_STAT && info_line < 61) {\n        int derivedStatIndex = info_line - 51;\n        int stat = ndinfoxlt[derivedStatIndex];\n        description = stat_description(stat);\n        title = stat_name(stat);\n        graphicId = ndrvd[derivedStatIndex];\n        DrawCard(graphicId, title, NULL, description);\n    } else if (info_line >= EDITOR_FIRST_SKILL && info_line < 79) {\n        int skill = info_line - 61;\n        const char* attributesDescription = skill_attribute(skill);\n\n        char formatted[150]; // TODO: Size is probably wrong.\n        const char* base = getmsg(&editor_message_file, &mesg, 137);\n        int defaultValue = skill_base(skill);\n        sprintf(formatted, \"%s %d%% %s\", base, defaultValue, attributesDescription);\n\n        graphicId = skill_pic(skill);\n        title = skill_name(skill);\n        description = skill_description(skill);\n        DrawCard(graphicId, title, formatted, description);\n    } else if (info_line >= 79 && info_line < 82) {\n        switch (info_line) {\n        case EDITOR_TAG_SKILL:\n            if (glblmode) {\n                // Tag Skill\n                description = getmsg(&editor_message_file, &mesg, 145);\n                title = getmsg(&editor_message_file, &mesg, 144);\n                DrawCard(27, title, NULL, description);\n            } else {\n                // Skill Points\n                description = getmsg(&editor_message_file, &mesg, 131);\n                title = getmsg(&editor_message_file, &mesg, 130);\n                DrawCard(27, title, NULL, description);\n            }\n            break;\n        case EDITOR_SKILLS:\n            // Skills\n            description = getmsg(&editor_message_file, &mesg, 151);\n            title = getmsg(&editor_message_file, &mesg, 150);\n            DrawCard(27, title, NULL, description);\n            break;\n        case EDITOR_OPTIONAL_TRAITS:\n            // Optional Traits\n            description = getmsg(&editor_message_file, &mesg, 147);\n            title = getmsg(&editor_message_file, &mesg, 146);\n            DrawCard(27, title, NULL, description);\n            break;\n        }\n    }\n}\n\n// 0x436C4C\nstatic int NameWindow()\n{\n    char* text;\n\n    int windowWidth = GInfo[EDITOR_GRAPHIC_CHARWIN].width;\n    int windowHeight = GInfo[EDITOR_GRAPHIC_CHARWIN].height;\n\n    int nameWindowX = 17;\n    int nameWindowY = 0;\n    int win = win_add(nameWindowX, nameWindowY, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n    if (win == -1) {\n        return -1;\n    }\n\n    unsigned char* windowBuf = win_get_buf(win);\n\n    // Copy background\n    memcpy(windowBuf, grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight);\n\n    trans_buf_to_buf(\n        grphbmp[EDITOR_GRAPHIC_NAME_BOX],\n        GInfo[EDITOR_GRAPHIC_NAME_BOX].width,\n        GInfo[EDITOR_GRAPHIC_NAME_BOX].height,\n        GInfo[EDITOR_GRAPHIC_NAME_BOX].width,\n        windowBuf + windowWidth * 13 + 13,\n        windowWidth);\n    trans_buf_to_buf(grphbmp[EDITOR_GRAPHIC_DONE_BOX],\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].width,\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].height,\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].width,\n        windowBuf + windowWidth * 40 + 13,\n        windowWidth);\n\n    text_font(103);\n\n    text = getmsg(&editor_message_file, &mesg, 100);\n    text_to_buf(windowBuf + windowWidth * 44 + 50, text, windowWidth, windowWidth, colorTable[18979]);\n\n    int doneBtn = win_register_button(win,\n        26,\n        44,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        500,\n        grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP],\n        grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (doneBtn != -1) {\n        win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    win_draw(win);\n\n    text_font(101);\n\n    char name[64];\n    strcpy(name, critter_name(obj_dude));\n\n    if (strcmp(name, \"None\") == 0) {\n        name[0] = '\\0';\n    }\n\n    // NOTE: I don't understand the nameCopy, not sure what it is used for. It's\n    // definitely there, but I just don' get it.\n    char nameCopy[64];\n    strcpy(nameCopy, name);\n\n    if (get_input_str(win, 500, nameCopy, 11, 23, 19, colorTable[992], 100, 0) != -1) {\n        if (nameCopy[0] != '\\0') {\n            critter_pc_set_name(nameCopy);\n            PrintBigname();\n            win_delete(win);\n            return 0;\n        }\n    }\n\n    // NOTE: original code is a bit different, the following chunk of code written two times.\n\n    text_font(101);\n    buf_to_buf(grphbmp[EDITOR_GRAPHIC_NAME_BOX],\n        GInfo[EDITOR_GRAPHIC_NAME_BOX].width,\n        GInfo[EDITOR_GRAPHIC_NAME_BOX].height,\n        GInfo[EDITOR_GRAPHIC_NAME_BOX].width,\n        windowBuf + GInfo[EDITOR_GRAPHIC_CHARWIN].width * 13 + 13,\n        GInfo[EDITOR_GRAPHIC_CHARWIN].width);\n\n    PrintName(windowBuf, GInfo[EDITOR_GRAPHIC_CHARWIN].width);\n\n    strcpy(nameCopy, name);\n\n    win_delete(win);\n\n    return 0;\n}\n\n// 0x436F70\nstatic void PrintName(unsigned char* buf, int pitch)\n{\n    char str[64];\n    char* v4;\n\n    memcpy(str, byte_431D93, 64);\n\n    text_font(101);\n\n    v4 = critter_name(obj_dude);\n\n    // TODO: Check.\n    strcpy(str, v4);\n\n    text_to_buf(buf + 19 * pitch + 21, str, pitch, pitch, colorTable[992]);\n}\n\n// 0x436FEC\nstatic int AgeWindow()\n{\n    int win;\n    unsigned char* windowBuf;\n    int windowWidth;\n    int windowHeight;\n    const char* messageListItemText;\n    int previousAge;\n    int age;\n    int doneBtn;\n    int prevBtn;\n    int nextBtn;\n    int keyCode;\n    int change;\n    int flags;\n\n    int savedAge = critterGetStat(obj_dude, STAT_AGE);\n\n    windowWidth = GInfo[EDITOR_GRAPHIC_CHARWIN].width;\n    windowHeight = GInfo[EDITOR_GRAPHIC_CHARWIN].height;\n\n    int ageWindowX = GInfo[EDITOR_GRAPHIC_NAME_ON].width + 9;\n    int ageWindowY = 0;\n    win = win_add(ageWindowX, ageWindowY, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n    if (win == -1) {\n        return -1;\n    }\n\n    windowBuf = win_get_buf(win);\n\n    memcpy(windowBuf, grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight);\n\n    trans_buf_to_buf(\n        grphbmp[EDITOR_GRAPHIC_AGE_BOX],\n        GInfo[EDITOR_GRAPHIC_AGE_BOX].width,\n        GInfo[EDITOR_GRAPHIC_AGE_BOX].height,\n        GInfo[EDITOR_GRAPHIC_AGE_BOX].width,\n        windowBuf + windowWidth * 7 + 8,\n        windowWidth);\n    trans_buf_to_buf(\n        grphbmp[EDITOR_GRAPHIC_DONE_BOX],\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].width,\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].height,\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].width,\n        windowBuf + windowWidth * 40 + 13,\n        GInfo[EDITOR_GRAPHIC_CHARWIN].width);\n\n    text_font(103);\n\n    messageListItemText = getmsg(&editor_message_file, &mesg, 100);\n    text_to_buf(windowBuf + windowWidth * 44 + 50, messageListItemText, windowWidth, windowWidth, colorTable[18979]);\n\n    age = critterGetStat(obj_dude, STAT_AGE);\n    PrintBigNum(55, 10, 0, age, 0, win);\n\n    doneBtn = win_register_button(win,\n        26,\n        44,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        500,\n        grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP],\n        grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (doneBtn != -1) {\n        win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    nextBtn = win_register_button(win,\n        105,\n        13,\n        GInfo[EDITOR_GRAPHIC_LEFT_ARROW_DOWN].width,\n        GInfo[EDITOR_GRAPHIC_LEFT_ARROW_DOWN].height,\n        -1,\n        503,\n        501,\n        503,\n        grphbmp[EDITOR_GRAPHIC_RIGHT_ARROW_UP],\n        grphbmp[EDITOR_GRAPHIC_RIGHT_ARROW_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (nextBtn != -1) {\n        win_register_button_sound_func(nextBtn, gsound_med_butt_press, NULL);\n    }\n\n    prevBtn = win_register_button(win,\n        19,\n        13,\n        GInfo[EDITOR_GRAPHIC_RIGHT_ARROW_DOWN].width,\n        GInfo[EDITOR_GRAPHIC_RIGHT_ARROW_DOWN].height,\n        -1,\n        504,\n        502,\n        504,\n        grphbmp[EDITOR_GRAPHIC_LEFT_ARROW_UP],\n        grphbmp[EDITOR_GRAPHIC_LEFT_ARROW_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (prevBtn != -1) {\n        win_register_button_sound_func(prevBtn, gsound_med_butt_press, NULL);\n    }\n\n    while (true) {\n        _frame_time = get_time();\n        change = 0;\n        flags = 0;\n        int v32 = 0;\n\n        keyCode = get_input();\n\n        if (keyCode == KEY_RETURN || keyCode == 500) {\n            if (keyCode != 500) {\n                gsound_play_sfx_file(\"ib1p1xx1\");\n            }\n\n            win_delete(win);\n            return 0;\n        } else if (keyCode == KEY_ESCAPE || game_user_wants_to_quit != 0) {\n            break;\n        } else if (keyCode == 501) {\n            age = critterGetStat(obj_dude, STAT_AGE);\n            if (age < 35) {\n                change = 1;\n            }\n        } else if (keyCode == 502) {\n            age = critterGetStat(obj_dude, STAT_AGE);\n            if (age > 16) {\n                change = -1;\n            }\n        } else if (keyCode == KEY_PLUS || keyCode == KEY_UPPERCASE_N || keyCode == KEY_ARROW_UP) {\n            previousAge = critterGetStat(obj_dude, STAT_AGE);\n            if (previousAge < 35) {\n                flags = ANIMATE;\n                if (inc_stat(obj_dude, STAT_AGE) != 0) {\n                    flags = 0;\n                }\n                age = critterGetStat(obj_dude, STAT_AGE);\n                PrintBigNum(55, 10, flags, age, previousAge, win);\n            }\n        } else if (keyCode == KEY_MINUS || keyCode == KEY_UPPERCASE_J || keyCode == KEY_ARROW_DOWN) {\n            previousAge = critterGetStat(obj_dude, STAT_AGE);\n            if (previousAge > 16) {\n                flags = ANIMATE;\n                if (dec_stat(obj_dude, STAT_AGE) != 0) {\n                    flags = 0;\n                }\n                age = critterGetStat(obj_dude, STAT_AGE);\n\n                PrintBigNum(55, 10, flags, age, previousAge, win);\n            }\n        }\n\n        if (flags == ANIMATE) {\n            PrintAgeBig();\n            PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n            ListDrvdStats();\n            win_draw(edit_win);\n            win_draw(win);\n        }\n\n        if (change != 0) {\n            int v33 = 0;\n\n            _repFtime = 4;\n\n            while (true) {\n                _frame_time = get_time();\n\n                v33++;\n\n                if ((!v32 && v33 == 1) || (v32 && v33 > 14.4)) {\n                    v32 = true;\n\n                    if (v33 > 14.4) {\n                        _repFtime++;\n                        if (_repFtime > 24) {\n                            _repFtime = 24;\n                        }\n                    }\n\n                    flags = ANIMATE;\n                    previousAge = critterGetStat(obj_dude, STAT_AGE);\n\n                    if (change == 1) {\n                        if (previousAge < 35) {\n                            if (inc_stat(obj_dude, STAT_AGE) != 0) {\n                                flags = 0;\n                            }\n                        }\n                    } else {\n                        if (previousAge >= 16) {\n                            if (dec_stat(obj_dude, STAT_AGE) != 0) {\n                                flags = 0;\n                            }\n                        }\n                    }\n\n                    age = critterGetStat(obj_dude, STAT_AGE);\n                    PrintBigNum(55, 10, flags, age, previousAge, win);\n                    if (flags == ANIMATE) {\n                        PrintAgeBig();\n                        PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n                        ListDrvdStats();\n                        win_draw(edit_win);\n                        win_draw(win);\n                    }\n                }\n\n                if (v33 > 14.4) {\n                    while (elapsed_time(_frame_time) < 1000 / _repFtime)\n                        ;\n                } else {\n                    while (elapsed_time(_frame_time) < 1000 / 24)\n                        ;\n                }\n\n                keyCode = get_input();\n                if (keyCode == 503 || keyCode == 504 || game_user_wants_to_quit != 0) {\n                    break;\n                }\n            }\n        } else {\n            win_draw(win);\n\n            while (elapsed_time(_frame_time) < 1000 / 24)\n                ;\n        }\n    }\n\n    stat_set_base(obj_dude, STAT_AGE, savedAge);\n    PrintAgeBig();\n    PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n    ListDrvdStats();\n    win_draw(edit_win);\n    win_draw(win);\n    win_delete(win);\n    return 0;\n}\n\n// 0x437664\nstatic void SexWindow()\n{\n    char* text;\n\n    int windowWidth = GInfo[EDITOR_GRAPHIC_CHARWIN].width;\n    int windowHeight = GInfo[EDITOR_GRAPHIC_CHARWIN].height;\n\n    int genderWindowX = 9\n        + GInfo[EDITOR_GRAPHIC_NAME_ON].width\n        + GInfo[EDITOR_GRAPHIC_AGE_ON].width;\n    int genderWindowY = 0;\n    int win = win_add(genderWindowX, genderWindowY, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n\n    if (win == -1) {\n        return;\n    }\n\n    unsigned char* windowBuf = win_get_buf(win);\n\n    // Copy background\n    memcpy(windowBuf, grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight);\n\n    trans_buf_to_buf(grphbmp[EDITOR_GRAPHIC_DONE_BOX],\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].width,\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].height,\n        GInfo[EDITOR_GRAPHIC_DONE_BOX].width,\n        windowBuf + windowWidth * 44 + 15,\n        windowWidth);\n\n    text_font(103);\n\n    text = getmsg(&editor_message_file, &mesg, 100);\n    text_to_buf(windowBuf + windowWidth * 48 + 52, text, windowWidth, windowWidth, colorTable[18979]);\n\n    int doneBtn = win_register_button(win,\n        28,\n        48,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        500,\n        grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP],\n        grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (doneBtn != -1) {\n        win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    int btns[2];\n    btns[0] = win_register_button(win,\n        22,\n        2,\n        GInfo[EDITOR_GRAPHIC_MALE_ON].width,\n        GInfo[EDITOR_GRAPHIC_MALE_ON].height,\n        -1,\n        -1,\n        501,\n        -1,\n        grphbmp[EDITOR_GRAPHIC_MALE_OFF],\n        grphbmp[EDITOR_GRAPHIC_MALE_ON],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x04 | BUTTON_FLAG_0x02 | BUTTON_FLAG_0x01);\n    if (btns[0] != -1) {\n        win_register_button_sound_func(doneBtn, gsound_red_butt_press, NULL);\n    }\n\n    btns[1] = win_register_button(win,\n        71,\n        3,\n        GInfo[EDITOR_GRAPHIC_FEMALE_ON].width,\n        GInfo[EDITOR_GRAPHIC_FEMALE_ON].height,\n        -1,\n        -1,\n        502,\n        -1,\n        grphbmp[EDITOR_GRAPHIC_FEMALE_OFF],\n        grphbmp[EDITOR_GRAPHIC_FEMALE_ON],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x04 | BUTTON_FLAG_0x02 | BUTTON_FLAG_0x01);\n    if (btns[1] != -1) {\n        win_group_radio_buttons(2, btns);\n        win_register_button_sound_func(doneBtn, gsound_red_butt_press, NULL);\n    }\n\n    int savedGender = critterGetStat(obj_dude, STAT_GENDER);\n    win_set_button_rest_state(btns[savedGender], 1, 0);\n\n    while (true) {\n        _frame_time = get_time();\n\n        int eventCode = get_input();\n\n        if (eventCode == KEY_RETURN || eventCode == 500) {\n            if (eventCode == KEY_RETURN) {\n                gsound_play_sfx_file(\"ib1p1xx1\");\n            }\n            break;\n        }\n\n        if (eventCode == KEY_ESCAPE || game_user_wants_to_quit != 0) {\n            stat_set_base(obj_dude, STAT_GENDER, savedGender);\n            PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n            ListDrvdStats();\n            win_draw(edit_win);\n            break;\n        }\n\n        switch (eventCode) {\n        case KEY_ARROW_LEFT:\n        case KEY_ARROW_RIGHT:\n            if (1) {\n                bool wasMale = win_button_down(btns[0]);\n                win_set_button_rest_state(btns[0], !wasMale, 1);\n                win_set_button_rest_state(btns[1], wasMale, 1);\n            }\n            break;\n        case 501:\n        case 502:\n            // TODO: Original code is slightly different.\n            stat_set_base(obj_dude, STAT_GENDER, eventCode - 501);\n            PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n            ListDrvdStats();\n            break;\n        }\n\n        win_draw(win);\n\n        while (elapsed_time(_frame_time) < 41)\n            ;\n    }\n\n    PrintGender();\n    win_delete(win);\n}\n\n// 0x4379BC\nstatic void StatButton(int eventCode)\n{\n    _repFtime = 4;\n\n    int savedRemainingCharacterPoints = character_points;\n\n    if (!glblmode) {\n        return;\n    }\n\n    int incrementingStat = eventCode - 503;\n    int decrementingStat = eventCode - 510;\n\n    int v11 = 0;\n\n    bool cont = true;\n    do {\n        _frame_time = get_time();\n        if (v11 <= 19.2) {\n            v11++;\n        }\n\n        if (v11 == 1 || v11 > 19.2) {\n            if (v11 > 19.2) {\n                _repFtime++;\n                if (_repFtime > 24) {\n                    _repFtime = 24;\n                }\n            }\n\n            if (eventCode >= 510) {\n                int previousValue = critterGetStat(obj_dude, decrementingStat);\n                if (dec_stat(obj_dude, decrementingStat) == 0) {\n                    character_points++;\n                } else {\n                    cont = false;\n                }\n\n                PrintBasicStat(decrementingStat, cont ? ANIMATE : 0, previousValue);\n                PrintBigNum(126, 282, cont ? ANIMATE : 0, character_points, savedRemainingCharacterPoints, edit_win);\n                stat_recalc_derived(obj_dude);\n                ListDrvdStats();\n                ListSkills(0);\n                info_line = decrementingStat;\n            } else {\n                int previousValue = stat_get_base(obj_dude, incrementingStat);\n                previousValue += stat_get_bonus(obj_dude, incrementingStat);\n                if (character_points > 0 && previousValue < 10 && inc_stat(obj_dude, incrementingStat) == 0) {\n                    character_points--;\n                } else {\n                    cont = false;\n                }\n\n                PrintBasicStat(incrementingStat, cont ? ANIMATE : 0, previousValue);\n                PrintBigNum(126, 282, cont ? ANIMATE : 0, character_points, savedRemainingCharacterPoints, edit_win);\n                stat_recalc_derived(obj_dude);\n                ListDrvdStats();\n                ListSkills(0);\n                info_line = incrementingStat;\n            }\n\n            win_draw(edit_win);\n        }\n\n        if (v11 >= 19.2) {\n            unsigned int delay = 1000 / _repFtime;\n            while (elapsed_time(_frame_time) < delay) {\n            }\n        } else {\n            while (elapsed_time(_frame_time) < 1000 / 24) {\n            }\n        }\n    } while (get_input() != 518 && cont);\n\n    DrawInfoWin();\n}\n\n// handle options dialog\n//\n// 0x437C08\nstatic int OptionWindow()\n{\n    int width = GInfo[43].width;\n    int height = GInfo[43].height;\n\n    // NOTE: The following is a block of general purpose string buffers used in\n    // this function. They are either store path, or strings from .msg files. I\n    // don't know if such usage was intentional in the original code or it's a\n    // result of some kind of compiler optimization.\n    char string1[512];\n    char string2[512];\n    char string3[512];\n    char string4[512];\n    char string5[512];\n\n    // Only two of the these blocks are used as a dialog body. Depending on the\n    // dialog either 1 or 2 strings used from this array.\n    const char* dialogBody[2] = {\n        string5,\n        string2,\n    };\n\n    if (glblmode) {\n        int optionsWindowX = 238;\n        int optionsWindowY = 90;\n        int win = win_add(optionsWindowX, optionsWindowY, GInfo[41].width, GInfo[41].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n        if (win == -1) {\n            return -1;\n        }\n\n        unsigned char* windowBuffer = win_get_buf(win);\n        memcpy(windowBuffer, grphbmp[41], GInfo[41].width * GInfo[41].height);\n\n        text_font(103);\n\n        int err = 0;\n        unsigned char* down[5];\n        unsigned char* up[5];\n        int size = width * height;\n        int y = 17;\n        int index;\n\n        for (index = 0; index < 5; index++) {\n            if (err != 0) {\n                break;\n            }\n\n            do {\n                down[index] = (unsigned char*)mem_malloc(size);\n                if (down[index] == NULL) {\n                    err = 1;\n                    break;\n                }\n\n                up[index] = (unsigned char*)mem_malloc(size);\n                if (up[index] == NULL) {\n                    err = 2;\n                    break;\n                }\n\n                memcpy(down[index], grphbmp[43], size);\n                memcpy(up[index], grphbmp[42], size);\n\n                strcpy(string4, getmsg(&editor_message_file, &mesg, 600 + index));\n\n                int offset = width * 7 + width / 2 - text_width(string4) / 2;\n                text_to_buf(up[index] + offset, string4, width, width, colorTable[18979]);\n                text_to_buf(down[index] + offset, string4, width, width, colorTable[14723]);\n\n                int btn = win_register_button(win, 13, y, width, height, -1, -1, -1, 500 + index, up[index], down[index], NULL, BUTTON_FLAG_TRANSPARENT);\n                if (btn != -1) {\n                    win_register_button_sound_func(btn, gsound_lrg_butt_press, NULL);\n                }\n            } while (0);\n\n            y += height + 3;\n        }\n\n        if (err != 0) {\n            if (err == 2) {\n                mem_free(down[index]);\n            }\n\n            while (--index >= 0) {\n                mem_free(up[index]);\n                mem_free(down[index]);\n            }\n\n            return -1;\n        }\n\n        text_font(101);\n\n        int rc = 0;\n        while (rc == 0) {\n            int keyCode = get_input();\n\n            if (game_user_wants_to_quit != 0) {\n                rc = 2;\n            } else if (keyCode == 504) {\n                rc = 2;\n            } else if (keyCode == KEY_RETURN || keyCode == KEY_UPPERCASE_D || keyCode == KEY_LOWERCASE_D) {\n                // DONE\n                rc = 2;\n                gsound_play_sfx_file(\"ib1p1xx1\");\n            } else if (keyCode == KEY_ESCAPE) {\n                rc = 2;\n            } else if (keyCode == 503 || keyCode == KEY_UPPERCASE_E || keyCode == KEY_LOWERCASE_E) {\n                // ERASE\n                strcpy(string5, getmsg(&editor_message_file, &mesg, 605));\n                strcpy(string2, getmsg(&editor_message_file, &mesg, 606));\n\n                if (dialog_out(NULL, dialogBody, 2, 169, 126, colorTable[992], NULL, colorTable[992], DIALOG_BOX_YES_NO) != 0) {\n                    ResetPlayer();\n                    skill_get_tags(temp_tag_skill, NUM_TAGGED_SKILLS);\n\n                    // NOTE: Uninline.\n                    tagskill_count = tagskl_free();\n\n                    trait_get(&temp_trait[0], &temp_trait[1]);\n\n                    // NOTE: Uninline.\n                    trait_count = get_trait_count();\n                    stat_recalc_derived(obj_dude);\n                    ResetScreen();\n                }\n            } else if (keyCode == 502 || keyCode == KEY_UPPERCASE_P || keyCode == KEY_LOWERCASE_P) {\n                // PRINT TO FILE\n                string4[0] = '\\0';\n\n                strcat(string4, \"*.\");\n                strcat(string4, \"TXT\");\n\n                char** fileList;\n                int fileListLength = db_get_file_list(string4, &fileList, 0, 0);\n                if (fileListLength != -1) {\n                    // PRINT\n                    strcpy(string1, getmsg(&editor_message_file, &mesg, 616));\n\n                    // PRINT TO FILE\n                    strcpy(string4, getmsg(&editor_message_file, &mesg, 602));\n\n                    if (save_file_dialog(string4, fileList, string1, fileListLength, 168, 80, 0) == 0) {\n                        strcat(string1, \".\");\n                        strcat(string1, \"TXT\");\n\n                        string4[0] = '\\0';\n                        strcat(string4, string1);\n\n                        if (!db_access(string4)) {\n                            // already exists\n                            sprintf(string4,\n                                \"%s %s\",\n                                strupr(string1),\n                                getmsg(&editor_message_file, &mesg, 609));\n\n                            strcpy(string5, getmsg(&editor_message_file, &mesg, 610));\n\n                            if (dialog_out(string4, dialogBody, 1, 169, 126, colorTable[32328], NULL, colorTable[32328], 0x10) != 0) {\n                                rc = 1;\n                            } else {\n                                rc = 0;\n                            }\n                        } else {\n                            rc = 1;\n                        }\n\n                        if (rc != 0) {\n                            string4[0] = '\\0';\n                            strcat(string4, string1);\n\n                            if (Save_as_ASCII(string4) == 0) {\n                                sprintf(string4,\n                                    \"%s%s\",\n                                    strupr(string1),\n                                    getmsg(&editor_message_file, &mesg, 607));\n                                dialog_out(string4, NULL, 0, 169, 126, colorTable[992], NULL, colorTable[992], 0);\n                            } else {\n                                gsound_play_sfx_file(\"iisxxxx1\");\n\n                                sprintf(string4,\n                                    \"%s%s%s\",\n                                    getmsg(&editor_message_file, &mesg, 611),\n                                    strupr(string1),\n                                    \"!\");\n                                dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[992], 0x01);\n                            }\n                        }\n                    }\n\n                    db_free_file_list(&fileList, 0);\n                } else {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    strcpy(string4, getmsg(&editor_message_file, &mesg, 615));\n                    dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0);\n\n                    rc = 0;\n                }\n            } else if (keyCode == 501 || keyCode == KEY_UPPERCASE_L || keyCode == KEY_LOWERCASE_L) {\n                // LOAD\n                string4[0] = '\\0';\n                strcat(string4, \"*.\");\n                strcat(string4, \"GCD\");\n\n                char** fileNameList;\n                int fileNameListLength = db_get_file_list(string4, &fileNameList, 0, 0);\n                if (fileNameListLength != -1) {\n                    // NOTE: This value is not copied as in save dialog.\n                    char* title = getmsg(&editor_message_file, &mesg, 601);\n                    int loadFileDialogRc = file_dialog(title, fileNameList, string3, fileNameListLength, 168, 80, 0);\n                    if (loadFileDialogRc == -1) {\n                        db_free_file_list(&fileNameList, 0);\n                        // FIXME: This branch ignores cleanup at the end of the loop.\n                        return -1;\n                    }\n\n                    if (loadFileDialogRc == 0) {\n                        string4[0] = '\\0';\n                        strcat(string4, string3);\n\n                        int oldRemainingCharacterPoints = character_points;\n\n                        ResetPlayer();\n\n                        if (pc_load_data(string4) == 0) {\n                            // NOTE: Uninline.\n                            CheckValidPlayer();\n\n                            skill_get_tags(temp_tag_skill, 4);\n\n                            // NOTE: Uninline.\n                            tagskill_count = tagskl_free();\n\n                            trait_get(&(temp_trait[0]), &(temp_trait[1]));\n\n                            // NOTE: Uninline.\n                            trait_count = get_trait_count();\n\n                            stat_recalc_derived(obj_dude);\n\n                            critter_adjust_hits(obj_dude, 1000);\n\n                            rc = 1;\n                        } else {\n                            RestorePlayer();\n                            character_points = oldRemainingCharacterPoints;\n                            critter_adjust_hits(obj_dude, 1000);\n                            gsound_play_sfx_file(\"iisxxxx1\");\n\n                            strcpy(string4, getmsg(&editor_message_file, &mesg, 612));\n                            strcat(string4, string3);\n                            strcat(string4, \"!\");\n\n                            dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0);\n                        }\n\n                        ResetScreen();\n                    }\n\n                    db_free_file_list(&fileNameList, 0);\n                } else {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    // Error reading file list!\n                    strcpy(string4, getmsg(&editor_message_file, &mesg, 615));\n                    rc = 0;\n\n                    dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0);\n                }\n            } else if (keyCode == 500 || keyCode == KEY_UPPERCASE_S || keyCode == KEY_LOWERCASE_S) {\n                // SAVE\n                string4[0] = '\\0';\n                strcat(string4, \"*.\");\n                strcat(string4, \"GCD\");\n\n                char** fileNameList;\n                int fileNameListLength = db_get_file_list(string4, &fileNameList, 0, 0);\n                if (fileNameListLength != -1) {\n                    strcpy(string1, getmsg(&editor_message_file, &mesg, 617));\n                    strcpy(string4, getmsg(&editor_message_file, &mesg, 600));\n\n                    if (save_file_dialog(string4, fileNameList, string1, fileNameListLength, 168, 80, 0) == 0) {\n                        strcat(string1, \".\");\n                        strcat(string1, \"GCD\");\n\n                        string4[0] = '\\0';\n                        strcat(string4, string1);\n\n                        bool shouldSave;\n                        if (db_access(string4)) {\n                            sprintf(string4, \"%s %s\",\n                                strupr(string1),\n                                getmsg(&editor_message_file, &mesg, 609));\n                            strcpy(string5, getmsg(&editor_message_file, &mesg, 610));\n\n                            if (dialog_out(string4, dialogBody, 1, 169, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_YES_NO) != 0) {\n                                shouldSave = true;\n                            } else {\n                                shouldSave = false;\n                            }\n                        } else {\n                            shouldSave = true;\n                        }\n\n                        if (shouldSave) {\n                            skill_set_tags(temp_tag_skill, 4);\n                            trait_set(temp_trait[0], temp_trait[1]);\n\n                            string4[0] = '\\0';\n                            strcat(string4, string1);\n\n                            if (pc_save_data(string4) != 0) {\n                                gsound_play_sfx_file(\"iisxxxx1\");\n                                sprintf(string4, \"%s%s!\",\n                                    strupr(string1),\n                                    getmsg(&editor_message_file, &mesg, 611));\n                                dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n                                rc = 0;\n                            } else {\n                                sprintf(string4, \"%s%s\",\n                                    strupr(string1),\n                                    getmsg(&editor_message_file, &mesg, 607));\n                                dialog_out(string4, NULL, 0, 169, 126, colorTable[992], NULL, colorTable[992], DIALOG_BOX_LARGE);\n                                rc = 1;\n                            }\n                        }\n                    }\n\n                    db_free_file_list(&fileNameList, 0);\n                } else {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    // Error reading file list!\n                    char* msg = getmsg(&editor_message_file, &mesg, 615);\n                    dialog_out(msg, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0);\n\n                    rc = 0;\n                }\n            }\n\n            win_draw(win);\n        }\n\n        win_delete(win);\n\n        for (index = 0; index < 5; index++) {\n            mem_free(up[index]);\n            mem_free(down[index]);\n        }\n\n        return 0;\n    }\n\n    // Character Editor is not in creation mode - this button is only for\n    // printing character details.\n\n    char pattern[512];\n    strcpy(pattern, \"*.TXT\");\n\n    char** fileNames;\n    int filesCount = db_get_file_list(pattern, &fileNames, 0, 0);\n    if (filesCount == -1) {\n        gsound_play_sfx_file(\"iisxxxx1\");\n\n        // Error reading file list!\n        strcpy(pattern, getmsg(&editor_message_file, &mesg, 615));\n        dialog_out(pattern, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0);\n        return 0;\n    }\n\n    // PRINT\n    char fileName[512];\n    strcpy(fileName, getmsg(&editor_message_file, &mesg, 616));\n\n    char title[512];\n    strcpy(title, getmsg(&editor_message_file, &mesg, 602));\n\n    if (save_file_dialog(title, fileNames, fileName, filesCount, 168, 80, 0) == 0) {\n        strcat(fileName, \".TXT\");\n\n        title[0] = '\\0';\n        strcat(title, fileName);\n\n        int v42 = 0;\n        if (db_access(title)) {\n            sprintf(title,\n                \"%s %s\",\n                strupr(fileName),\n                getmsg(&editor_message_file, &mesg, 609));\n\n            char line2[512];\n            strcpy(line2, getmsg(&editor_message_file, &mesg, 610));\n\n            const char* lines[] = { line2 };\n            v42 = dialog_out(title, lines, 1, 169, 126, colorTable[32328], NULL, colorTable[32328], 0x10);\n            if (v42) {\n                v42 = 1;\n            }\n        } else {\n            v42 = 1;\n        }\n\n        if (v42) {\n            title[0] = '\\0';\n            strcpy(title, fileName);\n\n            if (Save_as_ASCII(title) != 0) {\n                gsound_play_sfx_file(\"iisxxxx1\");\n\n                sprintf(title,\n                    \"%s%s%s\",\n                    getmsg(&editor_message_file, &mesg, 611),\n                    strupr(fileName),\n                    \"!\");\n                dialog_out(title, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 1);\n            }\n        }\n    }\n\n    db_free_file_list(&fileNames, 0);\n\n    return 0;\n}\n\n// 0x4390B4\nbool db_access(const char* fname)\n{\n    File* stream = db_fopen(fname, \"rb\");\n    if (stream == NULL) {\n        return false;\n    }\n\n    db_fclose(stream);\n    return true;\n}\n\n// 0x4390D0\nstatic int Save_as_ASCII(const char* fileName)\n{\n    File* stream = db_fopen(fileName, \"wt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    db_fputs(\"\\n\", stream);\n    db_fputs(\"\\n\", stream);\n\n    char title1[256];\n    char title2[256];\n    char title3[256];\n    char padding[256];\n\n    // FALLOUT\n    strcpy(title1, getmsg(&editor_message_file, &mesg, 620));\n\n    // NOTE: Uninline.\n    padding[0] = '\\0';\n    AddSpaces(padding, (80 - strlen(title1)) / 2 - 2);\n\n    strcat(padding, title1);\n    strcat(padding, \"\\n\");\n    db_fputs(padding, stream);\n\n    // VAULT-13 PERSONNEL RECORD\n    strcpy(title1, getmsg(&editor_message_file, &mesg, 621));\n\n    // NOTE: Uninline.\n    padding[0] = '\\0';\n    AddSpaces(padding, (80 - strlen(title1)) / 2 - 2);\n\n    strcat(padding, title1);\n    strcat(padding, \"\\n\");\n    db_fputs(padding, stream);\n\n    int month;\n    int day;\n    int year;\n    game_time_date(&month, &day, &year);\n\n    sprintf(title1, \"%.2d %s %d  %.4d %s\",\n        day,\n        getmsg(&editor_message_file, &mesg, 500 + month - 1),\n        year,\n        game_time_hour(),\n        getmsg(&editor_message_file, &mesg, 622));\n\n    // NOTE: Uninline.\n    padding[0] = '\\0';\n    AddSpaces(padding, (80 - strlen(title1)) / 2 - 2);\n\n    strcat(padding, title1);\n    strcat(padding, \"\\n\");\n    db_fputs(padding, stream);\n\n    // Blank line\n    db_fputs(\"\\n\", stream);\n\n    // Name\n    sprintf(title1,\n        \"%s %s\",\n        getmsg(&editor_message_file, &mesg, 642),\n        critter_name(obj_dude));\n\n    int paddingLength = 27 - strlen(title1);\n    if (paddingLength > 0) {\n        // NOTE: Uninline.\n        padding[0] = '\\0';\n        AddSpaces(padding, paddingLength);\n\n        strcat(title1, padding);\n    }\n\n    // Age\n    sprintf(title2,\n        \"%s%s %d\",\n        title1,\n        getmsg(&editor_message_file, &mesg, 643),\n        critterGetStat(obj_dude, STAT_AGE));\n\n    // Gender\n    sprintf(title3,\n        \"%s%s %s\",\n        title2,\n        getmsg(&editor_message_file, &mesg, 644),\n        getmsg(&editor_message_file, &mesg, 645 + critterGetStat(obj_dude, STAT_GENDER)));\n\n    db_fputs(title3, stream);\n    db_fputs(\"\\n\", stream);\n\n    sprintf(title1,\n        \"%s %.2d %s %s \",\n        getmsg(&editor_message_file, &mesg, 647),\n        stat_pc_get(PC_STAT_LEVEL),\n        getmsg(&editor_message_file, &mesg, 648),\n        itostndn(stat_pc_get(PC_STAT_EXPERIENCE), title3));\n\n    paddingLength = 12 - strlen(title3);\n    if (paddingLength > 0) {\n        // NOTE: Uninline.\n        padding[0] = '\\0';\n        AddSpaces(padding, paddingLength);\n\n        strcat(title1, padding);\n    }\n\n    sprintf(title2,\n        \"%s%s %s\",\n        title1,\n        getmsg(&editor_message_file, &mesg, 649),\n        itostndn(stat_pc_min_exp(), title3));\n    db_fputs(title2, stream);\n    db_fputs(\"\\n\", stream);\n    db_fputs(\"\\n\", stream);\n\n    // Statistics\n    sprintf(title1, \"%s\\n\", getmsg(&editor_message_file, &mesg, 623));\n\n    // Strength / Hit Points / Sequence\n    //\n    // FIXME: There is bug - it shows strength instead of sequence.\n    sprintf(title1,\n        \"%s %.2d %s %.3d/%.3d %s %.2d\",\n        getmsg(&editor_message_file, &mesg, 624),\n        critterGetStat(obj_dude, STAT_STRENGTH),\n        getmsg(&editor_message_file, &mesg, 625),\n        critter_get_hits(obj_dude),\n        critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS),\n        getmsg(&editor_message_file, &mesg, 626),\n        critterGetStat(obj_dude, STAT_STRENGTH));\n    db_fputs(title1, stream);\n    db_fputs(\"\\n\", stream);\n\n    // Perception / Armor Class / Healing Rate\n    sprintf(title1,\n        \"%s %.2d %s %.3d %s %.2d\",\n        getmsg(&editor_message_file, &mesg, 627),\n        critterGetStat(obj_dude, STAT_PERCEPTION),\n        getmsg(&editor_message_file, &mesg, 628),\n        critterGetStat(obj_dude, STAT_ARMOR_CLASS),\n        getmsg(&editor_message_file, &mesg, 629),\n        critterGetStat(obj_dude, STAT_HEALING_RATE));\n    db_fputs(title1, stream);\n    db_fputs(\"\\n\", stream);\n\n    // Endurance / Action Points / Critical Chance\n    sprintf(title1,\n        \"%s %.2d %s %.2d %s %.3d%%\",\n        getmsg(&editor_message_file, &mesg, 630),\n        critterGetStat(obj_dude, STAT_ENDURANCE),\n        getmsg(&editor_message_file, &mesg, 631),\n        critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS),\n        getmsg(&editor_message_file, &mesg, 632),\n        critterGetStat(obj_dude, STAT_CRITICAL_CHANCE));\n    db_fputs(title1, stream);\n    db_fputs(\"\\n\", stream);\n\n    // Charisma / Melee Damage / Carry Weight\n    sprintf(title1,\n        \"%s %.2d %s %.2d %s %.3d lbs.\",\n        getmsg(&editor_message_file, &mesg, 633),\n        critterGetStat(obj_dude, STAT_CHARISMA),\n        getmsg(&editor_message_file, &mesg, 634),\n        critterGetStat(obj_dude, STAT_MELEE_DAMAGE),\n        getmsg(&editor_message_file, &mesg, 635),\n        critterGetStat(obj_dude, STAT_CARRY_WEIGHT));\n    db_fputs(title1, stream);\n    db_fputs(\"\\n\", stream);\n\n    // Intelligence / Damage Resistance\n    sprintf(title1,\n        \"%s %.2d %s %.3d%%\",\n        getmsg(&editor_message_file, &mesg, 636),\n        critterGetStat(obj_dude, STAT_INTELLIGENCE),\n        getmsg(&editor_message_file, &mesg, 637),\n        critterGetStat(obj_dude, STAT_DAMAGE_RESISTANCE));\n    db_fputs(title1, stream);\n    db_fputs(\"\\n\", stream);\n\n    // Agility / Radiation Resistance\n    sprintf(title1,\n        \"%s %.2d %s %.3d%%\",\n        getmsg(&editor_message_file, &mesg, 638),\n        critterGetStat(obj_dude, STAT_AGILITY),\n        getmsg(&editor_message_file, &mesg, 639),\n        critterGetStat(obj_dude, STAT_RADIATION_RESISTANCE));\n    db_fputs(title1, stream);\n    db_fputs(\"\\n\", stream);\n\n    // Luck / Poison Resistance\n    sprintf(title1,\n        \"%s %.2d %s %.3d%%\",\n        getmsg(&editor_message_file, &mesg, 640),\n        critterGetStat(obj_dude, STAT_LUCK),\n        getmsg(&editor_message_file, &mesg, 641),\n        critterGetStat(obj_dude, STAT_POISON_RESISTANCE));\n    db_fputs(title1, stream);\n    db_fputs(\"\\n\", stream);\n\n    db_fputs(\"\\n\", stream);\n    db_fputs(\"\\n\", stream);\n\n    if (temp_trait[0] != -1) {\n        // ::: Traits :::\n        sprintf(title1, \"%s\\n\", getmsg(&editor_message_file, &mesg, 650));\n        db_fputs(title1, stream);\n\n        // NOTE: The original code does not use loop, or it was optimized away.\n        for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) {\n            if (temp_trait[index] != -1) {\n                sprintf(title1, \"  %s\", trait_name(temp_trait[index]));\n                db_fputs(title1, stream);\n                db_fputs(\"\\n\", stream);\n            }\n        }\n    }\n\n    int perk = 0;\n    for (; perk < PERK_COUNT; perk++) {\n        if (perk_level(obj_dude, perk) != 0) {\n            break;\n        }\n    }\n\n    if (perk < PERK_COUNT) {\n        // ::: Perks :::\n        sprintf(title1, \"%s\\n\", getmsg(&editor_message_file, &mesg, 651));\n        db_fputs(title1, stream);\n\n        for (perk = 0; perk < PERK_COUNT; perk++) {\n            int rank = perk_level(obj_dude, perk);\n            if (rank != 0) {\n                if (rank == 1) {\n                    sprintf(title1, \"  %s\", perk_name(perk));\n                } else {\n                    sprintf(title1, \"  %s (%d)\", perk_name(perk), rank);\n                }\n\n                db_fputs(title1, stream);\n                db_fputs(\"\\n\", stream);\n            }\n        }\n    }\n\n    db_fputs(\"\\n\", stream);\n\n    // ::: Karma :::\n    sprintf(title1, \"%s\\n\", getmsg(&editor_message_file, &mesg, 652));\n    db_fputs(title1, stream);\n\n    for (int index = 0; index < karma_vars_count; index++) {\n        KarmaEntry* karmaEntry = &(karma_vars[index]);\n        if (karmaEntry->gvar == GVAR_PLAYER_REPUTATION) {\n            int reputation = 0;\n            for (; reputation < general_reps_count; reputation++) {\n                GenericReputationEntry* reputationDescription = &(general_reps[reputation]);\n                if (game_global_vars[GVAR_PLAYER_REPUTATION] >= reputationDescription->threshold) {\n                    break;\n                }\n            }\n\n            if (reputation < general_reps_count) {\n                GenericReputationEntry* reputationDescription = &(general_reps[reputation]);\n                sprintf(title1,\n                    \"  %s: %s (%s)\",\n                    getmsg(&editor_message_file, &mesg, 125),\n                    itoa(game_global_vars[GVAR_PLAYER_REPUTATION], title2, 10),\n                    getmsg(&editor_message_file, &mesg, reputationDescription->name));\n                db_fputs(title1, stream);\n                db_fputs(\"\\n\", stream);\n            }\n        } else {\n            if (game_global_vars[karmaEntry->gvar] != 0) {\n                sprintf(title1, \"  %s\", getmsg(&editor_message_file, &mesg, karmaEntry->name));\n                db_fputs(title1, stream);\n                db_fputs(\"\\n\", stream);\n            }\n        }\n    }\n\n    bool hasTownReputationHeading = false;\n    for (int index = 0; index < TOWN_REPUTATION_COUNT; index++) {\n        const TownReputationEntry* pair = &(town_rep_info[index]);\n        if (wmAreaIsKnown(pair->city)) {\n            if (!hasTownReputationHeading) {\n                db_fputs(\"\\n\", stream);\n\n                // ::: Reputation :::\n                sprintf(title1, \"%s\\n\", getmsg(&editor_message_file, &mesg, 657));\n                db_fputs(title1, stream);\n                hasTownReputationHeading = true;\n            }\n\n            wmGetAreaIdxName(pair->city, title2);\n\n            int townReputation = game_global_vars[pair->gvar];\n\n            int townReputationMessageId;\n\n            if (townReputation < -30) {\n                townReputationMessageId = 2006; // Vilified\n            } else if (townReputation < -15) {\n                townReputationMessageId = 2005; // Hated\n            } else if (townReputation < 0) {\n                townReputationMessageId = 2004; // Antipathy\n            } else if (townReputation == 0) {\n                townReputationMessageId = 2003; // Neutral\n            } else if (townReputation < 15) {\n                townReputationMessageId = 2002; // Accepted\n            } else if (townReputation < 30) {\n                townReputationMessageId = 2001; // Liked\n            } else {\n                townReputationMessageId = 2000; // Idolized\n            }\n\n            sprintf(title1,\n                \"  %s: %s\",\n                title2,\n                getmsg(&editor_message_file, &mesg, townReputationMessageId));\n            db_fputs(title1, stream);\n            db_fputs(\"\\n\", stream);\n        }\n    }\n\n    bool hasAddictionsHeading = false;\n    for (int index = 0; index < ADDICTION_REPUTATION_COUNT; index++) {\n        if (game_global_vars[addiction_vars[index]] != 0) {\n            if (!hasAddictionsHeading) {\n                db_fputs(\"\\n\", stream);\n\n                // ::: Addictions :::\n                sprintf(title1, \"%s\\n\", getmsg(&editor_message_file, &mesg, 656));\n                db_fputs(title1, stream);\n                hasAddictionsHeading = true;\n            }\n\n            sprintf(title1,\n                \"  %s\",\n                getmsg(&editor_message_file, &mesg, 1004 + index));\n            db_fputs(title1, stream);\n            db_fputs(\"\\n\", stream);\n        }\n    }\n\n    db_fputs(\"\\n\", stream);\n\n    // ::: Skills ::: / ::: Kills :::\n    sprintf(title1, \"%s\\n\", getmsg(&editor_message_file, &mesg, 653));\n    db_fputs(title1, stream);\n\n    int killType = 0;\n    for (int skill = 0; skill < SKILL_COUNT; skill++) {\n        sprintf(title1, \"%s \", skill_name(skill));\n\n        // NOTE: Uninline.\n        AddDots(title1 + strlen(title1), 16 - strlen(title1));\n\n        bool hasKillType = false;\n\n        for (; killType < KILL_TYPE_COUNT; killType++) {\n            int killsCount = critter_kill_count(killType);\n            if (killsCount > 0) {\n                sprintf(title2, \"%s \", critter_kill_name(killType));\n\n                // NOTE: Uninline.\n                AddDots(title2 + strlen(title2), 16 - strlen(title2));\n\n                sprintf(title3,\n                    \"  %s %.3d%%        %s %.3d\\n\",\n                    title1,\n                    skill_level(obj_dude, skill),\n                    title2,\n                    killsCount);\n                hasKillType = true;\n                break;\n            }\n        }\n\n        if (!hasKillType) {\n            sprintf(title3,\n                \"  %s %.3d%%\\n\",\n                title1,\n                skill_level(obj_dude, skill));\n        }\n    }\n\n    db_fputs(\"\\n\", stream);\n    db_fputs(\"\\n\", stream);\n\n    // ::: Inventory :::\n    sprintf(title1, \"%s\\n\", getmsg(&editor_message_file, &mesg, 654));\n    db_fputs(title1, stream);\n\n    Inventory* inventory = &(obj_dude->data.inventory);\n    for (int index = 0; index < inventory->length; index += 3) {\n        title1[0] = '\\0';\n\n        for (int column = 0; column < 3; column++) {\n            int inventoryItemIndex = index + column;\n            if (inventoryItemIndex >= inventory->length) {\n                break;\n            }\n\n            InventoryItem* inventoryItem = &(inventory->items[inventoryItemIndex]);\n\n            sprintf(title2,\n                \"  %sx %s\",\n                itostndn(inventoryItem->quantity, title3),\n                object_name(inventoryItem->item));\n\n            int length = 25 - strlen(title2);\n            if (length < 0) {\n                length = 0;\n            }\n\n            AddSpaces(title2, length);\n\n            strcat(title1, title2);\n        }\n\n        strcat(title1, \"\\n\");\n        db_fputs(title1, stream);\n    }\n\n    db_fputs(\"\\n\", stream);\n\n    // Total Weight:\n    sprintf(title1,\n        \"%s %d lbs.\",\n        getmsg(&editor_message_file, &mesg, 655),\n        item_total_weight(obj_dude));\n    db_fputs(title1, stream);\n\n    db_fputs(\"\\n\", stream);\n    db_fputs(\"\\n\", stream);\n    db_fputs(\"\\n\", stream);\n    db_fclose(stream);\n\n    return 0;\n}\n\n// 0x43A55C\nchar* AddSpaces(char* string, int length)\n{\n    char* pch = string + strlen(string);\n\n    for (int index = 0; index < length; index++) {\n        *pch++ = ' ';\n    }\n\n    *pch = '\\0';\n\n    return string;\n}\n\n// NOTE: Inlined.\n//\n// 0x43A58C\nstatic char* AddDots(char* string, int length)\n{\n    char* pch = string + strlen(string);\n\n    for (int index = 0; index < length; index++) {\n        *pch++ = '.';\n    }\n\n    *pch = '\\0';\n\n    return string;\n}\n\n// 0x43A4BC\nstatic void ResetScreen()\n{\n    info_line = 0;\n    skill_cursor = 0;\n    slider_y = 27;\n    folder = 0;\n\n    if (glblmode) {\n        PrintBigNum(126, 282, 0, character_points, 0, edit_win);\n    } else {\n        DrawFolder();\n        PrintLevelWin();\n    }\n\n    PrintBigname();\n    PrintAgeBig();\n    PrintGender();\n    ListTraits();\n    ListSkills(0);\n    PrintBasicStat(7, 0, 0);\n    ListDrvdStats();\n    DrawInfoWin();\n    win_draw(edit_win);\n}\n\n// 0x43A5BC\nstatic void RegInfoAreas()\n{\n    win_register_button(edit_win, 19, 38, 125, 227, -1, -1, 525, -1, NULL, NULL, NULL, 0);\n    win_register_button(edit_win, 28, 280, 124, 32, -1, -1, 526, -1, NULL, NULL, NULL, 0);\n\n    if (glblmode) {\n        win_register_button(edit_win, 52, 324, 169, 20, -1, -1, 533, -1, NULL, NULL, NULL, 0);\n        win_register_button(edit_win, 47, 353, 245, 100, -1, -1, 534, -1, NULL, NULL, NULL, 0);\n    } else {\n        win_register_button(edit_win, 28, 363, 283, 105, -1, -1, 527, -1, NULL, NULL, NULL, 0);\n    }\n\n    win_register_button(edit_win, 191, 41, 122, 110, -1, -1, 528, -1, NULL, NULL, NULL, 0);\n    win_register_button(edit_win, 191, 175, 122, 135, -1, -1, 529, -1, NULL, NULL, NULL, 0);\n    win_register_button(edit_win, 376, 5, 223, 20, -1, -1, 530, -1, NULL, NULL, NULL, 0);\n    win_register_button(edit_win, 370, 27, 223, 195, -1, -1, 531, -1, NULL, NULL, NULL, 0);\n    win_register_button(edit_win, 396, 228, 171, 25, -1, -1, 532, -1, NULL, NULL, NULL, 0);\n}\n\n// NOTE: Inlined.\n//\n// 0x43A79C\nstatic int CheckValidPlayer()\n{\n    int stat;\n\n    stat_recalc_derived(obj_dude);\n    stat_pc_set_defaults();\n\n    for (stat = 0; stat < SAVEABLE_STAT_COUNT; stat++) {\n        stat_set_bonus(obj_dude, stat, 0);\n    }\n\n    perk_reset();\n    stat_recalc_derived(obj_dude);\n\n    return 1;\n}\n\n// copy character to editor\n//\n// 0x43A7DC\nstatic void SavePlayer()\n{\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n    critter_copy(&dude_data, &(proto->critter.data));\n\n    hp_back = critter_get_hits(obj_dude);\n\n    strncpy(name_save, critter_name(obj_dude), 32);\n\n    last_level_back = last_level;\n\n    // NOTE: Uninline.\n    push_perks();\n\n    free_perk_back = free_perk;\n\n    upsent_points_back = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS);\n\n    skill_get_tags(tag_skill_back, NUM_TAGGED_SKILLS);\n\n    trait_get(&(trait_back[0]), &(trait_back[1]));\n\n    for (int skill = 0; skill < SKILL_COUNT; skill++) {\n        skillsav[skill] = skill_level(obj_dude, skill);\n    }\n}\n\n// copy editor to character\n//\n// 0x43A8BC\nstatic void RestorePlayer()\n{\n    Proto* proto;\n    int cur_hp;\n\n    pop_perks();\n\n    proto_ptr(obj_dude->pid, &proto);\n    critter_copy(&(proto->critter.data), &dude_data);\n\n    critter_pc_set_name(name_save);\n\n    last_level = last_level_back;\n    free_perk = free_perk_back;\n\n    stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, upsent_points_back);\n\n    skill_set_tags(tag_skill_back, NUM_TAGGED_SKILLS);\n\n    trait_set(trait_back[0], trait_back[1]);\n\n    skill_get_tags(temp_tag_skill, NUM_TAGGED_SKILLS);\n\n    // NOTE: Uninline.\n    tagskill_count = tagskl_free();\n\n    trait_get(&(temp_trait[0]), &(temp_trait[1]));\n\n    // NOTE: Uninline.\n    trait_count = get_trait_count();\n\n    stat_recalc_derived(obj_dude);\n\n    cur_hp = critter_get_hits(obj_dude);\n    critter_adjust_hits(obj_dude, hp_back - cur_hp);\n}\n\n// 0x43A9CC\nchar* itostndn(int value, char* dest)\n{\n    // 0x431DD4\n    static const int v16[7] = {\n        1000000,\n        100000,\n        10000,\n        1000,\n        100,\n        10,\n        1,\n    };\n\n    char* savedDest = dest;\n\n    if (value != 0) {\n        *dest = '\\0';\n\n        bool v3 = false;\n        for (int index = 0; index < 7; index++) {\n            int v18 = value / v16[index];\n            if (v18 > 0 || v3) {\n                char temp[64]; // TODO: Size is probably wrong.\n                itoa(v18, temp, 10);\n                strcat(dest, temp);\n\n                v3 = true;\n\n                value -= v16[index] * v18;\n\n                if (index == 0 || index == 3) {\n                    strcat(dest, \",\");\n                }\n            }\n        }\n    } else {\n        strcpy(dest, \"0\");\n    }\n\n    return savedDest;\n}\n\n// 0x43AAEC\nstatic int DrawCard(int graphicId, const char* name, const char* attributes, char* description)\n{\n    CacheEntry* graphicHandle;\n    Size size;\n    int fid;\n    unsigned char* buf;\n    unsigned char* ptr;\n    int v9;\n    int x;\n    int y;\n    short beginnings[WORD_WRAP_MAX_COUNT];\n    short beginningsCount;\n\n    fid = art_id(OBJ_TYPE_SKILLDEX, graphicId, 0, 0, 0);\n    buf = art_lock(fid, &graphicHandle, &(size.width), &(size.height));\n    if (buf == NULL) {\n        return -1;\n    }\n\n    buf_to_buf(buf, size.width, size.height, size.width, win_buf + 640 * 309 + 484, 640);\n\n    v9 = 150;\n    ptr = buf;\n    for (y = 0; y < size.height; y++) {\n        for (x = 0; x < size.width; x++) {\n            if (HighRGB(*ptr) < 2 && v9 >= x) {\n                v9 = x;\n            }\n            ptr++;\n        }\n    }\n\n    v9 -= 8;\n    if (v9 < 0) {\n        v9 = 0;\n    }\n\n    text_font(102);\n\n    text_to_buf(win_buf + 640 * 272 + 348, name, 640, 640, colorTable[0]);\n    int nameFontLineHeight = text_height();\n    if (attributes != NULL) {\n        int nameWidth = text_width(name);\n\n        text_font(101);\n        int attributesFontLineHeight = text_height();\n        text_to_buf(win_buf + 640 * (268 + nameFontLineHeight - attributesFontLineHeight) + 348 + nameWidth + 8, attributes, 640, 640, colorTable[0]);\n    }\n\n    y = nameFontLineHeight;\n    win_line(edit_win, 348, y + 272, 613, y + 272, colorTable[0]);\n    win_line(edit_win, 348, y + 273, 613, y + 273, colorTable[0]);\n\n    text_font(101);\n\n    int descriptionFontLineHeight = text_height();\n\n    if (word_wrap(description, v9 + 136, beginnings, &beginningsCount) != 0) {\n        // TODO: Leaking graphic handle.\n        return -1;\n    }\n\n    y = 315;\n    for (short i = 0; i < beginningsCount - 1; i++) {\n        short beginning = beginnings[i];\n        short ending = beginnings[i + 1];\n        char c = description[ending];\n        description[ending] = '\\0';\n        text_to_buf(win_buf + 640 * y + 348, description + beginning, 640, 640, colorTable[0]);\n        description[ending] = c;\n        y += descriptionFontLineHeight;\n    }\n\n    if (graphicId != old_fid1 || strcmp(name, old_str1) != 0) {\n        if (frstc_draw1) {\n            gsound_play_sfx_file(\"isdxxxx1\");\n        }\n    }\n\n    strcpy(old_str1, name);\n    old_fid1 = graphicId;\n    frstc_draw1 = true;\n\n    art_ptr_unlock(graphicHandle);\n\n    return 0;\n}\n\n// 0x43AE84\nstatic void FldrButton()\n{\n    mouse_get_position(&mouse_xpos, &mouse_ypos);\n    gsound_play_sfx_file(\"ib3p1xx1\");\n\n    if (mouse_xpos >= 208) {\n        info_line = 41;\n        folder = EDITOR_FOLDER_KILLS;\n    } else if (mouse_xpos > 110) {\n        info_line = 42;\n        folder = EDITOR_FOLDER_KARMA;\n    } else {\n        info_line = 40;\n        folder = EDITOR_FOLDER_PERKS;\n    }\n\n    DrawFolder();\n    DrawInfoWin();\n}\n\n// 0x43AF40\nstatic void InfoButton(int eventCode)\n{\n    mouse_get_position(&mouse_xpos, &mouse_ypos);\n\n    switch (eventCode) {\n    case 525:\n        if (1) {\n            // TODO: Original code is slightly different.\n            double mouseY = mouse_ypos;\n            for (int index = 0; index < 7; index++) {\n                double buttonTop = StatYpos[index];\n                double buttonBottom = StatYpos[index] + 22;\n                double allowance = 5.0 - index * 0.25;\n                if (mouseY >= buttonTop - allowance && mouseY <= buttonBottom + allowance) {\n                    info_line = index;\n                    break;\n                }\n            }\n        }\n        break;\n    case 526:\n        if (glblmode) {\n            info_line = 7;\n        } else {\n            int offset = mouse_ypos - 280;\n            if (offset < 0) {\n                offset = 0;\n            }\n\n            info_line = offset / 10 + 7;\n        }\n        break;\n    case 527:\n        if (!glblmode) {\n            text_font(101);\n            int offset = mouse_ypos - 364;\n            if (offset < 0) {\n                offset = 0;\n            }\n            info_line = offset / (text_height() + 1) + 10;\n        }\n        break;\n    case 528:\n        if (1) {\n            int offset = mouse_ypos - 41;\n            if (offset < 0) {\n                offset = 0;\n            }\n\n            info_line = offset / 13 + 43;\n        }\n        break;\n    case 529: {\n        int offset = mouse_ypos - 175;\n        if (offset < 0) {\n            offset = 0;\n        }\n\n        info_line = offset / 13 + 51;\n        break;\n    }\n    case 530:\n        info_line = 80;\n        break;\n    case 531:\n        if (1) {\n            int offset = mouse_ypos - 27;\n            if (offset < 0) {\n                offset = 0;\n            }\n\n            skill_cursor = (int)(offset * 0.092307694);\n            if (skill_cursor >= 18) {\n                skill_cursor = 17;\n            }\n\n            info_line = skill_cursor + 61;\n        }\n        break;\n    case 532:\n        info_line = 79;\n        break;\n    case 533:\n        info_line = 81;\n        break;\n    case 534:\n        if (1) {\n            text_font(101);\n\n            // TODO: Original code is slightly different.\n            double mouseY = mouse_ypos;\n            double fontLineHeight = text_height();\n            double y = 353.0;\n            double step = text_height() + 3 + 0.56;\n            int index;\n            for (index = 0; index < 8; index++) {\n                if (mouseY >= y - 4.0 && mouseY <= y + fontLineHeight) {\n                    break;\n                }\n                y += step;\n            }\n\n            if (index == 8) {\n                index = 7;\n            }\n\n            info_line = index + 82;\n            if (mouse_xpos >= 169) {\n                info_line += 8;\n            }\n        }\n        break;\n    }\n\n    PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n    ListTraits();\n    ListSkills(0);\n    PrintLevelWin();\n    DrawFolder();\n    ListDrvdStats();\n    DrawInfoWin();\n}\n\n// 0x43B230\nstatic void SliderBtn(int keyCode)\n{\n    if (glblmode) {\n        return;\n    }\n\n    int unspentSp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS);\n    _repFtime = 4;\n\n    bool isUsingKeyboard = false;\n    int rc = 0;\n\n    switch (keyCode) {\n    case KEY_PLUS:\n    case KEY_UPPERCASE_N:\n    case KEY_ARROW_RIGHT:\n        isUsingKeyboard = true;\n        keyCode = 521;\n        break;\n    case KEY_MINUS:\n    case KEY_UPPERCASE_J:\n    case KEY_ARROW_LEFT:\n        isUsingKeyboard = true;\n        keyCode = 523;\n        break;\n    }\n\n    char title[64];\n    char body1[64];\n    char body2[64];\n\n    const char* body[] = {\n        body1,\n        body2,\n    };\n\n    int repeatDelay = 0;\n    for (;;) {\n        _frame_time = get_time();\n        if (repeatDelay <= 19.2) {\n            repeatDelay++;\n        }\n\n        if (repeatDelay == 1 || repeatDelay > 19.2) {\n            if (repeatDelay > 19.2) {\n                _repFtime++;\n                if (_repFtime > 24) {\n                    _repFtime = 24;\n                }\n            }\n\n            rc = 1;\n            if (keyCode == 521) {\n                if (stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS) > 0) {\n                    if (skill_inc_point(obj_dude, skill_cursor) == -3) {\n                        gsound_play_sfx_file(\"iisxxxx1\");\n\n                        sprintf(title, \"%s:\", skill_name(skill_cursor));\n                        // At maximum level.\n                        strcpy(body1, getmsg(&editor_message_file, &mesg, 132));\n                        // Unable to increment it.\n                        strcpy(body2, getmsg(&editor_message_file, &mesg, 133));\n                        dialog_out(title, body, 2, 192, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n                        rc = -1;\n                    }\n                } else {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    // Not enough skill points available.\n                    strcpy(title, getmsg(&editor_message_file, &mesg, 136));\n                    dialog_out(title, NULL, 0, 192, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n                    rc = -1;\n                }\n            } else if (keyCode == 523) {\n                if (skill_level(obj_dude, skill_cursor) <= skillsav[skill_cursor]) {\n                    rc = 0;\n                } else {\n                    if (skill_dec_point(obj_dude, skill_cursor) == -2) {\n                        rc = 0;\n                    }\n                }\n\n                if (rc == 0) {\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    sprintf(title, \"%s:\", skill_name(skill_cursor));\n                    // At minimum level.\n                    strcpy(body1, getmsg(&editor_message_file, &mesg, 134));\n                    // Unable to decrement it.\n                    strcpy(body2, getmsg(&editor_message_file, &mesg, 135));\n                    dialog_out(title, body, 2, 192, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n                    rc = -1;\n                }\n            }\n\n            info_line = skill_cursor + 61;\n            DrawInfoWin();\n            ListSkills(1);\n\n            int flags;\n            if (rc == 1) {\n                flags = ANIMATE;\n            } else {\n                flags = 0;\n            }\n\n            PrintBigNum(522, 228, flags, stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS), unspentSp, edit_win);\n\n            win_draw(edit_win);\n        }\n\n        if (!isUsingKeyboard) {\n            unspentSp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS);\n            if (repeatDelay >= 19.2) {\n                while (elapsed_time(_frame_time) < 1000 / _repFtime) {\n                }\n            } else {\n                while (elapsed_time(_frame_time) < 1000 / 24) {\n                }\n            }\n\n            int keyCode = get_input();\n            if (keyCode != 522 && keyCode != 524 && rc != -1) {\n                continue;\n            }\n        }\n        return;\n    }\n}\n\n// 0x43B64C\nstatic int tagskl_free()\n{\n    int taggedSkillCount;\n    int index;\n\n    taggedSkillCount = 0;\n    for (index = 3; index >= 0; index--) {\n        if (temp_tag_skill[index] != -1) {\n            break;\n        }\n\n        taggedSkillCount++;\n    }\n\n    if (glblmode == 1) {\n        taggedSkillCount--;\n    }\n\n    return taggedSkillCount;\n}\n\n// 0x43B67C\nstatic void TagSkillSelect(int skill)\n{\n    int insertionIndex;\n\n    // NOTE: Uninline.\n    old_tags = tagskl_free();\n\n    if (skill == temp_tag_skill[0] || skill == temp_tag_skill[1] || skill == temp_tag_skill[2] || skill == temp_tag_skill[3]) {\n        if (skill == temp_tag_skill[0]) {\n            temp_tag_skill[0] = temp_tag_skill[1];\n            temp_tag_skill[1] = temp_tag_skill[2];\n            temp_tag_skill[2] = -1;\n        } else if (skill == temp_tag_skill[1]) {\n            temp_tag_skill[1] = temp_tag_skill[2];\n            temp_tag_skill[2] = -1;\n        } else {\n            temp_tag_skill[2] = -1;\n        }\n    } else {\n        if (tagskill_count > 0) {\n            insertionIndex = 0;\n            for (int index = 0; index < 3; index++) {\n                if (temp_tag_skill[index] == -1) {\n                    break;\n                }\n                insertionIndex++;\n            }\n            temp_tag_skill[insertionIndex] = skill;\n        } else {\n            gsound_play_sfx_file(\"iisxxxx1\");\n\n            char line1[128];\n            strcpy(line1, getmsg(&editor_message_file, &mesg, 140));\n\n            char line2[128];\n            strcpy(line2, getmsg(&editor_message_file, &mesg, 141));\n\n            const char* lines[] = { line2 };\n            dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0);\n        }\n    }\n\n    // NOTE: Uninline.\n    tagskill_count = tagskl_free();\n\n    info_line = skill + 61;\n    PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n    ListDrvdStats();\n    ListSkills(2);\n    DrawInfoWin();\n    win_draw(edit_win);\n}\n\n// 0x43B8A8\nstatic void ListTraits()\n{\n    int v0 = -1;\n    int i;\n    int color;\n    const char* traitName;\n    double step;\n    double y;\n\n    if (glblmode != 1) {\n        return;\n    }\n\n    if (info_line >= 82 && info_line < 98) {\n        v0 = info_line - 82;\n    }\n\n    buf_to_buf(bckgnd + 640 * 353 + 47, 245, 100, 640, win_buf + 640 * 353 + 47, 640);\n\n    text_font(101);\n\n    trait_set(temp_trait[0], temp_trait[1]);\n\n    step = text_height() + 3 + 0.56;\n    y = 353;\n    for (i = 0; i < 8; i++) {\n        if (i == v0) {\n            if (i != temp_trait[0] && i != temp_trait[1]) {\n                color = colorTable[32747];\n            } else {\n                color = colorTable[32767];\n            }\n\n            folder_card_fid = trait_pic(i);\n            folder_card_title = trait_name(i);\n            folder_card_title2 = NULL;\n            folder_card_desc = trait_description(i);\n        } else {\n            if (i != temp_trait[0] && i != temp_trait[1]) {\n                color = colorTable[992];\n            } else {\n                color = colorTable[21140];\n            }\n        }\n\n        traitName = trait_name(i);\n        text_to_buf(win_buf + 640 * (int)y + 47, traitName, 640, 640, color);\n        y += step;\n    }\n\n    y = 353;\n    for (i = 8; i < 16; i++) {\n        if (i == v0) {\n            if (i != temp_trait[0] && i != temp_trait[1]) {\n                color = colorTable[32747];\n            } else {\n                color = colorTable[32767];\n            }\n\n            folder_card_fid = trait_pic(i);\n            folder_card_title = trait_name(i);\n            folder_card_title2 = NULL;\n            folder_card_desc = trait_description(i);\n        } else {\n            if (i != temp_trait[0] && i != temp_trait[1]) {\n                color = colorTable[992];\n            } else {\n                color = colorTable[21140];\n            }\n        }\n\n        traitName = trait_name(i);\n        text_to_buf(win_buf + 640 * (int)y + 199, traitName, 640, 640, color);\n        y += step;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x43BAE8\nstatic int get_trait_count()\n{\n    int traitCount;\n    int index;\n\n    traitCount = 0;\n    for (index = 1; index >= 0; index--) {\n        if (temp_trait[index] != -1) {\n            break;\n        }\n\n        traitCount++;\n    }\n\n    return traitCount;\n}\n\n// 0x43BB0C\nstatic void TraitSelect(int trait)\n{\n    if (trait == temp_trait[0] || trait == temp_trait[1]) {\n        if (trait == temp_trait[0]) {\n            temp_trait[0] = temp_trait[1];\n            temp_trait[1] = -1;\n        } else {\n            temp_trait[1] = -1;\n        }\n    } else {\n        if (trait_count == 0) {\n            gsound_play_sfx_file(\"iisxxxx1\");\n\n            char line1[128];\n            strcpy(line1, getmsg(&editor_message_file, &mesg, 148));\n\n            char line2[128];\n            strcpy(line2, getmsg(&editor_message_file, &mesg, 149));\n\n            const char* lines = { line2 };\n            dialog_out(line1, &lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0);\n        } else {\n            for (int index = 0; index < 2; index++) {\n                if (temp_trait[index] == -1) {\n                    temp_trait[index] = trait;\n                    break;\n                }\n            }\n        }\n    }\n\n    // NOTE: Uninline.\n    trait_count = get_trait_count();\n\n    info_line = trait + EDITOR_FIRST_TRAIT;\n\n    ListTraits();\n    ListSkills(0);\n    stat_recalc_derived(obj_dude);\n    PrintBigNum(126, 282, 0, character_points, 0, edit_win);\n    PrintBasicStat(RENDER_ALL_STATS, false, 0);\n    ListDrvdStats();\n    DrawInfoWin();\n    win_draw(edit_win);\n}\n\n// 0x43BCE0\nstatic void list_karma()\n{\n    char* msg;\n    char formattedText[256];\n\n    folder_clear();\n\n    bool hasSelection = false;\n    for (int index = 0; index < karma_vars_count; index++) {\n        KarmaEntry* karmaDescription = &(karma_vars[index]);\n        if (karmaDescription->gvar == GVAR_PLAYER_REPUTATION) {\n            int reputation;\n            for (reputation = 0; reputation < general_reps_count; reputation++) {\n                GenericReputationEntry* reputationDescription = &(general_reps[reputation]);\n                if (game_global_vars[GVAR_PLAYER_REPUTATION] >= reputationDescription->threshold) {\n                    break;\n                }\n            }\n\n            if (reputation != general_reps_count) {\n                GenericReputationEntry* reputationDescription = &(general_reps[reputation]);\n\n                char reputationValue[32];\n                itoa(game_global_vars[GVAR_PLAYER_REPUTATION], reputationValue, 10);\n\n                sprintf(formattedText,\n                    \"%s: %s (%s)\",\n                    getmsg(&editor_message_file, &mesg, 125),\n                    reputationValue,\n                    getmsg(&editor_message_file, &mesg, reputationDescription->name));\n\n                if (folder_print_line(formattedText)) {\n                    folder_card_fid = karmaDescription->art_num;\n                    folder_card_title = getmsg(&editor_message_file, &mesg, 125);\n                    folder_card_title2 = NULL;\n                    folder_card_desc = getmsg(&editor_message_file, &mesg, karmaDescription->description);\n                    hasSelection = true;\n                }\n            }\n        } else {\n            if (game_global_vars[karmaDescription->gvar] != 0) {\n                msg = getmsg(&editor_message_file, &mesg, karmaDescription->name);\n                if (folder_print_line(msg)) {\n                    folder_card_fid = karmaDescription->art_num;\n                    folder_card_title = getmsg(&editor_message_file, &mesg, karmaDescription->name);\n                    folder_card_title2 = NULL;\n                    folder_card_desc = getmsg(&editor_message_file, &mesg, karmaDescription->description);\n                    hasSelection = true;\n                }\n            }\n        }\n    }\n\n    bool hasTownReputationHeading = false;\n    for (int index = 0; index < TOWN_REPUTATION_COUNT; index++) {\n        const TownReputationEntry* pair = &(town_rep_info[index]);\n        if (wmAreaIsKnown(pair->city)) {\n            if (!hasTownReputationHeading) {\n                msg = getmsg(&editor_message_file, &mesg, 4000);\n                if (folder_print_seperator(msg)) {\n                    folder_card_fid = 48;\n                    folder_card_title = getmsg(&editor_message_file, &mesg, 4000);\n                    folder_card_title2 = NULL;\n                    folder_card_desc = getmsg(&editor_message_file, &mesg, 4100);\n                }\n                hasTownReputationHeading = true;\n            }\n\n            char cityShortName[40];\n            wmGetAreaIdxName(pair->city, cityShortName);\n\n            int townReputation = game_global_vars[pair->gvar];\n\n            int townReputationGraphicId;\n            int townReputationBaseMessageId;\n\n            if (townReputation < -30) {\n                townReputationGraphicId = 150;\n                townReputationBaseMessageId = 2006; // Vilified\n            } else if (townReputation < -15) {\n                townReputationGraphicId = 153;\n                townReputationBaseMessageId = 2005; // Hated\n            } else if (townReputation < 0) {\n                townReputationGraphicId = 153;\n                townReputationBaseMessageId = 2004; // Antipathy\n            } else if (townReputation == 0) {\n                townReputationGraphicId = 141;\n                townReputationBaseMessageId = 2003; // Neutral\n            } else if (townReputation < 15) {\n                townReputationGraphicId = 137;\n                townReputationBaseMessageId = 2002; // Accepted\n            } else if (townReputation < 30) {\n                townReputationGraphicId = 137;\n                townReputationBaseMessageId = 2001; // Liked\n            } else {\n                townReputationGraphicId = 135;\n                townReputationBaseMessageId = 2000; // Idolized\n            }\n\n            msg = getmsg(&editor_message_file, &mesg, townReputationBaseMessageId);\n            sprintf(formattedText,\n                \"%s: %s\",\n                cityShortName,\n                msg);\n\n            if (folder_print_line(formattedText)) {\n                folder_card_fid = townReputationGraphicId;\n                folder_card_title = getmsg(&editor_message_file, &mesg, townReputationBaseMessageId);\n                folder_card_title2 = NULL;\n                folder_card_desc = getmsg(&editor_message_file, &mesg, townReputationBaseMessageId + 100);\n                hasSelection = 1;\n            }\n        }\n    }\n\n    bool hasAddictionsHeading = false;\n    for (int index = 0; index < ADDICTION_REPUTATION_COUNT; index++) {\n        if (game_global_vars[addiction_vars[index]] != 0) {\n            if (!hasAddictionsHeading) {\n                // Addictions\n                msg = getmsg(&editor_message_file, &mesg, 4001);\n                if (folder_print_seperator(msg)) {\n                    folder_card_fid = 53;\n                    folder_card_title = getmsg(&editor_message_file, &mesg, 4001);\n                    folder_card_title2 = NULL;\n                    folder_card_desc = getmsg(&editor_message_file, &mesg, 4101);\n                    hasSelection = 1;\n                }\n                hasAddictionsHeading = true;\n            }\n\n            msg = getmsg(&editor_message_file, &mesg, 1004 + index);\n            if (folder_print_line(msg)) {\n                folder_card_fid = addiction_pics[index];\n                folder_card_title = getmsg(&editor_message_file, &mesg, 1004 + index);\n                folder_card_title2 = NULL;\n                folder_card_desc = getmsg(&editor_message_file, &mesg, 1104 + index);\n                hasSelection = 1;\n            }\n        }\n    }\n\n    if (!hasSelection) {\n        folder_card_fid = 47;\n        folder_card_title = getmsg(&editor_message_file, &mesg, 125);\n        folder_card_title2 = NULL;\n        folder_card_desc = getmsg(&editor_message_file, &mesg, 128);\n    }\n}\n\n// 0x43C1B0\nint editor_save(File* stream)\n{\n    if (db_fwriteInt(stream, last_level) == -1)\n        return -1;\n    if (db_fwriteByte(stream, free_perk) == -1)\n        return -1;\n\n    return 0;\n}\n\n// 0x43C1E0\nint editor_load(File* stream)\n{\n    if (db_freadInt(stream, &last_level) == -1)\n        return -1;\n    if (db_freadByte(stream, &free_perk) == -1)\n        return -1;\n\n    return 0;\n}\n\n// 0x43C20C\nvoid editor_reset()\n{\n    character_points = 5;\n    last_level = 1;\n}\n\n// level up if needed\n//\n// 0x43C228\nstatic int UpdateLevel()\n{\n    int level = stat_pc_get(PC_STAT_LEVEL);\n    if (level != last_level && level <= PC_LEVEL_MAX) {\n        for (int nextLevel = last_level + 1; nextLevel <= level; nextLevel++) {\n            int sp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS);\n            sp += 5;\n            sp += stat_get_base(obj_dude, STAT_INTELLIGENCE) * 2;\n            sp += perk_level(obj_dude, PERK_EDUCATED) * 2;\n            sp += trait_level(TRAIT_SKILLED) * 5;\n            if (trait_level(TRAIT_GIFTED)) {\n                sp -= 5;\n                if (sp < 0) {\n                    sp = 0;\n                }\n            }\n            if (sp > 99) {\n                sp = 99;\n            }\n\n            stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, sp);\n\n            // NOTE: Uninline.\n            int selectedPerksCount = PerkCount();\n\n            if (selectedPerksCount < 37) {\n                int progression = 3;\n                if (trait_level(TRAIT_SKILLED)) {\n                    progression += 1;\n                }\n\n                if (nextLevel % progression == 0) {\n                    free_perk = 1;\n                }\n            }\n        }\n    }\n\n    if (free_perk != 0) {\n        folder = 0;\n        DrawFolder();\n        win_draw(edit_win);\n\n        int rc = perks_dialog();\n        if (rc == -1) {\n            debug_printf(\"\\n *** Error running perks dialog! ***\\n\");\n            return -1;\n        } else if (rc == 0) {\n            DrawFolder();\n        } else if (rc == 1) {\n            DrawFolder();\n            free_perk = 0;\n        }\n    }\n\n    last_level = level;\n\n    return 1;\n}\n\n// 0x43C398\nstatic void RedrwDPrks()\n{\n    buf_to_buf(\n        pbckgnd + 280,\n        293,\n        PERK_WINDOW_HEIGHT,\n        PERK_WINDOW_WIDTH,\n        pwin_buf + 280,\n        PERK_WINDOW_WIDTH);\n\n    ListDPerks();\n\n    // NOTE: Original code is slightly different, but basically does the same thing.\n    int perk = name_sort_list[crow + cline].value;\n    int perkFrmId = perk_skilldex_fid(perk);\n    char* perkName = perk_name(perk);\n    char* perkDescription = perk_description(perk);\n    char* perkRank = NULL;\n    char perkRankBuffer[32];\n\n    int rank = perk_level(obj_dude, perk);\n    if (rank != 0) {\n        sprintf(perkRankBuffer, \"(%d)\", rank);\n        perkRank = perkRankBuffer;\n    }\n\n    DrawCard2(perkFrmId, perkName, perkRank, perkDescription);\n\n    win_draw(pwin);\n}\n\n// 0x43C4F0\nstatic int perks_dialog()\n{\n    crow = 0;\n    cline = 0;\n    old_fid2 = -1;\n    old_str2[0] = '\\0';\n    frstc_draw2 = false;\n\n    CacheEntry* backgroundFrmHandle;\n    int backgroundWidth;\n    int backgroundHeight;\n    int fid = art_id(OBJ_TYPE_INTERFACE, 86, 0, 0, 0);\n    pbckgnd = art_lock(fid, &backgroundFrmHandle, &backgroundWidth, &backgroundHeight);\n    if (pbckgnd == NULL) {\n        debug_printf(\"\\n *** Error running perks dialog window ***\\n\");\n        return -1;\n    }\n\n    int perkWindowX = PERK_WINDOW_X;\n    int perkWindowY = PERK_WINDOW_Y;\n    pwin = win_add(perkWindowX, perkWindowY, PERK_WINDOW_WIDTH, PERK_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n    if (pwin == -1) {\n        art_ptr_unlock(backgroundFrmHandle);\n        debug_printf(\"\\n *** Error running perks dialog window ***\\n\");\n        return -1;\n    }\n\n    pwin_buf = win_get_buf(pwin);\n    memcpy(pwin_buf, pbckgnd, PERK_WINDOW_WIDTH * PERK_WINDOW_HEIGHT);\n\n    int btn;\n\n    btn = win_register_button(pwin,\n        48,\n        186,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        500,\n        grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP],\n        grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    btn = win_register_button(pwin,\n        153,\n        186,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width,\n        GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        502,\n        grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP],\n        grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    btn = win_register_button(pwin,\n        25,\n        46,\n        GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].width,\n        GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].height,\n        -1,\n        574,\n        572,\n        574,\n        grphbmp[EDITOR_GRAPHIC_UP_ARROW_OFF],\n        grphbmp[EDITOR_GRAPHIC_UP_ARROW_ON],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, NULL);\n    }\n\n    btn = win_register_button(pwin,\n        25,\n        47 + GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].height,\n        GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].width,\n        GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].height,\n        -1,\n        575,\n        573,\n        575,\n        grphbmp[EDITOR_GRAPHIC_DOWN_ARROW_OFF],\n        grphbmp[EDITOR_GRAPHIC_DOWN_ARROW_ON],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, NULL);\n    }\n\n    win_register_button(pwin,\n        PERK_WINDOW_LIST_X,\n        PERK_WINDOW_LIST_Y,\n        PERK_WINDOW_LIST_WIDTH,\n        PERK_WINDOW_LIST_HEIGHT,\n        -1,\n        -1,\n        -1,\n        501,\n        NULL,\n        NULL,\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n\n    text_font(103);\n\n    const char* msg;\n\n    // PICK A NEW PERK\n    msg = getmsg(&editor_message_file, &mesg, 152);\n    text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 16 + 49, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]);\n\n    // DONE\n    msg = getmsg(&editor_message_file, &mesg, 100);\n    text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 186 + 69, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]);\n\n    // CANCEL\n    msg = getmsg(&editor_message_file, &mesg, 102);\n    text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 186 + 171, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]);\n\n    int count = ListDPerks();\n\n    // NOTE: Original code is slightly different, but does the same thing.\n    int perk = name_sort_list[crow + cline].value;\n    int perkFrmId = perk_skilldex_fid(perk);\n    char* perkName = perk_name(perk);\n    char* perkDescription = perk_description(perk);\n    char* perkRank = NULL;\n    char perkRankBuffer[32];\n\n    int rank = perk_level(obj_dude, perk);\n    if (rank != 0) {\n        sprintf(perkRankBuffer, \"(%d)\", rank);\n        perkRank = perkRankBuffer;\n    }\n\n    DrawCard2(perkFrmId, perkName, perkRank, perkDescription);\n    win_draw(pwin);\n\n    int rc = InputPDLoop(count, RedrwDPrks);\n\n    if (rc == 1) {\n        if (perk_add(obj_dude, name_sort_list[crow + cline].value) == -1) {\n            debug_printf(\"\\n*** Unable to add perk! ***\\n\");\n            rc = 2;\n        }\n    }\n\n    rc &= 1;\n\n    if (rc != 0) {\n        if (perk_level(obj_dude, PERK_TAG) != 0 && perk_back[PERK_TAG] == 0) {\n            if (!Add4thTagSkill()) {\n                perk_sub(obj_dude, PERK_TAG);\n            }\n        } else if (perk_level(obj_dude, PERK_MUTATE) != 0 && perk_back[PERK_MUTATE] == 0) {\n            if (!GetMutateTrait()) {\n                perk_sub(obj_dude, PERK_MUTATE);\n            }\n        } else if (perk_level(obj_dude, PERK_LIFEGIVER) != perk_back[PERK_LIFEGIVER]) {\n            int maxHp = stat_get_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n            stat_set_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS, maxHp + 4);\n            critter_adjust_hits(obj_dude, 4);\n        } else if (perk_level(obj_dude, PERK_EDUCATED) != perk_back[PERK_EDUCATED]) {\n            int sp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS);\n            stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, sp + 2);\n        }\n    }\n\n    ListSkills(0);\n    PrintBasicStat(RENDER_ALL_STATS, 0, 0);\n    PrintLevelWin();\n    ListDrvdStats();\n    DrawFolder();\n    DrawInfoWin();\n    win_draw(edit_win);\n\n    art_ptr_unlock(backgroundFrmHandle);\n\n    win_delete(pwin);\n\n    return rc;\n}\n\n// 0x43CACC\nstatic int InputPDLoop(int count, void (*refreshProc)())\n{\n    text_font(101);\n\n    int v3 = count - 11;\n\n    int height = text_height();\n    oldsline = -2;\n    int v16 = height + 2;\n\n    int v7 = 0;\n\n    int rc = 0;\n    while (rc == 0) {\n        int keyCode = get_input();\n        int v19 = 0;\n\n        if (keyCode == 500) {\n            rc = 1;\n        } else if (keyCode == KEY_RETURN) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            rc = 1;\n        } else if (keyCode == 501) {\n            mouse_get_position(&mouse_xpos, &mouse_ypos);\n            cline = (mouse_ypos - (PERK_WINDOW_Y + PERK_WINDOW_LIST_Y)) / v16;\n            if (cline >= 0) {\n                if (count - 1 < cline)\n                    cline = count - 1;\n            } else {\n                cline = 0;\n            }\n\n            if (cline == oldsline) {\n                gsound_play_sfx_file(\"ib1p1xx1\");\n                rc = 1;\n            }\n            oldsline = cline;\n            refreshProc();\n        } else if (keyCode == 502 || keyCode == KEY_ESCAPE || game_user_wants_to_quit != 0) {\n            rc = 2;\n        } else {\n            switch (keyCode) {\n            case KEY_ARROW_UP:\n                oldsline = -2;\n\n                crow--;\n                if (crow < 0) {\n                    crow = 0;\n\n                    cline--;\n                    if (cline < 0) {\n                        cline = 0;\n                    }\n                }\n\n                refreshProc();\n                break;\n            case KEY_PAGE_UP:\n                oldsline = -2;\n\n                for (int index = 0; index < 11; index++) {\n                    crow--;\n                    if (crow < 0) {\n                        crow = 0;\n\n                        cline--;\n                        if (cline < 0) {\n                            cline = 0;\n                        }\n                    }\n                }\n\n                refreshProc();\n                break;\n            case KEY_ARROW_DOWN:\n                oldsline = -2;\n\n                if (count > 11) {\n                    crow++;\n                    if (crow > count - 11) {\n                        crow = count - 11;\n\n                        cline++;\n                        if (cline > 10) {\n                            cline = 10;\n                        }\n                    }\n                } else {\n                    cline++;\n                    if (cline > count - 1) {\n                        cline = count - 1;\n                    }\n                }\n\n                refreshProc();\n                break;\n            case KEY_PAGE_DOWN:\n                oldsline = -2;\n\n                for (int index = 0; index < 11; index++) {\n                    if (count > 11) {\n                        crow++;\n                        if (crow > count - 11) {\n                            crow = count - 11;\n\n                            cline++;\n                            if (cline > 10) {\n                                cline = 10;\n                            }\n                        }\n                    } else {\n                        cline++;\n                        if (cline > count - 1) {\n                            cline = count - 1;\n                        }\n                    }\n                }\n\n                refreshProc();\n                break;\n            case 572:\n                _repFtime = 4;\n                oldsline = -2;\n\n                do {\n                    _frame_time = get_time();\n                    if (v19 <= 14.4) {\n                        v19++;\n                    }\n\n                    if (v19 == 1 || v19 > 14.4) {\n                        if (v19 > 14.4) {\n                            _repFtime++;\n                            if (_repFtime > 24) {\n                                _repFtime = 24;\n                            }\n                        }\n\n                        crow--;\n                        if (crow < 0) {\n                            crow = 0;\n\n                            cline--;\n                            if (cline < 0) {\n                                cline = 0;\n                            }\n                        }\n                        refreshProc();\n                    }\n\n                    if (v19 < 14.4) {\n                        while (elapsed_time(_frame_time) < 1000 / 24) {\n                        }\n                    } else {\n                        while (elapsed_time(_frame_time) < 1000 / _repFtime) {\n                        }\n                    }\n                } while (get_input() != 574);\n\n                break;\n            case 573:\n                oldsline = -2;\n                _repFtime = 4;\n\n                if (count > 11) {\n                    do {\n                        _frame_time = get_time();\n                        if (v19 <= 14.4) {\n                            v19++;\n                        }\n\n                        if (v19 == 1 || v19 > 14.4) {\n                            if (v19 > 14.4) {\n                                _repFtime++;\n                                if (_repFtime > 24) {\n                                    _repFtime = 24;\n                                }\n                            }\n\n                            crow++;\n                            if (crow > count - 11) {\n                                crow = count - 11;\n\n                                cline++;\n                                if (cline > 10) {\n                                    cline = 10;\n                                }\n                            }\n\n                            refreshProc();\n                        }\n\n                        if (v19 < 14.4) {\n                            while (elapsed_time(_frame_time) < 1000 / 24) {\n                            }\n                        } else {\n                            while (elapsed_time(_frame_time) < 1000 / _repFtime) {\n                            }\n                        }\n                    } while (get_input() != 575);\n                } else {\n                    do {\n                        _frame_time = get_time();\n                        if (v19 <= 14.4) {\n                            v19++;\n                        }\n\n                        if (v19 == 1 || v19 > 14.4) {\n                            if (v19 > 14.4) {\n                                _repFtime++;\n                                if (_repFtime > 24) {\n                                    _repFtime = 24;\n                                }\n                            }\n\n                            cline++;\n                            if (cline > count - 1) {\n                                cline = count - 1;\n                            }\n\n                            refreshProc();\n                        }\n\n                        if (v19 < 14.4) {\n                            while (elapsed_time(_frame_time) < 1000 / 24) {\n                            }\n                        } else {\n                            while (elapsed_time(_frame_time) < 1000 / _repFtime) {\n                            }\n                        }\n                    } while (get_input() != 575);\n                }\n                break;\n            case KEY_HOME:\n                crow = 0;\n                cline = 0;\n                oldsline = -2;\n                refreshProc();\n                break;\n            case KEY_END:\n                oldsline = -2;\n                if (count > 11) {\n                    crow = count - 11;\n                    cline = 10;\n                } else {\n                    cline = count - 1;\n                }\n                refreshProc();\n                break;\n            default:\n                if (elapsed_time(_frame_time) > 700) {\n                    _frame_time = get_time();\n                    oldsline = -2;\n                }\n                break;\n            }\n        }\n    }\n\n    return rc;\n}\n\n// 0x43D0BC\nstatic int ListDPerks()\n{\n    buf_to_buf(\n        pbckgnd + PERK_WINDOW_WIDTH * 43 + 45,\n        192,\n        129,\n        PERK_WINDOW_WIDTH,\n        pwin_buf + PERK_WINDOW_WIDTH * 43 + 45,\n        PERK_WINDOW_WIDTH);\n\n    text_font(101);\n\n    int perks[PERK_COUNT];\n    int count = perk_make_list(obj_dude, perks);\n    if (count == 0) {\n        return 0;\n    }\n\n    for (int perk = 0; perk < PERK_COUNT; perk++) {\n        name_sort_list[perk].value = 0;\n        name_sort_list[perk].name = NULL;\n    }\n\n    for (int index = 0; index < count; index++) {\n        name_sort_list[index].value = perks[index];\n        name_sort_list[index].name = perk_name(perks[index]);\n    }\n\n    qsort(name_sort_list, count, sizeof(*name_sort_list), name_sort_comp);\n\n    int v16 = count - crow;\n    if (v16 > 11) {\n        v16 = 11;\n    }\n\n    v16 += crow;\n\n    int y = 43;\n    int yStep = text_height() + 2;\n    for (int index = crow; index < v16; index++) {\n        int color;\n        if (index == crow + cline) {\n            color = colorTable[32747];\n        } else {\n            color = colorTable[992];\n        }\n\n        text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 45, name_sort_list[index].name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color);\n\n        if (perk_level(obj_dude, name_sort_list[index].value) != 0) {\n            char rankString[256];\n            sprintf(rankString, \"(%d)\", perk_level(obj_dude, name_sort_list[index].value));\n            text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 207, rankString, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color);\n        }\n\n        y += yStep;\n    }\n\n    return count;\n}\n\n// 0x43D2F8\nvoid RedrwDMPrk()\n{\n    buf_to_buf(pbckgnd + 280, 293, PERK_WINDOW_HEIGHT, PERK_WINDOW_WIDTH, pwin_buf + 280, PERK_WINDOW_WIDTH);\n\n    ListMyTraits(optrt_count);\n\n    char* traitName = name_sort_list[crow + cline].name;\n    char* tratDescription = trait_description(name_sort_list[crow + cline].value);\n    int frmId = trait_pic(name_sort_list[crow + cline].value);\n    DrawCard2(frmId, traitName, NULL, tratDescription);\n\n    win_draw(pwin);\n}\n\n// 0x43D38C\nstatic bool GetMutateTrait()\n{\n    old_fid2 = -1;\n    old_str2[0] = '\\0';\n    frstc_draw2 = false;\n\n    // NOTE: Uninline.\n    trait_count = TRAITS_MAX_SELECTED_COUNT - get_trait_count();\n\n    bool result = true;\n    if (trait_count >= 1) {\n        text_font(103);\n\n        buf_to_buf(pbckgnd + PERK_WINDOW_WIDTH * 14 + 49, 206, text_height() + 2, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 15 + 49, PERK_WINDOW_WIDTH);\n\n        // LOSE A TRAIT\n        char* msg = getmsg(&editor_message_file, &mesg, 154);\n        text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 16 + 49, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]);\n\n        optrt_count = 0;\n        cline = 0;\n        crow = 0;\n        RedrwDMPrk();\n\n        int rc = InputPDLoop(trait_count, RedrwDMPrk);\n        if (rc == 1) {\n            if (cline == 0) {\n                if (trait_count == 1) {\n                    temp_trait[0] = -1;\n                    temp_trait[1] = -1;\n                } else {\n                    if (name_sort_list[0].value == temp_trait[0]) {\n                        temp_trait[0] = temp_trait[1];\n                        temp_trait[1] = -1;\n                    } else {\n                        temp_trait[1] = -1;\n                    }\n                }\n            } else {\n                if (name_sort_list[0].value == temp_trait[0]) {\n                    temp_trait[1] = -1;\n                } else {\n                    temp_trait[0] = temp_trait[1];\n                    temp_trait[1] = -1;\n                }\n            }\n        } else {\n            result = false;\n        }\n    }\n\n    if (result) {\n        text_font(103);\n\n        buf_to_buf(pbckgnd + PERK_WINDOW_WIDTH * 14 + 49, 206, text_height() + 2, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 15 + 49, PERK_WINDOW_WIDTH);\n\n        // PICK A NEW TRAIT\n        char* msg = getmsg(&editor_message_file, &mesg, 153);\n        text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 16 + 49, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]);\n\n        cline = 0;\n        crow = 0;\n        optrt_count = 1;\n\n        RedrwDMPrk();\n\n        int count = 16 - trait_count;\n        if (count > 16) {\n            count = 16;\n        }\n\n        int rc = InputPDLoop(count, RedrwDMPrk);\n        if (rc == 1) {\n            if (trait_count != 0) {\n                temp_trait[1] = name_sort_list[cline + crow].value;\n            } else {\n                temp_trait[0] = name_sort_list[cline + crow].value;\n                temp_trait[1] = -1;\n            }\n\n            trait_set(temp_trait[0], temp_trait[1]);\n        } else {\n            result = false;\n        }\n    }\n\n    if (!result) {\n        memcpy(temp_trait, trait_back, sizeof(temp_trait));\n    }\n\n    return result;\n}\n\n// 0x43D668\nstatic void RedrwDMTagSkl()\n{\n    buf_to_buf(pbckgnd + 280, 293, PERK_WINDOW_HEIGHT, PERK_WINDOW_WIDTH, pwin_buf + 280, PERK_WINDOW_WIDTH);\n\n    ListNewTagSkills();\n\n    char* name = name_sort_list[crow + cline].name;\n    char* description = skill_description(name_sort_list[crow + cline].value);\n    int frmId = skill_pic(name_sort_list[crow + cline].value);\n    DrawCard2(frmId, name, NULL, description);\n\n    win_draw(pwin);\n}\n\n// 0x43D6F8\nstatic bool Add4thTagSkill()\n{\n    text_font(103);\n\n    buf_to_buf(pbckgnd + 573 * 14 + 49, 206, text_height() + 2, 573, pwin_buf + 573 * 15 + 49, 573);\n\n    // PICK A NEW TAG SKILL\n    char* messageListItemText = getmsg(&editor_message_file, &mesg, 155);\n    text_to_buf(pwin_buf + 573 * 16 + 49, messageListItemText, 573, 573, colorTable[18979]);\n\n    cline = 0;\n    crow = 0;\n    old_fid2 = -1;\n    old_str2[0] = '\\0';\n    frstc_draw2 = false;\n    RedrwDMTagSkl();\n\n    int rc = InputPDLoop(optrt_count, RedrwDMTagSkl);\n    if (rc != 1) {\n        memcpy(temp_tag_skill, tag_skill_back, sizeof(temp_tag_skill));\n        skill_set_tags(tag_skill_back, NUM_TAGGED_SKILLS);\n        return false;\n    }\n\n    temp_tag_skill[3] = name_sort_list[crow + cline].value;\n    skill_set_tags(temp_tag_skill, NUM_TAGGED_SKILLS);\n\n    return true;\n}\n\n// 0x43D81C\nstatic void ListNewTagSkills()\n{\n    buf_to_buf(pbckgnd + PERK_WINDOW_WIDTH * 43 + 45, 192, 129, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 43 + 45, PERK_WINDOW_WIDTH);\n\n    text_font(101);\n\n    optrt_count = 0;\n\n    int y = 43;\n    int yStep = text_height() + 2;\n\n    for (int skill = 0; skill < SKILL_COUNT; skill++) {\n        if (skill != temp_tag_skill[0] && skill != temp_tag_skill[1] && skill != temp_tag_skill[2] && skill != temp_tag_skill[3]) {\n            name_sort_list[optrt_count].value = skill;\n            name_sort_list[optrt_count].name = skill_name(skill);\n            optrt_count++;\n        }\n    }\n\n    qsort(name_sort_list, optrt_count, sizeof(*name_sort_list), name_sort_comp);\n\n    for (int index = crow; index < crow + 11; index++) {\n        int color;\n        if (index == cline + crow) {\n            color = colorTable[32747];\n        } else {\n            color = colorTable[992];\n        }\n\n        text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 45, name_sort_list[index].name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color);\n        y += yStep;\n    }\n}\n\n// 0x43D960\nstatic int ListMyTraits(int a1)\n{\n    buf_to_buf(pbckgnd + PERK_WINDOW_WIDTH * 43 + 45, 192, 129, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 43 + 45, PERK_WINDOW_WIDTH);\n\n    text_font(101);\n\n    int y = 43;\n    int yStep = text_height() + 2;\n\n    if (a1 != 0) {\n        int count = 0;\n        for (int trait = 0; trait < TRAIT_COUNT; trait++) {\n            if (trait != trait_back[0] && trait != trait_back[1]) {\n                name_sort_list[count].value = trait;\n                name_sort_list[count].name = trait_name(trait);\n                count++;\n            }\n        }\n\n        qsort(name_sort_list, count, sizeof(*name_sort_list), name_sort_comp);\n\n        for (int index = crow; index < crow + 11; index++) {\n            int color;\n            if (index == cline + crow) {\n                color = colorTable[32747];\n            } else {\n                color = colorTable[992];\n            }\n\n            text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 45, name_sort_list[index].name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color);\n            y += yStep;\n        }\n    } else {\n        // NOTE: Original code does not use loop.\n        for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) {\n            name_sort_list[index].value = temp_trait[index];\n            name_sort_list[index].name = trait_name(temp_trait[index]);\n        }\n\n        if (trait_count > 1) {\n            qsort(name_sort_list, trait_count, sizeof(*name_sort_list), name_sort_comp);\n        }\n\n        for (int index = 0; index < trait_count; index++) {\n            int color;\n            if (index == cline) {\n                color = colorTable[32747];\n            } else {\n                color = colorTable[992];\n            }\n\n            text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 45, name_sort_list[index].name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color);\n            y += yStep;\n        }\n    }\n    return 0;\n}\n\n// 0x43DB48\nstatic int name_sort_comp(const void* a1, const void* a2)\n{\n    PerkDialogOption* v1 = (PerkDialogOption*)a1;\n    PerkDialogOption* v2 = (PerkDialogOption*)a2;\n    return strcmp(v1->name, v2->name);\n}\n\n// 0x43DB54\nstatic int DrawCard2(int frmId, const char* name, const char* rank, char* description)\n{\n    int fid = art_id(OBJ_TYPE_SKILLDEX, frmId, 0, 0, 0);\n\n    CacheEntry* handle;\n    int width;\n    int height;\n    unsigned char* data = art_lock(fid, &handle, &width, &height);\n    if (data == NULL) {\n        return -1;\n    }\n\n    buf_to_buf(data, width, height, width, pwin_buf + PERK_WINDOW_WIDTH * 64 + 413, PERK_WINDOW_WIDTH);\n\n    // Calculate width of transparent pixels on the left side of the image. This\n    // space will be occupied by description (in addition to fixed width).\n    int extraDescriptionWidth = 150;\n    for (int y = 0; y < height; y++) {\n        unsigned char* stride = data;\n        for (int x = 0; x < width; x++) {\n            if (HighRGB(*stride) < 2) {\n                if (extraDescriptionWidth > x) {\n                    extraDescriptionWidth = x;\n                }\n            }\n            stride++;\n        }\n        data += width;\n    }\n\n    // Add gap between description and image.\n    extraDescriptionWidth -= 8;\n    if (extraDescriptionWidth < 0) {\n        extraDescriptionWidth = 0;\n    }\n\n    text_font(102);\n    int nameHeight = text_height();\n\n    text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 27 + 280, name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[0]);\n\n    if (rank != NULL) {\n        int rankX = text_width(name) + 280 + 8;\n        text_font(101);\n\n        int rankHeight = text_height();\n        text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * (23 + nameHeight - rankHeight) + rankX, rank, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[0]);\n    }\n\n    win_line(pwin, 280, 27 + nameHeight, 545, 27 + nameHeight, colorTable[0]);\n    win_line(pwin, 280, 28 + nameHeight, 545, 28 + nameHeight, colorTable[0]);\n\n    text_font(101);\n\n    int yStep = text_height() + 1;\n    int y = 70;\n\n    short beginnings[WORD_WRAP_MAX_COUNT];\n    short count;\n    if (word_wrap(description, 133 + extraDescriptionWidth, beginnings, &count) != 0) {\n        // FIXME: Leaks handle.\n        return -1;\n    }\n\n    for (int index = 0; index < count - 1; index++) {\n        char* beginning = description + beginnings[index];\n        char* ending = description + beginnings[index + 1];\n\n        char ch = *ending;\n        *ending = '\\0';\n\n        text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 280, beginning, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[0]);\n\n        *ending = ch;\n\n        y += yStep;\n    }\n\n    if (frmId != old_fid2 || strcmp(old_str2, name) != 0) {\n        if (frstc_draw2) {\n            gsound_play_sfx_file(\"isdxxxx1\");\n        }\n    }\n\n    strcpy(old_str2, name);\n    old_fid2 = frmId;\n    frstc_draw2 = true;\n\n    art_ptr_unlock(handle);\n\n    return 0;\n}\n\n// 0x43DE94\nstatic void push_perks()\n{\n    int perk;\n\n    for (perk = 0; perk < PERK_COUNT; perk++) {\n        perk_back[perk] = perk_level(obj_dude, perk);\n    }\n}\n\n// copy editor perks to character\n//\n// 0x43DEBC\nstatic void pop_perks()\n{\n    for (int perk = 0; perk < PERK_COUNT; perk++) {\n        for (;;) {\n            int rank = perk_level(obj_dude, perk);\n            if (rank <= perk_back[perk]) {\n                break;\n            }\n\n            perk_sub(obj_dude, perk);\n        }\n    }\n\n    for (int i = 0; i < PERK_COUNT; i++) {\n        for (;;) {\n            int rank = perk_level(obj_dude, i);\n            if (rank >= perk_back[i]) {\n                break;\n            }\n\n            perk_add(obj_dude, i);\n        }\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x43DF24\nstatic int PerkCount()\n{\n    int perk;\n    int perkCount;\n\n    perkCount = 0;\n    for (perk = 0; perk < PERK_COUNT; perk++) {\n        if (perk_level(obj_dude, perk) > 0) {\n            perkCount++;\n            if (perkCount >= 37) {\n                break;\n            }\n        }\n    }\n\n    return perkCount;\n}\n\n// validate SPECIAL stats are <= 10\n//\n// 0x43DF50\nstatic int is_supper_bonus()\n{\n    for (int stat = 0; stat < 7; stat++) {\n        int v1 = stat_get_base(obj_dude, stat);\n        int v2 = stat_get_bonus(obj_dude, stat);\n        if (v1 + v2 > 10) {\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x43DF8C\nstatic int folder_init()\n{\n    folder_karma_top_line = 0;\n    folder_perk_top_line = 0;\n    folder_kills_top_line = 0;\n\n    if (folder_up_button == -1) {\n        folder_up_button = win_register_button(edit_win, 317, 364, GInfo[22].width, GInfo[22].height, -1, -1, -1, 17000, grphbmp[21], grphbmp[22], NULL, 32);\n        if (folder_up_button == -1) {\n            return -1;\n        }\n\n        win_register_button_sound_func(folder_up_button, gsound_red_butt_press, NULL);\n    }\n\n    if (folder_down_button == -1) {\n        folder_down_button = win_register_button(edit_win,\n            317,\n            365 + GInfo[22].height,\n            GInfo[4].width,\n            GInfo[4].height,\n            folder_down_button,\n            folder_down_button,\n            folder_down_button,\n            17001,\n            grphbmp[3],\n            grphbmp[4],\n            0,\n            32);\n        if (folder_down_button == -1) {\n            win_delete_button(folder_up_button);\n            return -1;\n        }\n\n        win_register_button_sound_func(folder_down_button, gsound_red_butt_press, NULL);\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x43E090\nstatic void folder_exit()\n{\n    if (folder_down_button != -1) {\n        win_delete_button(folder_down_button);\n        folder_down_button = -1;\n    }\n\n    if (folder_up_button != -1) {\n        win_delete_button(folder_up_button);\n        folder_up_button = -1;\n    }\n}\n\n// 0x43E0D4\nstatic void folder_scroll(int direction)\n{\n    int* v1;\n\n    switch (folder) {\n    case EDITOR_FOLDER_PERKS:\n        v1 = &folder_perk_top_line;\n        break;\n    case EDITOR_FOLDER_KARMA:\n        v1 = &folder_karma_top_line;\n        break;\n    case EDITOR_FOLDER_KILLS:\n        v1 = &folder_kills_top_line;\n        break;\n    default:\n        return;\n    }\n\n    if (direction >= 0) {\n        if (folder_max_lines + folder_top_line <= folder_line) {\n            folder_top_line++;\n            if (info_line >= 10 && info_line < 43 && info_line != 10) {\n                info_line--;\n            }\n        }\n    } else {\n        if (folder_top_line > 0) {\n            folder_top_line--;\n            if (info_line >= 10 && info_line < 43 && folder_max_lines + 9 > info_line) {\n                info_line++;\n            }\n        }\n    }\n\n    *v1 = folder_top_line;\n    DrawFolder();\n\n    if (info_line >= 10 && info_line < 43) {\n        buf_to_buf(\n            bckgnd + 640 * 267 + 345,\n            277,\n            170,\n            640,\n            win_buf + 640 * 267 + 345,\n            640);\n        DrawCard(folder_card_fid, folder_card_title, folder_card_title2, folder_card_desc);\n    }\n}\n\n// 0x43E200\nstatic void folder_clear()\n{\n    int v0;\n\n    folder_line = 0;\n    folder_ypos = 364;\n\n    v0 = text_height();\n\n    folder_max_lines = 9;\n    folder_yoffset = v0 + 1;\n\n    if (info_line < 10 || info_line >= 43)\n        folder_highlight_line = -1;\n    else\n        folder_highlight_line = info_line - 10;\n\n    if (folder < 1) {\n        if (folder)\n            return;\n\n        folder_top_line = folder_perk_top_line;\n    } else if (folder == 1) {\n        folder_top_line = folder_karma_top_line;\n    } else if (folder == 2) {\n        folder_top_line = folder_kills_top_line;\n    }\n}\n\n// render heading string with line\n//\n// 0x43E28C\nstatic int folder_print_seperator(const char* string)\n{\n    int lineHeight;\n    int x;\n    int y;\n    int lineLen;\n    int gap;\n    int v8 = 0;\n\n    if (folder_max_lines + folder_top_line > folder_line) {\n        if (folder_line >= folder_top_line) {\n            if (folder_line - folder_top_line == folder_highlight_line) {\n                v8 = 1;\n            }\n            lineHeight = text_height();\n            x = 280;\n            y = folder_ypos + lineHeight / 2;\n            if (string != NULL) {\n                gap = text_spacing();\n                // TODO: Not sure about this.\n                lineLen = text_width(string) + gap * 4;\n                x = (x - lineLen) / 2;\n                text_to_buf(win_buf + 640 * folder_ypos + 34 + x + gap * 2, string, 640, 640, colorTable[992]);\n                win_line(edit_win, 34 + x + lineLen, y, 34 + 280, y, colorTable[992]);\n            }\n            win_line(edit_win, 34, y, 34 + x, y, colorTable[992]);\n            folder_ypos += folder_yoffset;\n        }\n        folder_line++;\n        return v8;\n    } else {\n        return 0;\n    }\n}\n\n// 0x43E3D8\nstatic bool folder_print_line(const char* string)\n{\n    bool success = false;\n    int color;\n\n    if (folder_max_lines + folder_top_line > folder_line) {\n        if (folder_line >= folder_top_line) {\n            if (folder_line - folder_top_line == folder_highlight_line) {\n                success = true;\n                color = colorTable[32747];\n            } else {\n                color = colorTable[992];\n            }\n\n            text_to_buf(win_buf + 640 * folder_ypos + 34, string, 640, 640, color);\n            folder_ypos += folder_yoffset;\n        }\n\n        folder_line++;\n    }\n\n    return success;\n}\n\n// 0x43E470\nstatic bool folder_print_kill(const char* name, int kills)\n{\n    char killsString[8];\n    int color;\n    int gap;\n\n    bool success = false;\n    if (folder_max_lines + folder_top_line > folder_line) {\n        if (folder_line >= folder_top_line) {\n            if (folder_line - folder_top_line == folder_highlight_line) {\n                color = colorTable[32747];\n                success = true;\n            } else {\n                color = colorTable[992];\n            }\n\n            itoa(kills, killsString, 10);\n            int v6 = text_width(killsString);\n\n            // TODO: Check.\n            gap = text_spacing();\n            int v11 = folder_ypos + text_height() / 2;\n\n            text_to_buf(win_buf + 640 * folder_ypos + 34, name, 640, 640, color);\n\n            int v12 = text_width(name);\n            win_line(edit_win, 34 + v12 + gap, v11, 314 - v6 - gap, v11, color);\n\n            text_to_buf(win_buf + 640 * folder_ypos + 314 - v6, killsString, 640, 640, color);\n            folder_ypos += folder_yoffset;\n        }\n\n        folder_line++;\n    }\n\n    return success;\n}\n\n// 0x43E5C4\nstatic int karma_vars_init()\n{\n    const char* delim = \" \\t,\";\n\n    if (karma_vars != NULL) {\n        mem_free(karma_vars);\n        karma_vars = NULL;\n    }\n\n    karma_vars_count = 0;\n\n    File* stream = db_fopen(\"data\\\\karmavar.txt\", \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    char string[256];\n    while (db_fgets(string, 256, stream)) {\n        KarmaEntry entry;\n\n        char* pch = string;\n        while (isspace(*pch & 0xFF)) {\n            pch++;\n        }\n\n        if (*pch == '#') {\n            continue;\n        }\n\n        char* tok = strtok(pch, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.gvar = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.art_num = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.name = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.description = atoi(tok);\n\n        KarmaEntry* entries = (KarmaEntry*)mem_realloc(karma_vars, sizeof(*entries) * (karma_vars_count + 1));\n        if (entries == NULL) {\n            db_fclose(stream);\n\n            return -1;\n        }\n\n        memcpy(&(entries[karma_vars_count]), &entry, sizeof(entry));\n\n        karma_vars = entries;\n        karma_vars_count++;\n    }\n\n    qsort(karma_vars, karma_vars_count, sizeof(*karma_vars), karma_vars_qsort_compare);\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x43E764\nstatic void karma_vars_exit()\n{\n    if (karma_vars != NULL) {\n        mem_free(karma_vars);\n        karma_vars = NULL;\n    }\n\n    karma_vars_count = 0;\n}\n\n// 0x43E78C\nstatic int karma_vars_qsort_compare(const void* a1, const void* a2)\n{\n    KarmaEntry* v1 = (KarmaEntry*)a1;\n    KarmaEntry* v2 = (KarmaEntry*)a2;\n    return v1->gvar - v2->gvar;\n}\n\n// 0x43E798\nstatic int general_reps_init()\n{\n    const char* delim = \" \\t,\";\n\n    if (general_reps != NULL) {\n        mem_free(general_reps);\n        general_reps = NULL;\n    }\n\n    general_reps_count = 0;\n\n    File* stream = db_fopen(\"data\\\\genrep.txt\", \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    char string[256];\n    while (db_fgets(string, 256, stream)) {\n        GenericReputationEntry entry;\n\n        char* pch = string;\n        while (isspace(*pch & 0xFF)) {\n            pch++;\n        }\n\n        if (*pch == '#') {\n            continue;\n        }\n\n        char* tok = strtok(pch, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.threshold = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.name = atoi(tok);\n\n        GenericReputationEntry* entries = (GenericReputationEntry*)mem_realloc(general_reps, sizeof(*entries) * (general_reps_count + 1));\n        if (entries == NULL) {\n            db_fclose(stream);\n\n            return -1;\n        }\n\n        memcpy(&(entries[general_reps_count]), &entry, sizeof(entry));\n\n        general_reps = entries;\n        general_reps_count++;\n    }\n\n    qsort(general_reps, general_reps_count, sizeof(*general_reps), general_reps_qsort_compare);\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x43E914\nstatic void general_reps_exit()\n{\n    if (general_reps != NULL) {\n        mem_free(general_reps);\n        general_reps = NULL;\n    }\n\n    general_reps_count = 0;\n}\n\n// 0x43E93C\nstatic int general_reps_qsort_compare(const void* a1, const void* a2)\n{\n    GenericReputationEntry* v1 = (GenericReputationEntry*)a1;\n    GenericReputationEntry* v2 = (GenericReputationEntry*)a2;\n\n    if (v2->threshold > v1->threshold) {\n        return 1;\n    } else if (v2->threshold < v1->threshold) {\n        return -1;\n    }\n    return 0;\n}\n"
  },
  {
    "path": "src/game/editor.h",
    "content": "#ifndef FALLOUT_GAME_EDITOR_H_\n#define FALLOUT_GAME_EDITOR_H_\n\n#include \"plib/db/db.h\"\n\n#define TOWN_REPUTATION_COUNT 19\n#define ADDICTION_REPUTATION_COUNT 8\n\ntypedef struct TownReputationEntry {\n    int gvar;\n    int city;\n} TownReputationEntry;\n\nextern int character_points;\nextern TownReputationEntry town_rep_info[TOWN_REPUTATION_COUNT];\nextern int addiction_vars[ADDICTION_REPUTATION_COUNT];\nextern int addiction_pics[ADDICTION_REPUTATION_COUNT];\n\nint editor_design(bool isCreationMode);\nvoid CharEditInit();\nint get_input_str(int win, int cancelKeyCode, char* text, int maxLength, int x, int y, int textColor, int backgroundColor, int flags);\nbool isdoschar(int ch);\nchar* strmfe(char* dest, const char* name, const char* ext);\nbool db_access(const char* fname);\nchar* AddSpaces(char* string, int length);\nchar* itostndn(int value, char* dest);\nint editor_save(File* stream);\nint editor_load(File* stream);\nvoid editor_reset();\nvoid RedrwDMPrk();\n\n#endif /* FALLOUT_GAME_EDITOR_H_ */\n"
  },
  {
    "path": "src/game/elevator.c",
    "content": "#include \"game/elevator.h\"\n\n#include <ctype.h>\n#include <string.h>\n\n#include \"plib/gnw/input.h\"\n#include \"game/art.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/map.h\"\n#include \"game/pipboy.h\"\n#include \"game/scripts.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n\n// The maximum number of elevator levels.\n#define ELEVATOR_LEVEL_MAX 4\n\n// NOTE: There are two variables which hold background data used in the\n// elevator window - [grphbmp[ELEVATOR_FRM_BACKGROUND]] and [grphbmp[ELEVATOR_FRM_PANEL]].\n// For unknown reason they are using -1 to denote that they are not set\n// (instead of using NULL).\n#define ELEVATOR_BACKGROUND_NULL ((unsigned char*)(-1))\n\n// NOTE: This enum is a little bit unsual. It contains two types of members:\n// static and dynamic. Static part of enum is always accessed with array\n// operations as commonly seen in other UI setup/teardown loops. Background and\n// panel are always accessed separately without using loops, but they are a part\n// of globals holding state (buffers, cache keys, dimensions).\ntypedef enum ElevatorFrm {\n    ELEVATOR_FRM_BUTTON_DOWN,\n    ELEVATOR_FRM_BUTTON_UP,\n    ELEVATOR_FRM_GAUGE,\n    ELEVATOR_FRM_BACKGROUND,\n    ELEVATOR_FRM_PANEL,\n    ELEVATOR_FRM_COUNT,\n    ELEVATOR_FRM_STATIC_COUNT = 3,\n} ElevatorFrm;\n\ntypedef struct ElevatorBackground {\n    int backgroundFrmId;\n    int panelFrmId;\n} ElevatorBackground;\n\ntypedef struct ElevatorDescription {\n    int map;\n    int elevation;\n    int tile;\n} ElevatorDescription;\n\nstatic int elevator_start(int elevator);\nstatic void elevator_end();\nstatic int Check4Keys(int elevator, int keyCode);\n\n// 0x43E950\nstatic const int grph_id[ELEVATOR_FRM_STATIC_COUNT] = {\n    141, // ebut_in.frm - map elevator screen\n    142, // ebut_out.frm - map elevator screen\n    149, // gaj000.frm - map elevator screen\n};\n\n// 0x43E95C\nstatic const ElevatorBackground intotal[ELEVATOR_COUNT] = {\n    { 143, -1 },\n    { 143, 150 },\n    { 144, -1 },\n    { 144, 145 },\n    { 146, -1 },\n    { 146, 147 },\n    { 146, -1 },\n    { 146, 151 },\n    { 148, -1 },\n    { 146, -1 },\n    { 146, -1 },\n    { 146, 147 },\n    { 388, -1 },\n    { 143, 150 },\n    { 148, -1 },\n    { 148, -1 },\n    { 148, -1 },\n    { 143, 150 },\n    { 143, 150 },\n    { 143, 150 },\n    { 143, 150 },\n    { 143, 150 },\n    { 143, 150 },\n    { 143, 150 },\n};\n\n// Number of levels for eleveators.\n//\n// 0x43EA1C\nstatic const int btncnt[ELEVATOR_COUNT] = {\n    4,\n    2,\n    3,\n    2,\n    3,\n    2,\n    3,\n    3,\n    3,\n    3,\n    3,\n    2,\n    4,\n    2,\n    3,\n    3,\n    3,\n    2,\n    2,\n    2,\n    2,\n    2,\n    2,\n    2,\n};\n\n// 0x43EA7C\nstatic const ElevatorDescription retvals[ELEVATOR_COUNT][ELEVATOR_LEVEL_MAX] = {\n    {\n        { 14, 0, 18940 },\n        { 14, 1, 18936 },\n        { 15, 0, 21340 },\n        { 15, 1, 21340 },\n    },\n    {\n        { 13, 0, 20502 },\n        { 14, 0, 14912 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 33, 0, 12498 },\n        { 33, 1, 20094 },\n        { 34, 0, 17312 },\n        { 0, 0, -1 },\n    },\n    {\n        { 34, 0, 16140 },\n        { 34, 1, 16140 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 49, 0, 14920 },\n        { 49, 1, 15120 },\n        { 50, 0, 12944 },\n        { 0, 0, -1 },\n    },\n    {\n        { 50, 0, 24520 },\n        { 50, 1, 25316 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 42, 0, 22526 },\n        { 42, 1, 22526 },\n        { 42, 2, 22526 },\n        { 0, 0, -1 },\n    },\n    {\n        { 42, 2, 14086 },\n        { 43, 0, 14086 },\n        { 43, 2, 14086 },\n        { 0, 0, -1 },\n    },\n    {\n        { 40, 0, 14104 },\n        { 40, 1, 22504 },\n        { 40, 2, 17312 },\n        { 0, 0, -1 },\n    },\n    {\n        { 9, 0, 13704 },\n        { 9, 1, 23302 },\n        { 9, 2, 17308 },\n        { 0, 0, -1 },\n    },\n    {\n        { 28, 0, 19300 },\n        { 28, 1, 19300 },\n        { 28, 2, 20110 },\n        { 0, 0, -1 },\n    },\n    {\n        { 28, 2, 20118 },\n        { 29, 0, 21710 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 28, 0, 20122 },\n        { 28, 1, 20124 },\n        { 28, 2, 20940 },\n        { 29, 0, 22540 },\n    },\n    {\n        { 12, 1, 16052 },\n        { 12, 2, 14480 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 6, 0, 14104 },\n        { 6, 1, 22504 },\n        { 6, 2, 17312 },\n        { 0, 0, -1 },\n    },\n    {\n        { 30, 0, 14104 },\n        { 30, 1, 22504 },\n        { 30, 2, 17312 },\n        { 0, 0, -1 },\n    },\n    {\n        { 36, 0, 13704 },\n        { 36, 1, 23302 },\n        { 36, 2, 17308 },\n        { 0, 0, -1 },\n    },\n    {\n        { 39, 0, 17285 },\n        { 36, 0, 19472 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 109, 2, 10701 },\n        { 109, 1, 10705 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 109, 2, 14697 },\n        { 109, 1, 15099 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 109, 2, 23877 },\n        { 109, 1, 25481 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 109, 2, 26130 },\n        { 109, 1, 24721 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 137, 0, 23953 },\n        { 148, 1, 16526 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n    {\n        { 62, 0, 13901 },\n        { 63, 1, 17923 },\n        { 0, 0, -1 },\n        { 0, 0, -1 },\n    },\n};\n\n// NOTE: These values are also used as key bindings.\n//\n// 0x43EEFC\nstatic const char keytable[ELEVATOR_COUNT][ELEVATOR_LEVEL_MAX] = {\n    { '1', '2', '3', '4' },\n    { 'G', '1', '\\0', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '3', '4', '\\0', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '3', '4', '\\0', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '3', '4', '6', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '3', '4', '\\0', '\\0' },\n    { '1', '2', '3', '4' },\n    { '1', '2', '\\0', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '1', '2', '3', '\\0' },\n    { '1', '2', '\\0', '\\0' },\n    { '1', '2', '\\0', '\\0' },\n    { '1', '2', '\\0', '\\0' },\n    { '1', '2', '\\0', '\\0' },\n    { '1', '2', '\\0', '\\0' },\n    { '1', '2', '\\0', '\\0' },\n    { '1', '2', '\\0', '\\0' },\n};\n\n// 0x51862C\nstatic const char* sfxtable[ELEVATOR_LEVEL_MAX - 1][ELEVATOR_LEVEL_MAX] = {\n    {\n        \"ELV1_1\",\n        \"ELV1_1\",\n        \"ERROR\",\n        \"ERROR\",\n    },\n    {\n        \"ELV1_2\",\n        \"ELV1_2\",\n        \"ELV1_1\",\n        \"ERROR\",\n    },\n    {\n        \"ELV1_3\",\n        \"ELV1_3\",\n        \"ELV2_3\",\n        \"ELV1_1\",\n    },\n};\n\n// 0x570A2C\nstatic Size GInfo[ELEVATOR_FRM_COUNT];\n\n// 0x570A54\nstatic int elev_win;\n\n// 0x570A58\nstatic CacheEntry* grph_key[ELEVATOR_FRM_COUNT];\n\n// 0x570A6C\nstatic unsigned char* win_buf;\n\n// 0x570A70\nstatic bool bk_enable;\n\n// 0x570A74\nstatic unsigned char* grphbmp[ELEVATOR_FRM_COUNT];\n\n// Presents elevator dialog for player to pick a desired level.\n//\n// 0x43EF5C\nint elevator_select(int elevator, int* mapPtr, int* elevationPtr, int* tilePtr)\n{\n    if (elevator < 0 || elevator >= ELEVATOR_COUNT) {\n        return -1;\n    }\n\n    if (elevator_start(elevator) == -1) {\n        return -1;\n    }\n\n    const ElevatorDescription* elevatorDescription = retvals[elevator];\n\n    int index;\n    for (index = 0; index < ELEVATOR_LEVEL_MAX; index++) {\n        if (elevatorDescription[index].map == *mapPtr) {\n            break;\n        }\n    }\n\n    if (index < ELEVATOR_LEVEL_MAX) {\n        if (elevatorDescription[*elevationPtr + index].tile != -1) {\n            *elevationPtr += index;\n        }\n    }\n\n    if (elevator == ELEVATOR_SIERRA_2) {\n        if (*elevationPtr <= 2) {\n            *elevationPtr -= 2;\n        } else {\n            *elevationPtr -= 3;\n        }\n    } else if (elevator == ELEVATOR_MILITARY_BASE_LOWER) {\n        if (*elevationPtr >= 2) {\n            *elevationPtr -= 2;\n        }\n    } else if (elevator == ELEVATOR_MILITARY_BASE_UPPER && *elevationPtr == 4) {\n        *elevationPtr -= 2;\n    }\n\n    if (*elevationPtr > 3) {\n        *elevationPtr -= 3;\n    }\n\n    debug_printf(\"\\n the start elev level %d\\n\", *elevationPtr);\n\n    int v18 = (GInfo[ELEVATOR_FRM_GAUGE].width * GInfo[ELEVATOR_FRM_GAUGE].height) / 13;\n    float v42 = 12.0f / (float)(btncnt[elevator] - 1);\n    buf_to_buf(\n        grphbmp[ELEVATOR_FRM_GAUGE] + v18 * (int)((float)(*elevationPtr) * v42),\n        GInfo[ELEVATOR_FRM_GAUGE].width,\n        GInfo[ELEVATOR_FRM_GAUGE].height / 13,\n        GInfo[ELEVATOR_FRM_GAUGE].width,\n        win_buf + GInfo[ELEVATOR_FRM_BACKGROUND].width * 41 + 121,\n        GInfo[ELEVATOR_FRM_BACKGROUND].width);\n    win_draw(elev_win);\n\n    bool done = false;\n    int keyCode;\n    while (!done) {\n        keyCode = get_input();\n        if (keyCode == KEY_ESCAPE) {\n            done = true;\n        }\n\n        if (keyCode >= 500 && keyCode < 504) {\n            done = true;\n        }\n\n        if (keyCode > 0 && keyCode < 500) {\n            int level = Check4Keys(elevator, keyCode);\n            if (level != 0) {\n                keyCode = 500 + level - 1;\n                done = true;\n            }\n        }\n    }\n\n    if (keyCode != KEY_ESCAPE) {\n        keyCode -= 500;\n\n        if (*elevationPtr != keyCode) {\n            float v43 = (float)(btncnt[elevator] - 1) / 12.0f;\n\n            unsigned int delay = (unsigned int)(v43 * 276.92307);\n\n            if (keyCode < *elevationPtr) {\n                v43 = -v43;\n            }\n\n            int numberOfLevelsTravelled = keyCode - *elevationPtr;\n            if (numberOfLevelsTravelled < 0) {\n                numberOfLevelsTravelled = -numberOfLevelsTravelled;\n            }\n\n            gsound_play_sfx_file(sfxtable[btncnt[elevator] - 2][numberOfLevelsTravelled]);\n\n            float v41 = (float)keyCode * v42;\n            float v44 = (float)(*elevationPtr) * v42;\n            do {\n                unsigned int tick = get_time();\n                v44 += v43;\n                buf_to_buf(\n                    grphbmp[ELEVATOR_FRM_GAUGE] + v18 * (int)v44,\n                    GInfo[ELEVATOR_FRM_GAUGE].width,\n                    GInfo[ELEVATOR_FRM_GAUGE].height / 13,\n                    GInfo[ELEVATOR_FRM_GAUGE].width,\n                    win_buf + GInfo[ELEVATOR_FRM_BACKGROUND].width * 41 + 121,\n                    GInfo[ELEVATOR_FRM_BACKGROUND].width);\n\n                win_draw(elev_win);\n\n                while (elapsed_time(tick) < delay) {\n                }\n            } while ((v43 <= 0.0 || v44 < v41) && (v43 > 0.0 || v44 > v41));\n\n            pause_for_tocks(200);\n        }\n    }\n\n    elevator_end();\n\n    if (keyCode != KEY_ESCAPE) {\n        const ElevatorDescription* description = &(elevatorDescription[keyCode]);\n        *mapPtr = description->map;\n        *elevationPtr = description->elevation;\n        *tilePtr = description->tile;\n    }\n\n    return 0;\n}\n\n// 0x43F324\nstatic int elevator_start(int elevator)\n{\n    bk_enable = map_disable_bk_processes();\n    cycle_disable();\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    gmouse_3d_off();\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    scr_disable();\n\n    int index;\n    for (index = 0; index < ELEVATOR_FRM_STATIC_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, grph_id[index], 0, 0, 0);\n        grphbmp[index] = art_lock(fid, &(grph_key[index]), &(GInfo[index].width), &(GInfo[index].height));\n        if (grphbmp[index] == NULL) {\n            break;\n        }\n    }\n\n    if (index != ELEVATOR_FRM_STATIC_COUNT) {\n        for (int reversedIndex = index - 1; reversedIndex >= 0; reversedIndex--) {\n            art_ptr_unlock(grph_key[reversedIndex]);\n        }\n\n        if (bk_enable) {\n            map_enable_bk_processes();\n        }\n\n        cycle_enable();\n        gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n        return -1;\n    }\n\n    grphbmp[ELEVATOR_FRM_PANEL] = ELEVATOR_BACKGROUND_NULL;\n    grphbmp[ELEVATOR_FRM_BACKGROUND] = ELEVATOR_BACKGROUND_NULL;\n\n    const ElevatorBackground* elevatorBackground = &(intotal[elevator]);\n    bool backgroundsLoaded = true;\n\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, elevatorBackground->backgroundFrmId, 0, 0, 0);\n    grphbmp[ELEVATOR_FRM_BACKGROUND] = art_lock(backgroundFid, &grph_key[ELEVATOR_FRM_BACKGROUND], &(GInfo[ELEVATOR_FRM_BACKGROUND].width), &(GInfo[ELEVATOR_FRM_BACKGROUND].height));\n    if (grphbmp[ELEVATOR_FRM_BACKGROUND] != NULL) {\n        if (elevatorBackground->panelFrmId != -1) {\n            int panelFid = art_id(OBJ_TYPE_INTERFACE, elevatorBackground->panelFrmId, 0, 0, 0);\n            grphbmp[ELEVATOR_FRM_PANEL] = art_lock(panelFid, &grph_key[ELEVATOR_FRM_PANEL], &(GInfo[ELEVATOR_FRM_PANEL].width), &(GInfo[ELEVATOR_FRM_PANEL].height));\n            if (grphbmp[ELEVATOR_FRM_PANEL] == NULL) {\n                grphbmp[ELEVATOR_FRM_PANEL] = ELEVATOR_BACKGROUND_NULL;\n                backgroundsLoaded = false;\n            }\n        }\n    } else {\n        grphbmp[ELEVATOR_FRM_BACKGROUND] = ELEVATOR_BACKGROUND_NULL;\n        backgroundsLoaded = false;\n    }\n\n    if (!backgroundsLoaded) {\n        if (grphbmp[ELEVATOR_FRM_BACKGROUND] != ELEVATOR_BACKGROUND_NULL) {\n            art_ptr_unlock(grph_key[ELEVATOR_FRM_BACKGROUND]);\n        }\n\n        if (grphbmp[ELEVATOR_FRM_PANEL] != ELEVATOR_BACKGROUND_NULL) {\n            art_ptr_unlock(grph_key[ELEVATOR_FRM_PANEL]);\n        }\n\n        for (int index = 0; index < ELEVATOR_FRM_STATIC_COUNT; index++) {\n            art_ptr_unlock(grph_key[index]);\n        }\n\n        if (bk_enable) {\n            map_enable_bk_processes();\n        }\n\n        cycle_enable();\n        gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n        return -1;\n    }\n\n    int elevatorWindowX = (640 - GInfo[ELEVATOR_FRM_BACKGROUND].width) / 2;\n    int elevatorWindowY = (480 - INTERFACE_BAR_HEIGHT - 1 - GInfo[ELEVATOR_FRM_BACKGROUND].height) / 2;\n    elev_win = win_add(\n        elevatorWindowX,\n        elevatorWindowY,\n        GInfo[ELEVATOR_FRM_BACKGROUND].width,\n        GInfo[ELEVATOR_FRM_BACKGROUND].height,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n    if (elev_win == -1) {\n        if (grphbmp[ELEVATOR_FRM_BACKGROUND] != ELEVATOR_BACKGROUND_NULL) {\n            art_ptr_unlock(grph_key[ELEVATOR_FRM_BACKGROUND]);\n        }\n\n        if (grphbmp[ELEVATOR_FRM_PANEL] != ELEVATOR_BACKGROUND_NULL) {\n            art_ptr_unlock(grph_key[ELEVATOR_FRM_PANEL]);\n        }\n\n        for (int index = 0; index < ELEVATOR_FRM_STATIC_COUNT; index++) {\n            art_ptr_unlock(grph_key[index]);\n        }\n\n        if (bk_enable) {\n            map_enable_bk_processes();\n        }\n\n        cycle_enable();\n        gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n        return -1;\n    }\n\n    win_buf = win_get_buf(elev_win);\n    memcpy(win_buf, (unsigned char*)grphbmp[ELEVATOR_FRM_BACKGROUND], GInfo[ELEVATOR_FRM_BACKGROUND].width * GInfo[ELEVATOR_FRM_BACKGROUND].height);\n\n    if (grphbmp[ELEVATOR_FRM_PANEL] != ELEVATOR_BACKGROUND_NULL) {\n        buf_to_buf((unsigned char*)grphbmp[ELEVATOR_FRM_PANEL],\n            GInfo[ELEVATOR_FRM_PANEL].width,\n            GInfo[ELEVATOR_FRM_PANEL].height,\n            GInfo[ELEVATOR_FRM_PANEL].width,\n            win_buf + GInfo[ELEVATOR_FRM_BACKGROUND].width * (GInfo[ELEVATOR_FRM_BACKGROUND].height - GInfo[ELEVATOR_FRM_PANEL].height),\n            GInfo[ELEVATOR_FRM_BACKGROUND].width);\n    }\n\n    int y = 40;\n    for (int level = 0; level < btncnt[elevator]; level++) {\n        int btn = win_register_button(elev_win,\n            13,\n            y,\n            GInfo[ELEVATOR_FRM_BUTTON_DOWN].width,\n            GInfo[ELEVATOR_FRM_BUTTON_DOWN].height,\n            -1,\n            -1,\n            -1,\n            500 + level,\n            grphbmp[ELEVATOR_FRM_BUTTON_UP],\n            grphbmp[ELEVATOR_FRM_BUTTON_DOWN],\n            NULL,\n            BUTTON_FLAG_TRANSPARENT);\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_red_butt_press, NULL);\n        }\n        y += 60;\n    }\n\n    return 0;\n}\n\n// 0x43F6D0\nstatic void elevator_end()\n{\n    win_delete(elev_win);\n\n    if (grphbmp[ELEVATOR_FRM_BACKGROUND] != ELEVATOR_BACKGROUND_NULL) {\n        art_ptr_unlock(grph_key[ELEVATOR_FRM_BACKGROUND]);\n    }\n\n    if (grphbmp[ELEVATOR_FRM_PANEL] != ELEVATOR_BACKGROUND_NULL) {\n        art_ptr_unlock(grph_key[ELEVATOR_FRM_PANEL]);\n    }\n\n    for (int index = 0; index < ELEVATOR_FRM_STATIC_COUNT; index++) {\n        art_ptr_unlock(grph_key[index]);\n    }\n\n    scr_enable();\n\n    if (bk_enable) {\n        map_enable_bk_processes();\n    }\n\n    cycle_enable();\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n}\n\n// 0x43F73C\nstatic int Check4Keys(int elevator, int keyCode)\n{\n    // TODO: Check if result is really unused?\n    toupper(keyCode);\n\n    for (int index = 0; index < ELEVATOR_LEVEL_MAX; index++) {\n        char c = keytable[elevator][index];\n        if (c == '\\0') {\n            break;\n        }\n\n        if (c == (char)(keyCode & 0xFF)) {\n            return index + 1;\n        }\n    }\n    return 0;\n}\n"
  },
  {
    "path": "src/game/elevator.h",
    "content": "#ifndef FALLOUT_GAME_ELEVATOR_H_\n#define FALLOUT_GAME_ELEVATOR_H_\n\ntypedef enum Elevator {\n    ELEVATOR_BROTHERHOOD_OF_STEEL_MAIN,\n    ELEVATOR_BROTHERHOOD_OF_STEEL_SURFACE,\n    ELEVATOR_MASTER_UPPER,\n    ELEVATOR_MASTER_LOWER,\n    ELEVATOR_MILITARY_BASE_UPPER,\n    ELEVATOR_MILITARY_BASE_LOWER,\n    ELEVATOR_GLOW_UPPER,\n    ELEVATOR_GLOW_LOWER,\n    ELEVATOR_VAULT_13,\n    ELEVATOR_NECROPOLIS,\n    ELEVATOR_SIERRA_1,\n    ELEVATOR_SIERRA_2,\n    ELEVATOR_SIERRA_SERVICE,\n    ELEVATOR_KLAMATH_TOXIC_CAVES,\n    ELEVATOR_14,\n    ELEVATOR_VAULT_CITY,\n    ELEVATOR_VAULT_15_MAIN,\n    ELEVATOR_VAULT_15_SURFACE,\n    ELEVATOR_NAVARRO_NORTHERN,\n    ELEVATOR_NAVARRO_CENTER,\n    ELEVATOR_NAVARRO_LAB,\n    ELEVATOR_NAVARRO_CANTEEN,\n    ELEVATOR_SAN_FRANCISCO_SHI_TEMPLE,\n    ELEVATOR_REDDING_WANAMINGO_MINE,\n    ELEVATOR_COUNT,\n} Elevator;\n\nint elevator_select(int elevator, int* mapPtr, int* elevationPtr, int* tilePtr);\n\n#endif /* FALLOUT_GAME_ELEVATOR_H_ */\n"
  },
  {
    "path": "src/game/endgame.c",
    "content": "#include \"game/endgame.h\"\n\n#include <ctype.h>\n#include <limits.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/credits.h\"\n#include \"game/cycle.h\"\n#include \"plib/db/db.h\"\n#include \"game/bmpdlog.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gmovie.h\"\n#include \"game/gsound.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/palette.h\"\n#include \"game/pipboy.h\"\n#include \"game/roll.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"game/wordwrap.h\"\n#include \"game/worldmap.h\"\n\n// The maximum number of subtitle lines per slide.\n#define ENDGAME_ENDING_MAX_SUBTITLES 50\n\n#define ENDGAME_ENDING_WINDOW_WIDTH 640\n#define ENDGAME_ENDING_WINDOW_HEIGHT 480\n\ntypedef struct EndgameDeathEnding {\n    int gvar;\n    int value;\n    int worldAreaKnown;\n    int worldAreaNotKnown;\n    int min_level;\n    int percentage;\n    char voiceOverBaseName[16];\n\n    // This flag denotes that the conditions for this ending is met and it was\n    // selected as a candidate for final random selection.\n    bool enabled;\n} EndgameDeathEnding;\n\ntypedef struct EndgameEnding {\n    int gvar;\n    int value;\n    int art_num;\n    char voiceOverBaseName[12];\n    int direction;\n} EndgameEnding;\n\nstatic void endgame_pan_desert(int direction, const char* narratorFileName);\nstatic void endgame_display_image(int fid, const char* narratorFileName);\nstatic int endgame_init();\nstatic void endgame_exit();\nstatic void endgame_load_voiceover(const char* fname);\nstatic void endgame_play_voiceover();\nstatic void endgame_stop_voiceover();\nstatic void endgame_load_palette(int type, int id);\nstatic void endgame_voiceover_callback();\nstatic int endgame_load_subtitles(const char* filePath);\nstatic void endgame_show_subtitles();\nstatic void endgame_clear_subtitles();\nstatic void endgame_movie_callback();\nstatic void endgame_movie_bk_process();\nstatic int endgame_load_slide_info();\nstatic void endgame_unload_slide_info();\nstatic int endgameSetupInit(int* percentage);\n\n// TODO: Remove.\n// 0x50B00C\nchar _aEnglish_2[] = ENGLISH;\n\n// The number of lines in current subtitles file.\n//\n// It's used as a length for two arrays:\n// - [endgame_subtitle_text]\n// - [endgame_subtitle_times]\n//\n// This value does not exceed [ENDGAME_ENDING_SUBTITLES_CAPACITY].\n//\n// 0x518668\nstatic int endgame_subtitle_count = 0;\n\n// The number of characters in current subtitles file.\n//\n// This value is used to determine\n//\n// 0x51866C\nstatic int endgame_subtitle_characters = 0;\n\n// 0x518670\nstatic int endgame_current_subtitle = 0;\n\n// 0x518674\nstatic int endgame_maybe_done = 0;\n\n// enddeath.txt\n//\n// 0x518678\nstatic EndgameDeathEnding* endDeathInfoList = NULL;\n\n// The number of death endings in [endDeathInfoList] array.\n//\n// 0x51867C\nstatic int maxEndDeathInfo = 0;\n\n// Base file name for death ending.\n//\n// This value does not include extension.\n//\n// 0x570A90\nstatic char endDeathSndChoice[40];\n\n// This flag denotes whether speech sound was successfully loaded for\n// the current slide.\n//\n// 0x570AB8\nstatic bool endgame_voiceover_loaded;\n\n// 0x570ABC\nstatic char endgame_subtitle_path[MAX_PATH];\n\n// The flag used to denote voice over speech for current slide has ended.\n//\n// 0x570BC0\nstatic bool endgame_voiceover_done;\n\n// endgame.txt\n//\n// 0x570BC4\nstatic EndgameEnding* slides;\n\n// The array of text lines in current subtitles file.\n//\n// The length is specified in [endgame_subtitle_count]. It's capacity\n// is [ENDGAME_ENDING_SUBTITLES_CAPACITY].\n//\n// 0x570BC8\nstatic char** endgame_subtitle_text;\n\n// 0x570BCC\nstatic bool endgame_do_subtitles;\n\n// The flag used to denote voice over subtitles for current slide has ended.\n//\n// 0x570BD0\nstatic bool endgame_subtitle_done;\n\n// 0x570BD4\nstatic bool endgame_map_enabled;\n\n// 0x570BD8\nstatic bool endgame_mouse_state;\n\n// The number of endings in [slides] array.\n//\n// 0x570BDC\nstatic int num_slides = 0;\n\n// This flag denotes whether subtitles was successfully loaded for\n// the current slide.\n//\n// 0x570BE0\nstatic bool endgame_subtitle_loaded;\n\n// Reference time is a timestamp when subtitle is first displayed.\n//\n// This value is used together with [endgame_subtitle_times] array to\n// determine when next line needs to be displayed.\n//\n// 0x570BE4\nstatic unsigned int endgame_subtitle_start_time;\n\n// The array of timings for each line in current subtitles file.\n//\n// The length is specified in [endgame_subtitle_count]. It's capacity\n// is [ENDGAME_ENDING_SUBTITLES_CAPACITY].\n//\n// 0x570BE8\nstatic unsigned int* endgame_subtitle_times;\n\n// Font that was current before endgame slideshow window was created.\n//\n// 0x570BEC\nstatic int endgame_old_font;\n\n// 0x570BF0\nstatic unsigned char* endgame_window_buffer;\n\n// 0x570BF4\nstatic int endgame_window;\n\n// 0x43F788\nvoid endgame_slideshow()\n{\n    if (endgame_init() == -1) {\n        return;\n    }\n\n    for (int index = 0; index < num_slides; index++) {\n        EndgameEnding* ending = &(slides[index]);\n        int value = game_get_global_var(ending->gvar);\n        if (value == ending->value) {\n            if (ending->art_num == 327) {\n                endgame_pan_desert(ending->direction, ending->voiceOverBaseName);\n            } else {\n                int fid = art_id(OBJ_TYPE_INTERFACE, ending->art_num, 0, 0, 0);\n                endgame_display_image(fid, ending->voiceOverBaseName);\n            }\n        }\n    }\n\n    endgame_exit();\n}\n\n// 0x43F810\nvoid endgame_movie()\n{\n    gsound_background_stop();\n    map_disable_bk_processes();\n    palette_fade_to(black_palette);\n    endgame_maybe_done = 0;\n    add_bk_process(endgame_movie_bk_process);\n    gsound_background_callback_set(endgame_movie_callback);\n    gsound_background_play(\"akiss\", 12, 14, 15);\n    pause_for_tocks(3000);\n\n    // NOTE: Result is ignored. I guess there was some kind of switch for male\n    // vs. female ending, but it was not implemented.\n    critterGetStat(obj_dude, STAT_GENDER);\n\n    credits(\"credits.txt\", -1, false);\n    gsound_background_stop();\n    gsound_background_callback_set(NULL);\n    remove_bk_process(endgame_movie_bk_process);\n    gsound_background_stop();\n    loadColorTable(\"color.pal\");\n    palette_fade_to(cmap);\n    map_enable_bk_processes();\n    endgameEndingHandleContinuePlaying();\n}\n\n// 0x43F8C4\nint endgameEndingHandleContinuePlaying()\n{\n    bool isoWasEnabled = map_disable_bk_processes();\n\n    bool gameMouseWasVisible;\n    if (isoWasEnabled) {\n        gameMouseWasVisible = gmouse_3d_is_on();\n    } else {\n        gameMouseWasVisible = false;\n    }\n\n    if (gameMouseWasVisible) {\n        gmouse_3d_off();\n    }\n\n    bool oldCursorIsHidden = mouse_hidden();\n    if (oldCursorIsHidden) {\n        mouse_show();\n    }\n\n    int oldCursor = gmouse_get_cursor();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    int rc;\n\n    MessageListItem messageListItem;\n    messageListItem.num = 30;\n    if (message_search(&misc_message_file, &messageListItem)) {\n        rc = dialog_out(messageListItem.text, NULL, 0, 169, 117, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_YES_NO);\n        if (rc == 0) {\n            game_user_wants_to_quit = 2;\n        }\n    } else {\n        rc = -1;\n    }\n\n    gmouse_set_cursor(oldCursor);\n    if (oldCursorIsHidden) {\n        mouse_hide();\n    }\n\n    if (gameMouseWasVisible) {\n        gmouse_3d_on();\n    }\n\n    if (isoWasEnabled) {\n        map_enable_bk_processes();\n    }\n\n    return rc;\n}\n\n// 0x43FBDC\nstatic void endgame_pan_desert(int direction, const char* narratorFileName)\n{\n    int fid = art_id(OBJ_TYPE_INTERFACE, 327, 0, 0, 0);\n\n    CacheEntry* backgroundHandle;\n    Art* background = art_ptr_lock(fid, &backgroundHandle);\n    if (background != NULL) {\n        int width = art_frame_width(background, 0, 0);\n        int height = art_frame_length(background, 0, 0);\n        unsigned char* backgroundData = art_frame_data(background, 0, 0);\n        buf_fill(endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, colorTable[0]);\n        endgame_load_palette(6, 327);\n\n        unsigned char palette[768];\n        memcpy(palette, cmap, 768);\n\n        palette_set_to(black_palette);\n        endgame_load_voiceover(narratorFileName);\n\n        // TODO: Unclear math.\n        int v8 = width - 640;\n        int v32 = v8 / 4;\n        unsigned int v9 = 16 * v8 / v8;\n        unsigned int v9_ = 16 * v8;\n\n        if (endgame_voiceover_loaded) {\n            unsigned int v10 = 1000 * gsound_speech_length_get();\n            if (v10 > v9_ / 2) {\n                v9 = (v10 + v9 * (v8 / 2)) / v8;\n            }\n        }\n\n        int start;\n        int end;\n        if (direction == -1) {\n            start = width - 640;\n            end = 0;\n        } else {\n            start = 0;\n            end = width - 640;\n        }\n\n        disable_bk();\n\n        bool subtitlesLoaded = false;\n\n        unsigned int since = 0;\n        while (start != end) {\n            int v12 = 640 - v32;\n\n            // TODO: Complex math, setup scene in debugger.\n            if (elapsed_time(since) >= v9) {\n                buf_to_buf(backgroundData + start, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, width, endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH);\n\n                if (subtitlesLoaded) {\n                    endgame_show_subtitles();\n                }\n\n                win_draw(endgame_window);\n\n                since = get_time();\n\n                bool v14;\n                double v31;\n                if (start > v32) {\n                    if (v12 > start) {\n                        v14 = false;\n                    } else {\n                        int v28 = v32 - (start - v12);\n                        v31 = (double)v28 / (double)v32;\n                        v14 = true;\n                    }\n                } else {\n                    v14 = true;\n                    v31 = (double)start / (double)v32;\n                }\n\n                if (v14) {\n                    unsigned char darkenedPalette[768];\n                    for (int index = 0; index < 768; index++) {\n                        darkenedPalette[index] = (unsigned char)trunc(palette[index] * v31);\n                    }\n                    palette_set_to(darkenedPalette);\n                }\n\n                start += direction;\n\n                if (direction == 1 && (start == v32)) {\n                    // NOTE: Uninline.\n                    endgame_play_voiceover();\n                    subtitlesLoaded = true;\n                } else if (direction == -1 && (start == v12)) {\n                    // NOTE: Uninline.\n                    endgame_play_voiceover();\n                    subtitlesLoaded = true;\n                }\n            }\n\n            soundContinueAll();\n\n            if (get_input() != -1) {\n                // NOTE: Uninline.\n                endgame_stop_voiceover();\n                break;\n            }\n        }\n\n        enable_bk();\n        art_ptr_unlock(backgroundHandle);\n\n        palette_fade_to(black_palette);\n        buf_fill(endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, colorTable[0]);\n        win_draw(endgame_window);\n    }\n\n    while (mouse_get_buttons() != 0) {\n        get_input();\n    }\n}\n\n// 0x440004\nstatic void endgame_display_image(int fid, const char* narratorFileName)\n{\n    CacheEntry* backgroundHandle;\n    Art* background = art_ptr_lock(fid, &backgroundHandle);\n    if (background == NULL) {\n        return;\n    }\n\n    unsigned char* backgroundData = art_frame_data(background, 0, 0);\n    if (backgroundData != NULL) {\n        buf_to_buf(backgroundData, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH);\n        win_draw(endgame_window);\n\n        endgame_load_palette(FID_TYPE(fid), fid & 0xFFF);\n\n        endgame_load_voiceover(narratorFileName);\n\n        unsigned int delay;\n        if (endgame_subtitle_loaded || endgame_voiceover_loaded) {\n            delay = UINT_MAX;\n        } else {\n            delay = 3000;\n        }\n\n        palette_fade_to(cmap);\n\n        pause_for_tocks(500);\n\n        // NOTE: Uninline.\n        endgame_play_voiceover();\n\n        unsigned int referenceTime = get_time();\n        disable_bk();\n\n        int keyCode;\n        while (true) {\n            keyCode = get_input();\n            if (keyCode != -1) {\n                break;\n            }\n\n            if (endgame_voiceover_done) {\n                break;\n            }\n\n            if (endgame_subtitle_done) {\n                break;\n            }\n\n            if (elapsed_time(referenceTime) > delay) {\n                break;\n            }\n\n            buf_to_buf(backgroundData, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH);\n            endgame_show_subtitles();\n            win_draw(endgame_window);\n            soundContinueAll();\n        }\n\n        enable_bk();\n        gsound_speech_stop();\n        endgame_clear_subtitles();\n\n        endgame_voiceover_loaded = false;\n        endgame_subtitle_loaded = false;\n\n        if (keyCode == -1) {\n            pause_for_tocks(500);\n        }\n\n        palette_fade_to(black_palette);\n\n        while (mouse_get_buttons() != 0) {\n            get_input();\n        }\n    }\n\n    art_ptr_unlock(backgroundHandle);\n}\n\n// 0x43F99C\nstatic int endgame_init()\n{\n    if (endgame_load_slide_info() != 0) {\n        return -1;\n    }\n\n    gsound_background_stop();\n\n    endgame_map_enabled = map_disable_bk_processes();\n\n    cycle_disable();\n    gmouse_set_cursor(MOUSE_CURSOR_NONE);\n\n    bool oldCursorIsHidden = mouse_hidden();\n    endgame_mouse_state = oldCursorIsHidden == 0;\n\n    if (oldCursorIsHidden) {\n        mouse_show();\n    }\n\n    endgame_old_font = text_curr();\n    text_font(101);\n\n    palette_fade_to(black_palette);\n\n    int windowEndgameEndingX = 0;\n    int windowEndgameEndingY = 0;\n    endgame_window = win_add(windowEndgameEndingX,\n        windowEndgameEndingY,\n        ENDGAME_ENDING_WINDOW_WIDTH,\n        ENDGAME_ENDING_WINDOW_HEIGHT,\n        colorTable[0],\n        WINDOW_FLAG_0x04);\n    if (endgame_window == -1) {\n        return -1;\n    }\n\n    endgame_window_buffer = win_get_buf(endgame_window);\n    if (endgame_window_buffer == NULL) {\n        return -1;\n    }\n\n    cycle_disable();\n\n    gsound_speech_callback_set(endgame_voiceover_callback);\n\n    endgame_do_subtitles = false;\n    configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &endgame_do_subtitles);\n    if (!endgame_do_subtitles) {\n        return 0;\n    }\n\n    char* language;\n    if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) {\n        endgame_do_subtitles = false;\n        return 0;\n    }\n\n    sprintf(endgame_subtitle_path, \"text\\\\%s\\\\cuts\\\\\", language);\n\n    endgame_subtitle_text = (char**)mem_malloc(sizeof(*endgame_subtitle_text) * ENDGAME_ENDING_MAX_SUBTITLES);\n    if (endgame_subtitle_text == NULL) {\n        endgame_do_subtitles = false;\n        return 0;\n    }\n\n    for (int index = 0; index < ENDGAME_ENDING_MAX_SUBTITLES; index++) {\n        endgame_subtitle_text[index] = NULL;\n    }\n\n    endgame_subtitle_times = (unsigned int*)mem_malloc(sizeof(*endgame_subtitle_times) * ENDGAME_ENDING_MAX_SUBTITLES);\n    if (endgame_subtitle_times == NULL) {\n        mem_free(endgame_subtitle_text);\n        endgame_do_subtitles = false;\n        return 0;\n    }\n\n    return 0;\n}\n\n// 0x43FB28\nstatic void endgame_exit()\n{\n    if (endgame_do_subtitles) {\n        endgame_clear_subtitles();\n\n        mem_free(endgame_subtitle_times);\n        mem_free(endgame_subtitle_text);\n\n        endgame_subtitle_text = NULL;\n        endgame_do_subtitles = false;\n    }\n\n    // NOTE: Uninline.\n    endgame_unload_slide_info();\n\n    text_font(endgame_old_font);\n\n    gsound_speech_callback_set(NULL);\n    win_delete(endgame_window);\n\n    if (!endgame_mouse_state) {\n        mouse_hide();\n    }\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    loadColorTable(\"color.pal\");\n    palette_fade_to(cmap);\n\n    cycle_enable();\n\n    if (endgame_map_enabled) {\n        map_enable_bk_processes();\n    }\n}\n\n// 0x4401A0\nstatic void endgame_load_voiceover(const char* fileBaseName)\n{\n    char path[MAX_PATH];\n\n    // NOTE: Uninline.\n    endgame_stop_voiceover();\n\n    endgame_voiceover_loaded = false;\n    endgame_subtitle_loaded = false;\n\n    // Build speech file path.\n    sprintf(path, \"%s%s\", \"narrator\\\\\", fileBaseName);\n\n    if (gsound_speech_play(path, 10, 14, 15) != -1) {\n        endgame_voiceover_loaded = true;\n    }\n\n    if (endgame_do_subtitles) {\n        // Build subtitles file path.\n        sprintf(path, \"%s%s.txt\", endgame_subtitle_path, fileBaseName);\n\n        if (endgame_load_subtitles(path) != 0) {\n            return;\n        }\n\n        double durationPerCharacter;\n        if (endgame_voiceover_loaded) {\n            durationPerCharacter = (double)gsound_speech_length_get() / (double)endgame_subtitle_characters;\n        } else {\n            durationPerCharacter = 0.08;\n        }\n\n        unsigned int timing = 0;\n        for (int index = 0; index < endgame_subtitle_count; index++) {\n            double charactersCount = strlen(endgame_subtitle_text[index]);\n            // NOTE: There is floating point math at 0x4402E6 used to add\n            // timing.\n            timing += (unsigned int)trunc(charactersCount * durationPerCharacter * 1000.0);\n            endgame_subtitle_times[index] = timing;\n        }\n\n        endgame_subtitle_loaded = true;\n    }\n}\n\n// NOTE: This function was inlined at every call site.\n//\n// 0x440324\nstatic void endgame_play_voiceover()\n{\n    endgame_subtitle_done = false;\n    endgame_voiceover_done = false;\n\n    if (endgame_voiceover_loaded) {\n        gsound_speech_play_preloaded();\n    }\n\n    if (endgame_subtitle_loaded) {\n        endgame_subtitle_start_time = get_time();\n    }\n}\n\n// NOTE: This function was inlined at every call site.\n//\n// 0x44035C\nstatic void endgame_stop_voiceover()\n{\n    gsound_speech_stop();\n    endgame_clear_subtitles();\n    endgame_voiceover_loaded = false;\n    endgame_subtitle_loaded = false;\n}\n\n// 0x440378\nstatic void endgame_load_palette(int type, int id)\n{\n    char fileName[13];\n    if (art_get_base_name(type, id, fileName) != 0) {\n        return;\n    }\n\n    // Remove extension from file name.\n    char* pch = strrchr(fileName, '.');\n    if (pch != NULL) {\n        *pch = '\\0';\n    }\n\n    if (strlen(fileName) <= 8) {\n        char path[MAX_PATH];\n        sprintf(path, \"%s\\\\%s.pal\", \"art\\\\intrface\", fileName);\n        loadColorTable(path);\n    }\n}\n\n// 0x4403F0\nstatic void endgame_voiceover_callback()\n{\n    endgame_voiceover_done = true;\n}\n\n// Loads subtitles file.\n//\n// 0x4403FC\nstatic int endgame_load_subtitles(const char* filePath)\n{\n    endgame_clear_subtitles();\n\n    File* stream = db_fopen(filePath, \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    // FIXME: There is at least one subtitle for Arroyo ending (nar_ar1) that\n    // does not fit into this buffer.\n    char string[256];\n    while (db_fgets(string, sizeof(string), stream)) {\n        char* pch;\n\n        // Find and clamp string at EOL.\n        pch = strchr(string, '\\n');\n        if (pch != NULL) {\n            *pch = '\\0';\n        }\n\n        // Find separator. The value before separator is ignored (as opposed to\n        // movie subtitles, where the value before separator is a timing).\n        pch = strchr(string, ':');\n        if (pch != NULL) {\n            if (endgame_subtitle_count < ENDGAME_ENDING_MAX_SUBTITLES) {\n                endgame_subtitle_text[endgame_subtitle_count] = mem_strdup(pch + 1);\n                endgame_subtitle_count++;\n                endgame_subtitle_characters += strlen(pch + 1);\n            }\n        }\n    }\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// Refreshes subtitles.\n//\n// 0x4404EC\nstatic void endgame_show_subtitles()\n{\n    if (endgame_subtitle_count <= endgame_current_subtitle) {\n        if (endgame_subtitle_loaded) {\n            endgame_subtitle_done = true;\n        }\n        return;\n    }\n\n    if (elapsed_time(endgame_subtitle_start_time) > endgame_subtitle_times[endgame_current_subtitle]) {\n        endgame_current_subtitle++;\n        return;\n    }\n\n    char* text = endgame_subtitle_text[endgame_current_subtitle];\n    if (text == NULL) {\n        return;\n    }\n\n    short beginnings[WORD_WRAP_MAX_COUNT];\n    short count;\n    if (word_wrap(text, 540, beginnings, &count) != 0) {\n        return;\n    }\n\n    int height = text_height();\n    int y = 480 - height * count;\n\n    for (int index = 0; index < count - 1; index++) {\n        char* beginning = text + beginnings[index];\n        char* ending = text + beginnings[index + 1];\n\n        if (ending[-1] == ' ') {\n            ending--;\n        }\n\n        char c = *ending;\n        *ending = '\\0';\n\n        int width = text_width(beginning);\n        int x = (640 - width) / 2;\n        buf_fill(endgame_window_buffer + 640 * y + x, width, height, 640, colorTable[0]);\n        text_to_buf(endgame_window_buffer + 640 * y + x, beginning, width, 640, colorTable[32767]);\n\n        *ending = c;\n\n        y += height;\n    }\n}\n\n// 0x4406CC\nstatic void endgame_clear_subtitles()\n{\n    for (int index = 0; index < endgame_subtitle_count; index++) {\n        if (endgame_subtitle_text[index] != NULL) {\n            mem_free(endgame_subtitle_text[index]);\n            endgame_subtitle_text[index] = NULL;\n        }\n    }\n\n    endgame_current_subtitle = 0;\n    endgame_subtitle_characters = 0;\n    endgame_subtitle_count = 0;\n}\n\n// 0x440728\nstatic void endgame_movie_callback()\n{\n    endgame_maybe_done = 1;\n}\n\n// 0x440734\nstatic void endgame_movie_bk_process()\n{\n    if (endgame_maybe_done) {\n        gsound_background_play(\"10labone\", 11, 14, 16);\n        gsound_background_callback_set(NULL);\n        remove_bk_process(endgame_movie_bk_process);\n    }\n}\n\n// 0x440770\nstatic int endgame_load_slide_info()\n{\n    File* stream;\n    char str[256];\n    char *ch, *tok;\n    const char* delim = \" \\t,\";\n    EndgameEnding entry;\n    EndgameEnding* entries;\n    int narrator_file_len;\n\n    if (slides != NULL) {\n        mem_free(slides);\n        slides = NULL;\n    }\n\n    num_slides = 0;\n\n    stream = db_fopen(\"data\\\\endgame.txt\", \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    while (db_fgets(str, sizeof(str), stream)) {\n        ch = str;\n        while (isspace(*ch)) {\n            ch++;\n        }\n\n        if (*ch == '#') {\n            continue;\n        }\n\n        tok = strtok(ch, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.gvar = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.value = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.art_num = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        strcpy(entry.voiceOverBaseName, tok);\n\n        narrator_file_len = strlen(entry.voiceOverBaseName);\n        if (isspace(entry.voiceOverBaseName[narrator_file_len - 1])) {\n            entry.voiceOverBaseName[narrator_file_len - 1] = '\\0';\n        }\n\n        tok = strtok(NULL, delim);\n        if (tok != NULL) {\n            entry.direction = atoi(tok);\n        } else {\n            entry.direction = 1;\n        }\n\n        entries = (EndgameEnding*)mem_realloc(slides, sizeof(*entries) * (num_slides + 1));\n        if (entries == NULL) {\n            goto err;\n        }\n\n        memcpy(&(entries[num_slides]), &entry, sizeof(entry));\n\n        slides = entries;\n        num_slides++;\n    }\n\n    db_fclose(stream);\n\n    return 0;\n\nerr:\n\n    db_fclose(stream);\n\n    return -1;\n}\n\n// NOTE: There are no references to this function. It was inlined.\n//\n// 0x44095C\nstatic void endgame_unload_slide_info()\n{\n    if (slides != NULL) {\n        mem_free(slides);\n        slides = NULL;\n    }\n\n    num_slides = 0;\n}\n\n// endgameDeathEndingInit\n// 0x440984\nint endgameDeathEndingInit()\n{\n    File* stream;\n    char str[256];\n    char* ch;\n    const char* delim = \" \\t,\";\n    char* tok;\n    EndgameDeathEnding entry;\n    EndgameDeathEnding* entries;\n    int narrator_file_len;\n\n    strcpy(endDeathSndChoice, \"narrator\\\\nar_5\");\n\n    stream = db_fopen(\"data\\\\enddeath.txt\", \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    while (db_fgets(str, 256, stream)) {\n        ch = str;\n        while (isspace(*ch)) {\n            ch++;\n        }\n\n        if (*ch == '#') {\n            continue;\n        }\n\n        tok = strtok(ch, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.gvar = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.value = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.worldAreaKnown = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.worldAreaNotKnown = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.min_level = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.percentage = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        // this code is slightly different from the original, but does the same thing\n        narrator_file_len = strlen(tok);\n        strncpy(entry.voiceOverBaseName, tok, narrator_file_len);\n\n        entry.enabled = false;\n\n        if (isspace(entry.voiceOverBaseName[narrator_file_len - 1])) {\n            entry.voiceOverBaseName[narrator_file_len - 1] = '\\0';\n        }\n\n        entries = (EndgameDeathEnding*)mem_realloc(endDeathInfoList, sizeof(*entries) * (maxEndDeathInfo + 1));\n        if (entries == NULL) {\n            goto err;\n        }\n\n        memcpy(&(entries[maxEndDeathInfo]), &entry, sizeof(entry));\n\n        endDeathInfoList = entries;\n        maxEndDeathInfo++;\n    }\n\n    db_fclose(stream);\n\n    return 0;\n\nerr:\n\n    db_fclose(stream);\n\n    return -1;\n}\n\n// 0x440BA8\nint endgameDeathEndingExit()\n{\n    if (endDeathInfoList != NULL) {\n        mem_free(endDeathInfoList);\n        endDeathInfoList = NULL;\n\n        maxEndDeathInfo = 0;\n    }\n\n    return 0;\n}\n\n// endgameSetupDeathEnding\n// 0x440BD0\nvoid endgameSetupDeathEnding(int reason)\n{\n    if (!maxEndDeathInfo) {\n        debug_printf(\"\\nError: endgameSetupDeathEnding: No endgame death info!\");\n        return;\n    }\n\n    // Build death ending file path.\n    strcpy(endDeathSndChoice, \"narrator\\\\\");\n\n    int percentage = 0;\n    endgameSetupInit(&percentage);\n\n    int selectedEnding = 0;\n    bool specialEndingSelected = false;\n\n    switch (reason) {\n    case ENDGAME_DEATH_ENDING_REASON_DEATH:\n        if (game_get_global_var(GVAR_MODOC_SHITTY_DEATH) != 0) {\n            selectedEnding = 12;\n            specialEndingSelected = true;\n        }\n        break;\n    case ENDGAME_DEATH_ENDING_REASON_TIMEOUT:\n        gmovie_play(MOVIE_TIMEOUT, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC);\n        break;\n    }\n\n    if (!specialEndingSelected) {\n        int chance = roll_random(0, percentage);\n\n        int accum = 0;\n        for (int index = 0; index < maxEndDeathInfo; index++) {\n            EndgameDeathEnding* deathEnding = &(endDeathInfoList[index]);\n\n            if (deathEnding->enabled) {\n                accum += deathEnding->percentage;\n                if (accum >= chance) {\n                    break;\n                }\n                selectedEnding++;\n            }\n        }\n    }\n\n    EndgameDeathEnding* deathEnding = &(endDeathInfoList[selectedEnding]);\n\n    strcat(endDeathSndChoice, deathEnding->voiceOverBaseName);\n\n    debug_printf(\"\\nendgameSetupDeathEnding: Death Filename Picked: %s\", endDeathSndChoice);\n}\n\n// Validates conditions imposed by death endings.\n//\n// Upon return [percentage] is set as a sum of all valid endings' percentages.\n// Always returns 0.\n//\n// 0x440CF4\nstatic int endgameSetupInit(int* percentage)\n{\n    *percentage = 0;\n\n    for (int index = 0; index < maxEndDeathInfo; index++) {\n        EndgameDeathEnding* deathEnding = &(endDeathInfoList[index]);\n\n        deathEnding->enabled = false;\n\n        if (deathEnding->gvar != -1) {\n            if (game_get_global_var(deathEnding->gvar) >= deathEnding->value) {\n                continue;\n            }\n        }\n\n        if (deathEnding->worldAreaKnown != -1) {\n            if (!wmAreaIsKnown(deathEnding->worldAreaKnown)) {\n                continue;\n            }\n        }\n\n        if (deathEnding->worldAreaNotKnown != -1) {\n            if (wmAreaIsKnown(deathEnding->worldAreaNotKnown)) {\n                continue;\n            }\n        }\n\n        if (stat_pc_get(PC_STAT_LEVEL) < deathEnding->min_level) {\n            continue;\n        }\n\n        deathEnding->enabled = true;\n\n        *percentage += deathEnding->percentage;\n    }\n\n    return 0;\n}\n\n// Returns file name for voice over for death ending.\n//\n// This path does not include extension.\n//\n// 0x440D8C\nchar* endgameGetDeathEndingFileName()\n{\n    if (maxEndDeathInfo == 0) {\n        debug_printf(\"\\nError: endgameSetupDeathEnding: No endgame death info!\");\n        strcpy(endDeathSndChoice, \"narrator\\\\nar_4\");\n    }\n\n    debug_printf(\"\\nendgameSetupDeathEnding: Death Filename: %s\", endDeathSndChoice);\n\n    return endDeathSndChoice;\n}\n"
  },
  {
    "path": "src/game/endgame.h",
    "content": "#ifndef FALLOUT_GAME_ENDGAME_H_\n#define FALLOUT_GAME_ENDGAME_H_\n\ntypedef enum EndgameDeathEndingReason {\n    // Dude died.\n    ENDGAME_DEATH_ENDING_REASON_DEATH = 0,\n\n    // 13 years passed.\n    ENDGAME_DEATH_ENDING_REASON_TIMEOUT = 2,\n} EndgameDeathEndingReason;\n\nextern char _aEnglish_2[];\n\nvoid endgame_slideshow();\nvoid endgame_movie();\nint endgameEndingHandleContinuePlaying();\nint endgameDeathEndingInit();\nint endgameDeathEndingExit();\nvoid endgameSetupDeathEnding(int reason);\nchar* endgameGetDeathEndingFileName();\n\n#endif /* FALLOUT_GAME_ENDGAME_H_ */\n"
  },
  {
    "path": "src/game/ereg.c",
    "content": "#include \"game/ereg.h\"\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"game/gconfig.h\"\n\n// 0x440DD0\nvoid annoy_user()\n{\n    int timesRun = 0;\n    config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_TIMES_RUN_KEY, &timesRun);\n    if (timesRun > 0 && timesRun < 5) {\n        char path[MAX_PATH];\n        if (GetModuleFileNameA(NULL, path, sizeof(path)) != 0) {\n            char* pch = strrchr(path, '\\\\');\n            if (pch == NULL) {\n                pch = path;\n            }\n\n            strcpy(pch, \"\\\\ereg\");\n\n            STARTUPINFOA startupInfo;\n            memset(&startupInfo, 0, sizeof(startupInfo));\n            startupInfo.cb = sizeof(startupInfo);\n\n            PROCESS_INFORMATION processInfo;\n\n            // FIXME: Leaking processInfo.hProcess and processInfo.hThread:\n            // https://docs.microsoft.com/en-us/cpp/code-quality/c6335.\n            if (CreateProcessA(\"ereg\\\\reg32a.exe\", NULL, NULL, NULL, FALSE, 0, NULL, path, &startupInfo, &processInfo)) {\n                WaitForSingleObject(processInfo.hProcess, INFINITE);\n            }\n        }\n\n        config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_TIMES_RUN_KEY, timesRun + 1);\n    } else {\n        if (timesRun == 0) {\n            config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_TIMES_RUN_KEY, 1);\n        }\n    }\n}\n"
  },
  {
    "path": "src/game/ereg.h",
    "content": "#ifndef FALLOUT_GAME_EREG_H_\n#define FALLOUT_GAME_EREG_H_\n\nvoid annoy_user();\n\n#endif /* FALLOUT_GAME_EREG_H_ */\n"
  },
  {
    "path": "src/game/fontmgr.c",
    "content": "#include \"game/fontmgr.h\"\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"plib/color/color.h\"\n#include \"plib/db/db.h\"\n#include \"int/memdbg.h\"\n#include \"plib/gnw/text.h\"\n\n// The maximum number of interface fonts.\n#define INTERFACE_FONT_MAX 16\n\ntypedef struct InterfaceFontGlyph {\n    short width;\n    short height;\n    int offset;\n} InterfaceFontGlyph;\n\ntypedef struct InterfaceFontDescriptor {\n    short maxHeight;\n    short letterSpacing;\n    short wordSpacing;\n    short lineSpacing;\n    short field_8;\n    short field_A;\n    InterfaceFontGlyph glyphs[256];\n    unsigned char* data;\n} InterfaceFontDescriptor;\n\nstatic int FMLoadFont(int font);\nstatic void Swap4(unsigned int* value);\nstatic void Swap2(unsigned short* value);\n\n// 0x518680\nstatic bool gFMInit = false;\n\n// 0x518684\nstatic int gNumFonts = 0;\n\n// 0x586838\nstatic InterfaceFontDescriptor gFontCache[INTERFACE_FONT_MAX];\n\n// 0x58E938\nstatic int gCurrentFontNum;\n\n// 0x58E93C\nstatic InterfaceFontDescriptor* gCurrentFont;\n\n// 0x441C80\nint FMInit()\n{\n    int currentFont = -1;\n\n    for (int font = 0; font < INTERFACE_FONT_MAX; font++) {\n        if (FMLoadFont(font) == -1) {\n            gFontCache[font].maxHeight = 0;\n            gFontCache[font].data = NULL;\n        } else {\n            ++gNumFonts;\n\n            if (currentFont == -1) {\n                currentFont = font;\n            }\n        }\n    }\n\n    if (currentFont == -1) {\n        return -1;\n    }\n\n    gFMInit = true;\n\n    FMtext_font(currentFont + 100);\n\n    return 0;\n}\n\n// 0x441CEC\nvoid FMExit()\n{\n    for (int font = 0; font < INTERFACE_FONT_MAX; font++) {\n        if (gFontCache[font].data != NULL) {\n            myfree(gFontCache[font].data, __FILE__, __LINE__); // FONTMGR.C, 124\n        }\n    }\n}\n\n// 0x441D20\nstatic int FMLoadFont(int font_index)\n{\n    InterfaceFontDescriptor* fontDescriptor = &(gFontCache[font_index]);\n\n    char path[56];\n    sprintf(path, \"font%d.aaf\", font_index);\n\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int fileSize = db_filelength(stream);\n\n    int sig;\n    if (db_fread(&sig, 4, 1, stream) != 1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    Swap4(&sig);\n    if (sig != 0x41414646) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    if (db_fread(&(fontDescriptor->maxHeight), 2, 1, stream) != 1) {\n        db_fclose(stream);\n        return -1;\n    }\n    Swap2(&(fontDescriptor->maxHeight));\n\n    if (db_fread(&(fontDescriptor->letterSpacing), 2, 1, stream) != 1) {\n        db_fclose(stream);\n        return -1;\n    }\n    Swap2(&(fontDescriptor->letterSpacing));\n\n    if (db_fread(&(fontDescriptor->wordSpacing), 2, 1, stream) != 1) {\n        db_fclose(stream);\n        return -1;\n    }\n    Swap2(&(fontDescriptor->wordSpacing));\n\n    if (db_fread(&(fontDescriptor->lineSpacing), 2, 1, stream) != 1) {\n        db_fclose(stream);\n        return -1;\n    }\n    Swap2(&(fontDescriptor->lineSpacing));\n\n    for (int index = 0; index < 256; index++) {\n        InterfaceFontGlyph* glyph = &(fontDescriptor->glyphs[index]);\n\n        if (db_fread(&(glyph->width), 2, 1, stream) != 1) {\n            db_fclose(stream);\n            return -1;\n        }\n        Swap2(&(glyph->width));\n\n        if (db_fread(&(glyph->height), 2, 1, stream) != 1) {\n            db_fclose(stream);\n            return -1;\n        }\n        Swap2(&(glyph->height));\n\n        if (db_fread(&(glyph->offset), 4, 1, stream) != 1) {\n            db_fclose(stream);\n            return -1;\n        }\n        Swap4(&(glyph->offset));\n    }\n\n    int glyphDataSize = fileSize - 2060;\n\n    fontDescriptor->data = (unsigned char*)mymalloc(glyphDataSize, __FILE__, __LINE__); // FONTMGR.C, 259\n    if (fontDescriptor->data == NULL) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    if (db_fread(fontDescriptor->data, glyphDataSize, 1, stream) != 1) {\n        myfree(fontDescriptor->data, __FILE__, __LINE__); // FONTMGR.C, 268\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// 0x442120\nvoid FMtext_font(int font)\n{\n    if (!gFMInit) {\n        return;\n    }\n\n    font -= 100;\n\n    if (gFontCache[font].data != NULL) {\n        gCurrentFontNum = font;\n        gCurrentFont = &(gFontCache[font]);\n    }\n}\n\n// 0x442168\nint FMtext_height()\n{\n    if (!gFMInit) {\n        return 0;\n    }\n\n    return gCurrentFont->lineSpacing + gCurrentFont->maxHeight;\n}\n\n// 0x442188\nint FMtext_width(const char* string)\n{\n    if (!gFMInit) {\n        return 0;\n    }\n\n    int stringWidth = 0;\n\n    while (*string != '\\0') {\n        unsigned char ch = (unsigned char)(*string++);\n\n        int characterWidth;\n        if (ch == ' ') {\n            characterWidth = gCurrentFont->wordSpacing;\n        } else {\n            characterWidth = gCurrentFont->glyphs[ch].width;\n        }\n\n        stringWidth += characterWidth + gCurrentFont->letterSpacing;\n    }\n\n    return stringWidth;\n}\n\n// 0x4421DC\nint FMtext_char_width(int ch)\n{\n    int width;\n\n    if (!gFMInit) {\n        return 0;\n    }\n\n    if (ch == ' ') {\n        width = gCurrentFont->wordSpacing;\n    } else {\n        width = gCurrentFont->glyphs[ch].width;\n    }\n\n    return width;\n}\n\n// 0x442210\nint FMtext_mono_width(const char* str)\n{\n    if (!gFMInit) {\n        return 0;\n    }\n\n    return FMtext_max() * strlen(str);\n}\n\n// 0x442240\nint FMtext_spacing()\n{\n    if (!gFMInit) {\n        return 0;\n    }\n\n    return gCurrentFont->letterSpacing;\n}\n\n// 0x442258\nint FMtext_size(const char* str)\n{\n    if (!gFMInit) {\n        return 0;\n    }\n\n    return FMtext_width(str) * FMtext_height();\n}\n\n// 0x442278\nint FMtext_max()\n{\n    if (!gFMInit) {\n        return 0;\n    }\n\n    int v1;\n    if (gCurrentFont->wordSpacing <= gCurrentFont->field_8) {\n        v1 = gCurrentFont->lineSpacing;\n    } else {\n        v1 = gCurrentFont->letterSpacing;\n    }\n\n    return v1 + gCurrentFont->maxHeight;\n}\n\n// NOTE: Unused.\n//\n// 0x4422AC\nint FMtext_curr()\n{\n    return gCurrentFontNum;\n}\n\n// 0x4422B4\nvoid FMtext_to_buf(unsigned char* buf, const char* string, int length, int pitch, int color)\n{\n    if (!gFMInit) {\n        return;\n    }\n\n    if ((color & FONT_SHADOW) != 0) {\n        color &= ~FONT_SHADOW;\n        // NOTE: Other font options preserved. This is different from text font\n        // shadows.\n        FMtext_to_buf(buf + pitch + 1, string, length, pitch, (color & ~0xFF) | colorTable[0]);\n    }\n\n    unsigned char* palette = getColorBlendTable(color & 0xFF);\n\n    int monospacedCharacterWidth;\n    if ((color & FONT_MONO) != 0) {\n        // NOTE: Uninline.\n        monospacedCharacterWidth = FMtext_max();\n    }\n\n    unsigned char* ptr = buf;\n    while (*string != '\\0') {\n        char ch = *string++;\n\n        int characterWidth;\n        if (ch == ' ') {\n            characterWidth = gCurrentFont->wordSpacing;\n        } else {\n            characterWidth = gCurrentFont->glyphs[ch & 0xFF].width;\n        }\n\n        unsigned char* end;\n        if ((color & FONT_MONO) != 0) {\n            end = ptr + monospacedCharacterWidth;\n            ptr += (monospacedCharacterWidth - characterWidth - gCurrentFont->letterSpacing) / 2;\n        } else {\n            end = ptr + characterWidth + gCurrentFont->letterSpacing;\n        }\n\n        if (end - buf > length) {\n            break;\n        }\n\n        InterfaceFontGlyph* glyph = &(gCurrentFont->glyphs[ch & 0xFF]);\n        unsigned char* glyphDataPtr = gCurrentFont->data + glyph->offset;\n\n        // Skip blank pixels (difference between font's line height and glyph height).\n        ptr += (gCurrentFont->maxHeight - glyph->height) * pitch;\n\n        for (int y = 0; y < glyph->height; y++) {\n            for (int x = 0; x < glyph->width; x++) {\n                unsigned char byte = *glyphDataPtr++;\n\n                *ptr++ = palette[(byte << 8) + *ptr];\n            }\n\n            ptr += pitch - glyph->width;\n        }\n\n        ptr = end;\n    }\n\n    if ((color & FONT_UNDERLINE) != 0) {\n        int length = ptr - buf;\n        unsigned char* underlinePtr = buf + pitch * (gCurrentFont->maxHeight - 1);\n        for (int index = 0; index < length; index++) {\n            *underlinePtr++ = color & 0xFF;\n        }\n    }\n\n    freeColorBlendTable(color & 0xFF);\n}\n\n// NOTE: Inlined.\n//\n// 0x442520\nstatic void Swap4(unsigned int* value)\n{\n    unsigned int swapped = *value;\n    unsigned short high = swapped >> 16;\n    // NOTE: Uninline.\n    Swap2(&high);\n    unsigned short low = swapped & 0xFFFF;\n    // NOTE: Uninline.\n    Swap2(&low);\n    *value = (low << 16) | high;\n}\n\n// 0x442568\nstatic void Swap2(unsigned short* value)\n{\n    unsigned short swapped = *value;\n    swapped = (swapped >> 8) | (swapped << 8);\n    *value = swapped;\n}\n"
  },
  {
    "path": "src/game/fontmgr.h",
    "content": "#ifndef FALLOUT_GAME_FONTMGR_H_\n#define FALLOUT_GAME_FONTMGR_H_\n\nint FMInit();\nvoid FMExit();\nvoid FMtext_font(int font);\nint FMtext_height();\nint FMtext_width(const char* string);\nint FMtext_char_width(int ch);\nint FMtext_mono_width(const char* string);\nint FMtext_spacing();\nint FMtext_size(const char* string);\nint FMtext_max();\nint FMtext_curr();\nvoid FMtext_to_buf(unsigned char* buf, const char* string, int length, int pitch, int color);\n\n#endif /* FALLOUT_GAME_FONTMGR_H_ */\n"
  },
  {
    "path": "src/game/game.c",
    "content": "#include \"game/game.h\"\n\n#include <io.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"int/window.h\"\n#include \"game/actions.h\"\n#include \"game/anim.h\"\n#include \"game/automap.h\"\n#include \"game/editor.h\"\n#include \"game/select.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/cycle.h\"\n#include \"plib/db/db.h\"\n#include \"game/bmpdlog.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/ereg.h\"\n#include \"game/endgame.h\"\n#include \"game/fontmgr.h\"\n#include \"game/gconfig.h\"\n#include \"game/gdialog.h\"\n#include \"game/gmemory.h\"\n#include \"game/gmouse.h\"\n#include \"game/gmovie.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/inventry.h\"\n#include \"game/item.h\"\n#include \"game/loadsave.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"int/movie.h\"\n#include \"game/moviefx.h\"\n#include \"game/object.h\"\n#include \"game/options.h\"\n#include \"game/palette.h\"\n#include \"game/party.h\"\n#include \"game/perk.h\"\n#include \"game/pipboy.h\"\n#include \"game/proto.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/skilldex.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n#include \"game/trap.h\"\n#include \"game/version.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/svga.h\"\n#include \"game/worldmap.h\"\n\n#define HELP_SCREEN_WIDTH 640\n#define HELP_SCREEN_HEIGHT 480\n\n#define SPLASH_WIDTH 640\n#define SPLASH_HEIGHT 480\n#define SPLASH_COUNT 10\n\nstatic void game_display_counter(double value);\nstatic int game_screendump(int width, int height, unsigned char* buffer, unsigned char* palette);\nstatic void game_unload_info();\nstatic void game_help();\nstatic int game_init_databases();\nstatic void game_splash_screen();\n\n// TODO: Remove.\n// 0x501C9C\nchar _aGame_0[] = \"game\\\\\";\n\n// TODO: Remove.\n// 0x5020B8\nchar _aDec11199816543[] = VERSION_BUILD_TIME;\n\n// 0x518688\nstatic FontMgr alias_mgr = {\n    100,\n    110,\n    FMtext_font,\n    FMtext_to_buf,\n    FMtext_height,\n    FMtext_width,\n    FMtext_char_width,\n    FMtext_mono_width,\n    FMtext_spacing,\n    FMtext_size,\n    FMtext_max,\n};\n\n// 0x5186B4\nstatic bool game_ui_disabled = false;\n\n// 0x5186B8\nstatic int game_state_cur = GAME_STATE_0;\n\n// 0x5186BC\nstatic bool game_in_mapper = false;\n\n// 0x5186C0\nint* game_global_vars = NULL;\n\n// 0x5186C4\nint num_game_global_vars = 0;\n\n// 0x5186C8\nconst char* msg_path = _aGame_0;\n\n// 0x5186CC\nint game_user_wants_to_quit = 0;\n\n// misc.msg\n//\n// 0x58E940\nMessageList misc_message_file;\n\n// master.dat loading result\n//\n// 0x58E948\nint master_db_handle;\n\n// critter.dat loading result\n//\n// 0x58E94C\nint critter_db_handle;\n\n// 0x442580\nint game_init(const char* windowTitle, bool isMapper, int font, int a4, int argc, char** argv)\n{\n    char path[MAX_PATH];\n\n    if (gmemory_init() == -1) {\n        return -1;\n    }\n\n    gconfig_init(isMapper, argc, argv);\n\n    game_in_mapper = isMapper;\n\n    if (game_init_databases() == -1) {\n        gconfig_exit(false);\n        return -1;\n    }\n\n    annoy_user();\n    win_set_minimized_title(windowTitle);\n    initWindow(1, a4);\n    palette_init();\n\n    char* language;\n    if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) {\n        if (stricmp(language, FRENCH) == 0) {\n            kb_set_layout(KEYBOARD_LAYOUT_FRENCH);\n        } else if (stricmp(language, GERMAN) == 0) {\n            kb_set_layout(KEYBOARD_LAYOUT_GERMAN);\n        } else if (stricmp(language, ITALIAN) == 0) {\n            kb_set_layout(KEYBOARD_LAYOUT_ITALIAN);\n        } else if (stricmp(language, SPANISH) == 0) {\n            kb_set_layout(KEYBOARD_LAYOUT_SPANISH);\n        }\n    }\n\n    if (!game_in_mapper) {\n        game_splash_screen();\n    }\n\n    trap_init();\n\n    FMInit();\n    text_add_manager(&alias_mgr);\n    text_font(font);\n\n    register_screendump(KEY_F12, game_screendump);\n    register_pause(-1, NULL);\n\n    tile_disable_refresh();\n\n    roll_init();\n    init_message();\n    skill_init();\n    stat_init();\n\n    if (partyMember_init() != 0) {\n        debug_printf(\"Failed on partyMember_init\\n\");\n        return -1;\n    }\n\n    perk_init();\n    trait_init();\n    item_init();\n    queue_init();\n    critter_init();\n    combat_ai_init();\n    inven_reset_dude();\n\n    if (gsound_init() != 0) {\n        debug_printf(\"Sound initialization failed.\\n\");\n    }\n\n    debug_printf(\">gsound_init\\t\");\n\n    initMovie();\n    debug_printf(\">initMovie\\t\\t\");\n\n    if (gmovie_init() != 0) {\n        debug_printf(\"Failed on gmovie_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">gmovie_init\\t\");\n\n    if (moviefx_init() != 0) {\n        debug_printf(\"Failed on moviefx_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">moviefx_init\\t\");\n\n    if (iso_init() != 0) {\n        debug_printf(\"Failed on iso_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">iso_init\\t\");\n\n    if (gmouse_init() != 0) {\n        debug_printf(\"Failed on gmouse_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">gmouse_init\\t\");\n\n    if (proto_init() != 0) {\n        debug_printf(\"Failed on proto_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">proto_init\\t\");\n\n    anim_init();\n    debug_printf(\">anim_init\\t\");\n\n    if (scr_init() != 0) {\n        debug_printf(\"Failed on scr_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">scr_init\\t\");\n\n    if (game_load_info() != 0) {\n        debug_printf(\"Failed on game_load_info\\n\");\n        return -1;\n    }\n\n    debug_printf(\">game_load_info\\t\");\n\n    if (scr_game_init() != 0) {\n        debug_printf(\"Failed on scr_game_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">scr_game_init\\t\");\n\n    if (wmWorldMap_init() != 0) {\n        debug_printf(\"Failed on wmWorldMap_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">wmWorldMap_init\\t\");\n\n    CharEditInit();\n    debug_printf(\">CharEditInit\\t\");\n\n    pip_init();\n    debug_printf(\">pip_init\\t\\t\");\n\n    InitLoadSave();\n    KillOldMaps();\n    debug_printf(\">InitLoadSave\\t\");\n\n    if (gdialogInit() != 0) {\n        debug_printf(\"Failed on gdialog_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">gdialog_init\\t\");\n\n    if (combat_init() != 0) {\n        debug_printf(\"Failed on combat_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">combat_init\\t\");\n\n    if (automap_init() != 0) {\n        debug_printf(\"Failed on automap_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">automap_init\\t\");\n\n    if (!message_init(&misc_message_file)) {\n        debug_printf(\"Failed on message_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">message_init\\t\");\n\n    sprintf(path, \"%s%s\", msg_path, \"misc.msg\");\n\n    if (!message_load(&misc_message_file, path)) {\n        debug_printf(\"Failed on message_load\\n\");\n        return -1;\n    }\n\n    debug_printf(\">message_load\\t\");\n\n    if (scr_disable() != 0) {\n        debug_printf(\"Failed on scr_disable\\n\");\n        return -1;\n    }\n\n    debug_printf(\">scr_disable\\t\");\n\n    if (init_options_menu() != 0) {\n        debug_printf(\"Failed on init_options_menu\\n\");\n        return -1;\n    }\n\n    debug_printf(\">init_options_menu\\n\");\n\n    if (endgameDeathEndingInit() != 0) {\n        debug_printf(\"Failed on endgameDeathEndingInit\");\n        return -1;\n    }\n\n    debug_printf(\">endgameDeathEndingInit\\n\");\n\n    return 0;\n}\n\n// 0x442B84\nvoid game_reset()\n{\n    tile_disable_refresh();\n    palette_reset();\n    roll_reset();\n    skill_reset();\n    stat_reset();\n    perk_reset();\n    trait_reset();\n    item_reset();\n    queue_reset();\n    anim_reset();\n    KillOldMaps();\n    critter_reset();\n    combat_ai_reset();\n    inven_reset_dude();\n    gsound_reset();\n    movieStop();\n    moviefx_reset();\n    gmovie_reset();\n    iso_reset();\n    gmouse_reset();\n    proto_reset();\n    scr_reset();\n    game_load_info();\n    scr_game_reset();\n    wmWorldMap_reset();\n    partyMember_reset();\n    CharEditInit();\n    pip_init();\n    ResetLoadSave();\n    gdialogReset();\n    combat_reset();\n    game_user_wants_to_quit = 0;\n    automap_reset();\n    init_options_menu();\n}\n\n// 0x442C34\nvoid game_exit()\n{\n    debug_printf(\"\\nGame Exit\\n\");\n\n    tile_disable_refresh();\n    message_exit(&misc_message_file);\n    combat_exit();\n    gdialogExit();\n    scr_game_exit();\n\n    // NOTE: Uninline.\n    game_unload_info();\n\n    scr_exit();\n    anim_exit();\n    proto_exit();\n    gmouse_exit();\n    iso_exit();\n    moviefx_exit();\n    movieClose();\n    gsound_exit();\n    combat_ai_exit();\n    critter_exit();\n    item_exit();\n    queue_exit();\n    perk_exit();\n    stat_exit();\n    skill_exit();\n    trait_exit();\n    roll_exit();\n    exit_message();\n    automap_exit();\n    palette_exit();\n    wmWorldMap_exit();\n    partyMember_exit();\n    endgameDeathEndingExit();\n    FMExit();\n    trap_exit();\n    windowClose();\n    db_exit();\n    gconfig_exit(true);\n}\n\n// 0x442D44\nint game_handle_input(int eventCode, bool isInCombatMode)\n{\n    // NOTE: Uninline.\n    if (game_state() == GAME_STATE_5) {\n        gdialogSystemEnter();\n    }\n\n    if (eventCode == -1) {\n        return 0;\n    }\n\n    if (eventCode == -2) {\n        int mouseState = mouse_get_buttons();\n        int mouseX;\n        int mouseY;\n        mouse_get_position(&mouseX, &mouseY);\n\n        if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n            if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0) {\n                if (mouseX == scr_size.ulx || mouseX == scr_size.lrx\n                    || mouseY == scr_size.uly || mouseY == scr_size.lry) {\n                    gmouse_clicked_on_edge = true;\n                } else {\n                    gmouse_clicked_on_edge = false;\n                }\n            }\n        } else {\n            if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n                gmouse_clicked_on_edge = false;\n            }\n        }\n\n        gmouse_handle_event(mouseX, mouseY, mouseState);\n        return 0;\n    }\n\n    if (gmouse_is_scrolling()) {\n        return 0;\n    }\n\n    switch (eventCode) {\n    case -20:\n        if (intface_is_enabled()) {\n            intface_use_item();\n        }\n        break;\n    case -2:\n        if (1) {\n            int mouseEvent = mouse_get_buttons();\n            int mouseX;\n            int mouseY;\n            mouse_get_position(&mouseX, &mouseY);\n\n            if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n                if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0) {\n                    if (mouseX == scr_size.ulx || mouseX == scr_size.lrx\n                        || mouseY == scr_size.uly || mouseY == scr_size.lry) {\n                        gmouse_clicked_on_edge = true;\n                    } else {\n                        gmouse_clicked_on_edge = false;\n                    }\n                }\n            } else {\n                if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n                    gmouse_clicked_on_edge = false;\n                }\n            }\n\n            gmouse_handle_event(mouseX, mouseY, mouseEvent);\n        }\n        break;\n    case KEY_CTRL_Q:\n    case KEY_CTRL_X:\n    case KEY_F10:\n        gsound_play_sfx_file(\"ib1p1xx1\");\n        game_quit_with_confirm();\n        break;\n    case KEY_TAB:\n        if (intface_is_enabled()\n            && keys[DIK_LALT] == 0\n            && keys[DIK_RALT] == 0) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            automap(true, false);\n        }\n        break;\n    case KEY_CTRL_P:\n        gsound_play_sfx_file(\"ib1p1xx1\");\n        PauseWindow(false);\n        break;\n    case KEY_UPPERCASE_A:\n    case KEY_LOWERCASE_A:\n        if (intface_is_enabled()) {\n            if (!isInCombatMode) {\n                combat(NULL);\n            }\n        }\n        break;\n    case KEY_UPPERCASE_N:\n    case KEY_LOWERCASE_N:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            intface_toggle_item_state();\n        }\n        break;\n    case KEY_UPPERCASE_M:\n    case KEY_LOWERCASE_M:\n        gmouse_3d_toggle_mode();\n        break;\n    case KEY_UPPERCASE_B:\n    case KEY_LOWERCASE_B:\n        // change active hand\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            intface_toggle_items(true);\n        }\n        break;\n    case KEY_UPPERCASE_C:\n    case KEY_LOWERCASE_C:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            bool isoWasEnabled = map_disable_bk_processes();\n            editor_design(false);\n            if (isoWasEnabled) {\n                map_enable_bk_processes();\n            }\n        }\n        break;\n    case KEY_UPPERCASE_I:\n    case KEY_LOWERCASE_I:\n        // open inventory\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            handle_inventory();\n        }\n        break;\n    case KEY_ESCAPE:\n    case KEY_UPPERCASE_O:\n    case KEY_LOWERCASE_O:\n        // options\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            do_options();\n        }\n        break;\n    case KEY_UPPERCASE_P:\n    case KEY_LOWERCASE_P:\n        // pipboy\n        if (intface_is_enabled()) {\n            if (isInCombatMode) {\n                gsound_play_sfx_file(\"iisxxxx1\");\n\n                // Pipboy not available in combat!\n                MessageListItem messageListItem;\n                char title[128];\n                strcpy(title, getmsg(&misc_message_file, &messageListItem, 7));\n                dialog_out(title, NULL, 0, 192, 116, colorTable[32328], NULL, colorTable[32328], 0);\n            } else {\n                gsound_play_sfx_file(\"ib1p1xx1\");\n                pipboy(false);\n            }\n        }\n        break;\n    case KEY_UPPERCASE_S:\n    case KEY_LOWERCASE_S:\n        // skilldex\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n\n            int mode = -1;\n\n            // NOTE: There is an `inc` for this value to build jump table which\n            // is not needed.\n            int rc = skilldex_select();\n\n            // Remap Skilldex result code to action.\n            switch (rc) {\n            case SKILLDEX_RC_ERROR:\n                debug_printf(\"\\n ** Error calling skilldex_select()! ** \\n\");\n                break;\n            case SKILLDEX_RC_SNEAK:\n                action_skill_use(SKILL_SNEAK);\n                break;\n            case SKILLDEX_RC_LOCKPICK:\n                mode = GAME_MOUSE_MODE_USE_LOCKPICK;\n                break;\n            case SKILLDEX_RC_STEAL:\n                mode = GAME_MOUSE_MODE_USE_STEAL;\n                break;\n            case SKILLDEX_RC_TRAPS:\n                mode = GAME_MOUSE_MODE_USE_TRAPS;\n                break;\n            case SKILLDEX_RC_FIRST_AID:\n                mode = GAME_MOUSE_MODE_USE_FIRST_AID;\n                break;\n            case SKILLDEX_RC_DOCTOR:\n                mode = GAME_MOUSE_MODE_USE_DOCTOR;\n                break;\n            case SKILLDEX_RC_SCIENCE:\n                mode = GAME_MOUSE_MODE_USE_SCIENCE;\n                break;\n            case SKILLDEX_RC_REPAIR:\n                mode = GAME_MOUSE_MODE_USE_REPAIR;\n                break;\n            default:\n                break;\n            }\n\n            if (mode != -1) {\n                gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n                gmouse_3d_set_mode(mode);\n            }\n        }\n        break;\n    case KEY_UPPERCASE_Z:\n    case KEY_LOWERCASE_Z:\n        if (intface_is_enabled()) {\n            if (isInCombatMode) {\n                gsound_play_sfx_file(\"iisxxxx1\");\n\n                // Pipboy not available in combat!\n                MessageListItem messageListItem;\n                char title[128];\n                strcpy(title, getmsg(&misc_message_file, &messageListItem, 7));\n                dialog_out(title, NULL, 0, 192, 116, colorTable[32328], NULL, colorTable[32328], 0);\n            } else {\n                gsound_play_sfx_file(\"ib1p1xx1\");\n                pipboy(true);\n            }\n        }\n        break;\n    case KEY_HOME:\n        if (obj_dude->elevation != map_elevation) {\n            map_set_elevation(obj_dude->elevation);\n        }\n\n        if (game_in_mapper) {\n            tile_set_center(obj_dude->tile, TILE_SET_CENTER_REFRESH_WINDOW);\n        } else {\n            tile_scroll_to(obj_dude->tile, 2);\n        }\n\n        break;\n    case KEY_1:\n    case KEY_EXCLAMATION:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n            action_skill_use(SKILL_SNEAK);\n        }\n        break;\n    case KEY_2:\n    case KEY_AT:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_LOCKPICK);\n        }\n        break;\n    case KEY_3:\n    case KEY_NUMBER_SIGN:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_STEAL);\n        }\n        break;\n    case KEY_4:\n    case KEY_DOLLAR:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_TRAPS);\n        }\n        break;\n    case KEY_5:\n    case KEY_PERCENT:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_FIRST_AID);\n        }\n        break;\n    case KEY_6:\n    case KEY_CARET:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_DOCTOR);\n        }\n        break;\n    case KEY_7:\n    case KEY_AMPERSAND:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_SCIENCE);\n        }\n        break;\n    case KEY_8:\n    case KEY_ASTERISK:\n        if (intface_is_enabled()) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_REPAIR);\n        }\n        break;\n    case KEY_MINUS:\n    case KEY_UNDERSCORE:\n        DecGamma();\n        break;\n    case KEY_EQUAL:\n    case KEY_PLUS:\n        IncGamma();\n        break;\n    case KEY_COMMA:\n    case KEY_LESS:\n        if (register_begin(ANIMATION_REQUEST_RESERVED) == 0) {\n            register_object_dec_rotation(obj_dude);\n            register_end();\n        }\n        break;\n    case KEY_DOT:\n    case KEY_GREATER:\n        if (register_begin(ANIMATION_REQUEST_RESERVED) == 0) {\n            register_object_inc_rotation(obj_dude);\n            register_end();\n        }\n        break;\n    case KEY_SLASH:\n    case KEY_QUESTION:\n        if (1) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n\n            int month;\n            int day;\n            int year;\n            game_time_date(&month, &day, &year);\n\n            MessageList messageList;\n            if (message_init(&messageList)) {\n                char path[FILENAME_MAX];\n                sprintf(path, \"%s%s\", msg_path, \"editor.msg\");\n\n                if (message_load(&messageList, path)) {\n                    MessageListItem messageListItem;\n                    messageListItem.num = 500 + month - 1;\n                    if (message_search(&messageList, &messageListItem)) {\n                        char* time = game_time_hour_str();\n\n                        char date[128];\n                        sprintf(date, \"%s: %d/%d %s\", messageListItem.text, day, year, time);\n\n                        display_print(date);\n                    }\n                }\n\n                message_exit(&messageList);\n            }\n        }\n        break;\n    case KEY_F1:\n        gsound_play_sfx_file(\"ib1p1xx1\");\n        game_help();\n        break;\n    case KEY_F2:\n        gsound_set_master_volume(gsound_get_master_volume() - 2047);\n        break;\n    case KEY_F3:\n        gsound_set_master_volume(gsound_get_master_volume() + 2047);\n        break;\n    case KEY_CTRL_S:\n    case KEY_F4:\n        gsound_play_sfx_file(\"ib1p1xx1\");\n        if (SaveGame(1) == -1) {\n            debug_printf(\"\\n ** Error calling SaveGame()! **\\n\");\n        }\n        break;\n    case KEY_CTRL_L:\n    case KEY_F5:\n        gsound_play_sfx_file(\"ib1p1xx1\");\n        if (LoadGame(LOAD_SAVE_MODE_NORMAL) == -1) {\n            debug_printf(\"\\n ** Error calling LoadGame()! **\\n\");\n        }\n        break;\n    case KEY_F6:\n        if (1) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n\n            int rc = SaveGame(LOAD_SAVE_MODE_QUICK);\n            if (rc == -1) {\n                debug_printf(\"\\n ** Error calling SaveGame()! **\\n\");\n            } else if (rc == 1) {\n                MessageListItem messageListItem;\n                // Quick save game successfully saved.\n                char* msg = getmsg(&misc_message_file, &messageListItem, 5);\n                display_print(msg);\n            }\n        }\n        break;\n    case KEY_F7:\n        if (1) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n\n            int rc = LoadGame(LOAD_SAVE_MODE_QUICK);\n            if (rc == -1) {\n                debug_printf(\"\\n ** Error calling LoadGame()! **\\n\");\n            } else if (rc == 1) {\n                MessageListItem messageListItem;\n                // Quick load game successfully loaded.\n                char* msg = getmsg(&misc_message_file, &messageListItem, 4);\n                display_print(msg);\n            }\n        }\n        break;\n    case KEY_CTRL_V:\n        if (1) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n\n            char version[VERSION_MAX];\n            getverstr(version);\n            display_print(version);\n            display_print(_aDec11199816543);\n        }\n        break;\n    case KEY_ARROW_LEFT:\n        map_scroll(-1, 0);\n        break;\n    case KEY_ARROW_RIGHT:\n        map_scroll(1, 0);\n        break;\n    case KEY_ARROW_UP:\n        map_scroll(0, -1);\n        break;\n    case KEY_ARROW_DOWN:\n        map_scroll(0, 1);\n        break;\n    }\n\n    return 0;\n}\n\n// game_ui_disable\n// 0x443BFC\nvoid game_ui_disable(int a1)\n{\n    if (!game_ui_disabled) {\n        gmouse_3d_off();\n        gmouse_disable(a1);\n        kb_disable();\n        intface_disable();\n        game_ui_disabled = true;\n    }\n}\n\n// game_ui_enable\n// 0x443C30\nvoid game_ui_enable()\n{\n    if (game_ui_disabled) {\n        intface_enable();\n        kb_enable();\n        kb_clear();\n        gmouse_enable();\n        gmouse_3d_on();\n        game_ui_disabled = false;\n    }\n}\n\n// game_ui_is_disabled\n// 0x443C60\nbool game_ui_is_disabled()\n{\n    return game_ui_disabled;\n}\n\n// 0x443C68\nint game_get_global_var(int var)\n{\n    if (var < 0 || var >= num_game_global_vars) {\n        debug_printf(\"ERROR: attempt to reference global var out of range: %d\", var);\n        return 0;\n    }\n\n    return game_global_vars[var];\n}\n\n// 0x443C98\nint game_set_global_var(int var, int value)\n{\n    if (var < 0 || var >= num_game_global_vars) {\n        debug_printf(\"ERROR: attempt to reference global var out of range: %d\", var);\n        return -1;\n    }\n\n    game_global_vars[var] = value;\n\n    return 0;\n}\n\n// game_load_info\n// 0x443CC8\nint game_load_info()\n{\n    return game_load_info_vars(\"data\\\\vault13.gam\", \"GAME_GLOBAL_VARS:\", &num_game_global_vars, &game_global_vars);\n}\n\n// 0x443CE8\nint game_load_info_vars(const char* path, const char* section, int* variablesListLengthPtr, int** variablesListPtr)\n{\n    inven_reset_dude();\n\n    File* stream = db_fopen(path, \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    if (*variablesListLengthPtr != 0) {\n        mem_free(*variablesListPtr);\n        *variablesListPtr = NULL;\n        *variablesListLengthPtr = 0;\n    }\n\n    char string[260];\n    if (section != NULL) {\n        while (db_fgets(string, 258, stream)) {\n            if (strncmp(string, section, 16) == 0) {\n                break;\n            }\n        }\n    }\n\n    while (db_fgets(string, 258, stream)) {\n        if (string[0] == '\\n') {\n            continue;\n        }\n\n        if (string[0] == '/' && string[1] == '/') {\n            continue;\n        }\n\n        char* semicolon = strchr(string, ';');\n        if (semicolon != NULL) {\n            *semicolon = '\\0';\n        }\n\n        *variablesListLengthPtr = *variablesListLengthPtr + 1;\n        *variablesListPtr = (int*)mem_realloc(*variablesListPtr, sizeof(int) * *variablesListLengthPtr);\n\n        if (*variablesListPtr == NULL) {\n            exit(1);\n        }\n\n        char* equals = strchr(string, '=');\n        if (equals != NULL) {\n            sscanf(equals + 1, \"%d\", *variablesListPtr + *variablesListLengthPtr - 1);\n        } else {\n            *variablesListPtr[*variablesListLengthPtr - 1] = 0;\n        }\n    }\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// 0x443E2C\nint game_state()\n{\n    return game_state_cur;\n}\n\n// 0x443E34\nint game_state_request(int a1)\n{\n    if (a1 == GAME_STATE_0) {\n        a1 = GAME_STATE_1;\n    } else if (a1 == GAME_STATE_2) {\n        a1 = GAME_STATE_3;\n    } else if (a1 == GAME_STATE_4) {\n        a1 = GAME_STATE_5;\n    }\n\n    if (game_state_cur != GAME_STATE_4 || a1 != GAME_STATE_5) {\n        game_state_cur = a1;\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x443E90\nvoid game_state_update()\n{\n    int v0;\n\n    v0 = game_state_cur;\n    switch (game_state_cur) {\n    case GAME_STATE_1:\n        v0 = GAME_STATE_0;\n        break;\n    case GAME_STATE_3:\n        v0 = GAME_STATE_2;\n        break;\n    case GAME_STATE_5:\n        v0 = GAME_STATE_4;\n    }\n\n    game_state_cur = v0;\n}\n\n// NOTE: Unused.\n//\n// 0x443EC0\nstatic void game_display_counter(double value)\n{\n    char stringBuffer[16];\n\n    sprintf(stringBuffer, \"%f\", value);\n    display_print(stringBuffer);\n}\n\n// 0x443EF0\nstatic int game_screendump(int width, int height, unsigned char* buffer, unsigned char* palette)\n{\n    MessageListItem messageListItem;\n\n    if (default_screendump(width, height, buffer, palette) != 0) {\n        // Error saving screenshot.\n        messageListItem.num = 8;\n        if (message_search(&misc_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n\n        return -1;\n    }\n\n    // Saved screenshot.\n    messageListItem.num = 3;\n    if (message_search(&misc_message_file, &messageListItem)) {\n        display_print(messageListItem.text);\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x443F50\nstatic void game_unload_info()\n{\n    num_game_global_vars = 0;\n    if (game_global_vars != NULL) {\n        mem_free(game_global_vars);\n        game_global_vars = NULL;\n    }\n}\n\n// 0x443F74\nstatic void game_help()\n{\n    bool isoWasEnabled = map_disable_bk_processes();\n    gmouse_3d_off();\n\n    gmouse_set_cursor(MOUSE_CURSOR_NONE);\n\n    bool colorCycleWasEnabled = cycle_is_enabled();\n    cycle_disable();\n\n    int helpWindowX = 0;\n    int helpWindowY = 0;\n    int win = win_add(helpWindowX, helpWindowY, HELP_SCREEN_WIDTH, HELP_SCREEN_HEIGHT, 0, WINDOW_HIDDEN | WINDOW_FLAG_0x04);\n    if (win != -1) {\n        unsigned char* windowBuffer = win_get_buf(win);\n        if (windowBuffer != NULL) {\n            int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 297, 0, 0, 0);\n            CacheEntry* backgroundHandle;\n            unsigned char* backgroundData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundHandle);\n            if (backgroundData != NULL) {\n                palette_set_to(black_palette);\n                buf_to_buf(backgroundData, HELP_SCREEN_WIDTH, HELP_SCREEN_HEIGHT, HELP_SCREEN_WIDTH, windowBuffer, HELP_SCREEN_WIDTH);\n                art_ptr_unlock(backgroundHandle);\n                win_show(win);\n                loadColorTable(\"art\\\\intrface\\\\helpscrn.pal\");\n                palette_set_to(cmap);\n\n                while (get_input() == -1 && game_user_wants_to_quit == 0) {\n                }\n\n                while (mouse_get_buttons() != 0) {\n                    get_input();\n                }\n\n                palette_set_to(black_palette);\n            }\n        }\n\n        win_delete(win);\n        loadColorTable(\"color.pal\");\n        palette_set_to(cmap);\n    }\n\n    if (colorCycleWasEnabled) {\n        cycle_enable();\n    }\n\n    gmouse_3d_on();\n\n    if (isoWasEnabled) {\n        map_enable_bk_processes();\n    }\n}\n\n// 0x4440B8\nint game_quit_with_confirm()\n{\n    bool isoWasEnabled = map_disable_bk_processes();\n\n    bool gameMouseWasVisible;\n    if (isoWasEnabled) {\n        gameMouseWasVisible = gmouse_3d_is_on();\n    } else {\n        gameMouseWasVisible = false;\n    }\n\n    if (gameMouseWasVisible) {\n        gmouse_3d_off();\n    }\n\n    bool cursorWasHidden = mouse_hidden();\n    if (cursorWasHidden) {\n        mouse_show();\n    }\n\n    int oldCursor = gmouse_get_cursor();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    int rc;\n\n    // Are you sure you want to quit?\n    MessageListItem messageListItem;\n    messageListItem.num = 0;\n    if (message_search(&misc_message_file, &messageListItem)) {\n        rc = dialog_out(messageListItem.text, 0, 0, 169, 117, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_YES_NO);\n        if (rc != 0) {\n            game_user_wants_to_quit = 2;\n        }\n    } else {\n        rc = -1;\n    }\n\n    gmouse_set_cursor(oldCursor);\n\n    if (cursorWasHidden) {\n        mouse_hide();\n    }\n\n    if (gameMouseWasVisible) {\n        gmouse_3d_on();\n    }\n\n    if (isoWasEnabled) {\n        map_enable_bk_processes();\n    }\n\n    return rc;\n}\n\n// 0x44418C\nstatic int game_init_databases()\n{\n    int hashing;\n    char* main_file_name;\n    char* patch_file_name;\n    int patch_index;\n    char filename[MAX_PATH];\n\n    hashing = 0;\n    main_file_name = NULL;\n    patch_file_name = NULL;\n\n    if (config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_HASHING_KEY, &hashing)) {\n        db_enable_hash_table();\n    }\n\n    config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY, &main_file_name);\n    if (*main_file_name == '\\0') {\n        main_file_name = NULL;\n    }\n\n    config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patch_file_name);\n    if (*patch_file_name == '\\0') {\n        patch_file_name = NULL;\n    }\n\n    master_db_handle = db_init(main_file_name, 0, patch_file_name, 1);\n    if (master_db_handle == -1) {\n        GNWSystemError(\"Could not find the master datafile. Please make sure the FALLOUT CD is in the drive and that you are running FALLOUT from the directory you installed it to.\");\n        return -1;\n    }\n\n    config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY, &main_file_name);\n    if (*main_file_name == '\\0') {\n        main_file_name = NULL;\n    }\n\n    config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY, &patch_file_name);\n    if (*patch_file_name == '\\0') {\n        patch_file_name = NULL;\n    }\n\n    critter_db_handle = db_init(main_file_name, 0, patch_file_name, 1);\n    if (critter_db_handle == -1) {\n        db_select(master_db_handle);\n        GNWSystemError(\"Could not find the critter datafile. Please make sure the FALLOUT CD is in the drive and that you are running FALLOUT from the directory you installed it to.\");\n        return -1;\n    }\n\n    for (patch_index = 0; patch_index < 1000; patch_index++) {\n        sprintf(filename, \"patch%03d.dat\", patch_index);\n\n        if (access(filename, 0) == 0) {\n            db_init(filename, 0, NULL, 1);\n        }\n    }\n\n    db_select(master_db_handle);\n\n    return 0;\n}\n\n// 0x444384\nstatic void game_splash_screen()\n{\n    int splash;\n    config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, &splash);\n\n    char path[64];\n    char* language;\n    if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language) && stricmp(language, ENGLISH) != 0) {\n        sprintf(path, \"art\\\\%s\\\\splash\\\\\", language);\n    } else {\n        sprintf(path, \"art\\\\splash\\\\\");\n    }\n\n    File* stream;\n    for (int index = 0; index < SPLASH_COUNT; index++) {\n        char filePath[64];\n        sprintf(filePath, \"%ssplash%d.rix\", path, splash);\n        stream = db_fopen(filePath, \"rb\");\n        if (stream != NULL) {\n            break;\n        }\n\n        splash++;\n\n        if (splash >= SPLASH_COUNT) {\n            splash = 0;\n        }\n    }\n\n    if (stream == NULL) {\n        return;\n    }\n\n    unsigned char* palette = (unsigned char*)mem_malloc(768);\n    if (palette == NULL) {\n        db_fclose(stream);\n        return;\n    }\n\n    unsigned char* data = (unsigned char*)mem_malloc(SPLASH_WIDTH * SPLASH_HEIGHT);\n    if (data == NULL) {\n        mem_free(palette);\n        db_fclose(stream);\n        return;\n    }\n\n    palette_set_to(black_palette);\n    db_fseek(stream, 10, SEEK_SET);\n    db_fread(palette, 1, 768, stream);\n    db_fread(data, 1, SPLASH_WIDTH * SPLASH_HEIGHT, stream);\n    db_fclose(stream);\n\n    int splashWindowX = 0;\n    int splashWindowY = 0;\n    scr_blit(data, SPLASH_WIDTH, SPLASH_HEIGHT, 0, 0, SPLASH_WIDTH, SPLASH_HEIGHT, splashWindowX, splashWindowY);\n    palette_fade_to(palette);\n\n    mem_free(data);\n    mem_free(palette);\n\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, splash + 1);\n}\n"
  },
  {
    "path": "src/game/game.h",
    "content": "#ifndef FALLOUT_GAME_GAME_H_\n#define FALLOUT_GAME_GAME_H_\n\n#include <stdbool.h>\n\n#include \"game/game_vars.h\"\n#include \"game/message.h\"\n\ntypedef enum GameState {\n    GAME_STATE_0,\n    GAME_STATE_1,\n    GAME_STATE_2,\n    GAME_STATE_3,\n    GAME_STATE_4,\n    GAME_STATE_5,\n} GameState;\n\nextern int* game_global_vars;\nextern int num_game_global_vars;\nextern const char* msg_path;\nextern int game_user_wants_to_quit;\n\nextern MessageList misc_message_file;\nextern int master_db_handle;\nextern int critter_db_handle;\n\nint game_init(const char* windowTitle, bool isMapper, int a3, int a4, int argc, char** argv);\nvoid game_reset();\nvoid game_exit();\nint game_handle_input(int eventCode, bool isInCombatMode);\nvoid game_ui_disable(int a1);\nvoid game_ui_enable();\nbool game_ui_is_disabled();\nint game_get_global_var(int var);\nint game_set_global_var(int var, int value);\nint game_load_info();\nint game_load_info_vars(const char* path, const char* section, int* variablesListLengthPtr, int** variablesListPtr);\nint game_state();\nint game_state_request(int a1);\nvoid game_state_update();\nint game_quit_with_confirm();\n\n#endif /* FALLOUT_GAME_GAME_H_ */\n"
  },
  {
    "path": "src/game/game_vars.h",
    "content": "#ifndef GAME_VARS_H\n#define GAME_VARS_H\n\ntypedef enum GameGlobalVar {\n    GVAR_PLAYER_REPUTATION,\n    GVAR_CHILDKILLER_REPUTATION,\n    GVAR_CHAMPION_REPUTATION,\n    GVAR_BERSERKER_REPUTATION,\n    GVAR_BAD_MONSTER,\n    GVAR_GOOD_MONSTER,\n    GVAR_PLAYER_MARRIED,\n    GVAR_ENEMY_ARROYO,\n    GVAR_KNOWLEDGE_HEALING_POWDER,\n    GVAR_KILL_EVIL_PLANTS,\n    GVAR_START_ARROYO_TRIAL,\n    GVAR_REPUTATION_SLAVER,\n    GVAR_REPUTATION_SLAVE_OWNER,\n    GVAR_DEN_MOM_STATUS,\n    GVAR_ENEMY_DEN,\n    GVAR_EXILE_DEN,\n    GVAR_DEN_ANNA_STATUS,\n    GVAR_DEN_WAREHOUSE_ACCESS,\n    GVAR_PLAYER_GOT_CAR,\n    GVAR_DEN_VIC_STATUS,\n    GVAR_DEN_MAGGIE_STILL,\n    GVAR_NUKA_COLA_ADDICT,\n    GVAR_BUFF_OUT_ADDICT,\n    GVAR_MENTATS_ADDICT,\n    GVAR_PSYCHO_ADDICT,\n    GVAR_RADAWAY_ADDICT,\n    GVAR_ALCOHOL_ADDICT,\n    GVAR_LOAD_MAP_INDEX,\n    GVAR_RUNNING_BURNING_GUY,\n    GVAR_VIC_DEVICE,\n    GVAR_SLAVE_RUN,\n    GVAR_SLAVES_COUNT,\n    GVAR_MAGGIE_STATUS,\n    GVAR_SLAVES_LOST,\n    GVAR_SLAVERS_LOST,\n    GVAR_PIP_BOY_ANNA_DIARY,\n    GVAR_FRANKIE_STATUS,\n    GVAR_KARMA_HOLY_WARRIOR,\n    GVAR_KARMA_GUARDIAN_OF_THE_WASTES,\n    GVAR_KARMA_SHIELD_OF_HOPE,\n    GVAR_KARMA_DEFENDER,\n    GVAR_KARMA_WANDERER,\n    GVAR_KARMA_BETRAYER,\n    GVAR_KARMA_SWORD_OF_DESPAIR,\n    GVAR_KARMA_SCOURGE_OF_THE_WASTE,\n    GVAR_KARMA_DEMON_SPAWN,\n    GVAR_MAP_EXIT_TILE,\n    GVAR_TOWN_REP_ARROYO,\n    GVAR_TOWN_REP_KLAMATH,\n    GVAR_TOWN_REP_THE_DEN,\n    GVAR_TOWN_REP_VAULT_CITY,\n    GVAR_TOWN_REP_GECKO,\n    GVAR_TOWN_REP_MODOC,\n    GVAR_TOWN_REP_SIERRA_BASE,\n    GVAR_TOWN_REP_BROKEN_HILLS,\n    GVAR_TOWN_REP_NEW_RENO,\n    GVAR_TOWN_REP_REDDING,\n    GVAR_TOWN_REP_NCR,\n    GVAR_TOWN_REP_BURIED_VAULT,\n    GVAR_TOWN_REP_VAULT_13,\n    GVAR_TOWN_REP_COLUSA,\n    GVAR_TOWN_REP_SAN_FRANCISCO,\n    GVAR_TOWN_REP_ENCLAVE,\n    GVAR_TOWN_REP_ABBEY,\n    GVAR_TOWN_REP_EPA,\n    GVAR_TOWN_REP_PRIMITIVE_TRIBE,\n    GVAR_TOWN_REP_RAIDERS,\n    GVAR_MAP_NEXT_TILE,\n    GVAR_ENEMY_KLAMATH,\n    GVAR_TORR_HARMED,\n    GVAR_TORR_DEAD,\n    GVAR_TORR_MISSING,\n    GVAR_TORR_SEARCH_SUCCESS,\n    GVAR_TRAPPER_RETURNED,\n    GVAR_DUNTONS_ANGRY,\n    GVAR_RUSTLE_FAIL_VIOLENT,\n    GVAR_RUSTLE_FAIL,\n    GVAR_RUSTLE_SUCCESS,\n    GVAR_TORR_GUARD_SUCCESS,\n    GVAR_VAULT_CITIZEN,\n    GVAR_VAULT_PLOW_PROBLEM,\n    GVAR_VAULT_CITIZENSHIP,\n    GVAR_VAULT_GECKO_PLANT,\n    GVAR_VAULT_PLANT_STATUS,\n    GVAR_VAULT_REDDING_PROBLEM,\n    GVAR_JET_QUEST,\n    GVAR_DAY_PASS_SHOWN,\n    GVAR_VAULT_CITIZEN_TEST,\n    GVAR_VAULT_RAIDERS,\n    GVAR_VAULT_DELIVER_HOLODISK,\n    GVAR_VAULT_FIND_THOMAS,\n    GVAR_QUEST_VAULT_CITIZEN,\n    GVAR_QUEST_PLOW_PROBLEM,\n    GVAR_QUEST_GECKO_PLANT,\n    GVAR_QUEST_REDDING_PROBLEM,\n    GVAR_QUEST_JET_QUEST,\n    GVAR_QUEST_RAIDERS,\n    GVAR_QUEST_DELIVER_HOLODISK,\n    GVAR_QUEST_FIND_THOMAS,\n    GVAR_MODOC_KILL_ALL_BRAHMIN_TIME,\n    GVAR_QUEST_VIC_DEVICE,\n    GVAR_QUEST_MAGGIE_STILL,\n    GVAR_QUEST_KILL_EVIL_PLANTS,\n    GVAR_QUEST_RUSTLE_CATTLE,\n    GVAR_BUST_SKEEVE,\n    GVAR_DUDE_STOMACH,\n    GVAR_MODOC_FAMILY_FEUD_SEED_ONE,\n    GVAR_MODOC_FAMILY_FEUD_SEED_TWO,\n    GVAR_MODOC_BRAHMIN_SEED,\n    GVAR_MODOC_KARL_PIP,\n    GVAR_MODOC_KARL_SEED,\n    GVAR_MODOC_VERMIN_HUNTER_SEED,\n    GVAR_MODOC_GHOST_FARM_SEED,\n    GVAR_SLAG_ATTACK,\n    GVAR_JONNY_STATE,\n    GVAR_JONNY_TILE,\n    GVAR_MODOC_BRAHMIN_ALIVE,\n    GVAR_MODOC_DOGS_ALIVE,\n    GVAR_MODOC_TOOL_FLAG,\n    GVAR_MODOC_SLAUGHTER_BESS_TIME,\n    GVAR_KARL_STATE,\n    GVAR_MODOC_BODIES,\n    GVAR_MODOC_SLAUGHTER_FLAG,\n    GVAR_MODOC_ROSE_FLAG,\n    GVAR_MODOC_TANNERY_FLAG,\n    GVAR_MODOC_POST_FLAG,\n    GVAR_HOSTILE_SLAVE_COUNT,\n    GVAR_LADDIE_STATE,\n    GVAR_LADDIE_TILE,\n    GVAR_MODOC_JONNY_HOME,\n    GVAR_MODOC_SPOKE_PROTECTOR,\n    GVAR_MODOC_MESSAGE,\n    GVAR_MUTATE,\n    GVAR_MUTATE_WHEN,\n    GVAR_SALVATORE_FAMILY_COUNTER,\n    GVAR_BISHOP_FAMILY_COUNTER,\n    GVAR_MORDINO_FAMILY_COUNTER,\n    GVAR_ENEMY_VAULT_CITY,\n    GVAR_VAULT_GET_LYNETTE_REWARD,\n    GVAR_VAULT_GET_MCCLURE_PART,\n    GVAR_VAULT_SERVANT,\n    GVAR_VAULT_VILLAGE,\n    GVAR_QUEST_VAULT_SERVANT,\n    GVAR_QUEST_VAULT_VILLAGE,\n    GVAR_VAULT_MONSTER_COUNT,\n    GVAR_SERVANT_RAID_DATE,\n    GVAR_ENEMY_VAULT_VILLAGE,\n    GVAR_BROKEN_HILLS_FRAUD,\n    GVAR_VAULT_BEEN_TO_RAIDERS,\n    GVAR_SIERRA_BASE_CONTAMINATION_TIMER,\n    GVAR_SIERRA_BASE_LEVEL_BREACH,\n    GVAR_SIERRA_BASE_ALERT,\n    GVAR_SIERRA_BASE_ENEMY,\n    GVAR_SIERRA_BASE_POWER,\n    GVAR_SIERRA_BASE_SECURITY,\n    GVAR_BRAIN_BOT_BRAIN,\n    GVAR_SIERRA_LOCKOUT,\n    GVAR_SIERRA_PASSWORD,\n    GVAR_GECKO_ECON_DISK,\n    GVAR_GECKO_REQ_FORM,\n    GVAR_GECKO_SKEETER_PART,\n    GVAR_GECKO_ANKH,\n    GVAR_DEN_SMITTY_PART,\n    GVAR_MCCLURE_KNOWN,\n    GVAR_HOLODISK_SIERRA_EVACUATION,\n    GVAR_HOLODISK_SIERRA_MED_LOG,\n    GVAR_HOLODISK_SIERRA_EXP_LOG,\n    GVAR_GECKO_SKEETER_STATUS,\n    GVAR_NCR_TANDI_WORK,\n    GVAR_NCR_TANDI_JOB_ACCEPT,\n    GVAR_NCR_BEAT_HOSS,\n    GVAR_NCR_SQUAT_DEAL,\n    GVAR_NCR_V15_DARION_DEAD,\n    GVAR_NCR_V15_DARION_DEAL,\n    GVAR_NEWRENO_SNUFF_WESTIN,\n    GVAR_NEWRENO_SNUFF_CARLSON,\n    GVAR_VAULT13_CLEAR,\n    GVAR_NCR_SPY_KNOWN,\n    GVAR_NCR_TANDI_WARN_CARLSON,\n    GVAR_RUSTLE_ACCEPT,\n    GVAR_RUSTLE_REFUSE,\n    GVAR_RUSTLE_REWARD,\n    GVAR_TORR_GUARD_STATUS,\n    GVAR_ARROYO_SPEAR,\n    GVAR_RUSTLE_OVER,\n    GVAR_NCR_BRAHMIN_PROTECT,\n    GVAR_NCR_DEATHCLAW_DEN,\n    GVAR_SLAVE_RUN_KILLED,\n    GVAR_DUNTON_DEAD,\n    GVAR_NCR_CAR_JACKED,\n    GVAR_NCR_MERK_WORK,\n    GVAR_ARROYO_DOG,\n    GVAR_HAVE_MUTATED,\n    GVAR_MUTATE_STAGE,\n    GVAR_PLAYER_SEX_LEVEL,\n    GVAR_NCR_VORTIS_QUEST_STATE,\n    GVAR_NCR_RANGERS_KNOWN,\n    GVAR_SMILEY_STATUS,\n    GVAR_STILL_STATUS,\n    GVAR_STILL_FAILURE,\n    GVAR_GRAVE_FLAGS_1,\n    GVAR_GRAVE_FLAGS_2,\n    GVAR_TORR_BRAHMIN_KILLED,\n    GVAR_ENEMY_TORR,\n    GVAR_ENEMY_DUNTON,\n    GVAR_ENEMY_SMILEY,\n    GVAR_NCR_SCMERK_HEREBEFORE,\n    GVAR_NCR_SCMERK_HOSTILE,\n    GVAR_NCR_SCMERK_PERSONAL_ENEMY,\n    GVAR_NCR_SCMERK_STATUS,\n    GVAR_NCR_SCMERK_SEED_STATUS,\n    GVAR_NCR_LENNY_MET,\n    GVAR_NCR_ELRON_ADJUST,\n    GVAR_NCR_FAKE_VAULT13_MAP,\n    GVAR_NCR_FAKE_VAULT13_HOLODISK,\n    GVAR_MILITARY_BASE_FLAGS,\n    GVAR_WRIGHT_FAMILY_COUNTER,\n    GVAR_NCR_MIRA_STATE,\n    GVAR_NCR_ROPE_KNOWN,\n    GVAR_NEW_RENO_WARNING_TIMER,\n    GVAR_HOLODISK_MB_OUTSIDE,\n    GVAR_HOLODISK_MB_LEVEL_1,\n    GVAR_HOLODISK_MB_LEVEL_2,\n    GVAR_HOLODISK_MB_LEVEL_3,\n    GVAR_HOLODISK_MB_LEVEL_4,\n    GVAR_NCR_GTEGRD_ATTACK,\n    GVAR_NCR_GATE_NIGHT,\n    GVAR_NCR_ENCLAVE_INFO,\n    GVAR_NCR_WESTIN_SEED,\n    GVAR_NCR_DOROTHY_SEED,\n    GVAR_NEW_RENO_MADE_MAN,\n    GVAR_NEW_RENO_PRIZEFIGHTER,\n    GVAR_NEW_RENO_PORN_STAR,\n    GVAR_VAULT13_FOUND_GECK,\n    GVAR_NCR_POWER_ON,\n    GVAR_SULIK_FREE,\n    GVAR_TORR_SEARCH_ACCEPT,\n    GVAR_NCR_HENRY_HYPO,\n    GVAR_ENEMY_GECKO,\n    GVAR_GECKO_COOLANT,\n    GVAR_NCR_POWERPLANT,\n    GVAR_NCR_PLAYER_RANGER,\n    GVAR_NCR_JACK_STATE,\n    GVAR_8_BALL_TOILET_SECRET,\n    GVAR_8_BALL_TRASH_SECRET,\n    GVAR_NCR_GENERIC_STATE,\n    GVAR_NEW_RENO_MCGEE_SEED,\n    GVAR_NEW_RENO_MCGEE_KNOWN,\n    GVAR_NEW_RENO_MCGEE_ATTACKED,\n    GVAR_GECKO_BRAIN_DEAD,\n    GVAR_GECKO_BRAIN_FRIEND,\n    GVAR_SALVATORE_WARNINGS,\n    GVAR_BISHOP_WARNINGS,\n    GVAR_MORDINO_WARNINGS,\n    GVAR_WRIGHT_WARNINGS,\n    GVAR_NEW_RENO_BISHOP,\n    GVAR_NCR_SNUFF_BISHOP,\n    GVAR_NEW_RENO_CARLSON_PRICE,\n    GVAR_NEW_RENO_WESTIN_PRICE,\n    GVAR_NEW_RENO_HAS_REP_PRIZEFIGHTER,\n    GVAR_NEW_RENO_ANGELA,\n    GVAR_PLACEHOLDER_002,\n    GVAR_NCR_ELISE_SEED,\n    GVAR_NEW_RENO_MRS_BISHOP,\n    GVAR_NCR_FELIX_SEED,\n    GVAR_NCR_BISHOP_PRICE,\n    GVAR_NCR_CATTLE_DRIVE,\n    GVAR_NCR_CATTLE_TIME_MIN,\n    GVAR_NCR_CATTLE_TIME_MAX,\n    GVAR_CARAVAN_STATUS,\n    GVAR_CARAVAN_START,\n    GVAR_CARAVAN_END,\n    GVAR_CARAVAN_DRIVERS,\n    GVAR_CARAVAN_GUARDS,\n    GVAR_CARAVAN_CARTS,\n    GVAR_CARAVAN_ENCOUNTERS,\n    GVAR_CARAVAN_BRAHMIN,\n    GVAR_CARAVAN_MASTERS,\n    GVAR_CARAVAN_DRIVERS_TOTAL,\n    GVAR_CARAVAN_GUARDS_TOTAL,\n    GVAR_CARAVAN_CARTS_TOTAL,\n    GVAR_CARAVAN_BRAHMIN_TOTAL,\n    GVAR_CARAVAN_MASTERS_TOTAL,\n    GVAR_CARAVAN_ENCOUNTERS_TOTAL,\n    GVAR_NEW_RENO_MYRON,\n    GVAR_NEW_RENO_WRIGHT_FLAGS,\n    GVAR_NEW_RENO_WRIGHT_MYSTERY,\n    GVAR_DEN_SLAVER_WARNINGS,\n    GVAR_V13_V15_DALIA_STATE,\n    GVAR_PARTY_CHILDKILLER,\n    GVAR_MODOC_STAGE_TIMER,\n    GVAR_MODOC_STAGE_STATE,\n    GVAR_REDDING_WHORE_CUT,\n    GVAR_V15_SEED_STATUS,\n    GVAR_TOWN_REP_VAULT_15,\n    GVAR_ADDICT_TRAGIC,\n    GVAR_ADDICT_JET,\n    GVAR_MODOC_GENERIC_FLAG_1,\n    GVAR_DEN_CEASAR_STATUS,\n    GVAR_MODOC_BRAHMIN_ESCAPED,\n    GVAR_BH_CHAD,\n    GVAR_BH_FTM,\n    GVAR_BH_MINE,\n    GVAR_BH_JAIL,\n    GVAR_BH_CONSPIRACY,\n    GVAR_BH_MISSING,\n    GVAR_BH_MIGHTY_MAN,\n    GVAR_BH_MINING,\n    GVAR_TOWN_REP_GHOST_FARM,\n    GVAR_ENEMY_BROKEN_HILLS,\n    GVAR_SLAG_CNT,\n    GVAR_NEW_RENO_SALVATORE_RESPECT,\n    GVAR_NEW_RENO_TRACK_LLOYD,\n    GVAR_NEW_RENO_GUARD_ASSIGNMENT,\n    GVAR_NEW_RENO_FLAG_1,\n    GVAR_NEW_RENO_SALVATORE,\n    GVAR_NEW_RENO_TRIBUTE,\n    GVAR_NEW_RENO_SALVATORE_PISTOL,\n    GVAR_NEW_RENO_ESCAPE,\n    GVAR_GRAVES_UNEARTHED,\n    GVAR_MOORE_STATE,\n    GVAR_MOORE_ACCEPT_DELIVERY,\n    GVAR_MOORE_REFUSE_DELIVERY,\n    GVAR_SPECIAL_ENCOUNTER_FLAGS,\n    GVAR_BH_BOSS,\n    GVAR_BH_HENCH_COUNT,\n    GVAR_BH_MALE_NAMES_USED,\n    GVAR_BH_FEMALE_NAMES_USED,\n    GVAR_BH_HENCH_KILLED,\n    GVAR_BH_CHECKED,\n    GVAR_BH_CARAVAN,\n    GVAR_BH_RANK_KILLED,\n    GVAR_REDDING_EXCAVATOR_CHIP,\n    GVAR_REDDING_JET_LEVEL,\n    GVAR_MAYOR_REDDING_STATUS,\n    GVAR_REDDING_MARGE_STATUS,\n    GVAR_REDDING_DAN_STATUS,\n    GVAR_REDDING_JOHNSON_STATUS,\n    GVAR_CATTLE_DRIVE_CARAVAN,\n    GVAR_MEDICINE_CARAVAN,\n    GVAR_JET_CARAVAN,\n    GVAR_GOLD_CARAVAN,\n    GVAR_REDDING_CARAVAN_STATUS,\n    GVAR_NEW_RENO_SAD,\n    GVAR_NEW_RENO_WRIGHT_STILL,\n    GVAR_NEW_RENO_FLAG_2,\n    GVAR_NEW_RENO_WRIGHT_STILL_MISSION,\n    GVAR_NEW_RENO_JULES_KITTY,\n    GVAR_NEW_RENO_STOLEN_CAR,\n    GVAR_NEW_RENO_JULES_ELDRIDGE,\n    GVAR_GRUTHAR_DSTATUS,\n    GVAR_WHIRLY,\n    GVAR_MYSTERIOUS_STRANGER,\n    GVAR_MYSTERIOUS_STRANGER_LEVEL,\n    GVAR_NEW_RENO_DELIVERY,\n    GVAR_NEW_RENO_EXTORTION_BROS,\n    GVAR_NEW_RENO_ASSASSINATION,\n    GVAR_NEW_RENO_LIL_JESUS_REFERS,\n    GVAR_SEX_COUNTER,\n    GVAR_RND_SALES_NAME,\n    GVAR_RND_SALES_ENCOUNTER,\n    GVAR_SAN_FRAN_FLAGS,\n    GVAR_SAN_FRAN_SUB,\n    GVAR_SAN_FRAN_TANKER,\n    GVAR_SAN_FRAN_SHIHACKED,\n    GVAR_SAN_FRAN_BADGER,\n    GVAR_SAN_FRAN_ELRON,\n    GVAR_SAN_FRAN_SPLEEN,\n    GVAR_KNOW_DOC_HOLIDAY,\n    GVAR_DUMAR_STATUS,\n    GVAR_NEW_RENO_JET_SOURCE,\n    GVAR_DEN_BECKY_JOB,\n    GVAR_HOLY_GRENADE,\n    GVAR_RAIDERS_FLAGS,\n    GVAR_DEN_FRED_STATUS,\n    GVAR_DEN_DEREK_STATUS,\n    GVAR_DEN_ROBBY_STATUS,\n    GVAR_RAIDERS_COUNT,\n    GVAR_DEN_HEATHER_STATUS,\n    GVAR_SUPER_CAR,\n    GVAR_BAR_BRAWL,\n    GVAR_WADE_STATUS,\n    GVAR_STANWELL_STATUS,\n    GVAR_SAVINELLI_STATUS,\n    GVAR_IMPLANTS_KNOWN,\n    GVAR_FROG_MORTON,\n    GVAR_REDDING_MORTON_BROTHERS,\n    GVAR_REDDING_SHERIFF,\n    GVAR_MODOC_ENDINGS,\n    GVAR_WANAMINGO_OCCUPADO,\n    GVAR_QUEST_RAT_GOD,\n    GVAR_QUEST_RESCUE_TORR,\n    GVAR_VAULT_JET_SOURCE,\n    GVAR_QUEST_SUPER_REPAIR_KIT,\n    GVAR_QUEST_PLASMA_TRANSFORMER,\n    GVAR_GECKO_MELTDOWN,\n    GVAR_QUEST_REPAIR_POWER_PLANT,\n    GVAR_QUEST_OPTIMIZE_POWER_PLANT,\n    GVAR_PARTY_NO_FOLLOW,\n    GVAR_RND_KAGA_STATE,\n    GVAR_VAULT_CITY_VENT,\n    GVAR_VAULT_PIP,\n    GVAR_MODOC_GENERIC_FLAG_2,\n    GVAR_SLAUGHTER_SLAG_TIME,\n    GVAR_PIPBOY_TOUR_GUIDE,\n    GVAR_PIPBOY_CREDITS,\n    GVAR_NCR_GRANT_HOSTILE,\n    GVAR_NCR_WFIELD_NOTIFY,\n    GVAR_ENDGAME_MOVIE_ARROYO,\n    GVAR_ENDGAME_MOVIE_MODOC,\n    GVAR_ENDGAME_MOVIE_DEN,\n    GVAR_ENDGAME_MOVIE_VAULT_CITY,\n    GVAR_ENDGAME_MOVIE_RENO,\n    GVAR_ENDGAME_MOVIE_RENO_ADD1,\n    GVAR_ENDGAME_MOVIE_RENO_ADD2,\n    GVAR_ENDGAME_MOVIE_RENO_ADD3,\n    GVAR_ENDGAME_MOVIE_RENO_ADD4,\n    GVAR_ENDGAME_MOVIE_GECKO,\n    GVAR_ENDGAME_MOVIE_REDDING,\n    GVAR_ENDGAME_MOVIE_BROKEN_HILLS,\n    GVAR_ENDGAME_MOVIE_NCR,\n    GVAR_ENDGAME_MOVIE_VAULT_15,\n    GVAR_ENDGAME_MOVIE_VAULT_13,\n    GVAR_ENDGAME_MOVIE_SAN_FRAN_SHI,\n    GVAR_ENDGAME_MOVIE_SAN_FRAN_ELRON,\n    GVAR_ENDGAME_MOVIE_SAN_FRAN_PUNKS,\n    GVAR_SAN_FRAN_STRUGGLE,\n    GVAR_SAN_FRAN_ELRON_WHIRLY,\n    GVAR_DR_TROY_STATUS,\n    GVAR_V13_STATUS_FLAGS,\n    GVAR_GECKO_TIMER_MELTDOWN,\n    GVAR_ENCLAVE_POWER_PLANT,\n    GVAR_ENCLAVE_GRANITE_JOINED,\n    GVAR_ENCLAVE_ALARM,\n    GVAR_ENCLAVE_TIMER,\n    GVAR_ENCLAVE_REACTOR,\n    GVAR_VAULT_LYNETTE_STATUS,\n    GVAR_DOC_JOHNSON_STATUS,\n    GVAR_NCR_GEN_FLAGS,\n    GVAR_CAR_BLOWER,\n    GVAR_ENCLAVE_COMPUTER,\n    GVAR_ENCLAVE_MARTIN,\n    GVAR_ENCLAVE_ELDER,\n    GVAR_JAIL_BREAK,\n    GVAR_SAN_FRAN_ARMOR,\n    GVAR_DEN_FLAG_1,\n    GVAR_DEN_FLAG_2,\n    GVAR_DEN_FLAG_3,\n    GVAR_SAN_FRAN_SPLEEN_TIME,\n    GVAR_PLAYER_WAS_MARRIED,\n    GVAR_DEN_SMITTY_DELIVER,\n    GVAR_SMITTY_DELIVER_TIME,\n    GVAR_DEN_VIC_KNOWN,\n    GVAR_CAR_UPGRADE_FUEL_CELL_REGULATOR,\n    GVAR_DEN_GANGWAR,\n    GVAR_NEW_RENO_CAR_UPGRADE,\n    GVAR_NEW_RENO_SUPER_CAR,\n    GVAR_DEN_SEE_VIC,\n    GVAR_STILL_START,\n    GVAR_QUEST_JOSHUA,\n    GVAR_ARDIN_FREEDOM,\n    GVAR_TOTAL_WANAMINGOS,\n    GVAR_SAN_FRAN_DAVE,\n    GVAR_VC_MET_ED,\n    GVAR_FRED_MONEY,\n    GVAR_CAN_ASK_ARDIN_ABOUT_SMILEY,\n    GVAR_VAULT_USED_TEACHING_SYSTEM,\n    GVAR_DEN_GANG_1_COUNT,\n    GVAR_DEN_GANG_2_COUNT,\n    GVAR_DEN_GANG_D_DAY,\n    GVAR_DEN_METZGER_GANG_KILL_TIMER,\n    GVAR_DEN_GANG_TRAP,\n    GVAR_DEN_GANG_DOOR,\n    GVAR_V15_CRISSY_QUEST,\n    GVAR_V15_KILL_DARION,\n    GVAR_V15_NCR_DEAL,\n    GVAR_V15_NCR_SPY,\n    GVAR_SAN_FRAN_EG_NOTIFY,\n    GVAR_SAN_FRAN_EG_A_OBJ,\n    GVAR_ELRON_GUARDS,\n    GVAR_ARROYO_RETURN_GECK,\n    GVAR_NCR_BRAHMN_QST,\n    GVAR_NCR_DRPAPR_QST,\n    GVAR_NCR_ELMBISHOP_QST,\n    GVAR_NCR_LYNETTE_HOLO_QST,\n    GVAR_NCR_ENLONE_LETTER_QST,\n    GVAR_NCR_KILL_ELRON_QST,\n    GVAR_V13_COMP_QST,\n    GVAR_V13_GORIS_QST,\n    GVAR_GIMP_FLAG,\n    GVAR_GECKO_ASSIGNED,\n    GVAR_MODOC_SHITTY_DEATH,\n    GVAR_ENEMY_REDDING,\n    GVAR_VAL_TOOLS,\n    GVAR_FALLOUT_2,\n    GVAR_NEW_RENO_FLAG_3,\n    GVAR_MR_BISHOP_SAFE,\n    GVAR_VAULT_BOOZE_SMUGGLING,\n    GVAR_ENCLAVE_COUNTDOWN,\n    GVAR_ENCLAVE_FRANK_DEAD,\n    GVAR_NCR_BRAHMIN_QST,\n    GVAR_NEW_RENO_KITTY_MAGAZINES,\n    GVAR_NCR_FREE_SLAVES_QST,\n    GVAR_NEW_RENO_STUART_DEAL,\n    GVAR_NEW_RENO_FIGHT_LEVEL,\n    GVAR_ENEMY_VAULT_COURTYARD,\n    GVAR_NEW_RENO_ROUND_NUMBER,\n    GVAR_NEW_RENO_ROUND_TIME,\n    GVAR_NEW_RENO_DUDE_SCORE,\n    GVAR_NEW_RENO_BOXER_SCORE,\n    GVAR_NEW_RENO_FIGHT_STATUS,\n    GVAR_NAVARRO_BASE_ALERT,\n    GVAR_NAVARRO_FOB,\n    GVAR_NAVARRO_K9,\n    GVAR_NAVARRO_POWER_CENTER,\n    GVAR_NAVARRO_VERTIBIRDS,\n    GVAR_STANWELL_PAYOUT,\n    GVAR_WADE_PAYOUT,\n    GVAR_SAVINE_PAYOUT,\n    GVAR_SAN_FRAN_SHI_WHIRLY,\n    GVAR_SIERRA_GNN_HOLODISK,\n    GVAR_SIERRA_MISSION_HOLODISK,\n    GVAR_ELRON_HOLODISK,\n    GVAR_NEW_RENO_KILL_DADDY_WEAPON,\n    GVAR_READ_FRANCIS_NOTE,\n    GVAR_ENEMY_CONSPIRATORS,\n    GVAR_MARCUS_DEAD,\n    GVAR_RAIDER_SECRET_ENTRANCE_KNOWN,\n    GVAR_COMING_FROM_INSIDE_RAIDERS,\n    GVAR_VAULT_STARK_RECON,\n    GVAR_NEW_RENO_MRS_BISHOP_COMBINATION,\n    GVAR_TALKED_TO_ELDER,\n    GVAR_SAN_FRAN_FUEL_TANK_QST,\n    GVAR_SAN_FRAN_NAV_TANK_QST,\n    GVAR_SAN_FRAN_FOB_TANK_QST,\n    GVAR_SAN_FRAN_ELRON_GAS_QST,\n    GVAR_SAN_FRAN_BADGER_GFRIEND_QST,\n    GVAR_SAN_FRAN_LOPAN_KDRAGON_QST,\n    GVAR_SAN_FRAN_DRAGON_KLOPAN_QST,\n    GVAR_SAN_FRAN_ARMOR_QST,\n    GVAR_FINISHED_STARK_RECON,\n    GVAR_VAULT_CITY_DESIGNER_NOTES,\n    GVAR_BH_POWER,\n    GVAR_NEW_RENO_SUSPECT_JJJ,\n    GVAR_NEW_RENO_SUSPECT_JULES,\n    GVAR_NEW_RENO_SUSPECT_LIL_JESUS,\n    GVAR_NEW_RENO_SUSPECT_RENESCO,\n    GVAR_NEW_RENO_WESTIN_SNUFF_PIP,\n    GVAR_NEW_RENO_CARLSON_SNUFF_PIP,\n    GVAR_NEW_RENO_ELDRIDGE_PISTOL_QUEST,\n    GVAR_DEN_CAR_PART_PIP,\n    GVAR_DEN_ANNA_LOCKET_PIP,\n    GVAR_NEW_RENO_POISON_STILL_TIME,\n    GVAR_SAN_FRAN_WONG_EAT_TIME,\n    GVAR_NAVARRO_XARN,\n    GVAR_SAN_FRAN_KILL_OZ9_QST,\n    GVAR_NEW_RENO_ETHYL_MEETING_TIME,\n    GVAR_SAN_FRAN_VERTI_STEAL_SHI_QST,\n    GVAR_SAN_FRAN_VERTI_STEAL_ELE_QST,\n    GVAR_SAN_FRAN_KILL_EMP_QST,\n    GVAR_SAN_FRAN_VERTI_SHI_QST,\n    GVAR_SAN_FRAN_VERTI_ELE_QST,\n    GVAR_BROKEN_HILLS_CARAVAN_POOCH_SCREW,\n    GVAR_CHAD_DEAD,\n    GVAR_SAN_FRAN_JASHUA_STATUS,\n    GVAR_SAN_FRAN_BOS_QUEST,\n    GVAR_NCR_GUARDS_CHECK_OBJ,\n    GVAR_ENEMY_BANK_GUARDS,\n    GVAR_ENCLAVE_TURRET_GUARD,\n    GVAR_ENCLAVE_TURRET_DETENTION,\n    GVAR_ENCLAVE_TURRET_SCIENCE,\n    GVAR_ENCLAVE_TURRET_PRESIDENT,\n    GVAR_ENCLAVE_TURRET_MAIN,\n    GVAR_HOLODISK_ENCLAVE_SECURITY,\n    GVAR_HOLODISK_ENCLAVE_STATE,\n    GVAR_HOLODISK_ENCLAVE_WORD,\n    GVAR_HOLODISK_ENCLAVE_CHEMICAL,\n    GVAR_HOLODISK_ENCLAVE_ATOMIC,\n    GVAR_ENCLAVE_TURRET_HELP_PLAYER,\n    GVAR_NEW_RENO_GUARD_MESSAGE_TIMER,\n    GVAR_MORTON_GANG,\n    GVAR_GECKO_WORKING_ON_PLANT,\n    GVAR_VIGNETTE_SEQUENCE,\n    GVAR_PLANT_SCHEDULED_FOR_CHANGE,\n    GVAR_DROP_PLAYER_BY_VAULT_8,\n    GVAR_ENCLAVE_COM_LINE,\n    GVAR_LEFT_CAR_AT_RAIDERS,\n    GVAR_RAIDERS_CAR_ELEVATION,\n    GVAR_SEXPERT,\n    GVAR_GIGALO,\n    GVAR_DUDE_VIRGIN,\n    GVAR_MADE_MAN_SALVATORE,\n    GVAR_MADE_MAN_BISHOP,\n    GVAR_MADE_MAN_MORDINO,\n    GVAR_MADE_MAN_WRIGHT,\n    GVAR_NCR_SPY_HOLO_DOWNLOAD,\n    GVAR_NCR_HISTORY_HOLO_DOWNLOAD,\n    GVAR_NCR_WESTIN_HOLO_DOWNLOAD,\n    GVAR_TYPHON_QUEST_STATUS,\n    GVAR_8_BALL_VAULT_TERMINAL,\n    GVAR_RAIDERS_DEAD,\n    GVAR_KLAMATH_GENERATOR,\n    GVAR_ENTERED_GUARDIAN,\n    GVAR_BATH_HOUSE_REJECT,\n    GVAR_SKYNET,\n    GVAR_SPECIAL_ENCOUNTER_BRIDGE,\n    GVAR_SPECIAL_ENCOUNTER_HOLY2,\n    GVAR_SPECIAL_ENCOUNTER_TOXIC,\n    GVAR_SPECIAL_ENCOUNTER_PARIAH,\n    GVAR_SPECIAL_ENCOUNTER_BRAHMIN,\n    GVAR_SPECIAL_ENCOUNTER_WHALE,\n    GVAR_SPECIAL_ENCOUNTER_HEAD,\n    GVAR_SPECIAL_ENCOUNTER_SHUTTLE,\n    GVAR_SPECIAL_ENCOUNTER_GUARDIAN,\n    GVAR_SPECIAL_ENCOUNTER_HOLY1,\n    GVAR_SPECIAL_ENCOUNTER_WOODSMAN,\n    GVAR_GECKO_FIND_WOODY,\n    GVAR_SPECIAL_ENCOUNTER_CAFE,\n    GVAR_GECKO_DESCENDANT_KNOWN,\n    GVAR_FIND_VIC,\n    GVAR_SPECIAL_ENCOUNTER_UNWASHED,\n    GVAR_KLAMATH_SCORPIONS_KILLED,\n    GVAR_KLAMATH_SCORPIONS_TOTAL,\n    GVAR_ENCLAVE_ENEMY_GUARD,\n    GVAR_ENCLAVE_ENEMY_PRESIDENT,\n    GVAR_ENCLAVE_ENEMY_TRAPS,\n    GVAR_ENCLAVE_ENEMY_REACTOR,\n    GVAR_ENCLAVE_ENEMY_DETENTION,\n    GVAR_TOWN_REP_NAVARRO,\n    GVAR_GAVE_GECK_EXP,\n    GVAR_DUDE_START_SEQ_1,\n    GVAR_MODOC_GHOST_SEED_PIP,\n    GVAR_PARTY_MEMBERS_HIDDEN,\n    GVAR_CAR_PLACED_TILE,\n    GVAR_RESERVED_VAR1,\n    GVAR_RESERVED_VAR2,\n    GVAR_RESERVED_VAR3,\n    GVAR_RESERVED_VAR4,\n    GVAR_RESERVED_VAR5,\n    GVAR_RESERVED_VAR6,\n    GVAR_RESERVED_VAR7,\n    GVAR_RESERVED_VAR8,\n    GVAR_RESERVED_VAR9,\n    GVAR_RESERVED_VAR10,\n    GVAR_RESERVED_VAR11,\n    GVAR_RESERVED_VAR12,\n    GVAR_RESERVED_VAR13,\n    GVAR_RESERVED_VAR14,\n    GVAR_RESERVED_VAR15,\n    GVAR_RESERVED_VAR16,\n    GVAR_RESERVED_VAR17,\n    GVAR_RESERVED_VAR18,\n    GVAR_RESERVED_VAR19,\n    GVAR_RESERVED_VAR20,\n    GVAR_RESERVED_VAR21,\n    GVAR_RESERVED_VAR22,\n    GVAR_RESERVED_VAR23,\n    GVAR_RESERVED_VAR24,\n    GVAR_RESERVED_VAR25,\n    GVAR_RESERVED_VAR26,\n    GVAR_RESERVED_VAR27,\n    GVAR_RESERVED_VAR28,\n    GVAR_RESERVED_VAR29,\n    GVAR_RESERVED_VAR30,\n    GVAR_RESERVED_VAR31,\n    GVAR_RESERVED_VAR32,\n    GVAR_RESERVED_VAR33,\n    GVAR_RESERVED_VAR34,\n    GVAR_RESERVED_VAR35,\n    GVAR_RESERVED_VAR36,\n    GVAR_RESERVED_VAR37,\n    GVAR_RESERVED_VAR38,\n    GVAR_RESERVED_VAR39,\n    GVAR_RESERVED_VAR40,\n    GVAR_RESERVED_VAR41,\n    GVAR_RESERVED_VAR42,\n    GVAR_RESERVED_VAR43,\n    GVAR_RESERVED_VAR44,\n    GVAR_RESERVED_VAR45,\n    GVAR_RESERVED_VAR46,\n    GVAR_RESERVED_VAR47,\n    GVAR_RESERVED_VAR48,\n    GVAR_RESERVED_VAR49,\n    GVAR_RESERVED_VAR50,\n    GVAR_RESERVED_VAR51,\n    GVAR_RESERVED_VAR52,\n    GVAR_RESERVED_VAR53,\n    GVAR_RESERVED_VAR54,\n    GVAR_RESERVED_VAR55,\n    GVAR_RESERVED_VAR56,\n    GVAR_RESERVED_VAR57,\n    GVAR_RESERVED_VAR58,\n    GVAR_RESERVED_VAR59,\n    GVAR_MODOC_JONNY_PIP,\n    GVAR_NEW_RENO_FLAG_4,\n    GVAR_PATCH_INVAIDITATOR,\n} GameGlobalVar;\n\n#endif /* GAME_VARS_H */\n"
  },
  {
    "path": "src/game/gconfig.c",
    "content": "#include \"game/gconfig.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n// A flag indicating if [game_config] was initialized.\n//\n// 0x5186D0\nstatic bool gconfig_initialized = false;\n\n// fallout2.cfg\n//\n// 0x58E950\nConfig game_config;\n\n// NOTE: There are additional 4 bytes following this array at 0x58EA7C, which\n// probably means it's size is 264 bytes.\n//\n// 0x58E978\nstatic char gconfig_file_name[FILENAME_MAX];\n\n// Inits main game config.\n//\n// [isMapper] is a flag indicating whether we're initing config for a main\n// game, or a mapper. This value is `false` for the game itself.\n//\n// [argc] and [argv] are command line arguments. The engine assumes there is\n// at least 1 element which is executable path at index 0. There is no\n// additional check for [argc], so it will crash if you pass NULL, or an empty\n// array into [argv].\n//\n// The executable path from [argv] is used resolve path to `fallout2.cfg`,\n// which should be in the same folder. This function provide defaults if\n// `fallout2.cfg` is not present, or cannot be read for any reason.\n//\n// Finally, this function merges key-value pairs from [argv] if any, see\n// [config_cmd_line_parse] for expected format.\n//\n// 0x444570\nbool gconfig_init(bool isMapper, int argc, char** argv)\n{\n    if (gconfig_initialized) {\n        return false;\n    }\n\n    if (!config_init(&game_config)) {\n        return false;\n    }\n\n    // Initialize defaults.\n    config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, \"game\");\n    config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY, \"master.dat\");\n    config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, \"data\");\n    config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY, \"critter.dat\");\n    config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY, \"data\");\n    config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, ENGLISH);\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SCROLL_LOCK_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_INTERRUPT_WALK_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_ART_CACHE_SIZE_KEY, 8);\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_COLOR_CYCLING_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_HASHING_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_FREE_SPACE_KEY, 20480);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, 3);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, 2);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEED_KEY, 0);\n    config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, 3.5);\n    config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, 1.399994);\n    config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, 1.0);\n    config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, 1.0);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_INITIALIZE_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEVICE_KEY, -1);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_PORT_KEY, -1);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_IRQ_KEY, -1);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DMA_KEY, -1);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SOUNDS_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_KEY, 1);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, 22281);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, 22281);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, 22281);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, 22281);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_CACHE_SIZE_KEY, 448);\n    config_set_string(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH1_KEY, \"sound\\\\music\\\\\");\n    config_set_string(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH2_KEY, \"sound\\\\music\\\\\");\n    config_set_string(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_MODE_KEY, \"environment\");\n    config_set_value(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_TILE_NUM_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_LOAD_INFO_KEY, 0);\n    config_set_value(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_OUTPUT_MAP_DATA_INFO_KEY, 0);\n\n    if (isMapper) {\n        config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, \"mapper\");\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_OVERRIDE_LIBRARIAN_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_LIBRARIAN_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_USE_ART_NOT_PROTOS_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_REBUILD_PROTOS_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_OBJECTS_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_INVENTORY_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_IGNORE_REBUILD_ERRORS_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SHOW_PID_NUMBERS_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SAVE_TEXT_MAPS_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_RUN_MAPPER_AS_GAME_KEY, 0);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_DEFAULT_F8_AS_GAME_KEY, 1);\n        config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SORT_SCRIPT_LIST_KEY, 0);\n    }\n\n    // Make `fallout2.cfg` file path.\n    char* executable = argv[0];\n    char* ch = strrchr(executable, '\\\\');\n    if (ch != NULL) {\n        *ch = '\\0';\n        sprintf(gconfig_file_name, \"%s\\\\%s\", executable, GAME_CONFIG_FILE_NAME);\n        *ch = '\\\\';\n    } else {\n        strcpy(gconfig_file_name, GAME_CONFIG_FILE_NAME);\n    }\n\n    // Read contents of `fallout2.cfg` into config. The values from the file\n    // will override the defaults above.\n    config_load(&game_config, gconfig_file_name, false);\n\n    // Add key-values from command line, which overrides both defaults and\n    // whatever was loaded from `fallout2.cfg`.\n    config_cmd_line_parse(&game_config, argc, argv);\n\n    gconfig_initialized = true;\n\n    return true;\n}\n\n// Saves game config into `fallout2.cfg`.\n//\n// 0x444C14\nbool gconfig_save()\n{\n    if (!gconfig_initialized) {\n        return false;\n    }\n\n    if (!config_save(&game_config, gconfig_file_name, false)) {\n        return false;\n    }\n\n    return true;\n}\n\n// Frees game config, optionally saving it.\n//\n// 0x444C3C\nbool gconfig_exit(bool shouldSave)\n{\n    if (!gconfig_initialized) {\n        return false;\n    }\n\n    bool result = true;\n\n    if (shouldSave) {\n        if (!config_save(&game_config, gconfig_file_name, false)) {\n            result = false;\n        }\n    }\n\n    config_exit(&game_config);\n\n    gconfig_initialized = false;\n\n    return result;\n}\n"
  },
  {
    "path": "src/game/gconfig.h",
    "content": "#ifndef FALLOUT_GAME_GCONFIG_H_\n#define FALLOUT_GAME_GCONFIG_H_\n\n#include <stdbool.h>\n\n#include \"game/config.h\"\n\n// The file name of the main config file.\n#define GAME_CONFIG_FILE_NAME \"fallout2.cfg\"\n\n#define GAME_CONFIG_SYSTEM_KEY \"system\"\n#define GAME_CONFIG_PREFERENCES_KEY \"preferences\"\n#define GAME_CONFIG_SOUND_KEY \"sound\"\n#define GAME_CONFIG_MAPPER_KEY \"mapper\"\n#define GAME_CONFIG_DEBUG_KEY \"debug\"\n\n#define GAME_CONFIG_EXECUTABLE_KEY \"executable\"\n#define GAME_CONFIG_MASTER_DAT_KEY \"master_dat\"\n#define GAME_CONFIG_MASTER_PATCHES_KEY \"master_patches\"\n#define GAME_CONFIG_CRITTER_DAT_KEY \"critter_dat\"\n#define GAME_CONFIG_CRITTER_PATCHES_KEY \"critter_patches\"\n#define GAME_CONFIG_PATCHES_KEY \"patches\"\n#define GAME_CONFIG_LANGUAGE_KEY \"language\"\n#define GAME_CONFIG_SCROLL_LOCK_KEY \"scroll_lock\"\n#define GAME_CONFIG_INTERRUPT_WALK_KEY \"interrupt_walk\"\n#define GAME_CONFIG_ART_CACHE_SIZE_KEY \"art_cache_size\"\n#define GAME_CONFIG_COLOR_CYCLING_KEY \"color_cycling\"\n#define GAME_CONFIG_CYCLE_SPEED_FACTOR_KEY \"cycle_speed_factor\"\n#define GAME_CONFIG_HASHING_KEY \"hashing\"\n#define GAME_CONFIG_SPLASH_KEY \"splash\"\n#define GAME_CONFIG_FREE_SPACE_KEY \"free_space\"\n#define GAME_CONFIG_TIMES_RUN_KEY \"times_run\"\n#define GAME_CONFIG_GAME_DIFFICULTY_KEY \"game_difficulty\"\n#define GAME_CONFIG_RUNNING_BURNING_GUY_KEY \"running_burning_guy\"\n#define GAME_CONFIG_COMBAT_DIFFICULTY_KEY \"combat_difficulty\"\n#define GAME_CONFIG_VIOLENCE_LEVEL_KEY \"violence_level\"\n#define GAME_CONFIG_TARGET_HIGHLIGHT_KEY \"target_highlight\"\n#define GAME_CONFIG_ITEM_HIGHLIGHT_KEY \"item_highlight\"\n#define GAME_CONFIG_COMBAT_LOOKS_KEY \"combat_looks\"\n#define GAME_CONFIG_COMBAT_MESSAGES_KEY \"combat_messages\"\n#define GAME_CONFIG_COMBAT_TAUNTS_KEY \"combat_taunts\"\n#define GAME_CONFIG_LANGUAGE_FILTER_KEY \"language_filter\"\n#define GAME_CONFIG_RUNNING_KEY \"running\"\n#define GAME_CONFIG_SUBTITLES_KEY \"subtitles\"\n#define GAME_CONFIG_COMBAT_SPEED_KEY \"combat_speed\"\n#define GAME_CONFIG_PLAYER_SPEED_KEY \"player_speed\"\n#define GAME_CONFIG_TEXT_BASE_DELAY_KEY \"text_base_delay\"\n#define GAME_CONFIG_TEXT_LINE_DELAY_KEY \"text_line_delay\"\n#define GAME_CONFIG_BRIGHTNESS_KEY \"brightness\"\n#define GAME_CONFIG_MOUSE_SENSITIVITY_KEY \"mouse_sensitivity\"\n#define GAME_CONFIG_INITIALIZE_KEY \"initialize\"\n#define GAME_CONFIG_DEVICE_KEY \"device\"\n#define GAME_CONFIG_PORT_KEY \"port\"\n#define GAME_CONFIG_IRQ_KEY \"irq\"\n#define GAME_CONFIG_DMA_KEY \"dma\"\n#define GAME_CONFIG_SOUNDS_KEY \"sounds\"\n#define GAME_CONFIG_MUSIC_KEY \"music\"\n#define GAME_CONFIG_SPEECH_KEY \"speech\"\n#define GAME_CONFIG_MASTER_VOLUME_KEY \"master_volume\"\n#define GAME_CONFIG_MUSIC_VOLUME_KEY \"music_volume\"\n#define GAME_CONFIG_SNDFX_VOLUME_KEY \"sndfx_volume\"\n#define GAME_CONFIG_SPEECH_VOLUME_KEY \"speech_volume\"\n#define GAME_CONFIG_CACHE_SIZE_KEY \"cache_size\"\n#define GAME_CONFIG_MUSIC_PATH1_KEY \"music_path1\"\n#define GAME_CONFIG_MUSIC_PATH2_KEY \"music_path2\"\n#define GAME_CONFIG_DEBUG_SFXC_KEY \"debug_sfxc\"\n#define GAME_CONFIG_MODE_KEY \"mode\"\n#define GAME_CONFIG_SHOW_TILE_NUM_KEY \"show_tile_num\"\n#define GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY \"show_script_messages\"\n#define GAME_CONFIG_SHOW_LOAD_INFO_KEY \"show_load_info\"\n#define GAME_CONFIG_OUTPUT_MAP_DATA_INFO_KEY \"output_map_data_info\"\n#define GAME_CONFIG_EXECUTABLE_KEY \"executable\"\n#define GAME_CONFIG_OVERRIDE_LIBRARIAN_KEY \"override_librarian\"\n#define GAME_CONFIG_LIBRARIAN_KEY \"librarian\"\n#define GAME_CONFIG_USE_ART_NOT_PROTOS_KEY \"use_art_not_protos\"\n#define GAME_CONFIG_REBUILD_PROTOS_KEY \"rebuild_protos\"\n#define GAME_CONFIG_FIX_MAP_OBJECTS_KEY \"fix_map_objects\"\n#define GAME_CONFIG_FIX_MAP_INVENTORY_KEY \"fix_map_inventory\"\n#define GAME_CONFIG_IGNORE_REBUILD_ERRORS_KEY \"ignore_rebuild_errors\"\n#define GAME_CONFIG_SHOW_PID_NUMBERS_KEY \"show_pid_numbers\"\n#define GAME_CONFIG_SAVE_TEXT_MAPS_KEY \"save_text_maps\"\n#define GAME_CONFIG_RUN_MAPPER_AS_GAME_KEY \"run_mapper_as_game\"\n#define GAME_CONFIG_DEFAULT_F8_AS_GAME_KEY \"default_f8_as_game\"\n#define GAME_CONFIG_SORT_SCRIPT_LIST_KEY \"sort_script_list\"\n#define GAME_CONFIG_PLAYER_SPEEDUP_KEY \"player_speedup\"\n\n#define ENGLISH \"english\"\n#define FRENCH \"french\"\n#define GERMAN \"german\"\n#define ITALIAN \"italian\"\n#define SPANISH \"spanish\"\n\ntypedef enum GameDifficulty {\n    GAME_DIFFICULTY_EASY,\n    GAME_DIFFICULTY_NORMAL,\n    GAME_DIFFICULTY_HARD,\n} GameDifficulty;\n\ntypedef enum CombatDifficulty {\n    COMBAT_DIFFICULTY_EASY,\n    COMBAT_DIFFICULTY_NORMAL,\n    COMBAT_DIFFICULTY_HARD,\n} CombatDifficulty;\n\ntypedef enum ViolenceLevel {\n    VIOLENCE_LEVEL_NONE,\n    VIOLENCE_LEVEL_MINIMAL,\n    VIOLENCE_LEVEL_NORMAL,\n    VIOLENCE_LEVEL_MAXIMUM_BLOOD,\n} ViolenceLevel;\n\ntypedef enum TargetHighlight {\n    TARGET_HIGHLIGHT_OFF,\n    TARGET_HIGHLIGHT_ON,\n    TARGET_HIGHLIGHT_TARGETING_ONLY,\n} TargetHighlight;\n\nextern Config game_config;\n\nbool gconfig_init(bool isMapper, int argc, char** argv);\nbool gconfig_save();\nbool gconfig_exit(bool shouldSave);\n\n#endif /* FALLOUT_GAME_GCONFIG_H_ */\n"
  },
  {
    "path": "src/game/gdebug.c",
    "content": "#include \"game/gdebug.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/gnw.h\"\n\n// 0x444C90\nvoid fatal_error(const char* format, const char* message, const char* file, int line)\n{\n    char stringBuffer[260];\n\n    debug_printf(\"\\n\");\n    debug_printf(format, message, file, line);\n\n    win_exit();\n\n    printf(\"\\n\\n\\n\\n\\n   \");\n    printf(format, message, file, line);\n    printf(\"\\n\\n\\n\\n\\n\");\n\n    sprintf(stringBuffer, format, message, file, line);\n    GNWSystemError(stringBuffer);\n\n    exit(1);\n}\n"
  },
  {
    "path": "src/game/gdebug.h",
    "content": "#ifndef FALLOUT_GAME_GDEBUG_H_\n#define FALLOUT_GAME_GDEBUG_H_\n\nvoid fatal_error(const char* format, const char* message, const char* file, int line);\n\n#endif /* FALLOUT_GAME_GDEBUG_H_ */\n"
  },
  {
    "path": "src/game/gdialog.c",
    "content": "#include \"game/gdialog.h\"\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"int/window.h\"\n#include \"game/actions.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/dialog.h\"\n#include \"game/display.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/lip_sync.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/svga.h\"\n\n// NOTE: Rare case - as a compatibility measure with Community Edition, this\n// define is actually an expression as original game used. However leaving it\n// in the code produces to too many diffs in this file, which is not good for\n// RE<->CE reconciliation.\n#define GAME_DIALOG_WINDOW_WIDTH (scr_size.lrx - scr_size.ulx + 1)\n#define GAME_DIALOG_WINDOW_HEIGHT 480\n\n#define GAME_DIALOG_REPLY_WINDOW_X 135\n#define GAME_DIALOG_REPLY_WINDOW_Y 225\n#define GAME_DIALOG_REPLY_WINDOW_WIDTH 379\n#define GAME_DIALOG_REPLY_WINDOW_HEIGHT 58\n\n#define GAME_DIALOG_OPTIONS_WINDOW_X 127\n#define GAME_DIALOG_OPTIONS_WINDOW_Y 335\n#define GAME_DIALOG_OPTIONS_WINDOW_WIDTH 393\n#define GAME_DIALOG_OPTIONS_WINDOW_HEIGHT 117\n\n// NOTE: See `GAME_DIALOG_WINDOW_WIDTH`.\n#define GAME_DIALOG_REVIEW_WINDOW_WIDTH (scr_size.lrx - scr_size.ulx + 1)\n#define GAME_DIALOG_REVIEW_WINDOW_HEIGHT 480\n\n#define DIALOG_REVIEW_ENTRIES_CAPACITY 80\n\n#define DIALOG_OPTION_ENTRIES_CAPACITY 30\n\ntypedef enum GameDialogReviewWindowButton {\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_UP,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_DOWN,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_DONE,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT,\n} GameDialogReviewWindowButton;\n\ntypedef enum GameDialogReviewWindowButtonFrm {\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_UP_NORMAL,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_UP_PRESSED,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_DOWN_NORMAL,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_DOWN_PRESSED,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_DONE_NORMAL,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_DONE_PRESSED,\n    GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT,\n} GameDialogReviewWindowButtonFrm;\n\ntypedef enum GameDialogReaction {\n    GAME_DIALOG_REACTION_GOOD = 49,\n    GAME_DIALOG_REACTION_NEUTRAL = 50,\n    GAME_DIALOG_REACTION_BAD = 51,\n} GameDialogReaction;\n\ntypedef struct GameDialogReviewEntry {\n    int replyMessageListId;\n    int replyMessageId;\n    // Can be NULL.\n    char* replyText;\n    int optionMessageListId;\n    int optionMessageId;\n    char* optionText;\n} GameDialogReviewEntry;\n\ntypedef struct GameDialogOptionEntry {\n    int messageListId;\n    int messageId;\n    int reaction;\n    int proc;\n    int btn;\n    int field_14;\n    char text[900];\n    int field_39C;\n} GameDialogOptionEntry;\n\ntypedef struct GameDialogBlock {\n    Program* program;\n    int replyMessageListId;\n    int replyMessageId;\n    int offset;\n\n    // NOTE: The is something odd about next two members. There are 2700 bytes,\n    // which is 3 x 900, but anywhere in the app only 900 characters is used.\n    // The length of text in [DialogOptionEntry] is definitely 900 bytes. There\n    // are two possible explanations:\n    // - it's an array of 3 elements.\n    // - there are three separate elements, two of which are not used, therefore\n    // they are not referenced anywhere, but they take up their space.\n    //\n    // See `gdProcessChoice` for more info how this unreferenced range plays\n    // important role.\n    char replyText[900];\n    char field_394[1800];\n    GameDialogOptionEntry options[DIALOG_OPTION_ENTRIES_CAPACITY];\n} GameDialogBlock;\n\n// Provides button configuration for party member combat control and\n// customization interface.\ntypedef struct GameDialogButtonData {\n    int x;\n    int y;\n    int upFrmId;\n    int downFrmId;\n    int disabledFrmId;\n    CacheEntry* upFrmHandle;\n    CacheEntry* downFrmHandle;\n    CacheEntry* disabledFrmHandle;\n    int keyCode;\n    int value;\n} GameDialogButtonData;\n\ntypedef struct STRUCT_5189E4 {\n    int messageId;\n    int value;\n} STRUCT_5189E4;\n\ntypedef enum PartyMemberCustomizationOption {\n    PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE,\n    PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE,\n    PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON,\n    PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE,\n    PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO,\n    PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE,\n    PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT,\n} PartyMemberCustomizationOption;\n\nstatic int gdHide();\nstatic int gdUnhide();\nstatic int gdUnhideReply();\nstatic int gdAddOption(int a1, int a2, int a3);\nstatic int gdAddOptionStr(int a1, const char* a2, int a3);\nstatic int gdReviewInit(int* win);\nstatic int gdReviewExit(int* win);\nstatic int gdReview();\nstatic void gdReviewPressed(int btn, int keyCode);\nstatic void gdReviewDisplay(int win, int origin);\nstatic void gdReviewFree();\nstatic int gdAddReviewReply(int messageListId, int messageId);\nstatic int gdAddReviewReplyStr(const char* text);\nstatic int gdAddReviewOptionChosen(int messageListId, int messageId);\nstatic int gdAddReviewOptionChosenStr(const char* text);\nstatic int gdProcessInit();\nstatic void gdProcessCleanup();\nstatic int gdProcessExit();\nstatic void gdUpdateMula();\nstatic int gdProcess();\nstatic int gdProcessChoice(int a1);\nstatic void gdProcessHighlight(int a1);\nstatic void gdProcessUnHighlight(int a1);\nstatic void gdProcessReply();\nstatic void gdProcessUpdate();\nstatic int gdCreateHeadWindow();\nstatic void gdDestroyHeadWindow();\nstatic void gdSetupFidget(int headFid, int reaction);\nstatic void gdWaitForFidget();\nstatic void gdPlayTransition(int a1);\nstatic void reply_arrow_up(int btn, int a2);\nstatic void reply_arrow_down(int btn, int a2);\nstatic void reply_arrow_restore(int btn, int a2);\nstatic void demo_copy_title(int win);\nstatic void demo_copy_options(int win);\nstatic void gDialogRefreshOptionsRect(int win, Rect* drawRect);\nstatic void gdialog_bk();\nstatic void gdialog_scroll_subwin(int a1, int a2, unsigned char* a3, unsigned char* a4, unsigned char* a5, int a6, int a7);\nstatic int text_num_lines(const char* a1, int a2);\nstatic int text_to_rect_wrapped(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color);\nstatic int text_to_rect_func(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color, int a7);\nstatic int gdialog_barter_create_win();\nstatic void gdialog_barter_destroy_win();\nstatic void gdialog_barter_cleanup_tables();\nstatic int gdControlCreateWin();\nstatic void gdControlDestroyWin();\nstatic void gdControlUpdateInfo();\nstatic void gdControlPressed(int a1, int a2);\nstatic int gdPickAIUpdateMsg(Object* obj);\nstatic int gdCanBarter();\nstatic void gdControl();\nstatic int gdCustomCreateWin();\nstatic void gdCustomDestroyWin();\nstatic void gdCustom();\nstatic void gdCustomUpdateInfo();\nstatic void gdCustomSelectRedraw(unsigned char* dest, int pitch, int type, int selectedIndex);\nstatic int gdCustomSelect(int a1);\nstatic void gdCustomUpdateSetting(int option, int value);\nstatic void gdialog_barter_pressed(int btn, int a2);\nstatic int gdialog_window_create();\nstatic void gdialog_window_destroy();\nstatic int talk_to_create_background_window();\nstatic int talk_to_refresh_background_window();\nstatic int talkToRefreshDialogWindowRect(Rect* rect);\nstatic void talk_to_translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int x, int y, int destPitch, unsigned char* a9, unsigned char* a10);\nstatic void gdDisplayFrame(Art* art, int frame);\nstatic void gdBlendTableInit();\nstatic void gdBlendTableExit();\n\n// 0x5186D4\nstatic int dialog_state_fix = 0;\n\n// 0x5186D8\nstatic int gdNumOptions = 0;\n\n// 0x5186DC\nstatic int curReviewSlot = 0;\n\n// 0x5186E0\nstatic unsigned char* headWindowBuffer = NULL;\n\n// 0x5186E4\nstatic int gReplyWin = -1;\n\n// 0x5186E8\nstatic int gOptionWin = -1;\n\n// 0x5186EC\nstatic bool gdialog_window_created = false;\n\n// 0x5186F0\nstatic int boxesWereDisabled = 0;\n\n// 0x5186F4\nstatic int fidgetFID = 0;\n\n// 0x5186F8\nstatic CacheEntry* fidgetKey = NULL;\n\n// 0x5186FC\nstatic Art* fidgetFp = NULL;\n\n// 0x518700\nstatic int backgroundIndex = 2;\n\n// 0x518704\nstatic int lipsFID = 0;\n\n// 0x518708\nstatic CacheEntry* lipsKey = NULL;\n\n// 0x51870C\nstatic Art* lipsFp = NULL;\n\n// 0x518710\nstatic bool gdialog_speech_playing = false;\n\n// 0x518714\nstatic int dialogue_state = 0;\n\n// 0x518718\nstatic int dialogue_switch_mode = 0;\n\n// 0x51871C\nstatic int gdialog_state = -1;\n\n// 0x518720\nstatic bool gdDialogWentOff = false;\n\n// 0x518724\nstatic bool gdDialogTurnMouseOff = false;\n\n// 0x518728\nstatic int gdReenterLevel = 0;\n\n// 0x51872C\nstatic bool gdReplyTooBig = false;\n\n// 0x518730\nstatic Object* peon_table_obj = NULL;\n\n// 0x518734\nstatic Object* barterer_table_obj = NULL;\n\n// 0x518738\nstatic Object* barterer_temp_obj = NULL;\n\n// 0x51873C\nstatic int gdBarterMod = 0;\n\n// 0x518740\nstatic int dialogueBackWindow = -1;\n\n// 0x518744\nstatic int dialogueWindow = -1;\n\n// 0x518748\nstatic Rect backgrndRects[8] = {\n    { 126, 14, 152, 40 },\n    { 488, 14, 514, 40 },\n    { 126, 188, 152, 214 },\n    { 488, 188, 514, 214 },\n    { 152, 14, 488, 24 },\n    { 152, 204, 488, 214 },\n    { 126, 40, 136, 188 },\n    { 504, 40, 514, 188 },\n};\n\n// 0x5187C8\nstatic int talk_need_to_center = 1;\n\n// 0x5187CC\nstatic bool can_start_new_fidget = false;\n\n// 0x5187D0\nstatic int gd_replyWin = -1;\n\n// 0x5187D4\nstatic int gd_optionsWin = -1;\n\n// 0x5187D8\nstatic int gDialogMusicVol = -1;\n\n// 0x5187DC\nstatic int gdCenterTile = -1;\n\n// 0x5187E0\nstatic int gdPlayerTile = -1;\n\n// 0x5187E4\nunsigned char* light_BlendTable = NULL;\n\n// 0x5187E8\nunsigned char* dark_BlendTable = NULL;\n\n// 0x5187EC\nstatic int dialogue_just_started = 0;\n\n// 0x5187F0\nstatic int dialogue_seconds_since_last_input = 0;\n\n// 0x5187F4\nstatic CacheEntry* reviewKeys[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT] = {\n    INVALID_CACHE_ENTRY,\n    INVALID_CACHE_ENTRY,\n    INVALID_CACHE_ENTRY,\n    INVALID_CACHE_ENTRY,\n    INVALID_CACHE_ENTRY,\n    INVALID_CACHE_ENTRY,\n};\n\n// 0x51880C\nstatic CacheEntry* reviewBackKey = INVALID_CACHE_ENTRY;\n\n// 0x518810\nstatic CacheEntry* reviewDispBackKey = INVALID_CACHE_ENTRY;\n\n// 0x518814\nstatic unsigned char* reviewDispBuf = NULL;\n\n// 0x518818\nstatic int reviewFidWids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT] = {\n    35,\n    35,\n    82,\n};\n\n// 0x518824\nstatic int reviewFidLens[GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT] = {\n    35,\n    37,\n    46,\n};\n\n// 0x518830\nstatic int reviewFids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT] = {\n    89, // di_bgdn1.frm - dialog big down arrow\n    90, // di_bgdn2.frm - dialog big down arrow\n    87, // di_bgup1.frm - dialog big up arrow\n    88, // di_bgup2.frm - dialog big up arrow\n    91, // di_done1.frm - dialog big done button up\n    92, // di_done2.frm - dialog big done button down\n};\n\n// 0x518848\nObject* dialog_target = NULL;\n\n// 0x51884C\nbool dialog_target_is_party = false;\n\n// 0x518850\nint dialogue_head = 0;\n\n// 0x518854\nint dialogue_scr_id = -1;\n\n// Maps phoneme to talking head frame.\n//\n// 0x518858\nstatic int head_phoneme_lookup[PHONEME_COUNT] = {\n    0,\n    3,\n    1,\n    1,\n    3,\n    1,\n    1,\n    1,\n    7,\n    8,\n    7,\n    3,\n    1,\n    8,\n    1,\n    7,\n    7,\n    6,\n    6,\n    2,\n    2,\n    2,\n    2,\n    4,\n    4,\n    5,\n    5,\n    2,\n    2,\n    2,\n    2,\n    2,\n    6,\n    2,\n    2,\n    5,\n    8,\n    2,\n    2,\n    2,\n    2,\n    8,\n};\n\n// 0x51890C\nstatic const char* react_strs[3] = {\n    \"Said Good\",\n    \"Said Neutral\",\n    \"Said Bad\",\n};\n\n// 0x518918\nstatic int dialogue_subwin_len = 0;\n\n// 0x51891C\nstatic GameDialogButtonData control_button_info[5] = {\n    { 438, 37, 397, 395, 396, NULL, NULL, NULL, 2098, 4 },\n    { 438, 67, 394, 392, 393, NULL, NULL, NULL, 2103, 3 },\n    { 438, 96, 406, 404, 405, NULL, NULL, NULL, 2102, 2 },\n    { 438, 126, 400, 398, 399, NULL, NULL, NULL, 2111, 1 },\n    { 438, 156, 403, 401, 402, NULL, NULL, NULL, 2099, 0 },\n};\n\n// 0x5189E4\nstatic STRUCT_5189E4 custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT][6] = {\n    {\n        { 100, AREA_ATTACK_MODE_ALWAYS }, // Always!\n        { 101, AREA_ATTACK_MODE_SOMETIMES }, // Sometimes, don't worry about hitting me\n        { 102, AREA_ATTACK_MODE_BE_SURE }, // Be sure you won't hit me\n        { 103, AREA_ATTACK_MODE_BE_CAREFUL }, // Be careful not to hit me\n        { 104, AREA_ATTACK_MODE_BE_ABSOLUTELY_SURE }, // Be absolutely sure you won't hit me\n        { -1, 0 },\n    },\n    {\n        { 200, RUN_AWAY_MODE_COWARD - 1 }, // Abject coward\n        { 201, RUN_AWAY_MODE_FINGER_HURTS - 1 }, // Your finger hurts\n        { 202, RUN_AWAY_MODE_BLEEDING - 1 }, // You're bleeding a bit\n        { 203, RUN_AWAY_MODE_NOT_FEELING_GOOD - 1 }, // Not feeling good\n        { 204, RUN_AWAY_MODE_TOURNIQUET - 1 }, // You need a tourniquet\n        { 205, RUN_AWAY_MODE_NEVER - 1 }, // Never!\n    },\n    {\n        { 300, BEST_WEAPON_NO_PREF }, // None\n        { 301, BEST_WEAPON_MELEE }, // Melee\n        { 302, BEST_WEAPON_MELEE_OVER_RANGED }, // Melee then ranged\n        { 303, BEST_WEAPON_RANGED_OVER_MELEE }, // Ranged then melee\n        { 304, BEST_WEAPON_RANGED }, // Ranged\n        { 305, BEST_WEAPON_UNARMED }, // Unarmed\n    },\n    {\n        { 400, DISTANCE_STAY_CLOSE }, // Stay close to me\n        { 401, DISTANCE_CHARGE }, // Charge!\n        { 402, DISTANCE_SNIPE }, // Snipe the enemy\n        { 403, DISTANCE_ON_YOUR_OWN }, // On your own\n        { 404, DISTANCE_STAY }, // Say where you are\n        { -1, 0 },\n    },\n    {\n        { 500, ATTACK_WHO_WHOMEVER_ATTACKING_ME }, // Whomever is attacking me\n        { 501, ATTACK_WHO_STRONGEST }, // The strongest\n        { 502, ATTACK_WHO_WEAKEST }, // The weakest\n        { 503, ATTACK_WHO_WHOMEVER }, // Whomever you want\n        { 504, ATTACK_WHO_CLOSEST }, // Whoever is closest\n        { -1, 0 },\n    },\n    {\n        { 600, CHEM_USE_CLEAN }, // I'm clean\n        { 601, CHEM_USE_STIMS_WHEN_HURT_LITTLE }, // Stimpacks when hurt a bit\n        { 602, CHEM_USE_STIMS_WHEN_HURT_LOTS }, // Stimpacks when hurt a lot\n        { 603, CHEM_USE_SOMETIMES }, // Any drug some of the time\n        { 604, CHEM_USE_ANYTIME }, // Any drug any time\n        { -1, 0 },\n    },\n};\n\n// 0x518B04\nstatic GameDialogButtonData custom_button_info[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT] = {\n    { 95, 9, 410, 409, -1, NULL, NULL, NULL, 0, 0 },\n    { 96, 38, 416, 415, -1, NULL, NULL, NULL, 1, 0 },\n    { 96, 68, 418, 417, -1, NULL, NULL, NULL, 2, 0 },\n    { 96, 98, 414, 413, -1, NULL, NULL, NULL, 3, 0 },\n    { 96, 127, 408, 407, -1, NULL, NULL, NULL, 4, 0 },\n    { 96, 157, 412, 411, -1, NULL, NULL, NULL, 5, 0 },\n};\n\n// 0x58EA80\nstatic int custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT];\n\n// custom.msg\n//\n// 0x58EA98\nstatic MessageList custom_msg_file;\n\n// 0x58EAA0\nunsigned char light_GrayTable[256];\n\n// 0x58EBA0\nunsigned char dark_GrayTable[256];\n\n// 0x58ECA0\nstatic unsigned char* backgrndBufs[8];\n\n// 0x58ECC0\nstatic Rect optionRect;\n\n// 0x58ECD0\nstatic Rect replyRect;\n\n// 0x58ECE0\nstatic GameDialogReviewEntry reviewList[DIALOG_REVIEW_ENTRIES_CAPACITY];\n\n// 0x58F460\nstatic int custom_buttons_start;\n\n// 0x58F464\nstatic int control_buttons_start;\n\n// 0x58F468\nstatic int reviewOldFont;\n\n// 0x58F46C\nstatic CacheEntry* dialog_red_button_up_key;\n\n// 0x58F470\nstatic int gdialog_buttons[9];\n\n// 0x58F494\nstatic CacheEntry* upper_hi_key;\n\n// 0x58F498\nstatic CacheEntry* gdialog_review_up_key;\n\n// 0x58F49C\nstatic int lower_hi_len;\n\n// 0x58F4A0\nstatic CacheEntry* gdialog_review_down_key;\n\n// 0x58F4A4\nstatic unsigned char* dialog_red_button_down_buf;\n\n// 0x58F4A8\nstatic int lower_hi_wid;\n\n// 0x58F4AC\nstatic unsigned char* dialog_red_button_up_buf;\n\n// 0x58F4B0\nstatic int upper_hi_wid;\n\n// Yellow highlight blick effect.\n//\n// 0x58F4B4\nstatic Art* lower_hi_fp;\n\n// 0x58F4B8\nstatic int upper_hi_len;\n\n// 0x58F4BC\nstatic CacheEntry* dialog_red_button_down_key;\n\n// 0x58F4C0\nstatic CacheEntry* lower_hi_key;\n\n// White highlight blick effect.\n//\n// This effect appears at the top-right corner on dialog display. Together with\n// [gDialogLowerHighlight] it gives an effect of depth of the monitor.\n//\n// 0x58F4C4\nstatic Art* upper_hi_fp;\n\n// 0x58F4C8\nstatic int oldFont;\n\n// 0x58F4CC\nstatic unsigned int fidgetLastTime;\n\n// 0x58F4D0\nstatic int fidgetAnim;\n\n// 0x58F4D4\nstatic GameDialogBlock dialogBlock;\n\n// 0x596C30\nstatic int talkOldFont;\n\n// 0x596C34\nstatic unsigned int fidgetTocksPerFrame;\n\n// 0x596C38\nstatic int fidgetFrameCounter;\n\n// 0x444D1C\nint gdialogInit()\n{\n    return 0;\n}\n\n// 0x444D20\nint gdialogReset()\n{\n    gdialogFreeSpeech();\n    return 0;\n}\n\n// 0x444D20.\nint gdialogExit()\n{\n    gdialogFreeSpeech();\n    return 0;\n}\n\n// 0x444D2C\nbool gdialogActive()\n{\n    return dialog_state_fix != 0;\n}\n\n// gdialogEnter\n// 0x444D3C\nvoid gdialogEnter(Object* a1, int a2)\n{\n    if (a1 == NULL) {\n        debug_printf(\"\\nError: gdialogEnter: target was NULL!\");\n        return;\n    }\n\n    gdDialogWentOff = false;\n\n    if (isInCombat()) {\n        return;\n    }\n\n    if (a1->sid == -1) {\n        return;\n    }\n\n    if (PID_TYPE(a1->pid) != OBJ_TYPE_ITEM && SID_TYPE(a1->sid) != SCRIPT_TYPE_SPATIAL) {\n        MessageListItem messageListItem;\n\n        int rc = action_can_talk_to(obj_dude, a1);\n        if (rc == -1) {\n            // You can't see there.\n            messageListItem.num = 660;\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                if (a2) {\n                    display_print(messageListItem.text);\n                } else {\n                    debug_printf(messageListItem.text);\n                }\n            } else {\n                debug_printf(\"\\nError: gdialog: Can't find message!\");\n            }\n            return;\n        }\n\n        if (rc == -2) {\n            // Too far away.\n            messageListItem.num = 661;\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                if (a2) {\n                    display_print(messageListItem.text);\n                } else {\n                    debug_printf(messageListItem.text);\n                }\n            } else {\n                debug_printf(\"\\nError: gdialog: Can't find message!\");\n            }\n            return;\n        }\n    }\n\n    gdCenterTile = tile_center_tile;\n    gdBarterMod = 0;\n    gdPlayerTile = obj_dude->tile;\n    map_disable_bk_processes();\n\n    dialog_state_fix = 1;\n    dialog_target = a1;\n    dialog_target_is_party = isPartyMember(a1);\n\n    dialogue_just_started = 1;\n\n    if (a1->sid != -1) {\n        exec_script_proc(a1->sid, SCRIPT_PROC_TALK);\n    }\n\n    Script* script;\n    if (scr_ptr(a1->sid, &script) == -1) {\n        gmouse_3d_on();\n        map_enable_bk_processes();\n        scr_exec_map_update_scripts();\n        dialog_state_fix = 0;\n        return;\n    }\n\n    if (script->scriptOverrides || dialogue_state != 4) {\n        dialogue_just_started = 0;\n        map_enable_bk_processes();\n        scr_exec_map_update_scripts();\n        dialog_state_fix = 0;\n        return;\n    }\n\n    gdialogFreeSpeech();\n\n    if (gdialog_state == 1) {\n        // TODO: Not sure about these conditions.\n        if (dialogue_switch_mode == 2) {\n            gdialog_window_destroy();\n        } else if (dialogue_switch_mode == 8) {\n            gdialog_window_destroy();\n        } else if (dialogue_switch_mode == 11) {\n            gdialog_window_destroy();\n        } else {\n            if (dialogue_switch_mode == gdialog_state) {\n                gdialog_barter_destroy_win();\n            } else if (dialogue_state == gdialog_state) {\n                gdialog_window_destroy();\n            } else if (dialogue_state == a2) {\n                gdialog_barter_destroy_win();\n            }\n        }\n        gdialogExitFromScript();\n    }\n\n    gdialog_state = 0;\n    dialogue_state = 0;\n\n    int tile = obj_dude->tile;\n    if (gdPlayerTile != tile) {\n        gdCenterTile = tile;\n    }\n\n    if (gdDialogWentOff) {\n        tile_scroll_to(gdCenterTile, 2);\n    }\n\n    map_enable_bk_processes();\n    scr_exec_map_update_scripts();\n\n    dialog_state_fix = 0;\n}\n\n// 0x444FE4\nvoid gdialogSystemEnter()\n{\n    game_state_update();\n\n    gdDialogTurnMouseOff = true;\n\n    soundContinueAll();\n    gdialogEnter(dialog_target, 0);\n    soundContinueAll();\n\n    if (gdPlayerTile != obj_dude->tile) {\n        gdCenterTile = obj_dude->tile;\n    }\n\n    if (gdDialogWentOff) {\n        tile_scroll_to(gdCenterTile, 2);\n    }\n\n    game_state_request(GAME_STATE_2);\n\n    game_state_update();\n}\n\n// 0x445050\nvoid gdialogSetupSpeech(const char* audioFileName)\n{\n    if (audioFileName == NULL) {\n        debug_printf(\"\\nGDialog: Bleep!\");\n        gsound_play_sfx_file(\"censor\");\n        return;\n    }\n\n    char name[16];\n    if (art_get_base_name(OBJ_TYPE_HEAD, dialogue_head & 0xFFF, name) == -1) {\n        return;\n    }\n\n    if (lips_load_file(audioFileName, name) == -1) {\n        return;\n    }\n\n    gdialog_speech_playing = true;\n\n    lips_play_speech();\n\n    debug_printf(\"Starting lipsynch speech\");\n}\n\n// 0x4450C4\nvoid gdialogFreeSpeech()\n{\n    if (gdialog_speech_playing) {\n        debug_printf(\"Ending lipsynch system\");\n        gdialog_speech_playing = false;\n\n        lips_free_speech();\n    }\n}\n\n// 0x4450EC\nint gdialogEnableBK()\n{\n    add_bk_process(gdialog_bk);\n    return 0;\n}\n\n// 0x4450FC\nint gdialogDisableBK()\n{\n    remove_bk_process(gdialog_bk);\n    return 0;\n}\n\n// 0x44510C\nint gdialogInitFromScript(int headFid, int reaction)\n{\n    if (dialogue_state == 1) {\n        return -1;\n    }\n\n    if (gdialog_state == 1) {\n        return 0;\n    }\n\n    anim_stop();\n\n    boxesWereDisabled = disable_box_bar_win();\n    dialog_target_is_party = isPartyMember(dialog_target);\n    oldFont = text_curr();\n    text_font(101);\n    dialogSetReplyWindow(135, 225, 379, 58, NULL);\n    dialogSetReplyColor(0.3f, 0.3f, 0.3f);\n    dialogSetOptionWindow(127, 335, 393, 117, NULL);\n    dialogSetOptionColor(0.2f, 0.2f, 0.2f);\n    dialogTitle(NULL);\n    dialogRegisterWinDrawCallbacks(demo_copy_title, demo_copy_options);\n    gdBlendTableInit();\n    cycle_disable();\n    if (gdDialogTurnMouseOff) {\n        gmouse_disable(0);\n    }\n    gmouse_3d_off();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    text_object_reset();\n\n    if (PID_TYPE(dialog_target->pid) != OBJ_TYPE_ITEM) {\n        tile_scroll_to(dialog_target->tile, 2);\n    }\n\n    talk_need_to_center = 1;\n\n    gdCreateHeadWindow();\n    add_bk_process(gdialog_bk);\n    gdSetupFidget(headFid, reaction);\n    gdialog_state = 1;\n    gmouse_disable_scrolling();\n\n    if (headFid == -1) {\n        gDialogMusicVol = gsound_background_volume_get_set(gDialogMusicVol / 2);\n    } else {\n        gDialogMusicVol = -1;\n        gsound_background_stop();\n    }\n\n    gdDialogWentOff = true;\n\n    return 0;\n}\n\n// 0x445298\nint gdialogExitFromScript()\n{\n    if (dialogue_switch_mode == 2 || dialogue_switch_mode == 8 || dialogue_switch_mode == 11) {\n        return -1;\n    }\n\n    if (gdialog_state == 0) {\n        return 0;\n    }\n\n    gdialogFreeSpeech();\n    gdReviewFree();\n    remove_bk_process(gdialog_bk);\n\n    if (PID_TYPE(dialog_target->pid) != OBJ_TYPE_ITEM) {\n        if (gdPlayerTile != obj_dude->tile) {\n            gdCenterTile = obj_dude->tile;\n        }\n        tile_scroll_to(gdCenterTile, 2);\n    }\n\n    gdDestroyHeadWindow();\n\n    text_font(oldFont);\n\n    if (fidgetFp != NULL) {\n        art_ptr_unlock(fidgetKey);\n        fidgetFp = NULL;\n    }\n\n    if (lipsKey != NULL) {\n        if (art_ptr_unlock(lipsKey) == -1) {\n            debug_printf(\"Failure unlocking lips frame!\\n\");\n        }\n        lipsKey = NULL;\n        lipsFp = NULL;\n        lipsFID = 0;\n    }\n\n    // NOTE: Uninline.\n    gdBlendTableExit();\n\n    gdialog_state = 0;\n    dialogue_state = 0;\n\n    cycle_enable();\n\n    if (!game_ui_is_disabled()) {\n        gmouse_enable_scrolling();\n    }\n\n    if (gDialogMusicVol == -1) {\n        gsound_background_restart_last(11);\n    } else {\n        gsound_background_volume_set(gDialogMusicVol);\n    }\n\n    if (boxesWereDisabled) {\n        enable_box_bar_win();\n    }\n\n    boxesWereDisabled = 0;\n\n    if (gdDialogTurnMouseOff) {\n        if (!game_ui_is_disabled()) {\n            gmouse_enable();\n        }\n\n        gdDialogTurnMouseOff = 0;\n    }\n\n    if (!game_ui_is_disabled()) {\n        gmouse_3d_on();\n    }\n\n    gdDialogWentOff = true;\n\n    return 0;\n}\n\n// 0x445438\nvoid gdialogSetBackground(int a1)\n{\n    if (a1 != -1) {\n        backgroundIndex = a1;\n    }\n}\n\n// Renders supplementary message in reply area of the dialog.\n//\n// 0x445448\nvoid gdialogDisplayMsg(char* msg)\n{\n    if (gd_replyWin == -1) {\n        debug_printf(\"\\nError: Reply window doesn't exist!\");\n        return;\n    }\n\n    replyRect.ulx = 5;\n    replyRect.uly = 10;\n    replyRect.lrx = 374;\n    replyRect.lry = 58;\n    demo_copy_title(gReplyWin);\n\n    unsigned char* windowBuffer = win_get_buf(gReplyWin);\n    int lineHeight = text_height();\n\n    int a4 = 0;\n\n    // NOTE: Uninline.\n    text_to_rect_wrapped(windowBuffer,\n        &replyRect,\n        msg,\n        &a4,\n        lineHeight,\n        379,\n        colorTable[992] | 0x2000000);\n\n    win_show(gd_replyWin);\n    win_draw(gReplyWin);\n}\n\n// 0x4454FC\nint gdialogStart()\n{\n    curReviewSlot = 0;\n    gdNumOptions = 0;\n    return 0;\n}\n\n// 0x445510\nint gdialogSayMessage()\n{\n    mouse_show();\n    gdialogGo();\n\n    gdNumOptions = 0;\n    dialogBlock.replyMessageListId = -1;\n\n    return 0;\n}\n\n// NOTE: If you look at the scripts handlers, my best guess that their intention\n// was to allow scripters to specify proc names instead of proc addresses. They\n// dropped this idea, probably because they've updated their own compiler, or\n// maybe there was not enough time to complete it. Any way, [procedure] is the\n// identifier of the procedure in the script, but it is silently ignored.\n//\n// 0x445538\nint gdialogOption(int messageListId, int messageId, const char* proc, int reaction)\n{\n    dialogBlock.options[gdNumOptions].proc = 0;\n\n    return gdAddOption(messageListId, messageId, reaction);\n}\n\n// NOTE: If you look at the script handlers, my best guess that their intention\n// was to allow scripters to specify proc names instead of proc addresses. They\n// dropped this idea, probably because they've updated their own compiler, or\n// maybe there was not enough time to complete it. Any way, [procedure] is the\n// identifier of the procedure in the script, but it is silently ignored.\n//\n// 0x445578\nint gdialogOptionStr(int messageListId, const char* text, const char* proc, int reaction)\n{\n    dialogBlock.options[gdNumOptions].proc = 0;\n\n    return gdAddOptionStr(messageListId, text, reaction);\n}\n\n// 0x4455B8\nint gdialogOptionProc(int messageListId, int messageId, int proc, int reaction)\n{\n    dialogBlock.options[gdNumOptions].proc = proc;\n\n    return gdAddOption(messageListId, messageId, reaction);\n}\n\n// 0x4455FC\nint gdialogOptionProcStr(int messageListId, const char* text, int proc, int reaction)\n{\n    dialogBlock.options[gdNumOptions].proc = proc;\n\n    return gdAddOptionStr(messageListId, text, reaction);\n}\n\n// 0x445640\nint gdialogReply(Program* program, int messageListId, int messageId)\n{\n    gdAddReviewReply(messageListId, messageId);\n\n    dialogBlock.program = program;\n    dialogBlock.replyMessageListId = messageListId;\n    dialogBlock.replyMessageId = messageId;\n    dialogBlock.offset = 0;\n    dialogBlock.replyText[0] = '\\0';\n    gdNumOptions = 0;\n\n    return 0;\n}\n\n// 0x44567C\nint gdialogReplyStr(Program* program, int messageListId, const char* text)\n{\n    gdAddReviewReplyStr(text);\n\n    dialogBlock.program = program;\n    dialogBlock.offset = 0;\n    dialogBlock.replyMessageListId = -4;\n    dialogBlock.replyMessageId = -4;\n\n    strcpy(dialogBlock.replyText, text);\n\n    gdNumOptions = 0;\n\n    return 0;\n}\n\n// 0x4456D8\nint gdialogGo()\n{\n    if (dialogBlock.replyMessageListId == -1) {\n        return 0;\n    }\n\n    int rc = 0;\n\n    if (gdNumOptions < 1) {\n        dialogBlock.options[gdNumOptions].proc = 0;\n\n        if (gdAddOption(-1, -1, 50) == -1) {\n            interpretError(\"Error setting option.\");\n            rc = -1;\n        }\n    }\n\n    if (rc != -1) {\n        rc = gdProcess();\n    }\n\n    gdNumOptions = 0;\n\n    return rc;\n}\n\n// 0x445764\nvoid gdialogUpdatePartyStatus()\n{\n    if (dialogue_state != 1) {\n        return;\n    }\n\n    bool is_party = isPartyMember(dialog_target);\n    if (is_party == dialog_target_is_party) {\n        return;\n    }\n\n    // NOTE: Uninline.\n    gdHide();\n\n    gdialog_window_destroy();\n\n    dialog_target_is_party = is_party;\n\n    gdialog_window_create();\n\n    // NOTE: Uninline.\n    gdUnhide();\n}\n\n// NOTE: Inlined.\n//\n// 0x4457EC\nstatic int gdHide()\n{\n    if (gd_replyWin != -1) {\n        win_hide(gd_replyWin);\n    }\n\n    if (gd_optionsWin != -1) {\n        win_hide(gd_optionsWin);\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x445818\nstatic int gdUnhide()\n{\n    if (gd_replyWin != -1) {\n        win_show(gd_replyWin);\n    }\n\n    if (gd_optionsWin != -1) {\n        win_show(gd_optionsWin);\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x445844\nstatic int gdUnhideReply()\n{\n    if (gd_replyWin != -1) {\n        win_show(gd_replyWin);\n    }\n\n    return 0;\n}\n\n// 0x44585C\nstatic int gdAddOption(int messageListId, int messageId, int reaction)\n{\n    if (gdNumOptions >= DIALOG_OPTION_ENTRIES_CAPACITY) {\n        debug_printf(\"\\nError: dialog: Ran out of options!\");\n        return -1;\n    }\n\n    GameDialogOptionEntry* optionEntry = &(dialogBlock.options[gdNumOptions]);\n    optionEntry->messageListId = messageListId;\n    optionEntry->messageId = messageId;\n    optionEntry->reaction = reaction;\n    optionEntry->btn = -1;\n    optionEntry->text[0] = '\\0';\n\n    gdNumOptions++;\n\n    return 0;\n}\n\n// 0x4458BC\nstatic int gdAddOptionStr(int messageListId, const char* text, int reaction)\n{\n    if (gdNumOptions >= DIALOG_OPTION_ENTRIES_CAPACITY) {\n        debug_printf(\"\\nError: dialog: Ran out of options!\");\n        return -1;\n    }\n\n    GameDialogOptionEntry* optionEntry = &(dialogBlock.options[gdNumOptions]);\n    optionEntry->messageListId = -4;\n    optionEntry->messageId = -4;\n    optionEntry->reaction = reaction;\n    optionEntry->btn = -1;\n    sprintf(optionEntry->text, \"%c %s\", '\\x95', text);\n\n    gdNumOptions++;\n\n    return 0;\n}\n\n// 0x445938\nstatic int gdReviewInit(int* win)\n{\n    if (gdialog_speech_playing) {\n        if (soundPlaying(lip_info.sound)) {\n            gdialogFreeSpeech();\n        }\n    }\n\n    reviewOldFont = text_curr();\n\n    if (win == NULL) {\n        return -1;\n    }\n\n    int reviewWindowX = 0;\n    int reviewWindowY = 0;\n    *win = win_add(reviewWindowX,\n        reviewWindowY,\n        GAME_DIALOG_REVIEW_WINDOW_WIDTH,\n        GAME_DIALOG_REVIEW_WINDOW_HEIGHT,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (*win == -1) {\n        return -1;\n    }\n\n    int fid = art_id(OBJ_TYPE_INTERFACE, 102, 0, 0, 0);\n    unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &reviewBackKey);\n    if (backgroundFrmData == NULL) {\n        win_delete(*win);\n        *win = -1;\n        return -1;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(*win);\n    buf_to_buf(backgroundFrmData,\n        GAME_DIALOG_REVIEW_WINDOW_WIDTH,\n        GAME_DIALOG_REVIEW_WINDOW_HEIGHT,\n        GAME_DIALOG_REVIEW_WINDOW_WIDTH,\n        windowBuffer,\n        GAME_DIALOG_REVIEW_WINDOW_WIDTH);\n\n    art_ptr_unlock(reviewBackKey);\n    reviewBackKey = INVALID_CACHE_ENTRY;\n\n    unsigned char* buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT];\n\n    int index;\n    for (index = 0; index < GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, reviewFids[index], 0, 0, 0);\n        buttonFrmData[index] = art_ptr_lock_data(fid, 0, 0, &(reviewKeys[index]));\n        if (buttonFrmData[index] == NULL) {\n            break;\n        }\n    }\n\n    if (index != GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT) {\n        gdReviewExit(win);\n        return -1;\n    }\n\n    int upBtn = win_register_button(*win,\n        475,\n        152,\n        reviewFidWids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_UP],\n        reviewFidLens[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_UP],\n        -1,\n        -1,\n        -1,\n        KEY_ARROW_UP,\n        buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_UP_NORMAL],\n        buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_UP_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (upBtn == -1) {\n        gdReviewExit(win);\n        return -1;\n    }\n\n    win_register_button_sound_func(upBtn, gsound_med_butt_press, gsound_med_butt_release);\n\n    int downBtn = win_register_button(*win,\n        475,\n        191,\n        reviewFidWids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_DOWN],\n        reviewFidLens[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_DOWN],\n        -1,\n        -1,\n        -1,\n        KEY_ARROW_DOWN,\n        buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_DOWN_NORMAL],\n        buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_DOWN_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (downBtn == -1) {\n        gdReviewExit(win);\n        return -1;\n    }\n\n    win_register_button_sound_func(downBtn, gsound_med_butt_press, gsound_med_butt_release);\n\n    int doneBtn = win_register_button(*win,\n        499,\n        398,\n        reviewFidWids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_DONE],\n        reviewFidLens[GAME_DIALOG_REVIEW_WINDOW_BUTTON_DONE],\n        -1,\n        -1,\n        -1,\n        KEY_ESCAPE,\n        buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_DONE_NORMAL],\n        buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_DONE_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (doneBtn == -1) {\n        gdReviewExit(win);\n        return -1;\n    }\n\n    win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release);\n\n    text_font(101);\n\n    win_draw(*win);\n\n    remove_bk_process(gdialog_bk);\n\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 102, 0, 0, 0);\n    reviewDispBuf = art_ptr_lock_data(backgroundFid, 0, 0, &reviewDispBackKey);\n    if (reviewDispBuf == NULL) {\n        gdReviewExit(win);\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x445C18\nstatic int gdReviewExit(int* win)\n{\n    add_bk_process(gdialog_bk);\n\n    for (int index = 0; index < GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT; index++) {\n        if (reviewKeys[index] != INVALID_CACHE_ENTRY) {\n            art_ptr_unlock(reviewKeys[index]);\n            reviewKeys[index] = INVALID_CACHE_ENTRY;\n        }\n    }\n\n    if (reviewDispBackKey != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(reviewDispBackKey);\n        reviewDispBackKey = INVALID_CACHE_ENTRY;\n        reviewDispBuf = NULL;\n    }\n\n    text_font(reviewOldFont);\n\n    if (win == NULL) {\n        return -1;\n    }\n\n    win_delete(*win);\n    *win = -1;\n\n    return 0;\n}\n\n// 0x445CA0\nstatic int gdReview()\n{\n    int win;\n\n    if (gdReviewInit(&win) == -1) {\n        debug_printf(\"\\nError initializing review window!\");\n        return -1;\n    }\n\n    // probably current top line or something like this, which is used to scroll\n    int v1 = 0;\n    gdReviewDisplay(win, v1);\n\n    while (true) {\n        int keyCode = get_input();\n        if (keyCode == 17 || keyCode == 24 || keyCode == 324) {\n            game_quit_with_confirm();\n        }\n\n        if (game_user_wants_to_quit != 0 || keyCode == KEY_ESCAPE) {\n            break;\n        }\n\n        // likely scrolling\n        if (keyCode == 328) {\n            v1 -= 1;\n            if (v1 >= 0) {\n                gdReviewDisplay(win, v1);\n            } else {\n                v1 = 0;\n            }\n        } else if (keyCode == 336) {\n            v1 += 1;\n            if (v1 <= curReviewSlot - 1) {\n                gdReviewDisplay(win, v1);\n            } else {\n                v1 = curReviewSlot - 1;\n            }\n        }\n    }\n\n    if (gdReviewExit(&win) == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x445CA0 with different signature.\nstatic void gdReviewPressed(int btn, int keyCode)\n{\n    gdReview();\n}\n\n// 0x445D44\nstatic void gdReviewDisplay(int win, int origin)\n{\n    Rect entriesRect;\n    entriesRect.ulx = 113;\n    entriesRect.uly = 76;\n    entriesRect.lrx = 422;\n    entriesRect.lry = 418;\n\n    int v20 = text_height() + 2;\n    unsigned char* windowBuffer = win_get_buf(win);\n    if (windowBuffer == NULL) {\n        debug_printf(\"\\nError: gdialog: review: can't find buffer!\");\n        return;\n    }\n\n    int width = GAME_DIALOG_WINDOW_WIDTH;\n    buf_to_buf(\n        reviewDispBuf + width * entriesRect.uly + entriesRect.ulx,\n        width,\n        entriesRect.lry - entriesRect.uly + 15,\n        width,\n        windowBuffer + width * entriesRect.uly + entriesRect.ulx,\n        width);\n\n    int y = 76;\n    for (int index = origin; index < curReviewSlot; index++) {\n        GameDialogReviewEntry* dialogReviewEntry = &(reviewList[index]);\n\n        char name[60];\n        sprintf(name, \"%s:\", object_name(dialog_target));\n        win_print(win, name, 180, 88, y, colorTable[992] | 0x2000000);\n        entriesRect.uly += v20;\n\n        char* replyText;\n        if (dialogReviewEntry->replyMessageListId <= -3) {\n            replyText = dialogReviewEntry->replyText;\n        } else {\n            replyText = scr_get_msg_str(dialogReviewEntry->replyMessageListId, dialogReviewEntry->replyMessageId);\n        }\n\n        if (replyText == NULL) {\n            GNWSystemError(\"\\nGDialog::Error Grabbing text message!\");\n            exit(1);\n        }\n\n        // NOTE: Uninline.\n        y = text_to_rect_wrapped(windowBuffer + 113,\n                &entriesRect,\n                replyText,\n                NULL,\n                text_height(),\n                640,\n                colorTable[768] | 0x2000000);\n\n        if (dialogReviewEntry->optionMessageListId != -3) {\n            sprintf(name, \"%s:\", object_name(obj_dude));\n            win_print(win, name, 180, 88, y, colorTable[21140] | 0x2000000);\n            entriesRect.uly += v20;\n\n            char* optionText;\n            if (dialogReviewEntry->optionMessageListId <= -3) {\n                optionText = dialogReviewEntry->optionText;\n            } else {\n                optionText = scr_get_msg_str(dialogReviewEntry->optionMessageListId, dialogReviewEntry->optionMessageId);\n            }\n\n            if (optionText == NULL) {\n                GNWSystemError(\"\\nGDialog::Error Grabbing text message!\");\n                exit(1);\n            }\n\n            // NOTE: Uninline.\n            y = text_to_rect_wrapped(windowBuffer + 113,\n                    &entriesRect,\n                    optionText,\n                    NULL,\n                    text_height(),\n                    640,\n                    colorTable[15855] | 0x2000000);\n        }\n\n        if (y >= 407) {\n            break;\n        }\n    }\n\n    entriesRect.ulx = 88;\n    entriesRect.uly = 76;\n    entriesRect.lry += 14;\n    entriesRect.lrx = 434;\n    win_draw_rect(win, &entriesRect);\n}\n\n// 0x445FDC\nstatic void gdReviewFree()\n{\n    for (int index = 0; index < curReviewSlot; index++) {\n        GameDialogReviewEntry* entry = &(reviewList[index]);\n        entry->replyMessageListId = 0;\n        entry->replyMessageId = 0;\n\n        if (entry->replyText != NULL) {\n            mem_free(entry->replyText);\n            entry->replyText = NULL;\n        }\n\n        entry->optionMessageListId = 0;\n        entry->optionMessageId = 0;\n    }\n}\n\n// 0x446040\nstatic int gdAddReviewReply(int messageListId, int messageId)\n{\n    if (curReviewSlot >= DIALOG_REVIEW_ENTRIES_CAPACITY) {\n        debug_printf(\"\\nError: Ran out of review slots!\");\n        return -1;\n    }\n\n    GameDialogReviewEntry* entry = &(reviewList[curReviewSlot]);\n    entry->replyMessageListId = messageListId;\n    entry->replyMessageId = messageId;\n\n    // NOTE: I'm not sure why there are two consequtive assignments.\n    entry->optionMessageListId = -1;\n    entry->optionMessageId = -1;\n\n    entry->optionMessageListId = -3;\n    entry->optionMessageId = -3;\n\n    curReviewSlot++;\n\n    return 0;\n}\n\n// 0x4460B4\nstatic int gdAddReviewReplyStr(const char* string)\n{\n    if (curReviewSlot >= DIALOG_REVIEW_ENTRIES_CAPACITY) {\n        debug_printf(\"\\nError: Ran out of review slots!\");\n        return -1;\n    }\n\n    GameDialogReviewEntry* entry = &(reviewList[curReviewSlot]);\n    entry->replyMessageListId = -4;\n    entry->replyMessageId = -4;\n\n    if (entry->replyText != NULL) {\n        mem_free(entry->replyText);\n        entry->replyText = NULL;\n    }\n\n    entry->replyText = (char*)mem_malloc(strlen(string) + 1);\n    strcpy(entry->replyText, string);\n\n    entry->optionMessageListId = -3;\n    entry->optionMessageId = -3;\n    entry->optionText = NULL;\n\n    curReviewSlot++;\n\n    return 0;\n}\n\n// 0x4461A4\nstatic int gdAddReviewOptionChosen(int messageListId, int messageId)\n{\n    if (curReviewSlot >= DIALOG_REVIEW_ENTRIES_CAPACITY) {\n        debug_printf(\"\\nError: Ran out of review slots!\");\n        return -1;\n    }\n\n    GameDialogReviewEntry* entry = &(reviewList[curReviewSlot - 1]);\n    entry->optionMessageListId = messageListId;\n    entry->optionMessageId = messageId;\n    entry->optionText = NULL;\n\n    return 0;\n}\n\n// 0x4461F0\nstatic int gdAddReviewOptionChosenStr(const char* string)\n{\n    if (curReviewSlot >= DIALOG_REVIEW_ENTRIES_CAPACITY) {\n        debug_printf(\"\\nError: Ran out of review slots!\");\n        return -1;\n    }\n\n    GameDialogReviewEntry* entry = &(reviewList[curReviewSlot - 1]);\n    entry->optionMessageListId = -4;\n    entry->optionMessageId = -4;\n\n    entry->optionText = (char*)mem_malloc(strlen(string) + 1);\n    strcpy(entry->optionText, string);\n\n    return 0;\n}\n\n// Creates dialog interface.\n//\n// 0x446288\nstatic int gdProcessInit()\n{\n    int upBtn;\n    int downBtn;\n    int optionsWindowX;\n    int optionsWindowY;\n    int fid;\n\n    int replyWindowX = GAME_DIALOG_REPLY_WINDOW_X;\n    int replyWindowY = GAME_DIALOG_REPLY_WINDOW_Y;\n    gReplyWin = win_add(replyWindowX,\n        replyWindowY,\n        GAME_DIALOG_REPLY_WINDOW_WIDTH,\n        GAME_DIALOG_REPLY_WINDOW_HEIGHT,\n        256,\n        WINDOW_FLAG_0x04);\n    if (gReplyWin == -1) {\n        goto err;\n    }\n\n    // Top part of the reply window - scroll up.\n    upBtn = win_register_button(gReplyWin, 1, 1, 377, 28, -1, -1, KEY_ARROW_UP, -1, NULL, NULL, NULL, 32);\n    if (upBtn == -1) {\n        goto err_1;\n    }\n\n    win_register_button_sound_func(upBtn, gsound_red_butt_press, gsound_red_butt_release);\n    win_register_button_func(upBtn, reply_arrow_up, reply_arrow_restore, 0, 0);\n\n    // Bottom part of the reply window - scroll down.\n    downBtn = win_register_button(gReplyWin, 1, 29, 377, 28, -1, -1, KEY_ARROW_DOWN, -1, NULL, NULL, NULL, 32);\n    if (downBtn == -1) {\n        goto err_1;\n    }\n\n    win_register_button_sound_func(downBtn, gsound_red_butt_press, gsound_red_butt_release);\n    win_register_button_func(downBtn, reply_arrow_down, reply_arrow_restore, 0, 0);\n\n    optionsWindowX = GAME_DIALOG_OPTIONS_WINDOW_X;\n    optionsWindowY = GAME_DIALOG_OPTIONS_WINDOW_Y;\n    gOptionWin = win_add(optionsWindowX, optionsWindowY, GAME_DIALOG_OPTIONS_WINDOW_WIDTH, GAME_DIALOG_OPTIONS_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x04);\n    if (gOptionWin == -1) {\n        goto err_2;\n    }\n\n    // di_rdbt2.frm - dialog red button down\n    fid = art_id(OBJ_TYPE_INTERFACE, 96, 0, 0, 0);\n    dialog_red_button_up_buf = art_ptr_lock_data(fid, 0, 0, &dialog_red_button_up_key);\n    if (dialog_red_button_up_buf == NULL) {\n        goto err_3;\n    }\n\n    // di_rdbt1.frm - dialog red button up\n    fid = art_id(OBJ_TYPE_INTERFACE, 95, 0, 0, 0);\n    dialog_red_button_down_buf = art_ptr_lock_data(fid, 0, 0, &dialog_red_button_down_key);\n    if (dialog_red_button_down_buf == NULL) {\n        goto err_3;\n    }\n\n    talkOldFont = text_curr();\n    text_font(101);\n\n    return 0;\n\nerr_3:\n\n    art_ptr_unlock(dialog_red_button_up_key);\n    dialog_red_button_up_key = NULL;\n\nerr_2:\n\n    win_delete(gOptionWin);\n    gOptionWin = -1;\n\nerr_1:\n\n    win_delete(gReplyWin);\n    gReplyWin = -1;\n\nerr:\n\n    return -1;\n}\n\n// RELASE: Rename/comment.\n// free dialog option buttons\n// 0x446454\nstatic void gdProcessCleanup()\n{\n    for (int index = 0; index < gdNumOptions; index++) {\n        GameDialogOptionEntry* optionEntry = &(dialogBlock.options[index]);\n\n        if (optionEntry->btn != -1) {\n            win_delete_button(optionEntry->btn);\n            optionEntry->btn = -1;\n        }\n    }\n}\n\n// RELASE: Rename/comment.\n// free dialog interface\n// 0x446498\nstatic int gdProcessExit()\n{\n    gdProcessCleanup();\n\n    art_ptr_unlock(dialog_red_button_down_key);\n    dialog_red_button_down_key = NULL;\n    dialog_red_button_down_buf = NULL;\n\n    art_ptr_unlock(dialog_red_button_up_key);\n    dialog_red_button_up_key = NULL;\n    dialog_red_button_up_buf = NULL;\n\n    win_delete(gReplyWin);\n    gReplyWin = -1;\n\n    win_delete(gOptionWin);\n    gOptionWin = -1;\n\n    text_font(talkOldFont);\n\n    return 0;\n}\n\n// 0x446504\nstatic void gdUpdateMula()\n{\n    Rect rect;\n    rect.ulx = 5;\n    rect.lrx = 70;\n    rect.uly = 36;\n    rect.lry = text_height() + 36;\n\n    talkToRefreshDialogWindowRect(&rect);\n\n    int oldFont = text_curr();\n    text_font(101);\n\n    int caps = item_caps_total(obj_dude);\n    char text[20];\n    sprintf(text, \"$%d\", caps);\n\n    int width = text_width(text);\n    if (width > 60) {\n        width = 60;\n    }\n\n    win_print(dialogueWindow, text, width, 38 - width / 2, 36, colorTable[992] | 0x7000000);\n\n    text_font(oldFont);\n}\n\n// 0x4465C0\nstatic int gdProcess()\n{\n    if (gdReenterLevel == 0) {\n        if (gdProcessInit() == -1) {\n            return -1;\n        }\n    }\n\n    gdReenterLevel += 1;\n\n    gdProcessUpdate();\n\n    int v18 = 0;\n    if (dialogBlock.offset != 0) {\n        v18 = 1;\n        gdReplyTooBig = 1;\n    }\n\n    unsigned int tick = get_time();\n    int pageCount = 0;\n    int pageIndex = 0;\n    int pageOffsets[10];\n    pageOffsets[0] = 0;\n    for (;;) {\n        int keyCode = get_input();\n\n        if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n            game_quit_with_confirm();\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        if (keyCode == KEY_CTRL_B && !mouse_click_in(135, 225, 514, 283)) {\n            if (gmouse_get_cursor() != MOUSE_CURSOR_ARROW) {\n                gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n            }\n        } else {\n            if (dialogue_switch_mode == 3) {\n                dialogue_state = 4;\n                barter_inventory(dialogueWindow, dialog_target, peon_table_obj, barterer_table_obj, gdBarterMod);\n                gdialog_barter_cleanup_tables();\n\n                int v5 = dialogue_state;\n                gdialog_barter_destroy_win();\n                dialogue_state = v5;\n\n                if (v5 == 4) {\n                    dialogue_switch_mode = 1;\n                    dialogue_state = 1;\n                }\n                continue;\n            } else if (dialogue_switch_mode == 9) {\n                dialogue_state = 10;\n                gdControl();\n                gdControlDestroyWin();\n                continue;\n            } else if (dialogue_switch_mode == 12) {\n                dialogue_state = 13;\n                gdCustom();\n                gdCustomDestroyWin();\n                continue;\n            }\n\n            if (keyCode == KEY_LOWERCASE_B) {\n                gdialog_barter_pressed(-1, -1);\n            }\n        }\n\n        if (gdReplyTooBig) {\n            unsigned int v6 = get_bk_time();\n            if (v18) {\n                if (elapsed_tocks(v6, tick) >= 10000 || keyCode == KEY_SPACE) {\n                    pageCount++;\n                    pageIndex++;\n                    pageOffsets[pageCount] = dialogBlock.offset;\n                    gdProcessReply();\n                    tick = v6;\n                    if (!dialogBlock.offset) {\n                        v18 = 0;\n                    }\n                }\n            }\n\n            if (keyCode == KEY_ARROW_UP) {\n                if (pageIndex > 0) {\n                    pageIndex--;\n                    dialogBlock.offset = pageOffsets[pageIndex];\n                    v18 = 0;\n                    gdProcessReply();\n                }\n            } else if (keyCode == KEY_ARROW_DOWN) {\n                if (pageIndex < pageCount) {\n                    pageIndex++;\n                    dialogBlock.offset = pageOffsets[pageIndex];\n                    v18 = 0;\n                    gdProcessReply();\n                } else {\n                    if (dialogBlock.offset != 0) {\n                        tick = v6;\n                        pageIndex++;\n                        pageCount++;\n                        pageOffsets[pageCount] = dialogBlock.offset;\n                        v18 = 0;\n                        gdProcessReply();\n                    }\n                }\n            }\n        }\n\n        if (keyCode != -1) {\n            if (keyCode >= 1200 && keyCode <= 1250) {\n                gdProcessHighlight(keyCode - 1200);\n            } else if (keyCode >= 1300 && keyCode <= 1330) {\n                gdProcessUnHighlight(keyCode - 1300);\n            } else if (keyCode >= 48 && keyCode <= 57) {\n                int v11 = keyCode - 49;\n                if (v11 < gdNumOptions) {\n                    pageCount = 0;\n                    pageIndex = 0;\n                    pageOffsets[0] = 0;\n                    gdReplyTooBig = 0;\n\n                    if (gdProcessChoice(v11) == -1) {\n                        break;\n                    }\n\n                    tick = get_time();\n\n                    if (dialogBlock.offset) {\n                        v18 = 1;\n                        gdReplyTooBig = 1;\n                    } else {\n                        v18 = 0;\n                    }\n                }\n            }\n        }\n    }\n\n    gdReenterLevel -= 1;\n\n    if (gdReenterLevel == 0) {\n        if (gdProcessExit() == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4468DC\nstatic int gdProcessChoice(int a1)\n{\n    // FIXME: There is a buffer underread bug when `a1` is -1 (pressing 0 on the\n    // keyboard, see `gdProcess`). When it happens the game looks into unused\n    // continuation of `dialogBlock.replyText` (within 0x58F868-0x58FF70 range) which\n    // is initialized to 0 according to C spec. I was not able to replicate the\n    // same behaviour by extending dialogBlock.replyText to 2700 bytes or introduce\n    // new 1800 bytes buffer in between, at least not in debug builds. In order\n    // to preserve original behaviour this dummy dialog option entry is used.\n    //\n    // TODO: Recheck behaviour after introducing |GameDialogBlock|.\n    GameDialogOptionEntry dummy;\n    memset(&dummy, 0, sizeof(dummy));\n\n    mouse_hide();\n    gdProcessCleanup();\n\n    GameDialogOptionEntry* dialogOptionEntry = a1 != -1 ? &(dialogBlock.options[a1]) : &dummy;\n    if (dialogOptionEntry->messageListId == -4) {\n        gdAddReviewOptionChosenStr(dialogOptionEntry->text);\n    } else {\n        gdAddReviewOptionChosen(dialogOptionEntry->messageListId, dialogOptionEntry->messageId);\n    }\n\n    can_start_new_fidget = false;\n\n    gdialogFreeSpeech();\n\n    int v1 = GAME_DIALOG_REACTION_NEUTRAL;\n    switch (dialogOptionEntry->reaction) {\n    case GAME_DIALOG_REACTION_GOOD:\n        v1 = -1;\n        break;\n    case GAME_DIALOG_REACTION_NEUTRAL:\n        v1 = 0;\n        break;\n    case GAME_DIALOG_REACTION_BAD:\n        v1 = 1;\n        break;\n    default:\n        // See 0x446907 in ecx but this branch should be unreachable. Due to the\n        // bug described above, this code is reachable.\n        v1 = GAME_DIALOG_REACTION_NEUTRAL;\n        debug_printf(\"\\nError: dialog: Empathy Perk: invalid reaction!\");\n        break;\n    }\n\n    demo_copy_title(gReplyWin);\n    demo_copy_options(gOptionWin);\n    win_draw(gReplyWin);\n    win_draw(gOptionWin);\n\n    gdProcessHighlight(a1);\n    talk_to_critter_reacts(v1);\n\n    gdNumOptions = 0;\n\n    if (gdReenterLevel < 2) {\n        if (dialogOptionEntry->proc != 0) {\n            executeProcedure(dialogBlock.program, dialogOptionEntry->proc);\n        }\n    }\n\n    mouse_show();\n\n    if (gdNumOptions == 0) {\n        return -1;\n    }\n\n    gdProcessUpdate();\n\n    return 0;\n}\n\n// 0x446A18\nstatic void gdProcessHighlight(int index)\n{\n    // FIXME: See explanation in `gdProcessChoice`.\n    GameDialogOptionEntry dummy;\n    memset(&dummy, 0, sizeof(dummy));\n\n    GameDialogOptionEntry* dialogOptionEntry = index != -1 ? &(dialogBlock.options[index]) : &dummy;\n    if (dialogOptionEntry->btn == 0) {\n        return;\n    }\n\n    optionRect.ulx = 0;\n    optionRect.uly = dialogOptionEntry->field_14;\n    optionRect.lrx = 391;\n    optionRect.lry = dialogOptionEntry->field_39C;\n    gDialogRefreshOptionsRect(gOptionWin, &optionRect);\n\n    optionRect.ulx = 5;\n    optionRect.lrx = 388;\n\n    int color = colorTable[32747] | 0x2000000;\n    if (perkHasRank(obj_dude, PERK_EMPATHY)) {\n        color = colorTable[32747] | 0x2000000;\n        switch (dialogOptionEntry->reaction) {\n        case GAME_DIALOG_REACTION_GOOD:\n            color = colorTable[31775] | 0x2000000;\n            break;\n        case GAME_DIALOG_REACTION_NEUTRAL:\n            break;\n        case GAME_DIALOG_REACTION_BAD:\n            color = colorTable[32074] | 0x2000000;\n            break;\n        default:\n            debug_printf(\"\\nError: dialog: Empathy Perk: invalid reaction!\");\n            break;\n        }\n    }\n\n    // NOTE: Uninline.\n    text_to_rect_wrapped(win_get_buf(gOptionWin),\n        &optionRect,\n        dialogOptionEntry->text,\n        NULL,\n        text_height(),\n        393,\n        color);\n\n    optionRect.ulx = 0;\n    optionRect.lrx = 391;\n    optionRect.uly = dialogOptionEntry->field_14;\n    win_draw_rect(gOptionWin, &optionRect);\n}\n\n// 0x446B5C\nstatic void gdProcessUnHighlight(int index)\n{\n    GameDialogOptionEntry* dialogOptionEntry = &(dialogBlock.options[index]);\n\n    optionRect.ulx = 0;\n    optionRect.uly = dialogOptionEntry->field_14;\n    optionRect.lrx = 391;\n    optionRect.lry = dialogOptionEntry->field_39C;\n    gDialogRefreshOptionsRect(gOptionWin, &optionRect);\n\n    int color = colorTable[992] | 0x2000000;\n    if (perk_level(obj_dude, PERK_EMPATHY) != 0) {\n        color = colorTable[32747] | 0x2000000;\n        switch (dialogOptionEntry->reaction) {\n        case GAME_DIALOG_REACTION_GOOD:\n            color = colorTable[31] | 0x2000000;\n            break;\n        case GAME_DIALOG_REACTION_NEUTRAL:\n            color = colorTable[992] | 0x2000000;\n            break;\n        case GAME_DIALOG_REACTION_BAD:\n            color = colorTable[31744] | 0x2000000;\n            break;\n        default:\n            debug_printf(\"\\nError: dialog: Empathy Perk: invalid reaction!\");\n            break;\n        }\n    }\n\n    optionRect.ulx = 5;\n    optionRect.lrx = 388;\n\n    // NOTE: Uninline.\n    text_to_rect_wrapped(win_get_buf(gOptionWin),\n        &optionRect,\n        dialogOptionEntry->text,\n        NULL,\n        text_height(),\n        393,\n        color);\n\n    optionRect.lrx = 391;\n    optionRect.uly = dialogOptionEntry->field_14;\n    optionRect.ulx = 0;\n    win_draw_rect(gOptionWin, &optionRect);\n}\n\n// 0x446C94\nstatic void gdProcessReply()\n{\n    replyRect.ulx = 5;\n    replyRect.uly = 10;\n    replyRect.lrx = 374;\n    replyRect.lry = 58;\n\n    // NOTE: There is an unused if condition.\n    perk_level(obj_dude, PERK_EMPATHY);\n\n    demo_copy_title(gReplyWin);\n\n    // NOTE: Uninline.\n    text_to_rect_wrapped(win_get_buf(gReplyWin),\n        &replyRect,\n        dialogBlock.replyText,\n        &(dialogBlock.offset),\n        text_height(),\n        379,\n        colorTable[992] | 0x2000000);\n    win_draw(gReplyWin);\n}\n\n// 0x446D30\nstatic void gdProcessUpdate()\n{\n    replyRect.ulx = 5;\n    replyRect.uly = 10;\n    replyRect.lrx = 374;\n    replyRect.lry = 58;\n\n    optionRect.ulx = 5;\n    optionRect.uly = 5;\n    optionRect.lrx = 388;\n    optionRect.lry = 112;\n\n    demo_copy_title(gReplyWin);\n    demo_copy_options(gOptionWin);\n\n    if (dialogBlock.replyMessageListId > 0) {\n        char* s = scr_get_msg_str_speech(dialogBlock.replyMessageListId, dialogBlock.replyMessageId, 1);\n        if (s == NULL) {\n            GNWSystemError(\"\\n'GDialog::Error Grabbing text message!\");\n            exit(1);\n        }\n\n        strncpy(dialogBlock.replyText, s, sizeof(dialogBlock.replyText) - 1);\n        *(dialogBlock.replyText + sizeof(dialogBlock.replyText) - 1) = '\\0';\n    }\n\n    gdProcessReply();\n\n    int color = colorTable[992] | 0x2000000;\n\n    bool hasEmpathy = perk_level(obj_dude, PERK_EMPATHY) != 0;\n\n    int width = optionRect.lrx - optionRect.ulx - 4;\n\n    MessageListItem messageListItem;\n\n    int v21 = 0;\n\n    for (int index = 0; index < gdNumOptions; index++) {\n        GameDialogOptionEntry* dialogOptionEntry = &(dialogBlock.options[index]);\n\n        if (hasEmpathy) {\n            switch (dialogOptionEntry->reaction) {\n            case GAME_DIALOG_REACTION_GOOD:\n                color = colorTable[31] | 0x2000000;\n                break;\n            case GAME_DIALOG_REACTION_NEUTRAL:\n                color = colorTable[992] | 0x2000000;\n                break;\n            case GAME_DIALOG_REACTION_BAD:\n                color = colorTable[31744] | 0x2000000;\n                break;\n            default:\n                debug_printf(\"\\nError: dialog: Empathy Perk: invalid reaction!\");\n                break;\n            }\n        }\n\n        if (dialogOptionEntry->messageListId >= 0) {\n            char* text = scr_get_msg_str_speech(dialogOptionEntry->messageListId, dialogOptionEntry->messageId, 0);\n            if (text == NULL) {\n                GNWSystemError(\"\\nGDialog::Error Grabbing text message!\");\n                exit(1);\n            }\n\n            sprintf(dialogOptionEntry->text, \"%c \", '\\x95');\n            strncat(dialogOptionEntry->text, text, 897);\n        } else if (dialogOptionEntry->messageListId == -1) {\n            if (index == 0) {\n                // Go on\n                messageListItem.num = 655;\n                if (critterGetStat(obj_dude, STAT_INTELLIGENCE) < 4) {\n                    if (message_search(&proto_main_msg_file, &messageListItem)) {\n                        strcpy(dialogOptionEntry->text, messageListItem.text);\n                    } else {\n                        debug_printf(\"\\nError...can't find message!\");\n                        return;\n                    }\n                }\n            } else {\n                // TODO: Why only space?\n                strcpy(dialogOptionEntry->text, \" \");\n            }\n        } else if (dialogOptionEntry->messageListId == -2) {\n            // [Done]\n            messageListItem.num = 650;\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                sprintf(dialogOptionEntry->text, \"%c %s\", '\\x95', messageListItem.text);\n            } else {\n                debug_printf(\"\\nError...can't find message!\");\n                return;\n            }\n        }\n\n        int v11 = text_num_lines(dialogOptionEntry->text, optionRect.lrx - optionRect.ulx) * text_height() + optionRect.uly + 2;\n        if (v11 < optionRect.lry) {\n            int y = optionRect.uly;\n\n            dialogOptionEntry->field_39C = v11;\n            dialogOptionEntry->field_14 = y;\n\n            if (index == 0) {\n                y = 0;\n            }\n\n            // NOTE: Uninline.\n            text_to_rect_wrapped(win_get_buf(gOptionWin),\n                &optionRect,\n                dialogOptionEntry->text,\n                NULL,\n                text_height(),\n                393,\n                color);\n\n            optionRect.uly += 2;\n\n            dialogOptionEntry->btn = win_register_button(gOptionWin, 2, y, width, optionRect.uly - y - 4, 1200 + index, 1300 + index, -1, 49 + index, NULL, NULL, NULL, 0);\n            if (dialogOptionEntry->btn != -1) {\n                win_register_button_sound_func(dialogOptionEntry->btn, gsound_red_butt_press, gsound_red_butt_release);\n            } else {\n                debug_printf(\"\\nError: Can't create button!\");\n            }\n        } else {\n            if (!v21) {\n                v21 = 1;\n            } else {\n                debug_printf(\"Error: couldn't make button because it went below the window.\\n\");\n            }\n        }\n    }\n\n    gdUpdateMula();\n    win_draw(gReplyWin);\n    win_draw(gOptionWin);\n}\n\n// 0x44715C\nstatic int gdCreateHeadWindow()\n{\n    dialogue_state = 1;\n\n    int windowWidth = GAME_DIALOG_WINDOW_WIDTH;\n\n    // NOTE: Uninline.\n    talk_to_create_background_window();\n    talk_to_refresh_background_window();\n\n    unsigned char* buf = win_get_buf(dialogueBackWindow);\n\n    for (int index = 0; index < 8; index++) {\n        soundContinueAll();\n\n        Rect* rect = &(backgrndRects[index]);\n        int width = rect->lrx - rect->ulx;\n        int height = rect->lry - rect->uly;\n        backgrndBufs[index] = (unsigned char*)mem_malloc(width * height);\n        if (backgrndBufs[index] == NULL) {\n            return -1;\n        }\n\n        unsigned char* src = buf;\n        src += windowWidth * rect->uly + rect->ulx;\n\n        buf_to_buf(src, width, height, windowWidth, backgrndBufs[index], width);\n    }\n\n    gdialog_window_create();\n\n    headWindowBuffer = win_get_buf(dialogueBackWindow) + windowWidth * 14 + 126;\n\n    if (headWindowBuffer == NULL) {\n        gdDestroyHeadWindow();\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x447294\nstatic void gdDestroyHeadWindow()\n{\n    if (dialogueWindow != -1) {\n        headWindowBuffer = NULL;\n    }\n\n    if (dialogue_state == 1) {\n        gdialog_window_destroy();\n    } else if (dialogue_state == 4) {\n        gdialog_barter_destroy_win();\n    }\n\n    if (dialogueBackWindow != -1) {\n        win_delete(dialogueBackWindow);\n        dialogueBackWindow = -1;\n    }\n\n    for (int index = 0; index < 8; index++) {\n        mem_free(backgrndBufs[index]);\n    }\n}\n\n// 0x447300\nstatic void gdSetupFidget(int headFrmId, int reaction)\n{\n    // 0x518900\n    static int phone_anim = 0;\n\n    fidgetFrameCounter = 0;\n\n    if (headFrmId == -1) {\n        fidgetFID = -1;\n        fidgetFp = NULL;\n        fidgetKey = INVALID_CACHE_ENTRY;\n        fidgetAnim = -1;\n        fidgetTocksPerFrame = 0;\n        fidgetLastTime = 0;\n        gdDisplayFrame(NULL, 0);\n        lipsFID = 0;\n        lipsKey = NULL;\n        lipsFp = 0;\n        return;\n    }\n\n    int anim = HEAD_ANIMATION_NEUTRAL_PHONEMES;\n    switch (reaction) {\n    case FIDGET_GOOD:\n        anim = HEAD_ANIMATION_GOOD_PHONEMES;\n        break;\n    case FIDGET_BAD:\n        anim = HEAD_ANIMATION_BAD_PHONEMES;\n        break;\n    }\n\n    if (lipsFID != 0) {\n        if (anim != phone_anim) {\n            if (art_ptr_unlock(lipsKey) == -1) {\n                debug_printf(\"failure unlocking lips frame!\\n\");\n            }\n            lipsKey = NULL;\n            lipsFp = NULL;\n            lipsFID = 0;\n        }\n    }\n\n    if (lipsFID == 0) {\n        phone_anim = anim;\n        lipsFID = art_id(OBJ_TYPE_HEAD, headFrmId, anim, 0, 0);\n        lipsFp = art_ptr_lock(lipsFID, &lipsKey);\n        if (lipsFp == NULL) {\n            debug_printf(\"failure!\\n\");\n\n            char stats[200];\n            cache_stats(&art_cache, stats);\n            debug_printf(\"%s\", stats);\n        }\n    }\n\n    int fid = art_id(OBJ_TYPE_HEAD, headFrmId, reaction, 0, 0);\n    int fidgetCount = art_head_fidgets(fid);\n    if (fidgetCount == -1) {\n        debug_printf(\"\\tError - No available fidgets for given frame id\\n\");\n        return;\n    }\n\n    int chance = roll_random(1, 100) + dialogue_seconds_since_last_input / 2;\n\n    int fidget = fidgetCount;\n    switch (fidgetCount) {\n    case 1:\n        fidget = 1;\n        break;\n    case 2:\n        if (chance < 68) {\n            fidget = 1;\n        } else {\n            fidget = 2;\n        }\n        break;\n    case 3:\n        dialogue_seconds_since_last_input = 0;\n        if (chance < 52) {\n            fidget = 1;\n        } else if (chance < 77) {\n            fidget = 2;\n        } else {\n            fidget = 3;\n        }\n        break;\n    }\n\n    debug_printf(\"Choosing fidget %d out of %d\\n\", fidget, fidgetCount);\n\n    if (fidgetFp != NULL) {\n        if (art_ptr_unlock(fidgetKey) == -1) {\n            debug_printf(\"failure!\\n\");\n        }\n    }\n\n    fidgetFID = art_id(OBJ_TYPE_HEAD, headFrmId, reaction, fidget, 0);\n    fidgetFrameCounter = 0;\n    fidgetFp = art_ptr_lock(fidgetFID, &fidgetKey);\n    if (fidgetFp == NULL) {\n        debug_printf(\"failure!\\n\");\n\n        char stats[200];\n        cache_stats(&art_cache, stats);\n        debug_printf(\"%s\", stats);\n    }\n\n    fidgetLastTime = 0;\n    fidgetAnim = reaction;\n    fidgetTocksPerFrame = 1000 / art_frame_fps(fidgetFp);\n}\n\n// 0x447598\nstatic void gdWaitForFidget()\n{\n    if (fidgetFp == NULL) {\n        return;\n    }\n\n    if (dialogueWindow == -1) {\n        return;\n    }\n\n    debug_printf(\"Waiting for fidget to complete...\\n\");\n\n    while (art_frame_max_frame(fidgetFp) > fidgetFrameCounter) {\n        if (elapsed_time(fidgetLastTime) >= fidgetTocksPerFrame) {\n            gdDisplayFrame(fidgetFp, fidgetFrameCounter);\n            fidgetLastTime = get_time();\n            fidgetFrameCounter++;\n        }\n    }\n\n    fidgetFrameCounter = 0;\n}\n\n// 0x447614\nstatic void gdPlayTransition(int anim)\n{\n    if (fidgetFp == NULL) {\n        return;\n    }\n\n    if (dialogueWindow == -1) {\n        return;\n    }\n\n    mouse_hide();\n\n    debug_printf(\"Starting transition...\\n\");\n\n    gdWaitForFidget();\n\n    if (fidgetFp != NULL) {\n        if (art_ptr_unlock(fidgetKey) == -1) {\n            debug_printf(\"\\tError unlocking fidget in transition func...\");\n        }\n        fidgetFp = NULL;\n    }\n\n    CacheEntry* headFrmHandle;\n    int headFid = art_id(OBJ_TYPE_HEAD, dialogue_head, anim, 0, 0);\n    Art* headFrm = art_ptr_lock(headFid, &headFrmHandle);\n    if (headFrm == NULL) {\n        debug_printf(\"\\tError locking transition...\\n\");\n    }\n\n    unsigned int delay = 1000 / art_frame_fps(headFrm);\n\n    int frame = 0;\n    unsigned int time = 0;\n    while (frame < art_frame_max_frame(headFrm)) {\n        if (elapsed_time(time) >= delay) {\n            gdDisplayFrame(headFrm, frame);\n            time = get_time();\n            frame++;\n        }\n    }\n\n    if (art_ptr_unlock(headFrmHandle) == -1) {\n        debug_printf(\"\\tError unlocking transition...\\n\");\n    }\n\n    debug_printf(\"Finished transition...\\n\");\n    mouse_show();\n}\n\n// 0x447724\nstatic void reply_arrow_up(int btn, int keyCode)\n{\n    if (gdReplyTooBig) {\n        gmouse_set_cursor(MOUSE_CURSOR_SMALL_ARROW_UP);\n    }\n}\n\n// 0x447738\nstatic void reply_arrow_down(int btn, int keyCode)\n{\n    if (gdReplyTooBig) {\n        gmouse_set_cursor(MOUSE_CURSOR_SMALL_ARROW_DOWN);\n    }\n}\n\n// 0x44774C\nstatic void reply_arrow_restore(int btn, int keyCode)\n{\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n}\n\n// demo_copy_title\n// 0x447758\nstatic void demo_copy_title(int win)\n{\n    gd_replyWin = win;\n\n    if (win == -1) {\n        debug_printf(\"\\nError: demo_copy_title: win invalid!\");\n        return;\n    }\n\n    int width = win_width(win);\n    if (width < 1) {\n        debug_printf(\"\\nError: demo_copy_title: width invalid!\");\n        return;\n    }\n\n    int height = win_height(win);\n    if (height < 1) {\n        debug_printf(\"\\nError: demo_copy_title: length invalid!\");\n        return;\n    }\n\n    if (dialogueBackWindow == -1) {\n        debug_printf(\"\\nError: demo_copy_title: dialogueBackWindow wasn't created!\");\n        return;\n    }\n\n    unsigned char* src = win_get_buf(dialogueBackWindow);\n    if (src == NULL) {\n        debug_printf(\"\\nError: demo_copy_title: couldn't get buffer!\");\n        return;\n    }\n\n    unsigned char* dest = win_get_buf(win);\n\n    buf_to_buf(src + 640 * 225 + 135, width, height, 640, dest, width);\n}\n\n// demo_copy_options\n// 0x447818\nstatic void demo_copy_options(int win)\n{\n    gd_optionsWin = win;\n\n    if (win == -1) {\n        debug_printf(\"\\nError: demo_copy_options: win invalid!\");\n        return;\n    }\n\n    int width = win_width(win);\n    if (width < 1) {\n        debug_printf(\"\\nError: demo_copy_options: width invalid!\");\n        return;\n    }\n\n    int height = win_height(win);\n    if (height < 1) {\n        debug_printf(\"\\nError: demo_copy_options: length invalid!\");\n        return;\n    }\n\n    if (dialogueBackWindow == -1) {\n        debug_printf(\"\\nError: demo_copy_options: dialogueBackWindow wasn't created!\");\n        return;\n    }\n\n    Rect windowRect;\n    win_get_rect(dialogueWindow, &windowRect);\n\n    unsigned char* src = win_get_buf(dialogueWindow);\n    if (src == NULL) {\n        debug_printf(\"\\nError: demo_copy_options: couldn't get buffer!\");\n        return;\n    }\n\n    unsigned char* dest = win_get_buf(win);\n    buf_to_buf(src + 640 * (335 - windowRect.uly) + 127, width, height, 640, dest, width);\n}\n\n// gDialogRefreshOptionsRect\n// 0x447914\nstatic void gDialogRefreshOptionsRect(int win, Rect* drawRect)\n{\n    if (drawRect == NULL) {\n        debug_printf(\"\\nError: gDialogRefreshOptionsRect: drawRect NULL!\");\n        return;\n    }\n\n    if (win == -1) {\n        debug_printf(\"\\nError: gDialogRefreshOptionsRect: win invalid!\");\n        return;\n    }\n\n    if (dialogueBackWindow == -1) {\n        debug_printf(\"\\nError: gDialogRefreshOptionsRect: dialogueBackWindow wasn't created!\");\n        return;\n    }\n\n    Rect windowRect;\n    win_get_rect(dialogueWindow, &windowRect);\n\n    unsigned char* src = win_get_buf(dialogueWindow);\n    if (src == NULL) {\n        debug_printf(\"\\nError: gDialogRefreshOptionsRect: couldn't get buffer!\");\n        return;\n    }\n\n    if (drawRect->uly >= drawRect->lry) {\n        debug_printf(\"\\nError: gDialogRefreshOptionsRect: Invalid Rect (too many options)!\");\n        return;\n    }\n\n    if (drawRect->ulx >= drawRect->lrx) {\n        debug_printf(\"\\nError: gDialogRefreshOptionsRect: Invalid Rect (too many options)!\");\n        return;\n    }\n\n    int destWidth = win_width(win);\n    unsigned char* dest = win_get_buf(win);\n\n    buf_to_buf(\n        src + (640 * (335 - windowRect.uly) + 127) + (640 * drawRect->uly + drawRect->ulx),\n        drawRect->lrx - drawRect->ulx,\n        drawRect->lry - drawRect->uly,\n        640,\n        dest + destWidth * drawRect->uly,\n        destWidth);\n}\n\n// 0x447A58\nstatic void gdialog_bk()\n{\n    // 0x518904\n    static int loop_cnt = -1;\n\n    // 0x518908\n    static unsigned int tocksWaiting = 10000;\n\n    switch (dialogue_switch_mode) {\n    case 2:\n        loop_cnt = -1;\n        dialogue_switch_mode = 3;\n        gdialog_window_destroy();\n        gdialog_barter_create_win();\n        break;\n    case 1:\n        loop_cnt = -1;\n        dialogue_switch_mode = 0;\n        gdialog_barter_destroy_win();\n        gdialog_window_create();\n\n        // NOTE: Uninline.\n        gdUnhide();\n\n        break;\n    case 8:\n        loop_cnt = -1;\n        dialogue_switch_mode = 9;\n        gdialog_window_destroy();\n        gdControlCreateWin();\n        break;\n    case 11:\n        loop_cnt = -1;\n        dialogue_switch_mode = 12;\n        gdialog_window_destroy();\n        gdCustomCreateWin();\n        break;\n    }\n\n    if (fidgetFp == NULL) {\n        return;\n    }\n\n    if (gdialog_speech_playing) {\n        lips_bkg_proc();\n\n        if (lips_draw_head) {\n            gdDisplayFrame(lipsFp, head_phoneme_lookup[head_phoneme_current]);\n            lips_draw_head = false;\n        }\n\n        if (!soundPlaying(lip_info.sound)) {\n            gdialogFreeSpeech();\n            gdDisplayFrame(lipsFp, 0);\n            can_start_new_fidget = true;\n            dialogue_seconds_since_last_input = 3;\n            fidgetFrameCounter = 0;\n        }\n        return;\n    }\n\n    if (can_start_new_fidget) {\n        if (elapsed_time(fidgetLastTime) >= tocksWaiting) {\n            can_start_new_fidget = false;\n            dialogue_seconds_since_last_input += tocksWaiting / 1000;\n            tocksWaiting = 1000 * (roll_random(0, 3) + 4);\n            gdSetupFidget(fidgetFID & 0xFFF, (fidgetFID & 0xFF0000) >> 16);\n        }\n        return;\n    }\n\n    if (elapsed_time(fidgetLastTime) >= fidgetTocksPerFrame) {\n        if (art_frame_max_frame(fidgetFp) <= fidgetFrameCounter) {\n            gdDisplayFrame(fidgetFp, 0);\n            can_start_new_fidget = true;\n        } else {\n            gdDisplayFrame(fidgetFp, fidgetFrameCounter);\n            fidgetLastTime = get_time();\n            fidgetFrameCounter += 1;\n        }\n    }\n}\n\n// FIXME: Due to the bug in `gdProcessChoice` this function can receive invalid\n// reaction value (50 instead of expected -1, 0, 1). It's handled gracefully by\n// the game.\n//\n// 0x447CA0\nvoid talk_to_critter_reacts(int a1)\n{\n    int v1 = a1 + 1;\n\n    debug_printf(\"Dialogue Reaction: \");\n    if (v1 < 3) {\n        debug_printf(\"%s\\n\", react_strs[v1]);\n    }\n\n    int v3 = a1 + 50;\n    dialogue_seconds_since_last_input = 0;\n\n    switch (v3) {\n    case GAME_DIALOG_REACTION_GOOD:\n        switch (fidgetAnim) {\n        case FIDGET_GOOD:\n            gdPlayTransition(HEAD_ANIMATION_VERY_GOOD_REACTION);\n            gdSetupFidget(dialogue_head, FIDGET_GOOD);\n            break;\n        case FIDGET_NEUTRAL:\n            gdPlayTransition(HEAD_ANIMATION_NEUTRAL_TO_GOOD);\n            gdSetupFidget(dialogue_head, FIDGET_GOOD);\n            break;\n        case FIDGET_BAD:\n            gdPlayTransition(HEAD_ANIMATION_BAD_TO_NEUTRAL);\n            gdSetupFidget(dialogue_head, FIDGET_NEUTRAL);\n            break;\n        }\n        break;\n    case GAME_DIALOG_REACTION_NEUTRAL:\n        break;\n    case GAME_DIALOG_REACTION_BAD:\n        switch (fidgetAnim) {\n        case FIDGET_GOOD:\n            gdPlayTransition(HEAD_ANIMATION_GOOD_TO_NEUTRAL);\n            gdSetupFidget(dialogue_head, FIDGET_NEUTRAL);\n            break;\n        case FIDGET_NEUTRAL:\n            gdPlayTransition(HEAD_ANIMATION_NEUTRAL_TO_BAD);\n            gdSetupFidget(dialogue_head, FIDGET_BAD);\n            break;\n        case FIDGET_BAD:\n            gdPlayTransition(HEAD_ANIMATION_VERY_BAD_REACTION);\n            gdSetupFidget(dialogue_head, FIDGET_BAD);\n            break;\n        }\n        break;\n    }\n}\n\n// 0x447D98\nstatic void gdialog_scroll_subwin(int win, int a2, unsigned char* a3, unsigned char* a4, unsigned char* a5, int a6, int a7)\n{\n    int v7;\n    unsigned char* v9;\n    Rect rect;\n    unsigned int tick;\n\n    v7 = a6;\n    v9 = a4;\n\n    if (a2 == 1) {\n        rect.ulx = 0;\n        rect.lrx = GAME_DIALOG_WINDOW_WIDTH - 1;\n        rect.lry = a6 - 1;\n\n        int v18 = a6 / 10;\n        if (a7 == -1) {\n            rect.uly = 10;\n            v18 = 0;\n        } else {\n            rect.uly = v18 * 10;\n            v7 = a6 % 10;\n            v9 += (GAME_DIALOG_WINDOW_WIDTH) * rect.uly;\n        }\n\n        for (; v18 >= 0; v18--) {\n            soundContinueAll();\n            buf_to_buf(a3,\n                GAME_DIALOG_WINDOW_WIDTH,\n                v7,\n                GAME_DIALOG_WINDOW_WIDTH,\n                v9,\n                GAME_DIALOG_WINDOW_WIDTH);\n            rect.uly -= 10;\n            win_draw_rect(win, &rect);\n            v7 += 10;\n            v9 -= 10 * (GAME_DIALOG_WINDOW_WIDTH);\n\n            tick = get_time();\n            while (elapsed_time(tick) < 33) {\n            }\n        }\n    } else {\n        rect.lrx = GAME_DIALOG_WINDOW_WIDTH - 1;\n        rect.lry = a6 - 1;\n        rect.ulx = 0;\n        rect.uly = 0;\n\n        for (int index = a6 / 10; index > 0; index--) {\n            soundContinueAll();\n\n            buf_to_buf(a5,\n                GAME_DIALOG_WINDOW_WIDTH,\n                10,\n                GAME_DIALOG_WINDOW_WIDTH,\n                v9,\n                GAME_DIALOG_WINDOW_WIDTH);\n\n            v9 += 10 * (GAME_DIALOG_WINDOW_WIDTH);\n            v7 -= 10;\n            a5 += 10 * (GAME_DIALOG_WINDOW_WIDTH);\n\n            buf_to_buf(a3,\n                GAME_DIALOG_WINDOW_WIDTH,\n                v7,\n                GAME_DIALOG_WINDOW_WIDTH,\n                v9,\n                GAME_DIALOG_WINDOW_WIDTH);\n\n            win_draw_rect(win, &rect);\n\n            rect.uly += 10;\n\n            tick = get_time();\n            while (elapsed_time(tick) < 33) {\n            }\n        }\n    }\n}\n\n// 0x447F64\nstatic int text_num_lines(const char* a1, int a2)\n{\n    int width = text_width(a1);\n\n    int v1 = 0;\n    while (width > 0) {\n        width -= a2;\n        v1++;\n    }\n\n    return v1;\n}\n\n// NOTE: Inlined.\n//\n// 0x447F80\nstatic int text_to_rect_wrapped(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color)\n{\n    return text_to_rect_func(buffer, rect, string, a4, height, pitch, color, 1);\n}\n\n// display_msg\n// 0x447FA0\nstatic int text_to_rect_func(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color, int a7)\n{\n    char* start;\n    if (a4 != NULL) {\n        start = string + *a4;\n    } else {\n        start = string;\n    }\n\n    int maxWidth = rect->lrx - rect->ulx;\n    char* end = NULL;\n    while (start != NULL && *start != '\\0') {\n        if (text_width(start) > maxWidth) {\n            end = start + 1;\n            while (*end != '\\0' && *end != ' ') {\n                end++;\n            }\n\n            if (*end != '\\0') {\n                char* lookahead = end + 1;\n                while (lookahead != NULL) {\n                    while (*lookahead != '\\0' && *lookahead != ' ') {\n                        lookahead++;\n                    }\n\n                    if (*lookahead == '\\0') {\n                        lookahead = NULL;\n                    } else {\n                        *lookahead = '\\0';\n                        if (text_width(start) >= maxWidth) {\n                            *lookahead = ' ';\n                            lookahead = NULL;\n                        } else {\n                            end = lookahead;\n                            *lookahead = ' ';\n                            lookahead++;\n                        }\n                    }\n                }\n\n                if (*end == ' ') {\n                    *end = '\\0';\n                }\n            } else {\n                if (rect->lry - text_height() < rect->uly) {\n                    return rect->uly;\n                }\n\n                if (a7 != 1 || start == string) {\n                    text_to_buf(buffer + pitch * rect->uly + 10, start, maxWidth, pitch, color);\n                } else {\n                    text_to_buf(buffer + pitch * rect->uly, start, maxWidth, pitch, color);\n                }\n\n                if (a4 != NULL) {\n                    *a4 += strlen(start) + 1;\n                }\n\n                rect->uly += height;\n                return rect->uly;\n            }\n        }\n\n        if (text_width(start) > maxWidth) {\n            debug_printf(\"\\nError: display_msg: word too long!\");\n            break;\n        }\n\n        if (a7 != 0) {\n            if (rect->lry - text_height() < rect->uly) {\n                if (end != NULL && *end == '\\0') {\n                    *end = ' ';\n                }\n                return rect->uly;\n            }\n\n            unsigned char* dest;\n            if (a7 != 1 || start == string) {\n                dest = buffer + 10;\n            } else {\n                dest = buffer;\n            }\n            text_to_buf(dest + pitch * rect->uly, start, maxWidth, pitch, color);\n        }\n\n        if (a4 != NULL && end != NULL) {\n            *a4 += strlen(start) + 1;\n        }\n\n        rect->uly += height;\n\n        if (end != NULL) {\n            start = end + 1;\n            if (*end == '\\0') {\n                *end = ' ';\n            }\n            end = NULL;\n        } else {\n            start = NULL;\n        }\n    }\n\n    if (a4 != NULL) {\n        *a4 = 0;\n    }\n\n    return rect->uly;\n}\n\n// 0x448214\nvoid gdialogSetBarterMod(int modifier)\n{\n    gdBarterMod = modifier;\n}\n\n// gdialog_barter\n// 0x44821C\nint gdActivateBarter(int modifier)\n{\n    if (!dialog_state_fix) {\n        return -1;\n    }\n\n    gdBarterMod = modifier;\n    gdialog_barter_pressed(-1, -1);\n    dialogue_state = 4;\n    dialogue_switch_mode = 2;\n\n    return 0;\n}\n\n// 0x448268\nvoid barter_end_to_talk_to()\n{\n    dialogQuit();\n    dialogClose();\n    updatePrograms();\n    updateWindows();\n    dialogue_state = 1;\n    dialogue_switch_mode = 1;\n}\n\n// 0x448290\nstatic int gdialog_barter_create_win()\n{\n    dialogue_state = 4;\n\n    int frmId;\n    if (dialog_target_is_party) {\n        // trade.frm - party member barter/trade interface\n        frmId = 420;\n    } else {\n        // barter.frm - barter window\n        frmId = 111;\n    }\n\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0);\n    CacheEntry* backgroundHandle;\n    Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundHandle);\n    if (backgroundFrm == NULL) {\n        return -1;\n    }\n\n    unsigned char* backgroundData = art_frame_data(backgroundFrm, 0, 0);\n    if (backgroundData == NULL) {\n        art_ptr_unlock(backgroundHandle);\n        return -1;\n    }\n\n    dialogue_subwin_len = art_frame_length(backgroundFrm, 0, 0);\n\n    int barterWindowX = 0;\n    int barterWindowY = GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len;\n    dialogueWindow = win_add(barterWindowX,\n        barterWindowY,\n        GAME_DIALOG_WINDOW_WIDTH,\n        dialogue_subwin_len,\n        256,\n        WINDOW_FLAG_0x02);\n    if (dialogueWindow == -1) {\n        art_ptr_unlock(backgroundHandle);\n        return -1;\n    }\n\n    int width = GAME_DIALOG_WINDOW_WIDTH;\n\n    unsigned char* windowBuffer = win_get_buf(dialogueWindow);\n    unsigned char* backgroundWindowBuffer = win_get_buf(dialogueBackWindow);\n    buf_to_buf(backgroundWindowBuffer + width * (480 - dialogue_subwin_len), width, dialogue_subwin_len, width, windowBuffer, width);\n\n    gdialog_scroll_subwin(dialogueWindow, 1, backgroundData, windowBuffer, NULL, dialogue_subwin_len, 0);\n\n    art_ptr_unlock(backgroundHandle);\n\n    // TRADE\n    gdialog_buttons[0] = win_register_button(dialogueWindow, 41, 163, 14, 14, -1, -1, -1, KEY_LOWERCASE_M, dialog_red_button_up_buf, dialog_red_button_down_buf, 0, BUTTON_FLAG_TRANSPARENT);\n    if (gdialog_buttons[0] != -1) {\n        win_register_button_sound_func(gdialog_buttons[0], gsound_med_butt_press, gsound_med_butt_release);\n\n        // TALK\n        gdialog_buttons[1] = win_register_button(dialogueWindow, 584, 162, 14, 14, -1, -1, -1, KEY_LOWERCASE_T, dialog_red_button_up_buf, dialog_red_button_down_buf, 0, BUTTON_FLAG_TRANSPARENT);\n        if (gdialog_buttons[1] != -1) {\n            win_register_button_sound_func(gdialog_buttons[1], gsound_med_butt_press, gsound_med_butt_release);\n\n            if (obj_new(&peon_table_obj, -1, -1) != -1) {\n                peon_table_obj->flags |= OBJECT_HIDDEN;\n\n                if (obj_new(&barterer_table_obj, -1, -1) != -1) {\n                    barterer_table_obj->flags |= OBJECT_HIDDEN;\n\n                    if (obj_new(&barterer_temp_obj, dialog_target->fid, -1) != -1) {\n                        barterer_temp_obj->flags |= OBJECT_HIDDEN | OBJECT_TEMPORARY;\n                        barterer_temp_obj->sid = -1;\n                        return 0;\n                    }\n\n                    obj_erase_object(barterer_table_obj, 0);\n                }\n\n                obj_erase_object(peon_table_obj, 0);\n            }\n\n            win_delete_button(gdialog_buttons[1]);\n            gdialog_buttons[1] = -1;\n        }\n\n        win_delete_button(gdialog_buttons[0]);\n        gdialog_buttons[0] = -1;\n    }\n\n    win_delete(dialogueWindow);\n    dialogueWindow = -1;\n\n    return -1;\n}\n\n// 0x44854C\nstatic void gdialog_barter_destroy_win()\n{\n    if (dialogueWindow == -1) {\n        return;\n    }\n\n    obj_erase_object(barterer_temp_obj, 0);\n    obj_erase_object(barterer_table_obj, 0);\n    obj_erase_object(peon_table_obj, 0);\n\n    for (int index = 0; index < 9; index++) {\n        win_delete_button(gdialog_buttons[index]);\n        gdialog_buttons[index] = -1;\n    }\n\n    unsigned char* backgroundWindowBuffer = win_get_buf(dialogueBackWindow);\n    backgroundWindowBuffer += (GAME_DIALOG_WINDOW_WIDTH) * (480 - dialogue_subwin_len);\n\n    int frmId;\n    if (dialog_target_is_party) {\n        // trade.frm - party member barter/trade interface\n        frmId = 420;\n    } else {\n        // barter.frm - barter window\n        frmId = 111;\n    }\n\n    CacheEntry* backgroundFrmHandle;\n    int fid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0);\n    unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle);\n    if (backgroundFrmData != NULL) {\n        unsigned char* windowBuffer = win_get_buf(dialogueWindow);\n        gdialog_scroll_subwin(dialogueWindow, 0, backgroundFrmData, windowBuffer, backgroundWindowBuffer, dialogue_subwin_len, 0);\n        art_ptr_unlock(backgroundFrmHandle);\n    }\n\n    win_delete(dialogueWindow);\n    dialogueWindow = -1;\n\n    cai_attempt_w_reload(dialog_target, 0);\n}\n\n// 0x448660\nstatic void gdialog_barter_cleanup_tables()\n{\n    Inventory* inventory;\n    int length;\n\n    inventory = &(peon_table_obj->data.inventory);\n    length = inventory->length;\n    for (int index = 0; index < length; index++) {\n        Object* item = inventory->items->item;\n        int quantity = item_count(peon_table_obj, item);\n        item_move_force(peon_table_obj, obj_dude, item, quantity);\n    }\n\n    inventory = &(barterer_table_obj->data.inventory);\n    length = inventory->length;\n    for (int index = 0; index < length; index++) {\n        Object* item = inventory->items->item;\n        int quantity = item_count(barterer_table_obj, item);\n        item_move_force(barterer_table_obj, dialog_target, item, quantity);\n    }\n\n    if (barterer_temp_obj != NULL) {\n        inventory = &(barterer_temp_obj->data.inventory);\n        length = inventory->length;\n        for (int index = 0; index < length; index++) {\n            Object* item = inventory->items->item;\n            int quantity = item_count(barterer_temp_obj, item);\n            item_move_force(barterer_temp_obj, dialog_target, item, quantity);\n        }\n    }\n}\n\n// 0x448740\nstatic int gdControlCreateWin()\n{\n    CacheEntry* backgroundFrmHandle;\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 390, 0, 0, 0);\n    Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle);\n    if (backgroundFrm == NULL) {\n        return -1;\n    }\n\n    unsigned char* backgroundData = art_frame_data(backgroundFrm, 0, 0);\n    if (backgroundData == NULL) {\n        gdControlDestroyWin();\n        return -1;\n    }\n\n    dialogue_subwin_len = art_frame_length(backgroundFrm, 0, 0);\n    int controlWindowX = 0;\n    int controlWindowY = GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len;\n    dialogueWindow = win_add(controlWindowX,\n        controlWindowY,\n        GAME_DIALOG_WINDOW_WIDTH,\n        dialogue_subwin_len,\n        256,\n        WINDOW_FLAG_0x02);\n    if (dialogueWindow == -1) {\n        gdControlDestroyWin();\n        return -1;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(dialogueWindow);\n    unsigned char* src = win_get_buf(dialogueBackWindow);\n    buf_to_buf(src + (GAME_DIALOG_WINDOW_WIDTH) * (GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len), GAME_DIALOG_WINDOW_WIDTH, dialogue_subwin_len, GAME_DIALOG_WINDOW_WIDTH, windowBuffer, GAME_DIALOG_WINDOW_WIDTH);\n    gdialog_scroll_subwin(dialogueWindow, 1, backgroundData, windowBuffer, 0, dialogue_subwin_len, 0);\n    art_ptr_unlock(backgroundFrmHandle);\n\n    // TALK\n    gdialog_buttons[0] = win_register_button(dialogueWindow, 593, 41, 14, 14, -1, -1, -1, KEY_ESCAPE, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (gdialog_buttons[0] == -1) {\n        gdControlDestroyWin();\n        return -1;\n    }\n    win_register_button_sound_func(gdialog_buttons[0], gsound_med_butt_press, gsound_med_butt_release);\n\n    // TRADE\n    gdialog_buttons[1] = win_register_button(dialogueWindow, 593, 97, 14, 14, -1, -1, -1, KEY_LOWERCASE_D, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (gdialog_buttons[1] == -1) {\n        gdControlDestroyWin();\n        return -1;\n    }\n    win_register_button_sound_func(gdialog_buttons[1], gsound_med_butt_press, gsound_med_butt_release);\n\n    // USE BEST WEAPON\n    gdialog_buttons[2] = win_register_button(dialogueWindow, 236, 15, 14, 14, -1, -1, -1, KEY_LOWERCASE_W, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (gdialog_buttons[2] == -1) {\n        gdControlDestroyWin();\n        return -1;\n    }\n    win_register_button_sound_func(gdialog_buttons[1], gsound_med_butt_press, gsound_med_butt_release);\n\n    // USE BEST ARMOR\n    gdialog_buttons[3] = win_register_button(dialogueWindow, 235, 46, 14, 14, -1, -1, -1, KEY_LOWERCASE_A, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (gdialog_buttons[3] == -1) {\n        gdControlDestroyWin();\n        return -1;\n    }\n    win_register_button_sound_func(gdialog_buttons[2], gsound_med_butt_press, gsound_med_butt_release);\n\n    control_buttons_start = 4;\n\n    int v21 = 3;\n\n    for (int index = 0; index < 5; index++) {\n        GameDialogButtonData* buttonData = &(control_button_info[index]);\n        int fid;\n\n        fid = art_id(OBJ_TYPE_INTERFACE, buttonData->upFrmId, 0, 0, 0);\n        Art* upButtonFrm = art_ptr_lock(fid, &(buttonData->upFrmHandle));\n        if (upButtonFrm == NULL) {\n            gdControlDestroyWin();\n            return -1;\n        }\n\n        int width = art_frame_width(upButtonFrm, 0, 0);\n        int height = art_frame_length(upButtonFrm, 0, 0);\n        unsigned char* upButtonFrmData = art_frame_data(upButtonFrm, 0, 0);\n\n        fid = art_id(OBJ_TYPE_INTERFACE, buttonData->downFrmId, 0, 0, 0);\n        Art* downButtonFrm = art_ptr_lock(fid, &(buttonData->downFrmHandle));\n        if (downButtonFrm == NULL) {\n            gdControlDestroyWin();\n            return -1;\n        }\n\n        unsigned char* downButtonFrmData = art_frame_data(downButtonFrm, 0, 0);\n\n        fid = art_id(OBJ_TYPE_INTERFACE, buttonData->disabledFrmId, 0, 0, 0);\n        Art* disabledButtonFrm = art_ptr_lock(fid, &(buttonData->disabledFrmHandle));\n        if (disabledButtonFrm == NULL) {\n            gdControlDestroyWin();\n            return -1;\n        }\n\n        unsigned char* disabledButtonFrmData = art_frame_data(disabledButtonFrm, 0, 0);\n\n        v21++;\n\n        gdialog_buttons[v21] = win_register_button(dialogueWindow,\n            buttonData->x,\n            buttonData->y,\n            width,\n            height,\n            -1,\n            -1,\n            buttonData->keyCode,\n            -1,\n            upButtonFrmData,\n            downButtonFrmData,\n            NULL,\n            BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x04 | BUTTON_FLAG_0x01);\n        if (gdialog_buttons[v21] == -1) {\n            gdControlDestroyWin();\n            return -1;\n        }\n\n        win_register_button_disable(gdialog_buttons[v21], disabledButtonFrmData, disabledButtonFrmData, disabledButtonFrmData);\n        win_register_button_sound_func(gdialog_buttons[v21], gsound_med_butt_press, gsound_med_butt_release);\n\n        if (!partyMemberHasAIDisposition(dialog_target, buttonData->value)) {\n            win_disable_button(gdialog_buttons[v21]);\n        }\n    }\n\n    win_group_radio_buttons(5, &(gdialog_buttons[control_buttons_start]));\n\n    int disposition = ai_get_disposition(dialog_target);\n    win_set_button_rest_state(gdialog_buttons[control_buttons_start + 4 - disposition], 1, 0);\n\n    gdControlUpdateInfo();\n\n    dialogue_state = 10;\n\n    win_draw(dialogueWindow);\n\n    return 0;\n}\n\n// 0x448C10\nstatic void gdControlDestroyWin()\n{\n    if (dialogueWindow == -1) {\n        return;\n    }\n\n    for (int index = 0; index < 9; index++) {\n        win_delete_button(gdialog_buttons[index]);\n        gdialog_buttons[index] = -1;\n    }\n\n    for (int index = 0; index < 5; index++) {\n        GameDialogButtonData* buttonData = &(control_button_info[index]);\n\n        if (buttonData->upFrmHandle) {\n            art_ptr_unlock(buttonData->upFrmHandle);\n            buttonData->upFrmHandle = NULL;\n        }\n\n        if (buttonData->downFrmHandle) {\n            art_ptr_unlock(buttonData->downFrmHandle);\n            buttonData->downFrmHandle = NULL;\n        }\n\n        if (buttonData->disabledFrmHandle) {\n            art_ptr_unlock(buttonData->disabledFrmHandle);\n            buttonData->disabledFrmHandle = NULL;\n        }\n    }\n\n    // control.frm - party member control interface\n    CacheEntry* backgroundFrmHandle;\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 390, 0, 0, 0);\n    unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n    if (backgroundFrmData != NULL) {\n        gdialog_scroll_subwin(dialogueWindow, 0, backgroundFrmData, win_get_buf(dialogueWindow), win_get_buf(dialogueBackWindow) + (GAME_DIALOG_WINDOW_WIDTH) * (480 - dialogue_subwin_len), dialogue_subwin_len, 0);\n        art_ptr_unlock(backgroundFrmHandle);\n    }\n\n    win_delete(dialogueWindow);\n    dialogueWindow = -1;\n}\n\n// 0x448D30\nstatic void gdControlUpdateInfo()\n{\n    int oldFont = text_curr();\n    text_font(101);\n\n    unsigned char* windowBuffer = win_get_buf(dialogueWindow);\n    int windowWidth = win_width(dialogueWindow);\n\n    CacheEntry* backgroundHandle;\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 390, 0, 0, 0);\n    Art* background = art_ptr_lock(backgroundFid, &backgroundHandle);\n    if (background != NULL) {\n        int width = art_frame_width(background, 0, 0);\n        unsigned char* buffer = art_frame_data(background, 0, 0);\n\n        // Clear \"Weapon Used:\".\n        buf_to_buf(buffer + width * 20 + 112, 110, text_height(), width, windowBuffer + windowWidth * 20 + 112, windowWidth);\n\n        // Clear \"Armor Used:\".\n        buf_to_buf(buffer + width * 49 + 112, 110, text_height(), width, windowBuffer + windowWidth * 49 + 112, windowWidth);\n\n        // Clear character preview.\n        buf_to_buf(buffer + width * 84 + 8, 70, 98, width, windowBuffer + windowWidth * 84 + 8, windowWidth);\n\n        // Clear ?\n        buf_to_buf(buffer + width * 80 + 232, 132, 106, width, windowBuffer + windowWidth * 80 + 232, windowWidth);\n\n        art_ptr_unlock(backgroundHandle);\n    }\n\n    MessageListItem messageListItem;\n    char* text;\n    char formattedText[256];\n\n    // Render item in right hand.\n    Object* item2 = inven_right_hand(dialog_target);\n    text = item2 != NULL ? item_name(item2) : getmsg(&proto_main_msg_file, &messageListItem, 10);\n    sprintf(formattedText, \"%s\", text);\n    text_to_buf(windowBuffer + windowWidth * 20 + 112, formattedText, 110, windowWidth, colorTable[992]);\n\n    // Render armor.\n    Object* armor = inven_worn(dialog_target);\n    text = armor != NULL ? item_name(armor) : getmsg(&proto_main_msg_file, &messageListItem, 10);\n    sprintf(formattedText, \"%s\", text);\n    text_to_buf(windowBuffer + windowWidth * 49 + 112, formattedText, 110, windowWidth, colorTable[992]);\n\n    // Render preview.\n    CacheEntry* previewHandle;\n    int previewFid = art_id(FID_TYPE(dialog_target->fid), dialog_target->fid & 0xFFF, ANIM_STAND, (dialog_target->fid & 0xF000) >> 12, ROTATION_SW);\n    Art* preview = art_ptr_lock(previewFid, &previewHandle);\n    if (preview != NULL) {\n        int width = art_frame_width(preview, 0, ROTATION_SW);\n        int height = art_frame_length(preview, 0, ROTATION_SW);\n        unsigned char* buffer = art_frame_data(preview, 0, ROTATION_SW);\n        trans_buf_to_buf(buffer, width, height, width, windowBuffer + windowWidth * (132 - height / 2) + 39 - width / 2, windowWidth);\n        art_ptr_unlock(previewHandle);\n    }\n\n    // Render hit points.\n    int maximumHitPoints = critterGetStat(dialog_target, STAT_MAXIMUM_HIT_POINTS);\n    int hitPoints = critterGetStat(dialog_target, STAT_CURRENT_HIT_POINTS);\n    sprintf(formattedText, \"%d/%d\", hitPoints, maximumHitPoints);\n    text_to_buf(windowBuffer + windowWidth * 96 + 240, formattedText, 115, windowWidth, colorTable[992]);\n\n    // Render best skill.\n    int bestSkill = partyMemberSkill(dialog_target);\n    text = skill_name(bestSkill);\n    sprintf(formattedText, \"%s\", text);\n    text_to_buf(windowBuffer + windowWidth * 113 + 240, formattedText, 115, windowWidth, colorTable[992]);\n\n    // Render weight summary.\n    int inventoryWeight = item_total_weight(dialog_target);\n    int carryWeight = critterGetStat(dialog_target, STAT_CARRY_WEIGHT);\n    sprintf(formattedText, \"%d/%d \", inventoryWeight, carryWeight);\n    text_to_buf(windowBuffer + windowWidth * 131 + 240, formattedText, 115, windowWidth, critterIsOverloaded(dialog_target) ? colorTable[31744] : colorTable[992]);\n\n    // Render melee damage.\n    int meleeDamage = critterGetStat(dialog_target, STAT_MELEE_DAMAGE);\n    sprintf(formattedText, \"%d\", meleeDamage);\n    text_to_buf(windowBuffer + windowWidth * 148 + 240, formattedText, 115, windowWidth, colorTable[992]);\n\n    int actionPoints;\n    if (isInCombat()) {\n        actionPoints = dialog_target->data.critter.combat.ap;\n    } else {\n        actionPoints = critterGetStat(dialog_target, STAT_MAXIMUM_ACTION_POINTS);\n    }\n    int maximumActionPoints = critterGetStat(dialog_target, STAT_MAXIMUM_ACTION_POINTS);\n    sprintf(formattedText, \"%d/%d \", actionPoints, maximumActionPoints);\n    text_to_buf(windowBuffer + windowWidth * 167 + 240, formattedText, 115, windowWidth, colorTable[992]);\n\n    text_font(oldFont);\n    win_draw(dialogueWindow);\n}\n\n// 0x44928C\nstatic void gdControlPressed(int btn, int keyCode)\n{\n    dialogue_switch_mode = 8;\n    dialogue_state = 10;\n\n    // NOTE: Uninline.\n    gdHide();\n}\n\n// 0x4492D0\nstatic int gdPickAIUpdateMsg(Object* critter)\n{\n    // TODO: Check.\n    // 0x444D10\n    static const int pids[3] = {\n        0x1000088,\n        0x1000156,\n        0x1000180,\n    };\n\n    for (int index = 0; index < 3; index++) {\n        if (critter->pid == pids[index]) {\n            return 677 + roll_random(0, 1);\n        }\n    }\n\n    return 670 + roll_random(0, 4);\n}\n\n// 0x449330\nstatic int gdCanBarter()\n{\n    if (PID_TYPE(dialog_target->pid) != OBJ_TYPE_CRITTER) {\n        return 1;\n    }\n\n    Proto* proto;\n    if (proto_ptr(dialog_target->pid, &proto) == -1) {\n        return 1;\n    }\n\n    if (proto->critter.data.flags & CRITTER_BARTER) {\n        return 1;\n    }\n\n    MessageListItem messageListItem;\n\n    // This person will not barter with you.\n    messageListItem.num = 903;\n    if (dialog_target_is_party) {\n        // This critter can't carry anything.\n        messageListItem.num = 913;\n    }\n\n    if (!message_search(&proto_main_msg_file, &messageListItem)) {\n        debug_printf(\"\\nError: gdialog: Can't find message!\");\n        return 0;\n    }\n\n    gdialogDisplayMsg(messageListItem.text);\n\n    return 0;\n}\n\n// 0x4493B8\nstatic void gdControl()\n{\n    MessageListItem messageListItem;\n\n    bool done = false;\n    while (!done) {\n        int keyCode = get_input();\n        if (keyCode != -1) {\n            if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n                game_quit_with_confirm();\n            }\n\n            if (game_user_wants_to_quit != 0) {\n                break;\n            }\n\n            if (keyCode == KEY_LOWERCASE_W) {\n                inven_unwield(dialog_target, 1);\n\n                Object* weapon = ai_search_inven_weap(dialog_target, 0, NULL);\n                if (weapon != NULL) {\n                    inven_wield(dialog_target, weapon, 1);\n                    cai_attempt_w_reload(dialog_target, 0);\n\n                    int num = gdPickAIUpdateMsg(dialog_target);\n                    char* msg = getmsg(&proto_main_msg_file, &messageListItem, num);\n                    gdialogDisplayMsg(msg);\n                    gdControlUpdateInfo();\n                }\n            } else if (keyCode == 2098) {\n                ai_set_disposition(dialog_target, 4);\n            } else if (keyCode == 2099) {\n                ai_set_disposition(dialog_target, 0);\n                dialogue_state = 13;\n                dialogue_switch_mode = 11;\n                done = true;\n            } else if (keyCode == 2102) {\n                ai_set_disposition(dialog_target, 2);\n            } else if (keyCode == 2103) {\n                ai_set_disposition(dialog_target, 3);\n            } else if (keyCode == 2111) {\n                ai_set_disposition(dialog_target, 1);\n            } else if (keyCode == KEY_ESCAPE) {\n                dialogue_switch_mode = 1;\n                dialogue_state = 1;\n                return;\n            } else if (keyCode == KEY_LOWERCASE_A) {\n                if (dialog_target->pid != 0x10000A1) {\n                    Object* armor = ai_search_inven_armor(dialog_target);\n                    if (armor != NULL) {\n                        inven_wield(dialog_target, armor, 0);\n                    }\n                }\n\n                int num = gdPickAIUpdateMsg(dialog_target);\n                char* msg = getmsg(&proto_main_msg_file, &messageListItem, num);\n                gdialogDisplayMsg(msg);\n                gdControlUpdateInfo();\n            } else if (keyCode == KEY_LOWERCASE_D) {\n                if (gdCanBarter()) {\n                    dialogue_switch_mode = 2;\n                    dialogue_state = 4;\n                    return;\n                }\n            } else if (keyCode == -2) {\n                if (mouse_click_in(441, 451, 540, 470)) {\n                    ai_set_disposition(dialog_target, 0);\n                    dialogue_state = 13;\n                    dialogue_switch_mode = 11;\n                    done = true;\n                }\n            }\n        }\n    }\n}\n\n// 0x4496A0\nstatic int gdCustomCreateWin()\n{\n    if (!message_init(&custom_msg_file)) {\n        return -1;\n    }\n\n    if (!message_load(&custom_msg_file, \"game\\\\custom.msg\")) {\n        return -1;\n    }\n\n    CacheEntry* backgroundFrmHandle;\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 391, 0, 0, 0);\n    Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle);\n    if (backgroundFrm == NULL) {\n        return -1;\n    }\n\n    unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0);\n    if (backgroundFrmData == NULL) {\n        // FIXME: Leaking background.\n        gdCustomDestroyWin();\n        return -1;\n    }\n\n    dialogue_subwin_len = art_frame_length(backgroundFrm, 0, 0);\n\n    int customizationWindowX = 0;\n    int customizationWindowY = GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len;\n    dialogueWindow = win_add(customizationWindowX,\n        customizationWindowY,\n        GAME_DIALOG_WINDOW_WIDTH,\n        dialogue_subwin_len,\n        256,\n        WINDOW_FLAG_0x02);\n    if (dialogueWindow == -1) {\n        gdCustomDestroyWin();\n        return -1;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(dialogueWindow);\n    unsigned char* parentWindowBuffer = win_get_buf(dialogueBackWindow);\n    buf_to_buf(parentWindowBuffer + (GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len) * GAME_DIALOG_WINDOW_WIDTH,\n        GAME_DIALOG_WINDOW_WIDTH,\n        dialogue_subwin_len,\n        GAME_DIALOG_WINDOW_WIDTH,\n        windowBuffer,\n        GAME_DIALOG_WINDOW_WIDTH);\n\n    gdialog_scroll_subwin(dialogueWindow, 1, backgroundFrmData, windowBuffer, NULL, dialogue_subwin_len, 0);\n    art_ptr_unlock(backgroundFrmHandle);\n\n    gdialog_buttons[0] = win_register_button(dialogueWindow, 593, 101, 14, 14, -1, -1, -1, 13, dialog_red_button_up_buf, dialog_red_button_down_buf, 0, BUTTON_FLAG_TRANSPARENT);\n    if (gdialog_buttons[0] == -1) {\n        gdCustomDestroyWin();\n        return -1;\n    }\n\n    win_register_button_sound_func(gdialog_buttons[0], gsound_med_butt_press, gsound_med_butt_release);\n\n    int optionButton = 0;\n    custom_buttons_start = 1;\n\n    for (int index = 0; index < PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT; index++) {\n        GameDialogButtonData* buttonData = &(custom_button_info[index]);\n\n        int upButtonFid = art_id(OBJ_TYPE_INTERFACE, buttonData->upFrmId, 0, 0, 0);\n        Art* upButtonFrm = art_ptr_lock(upButtonFid, &(buttonData->upFrmHandle));\n        if (upButtonFrm == NULL) {\n            gdCustomDestroyWin();\n            return -1;\n        }\n\n        int width = art_frame_width(upButtonFrm, 0, 0);\n        int height = art_frame_length(upButtonFrm, 0, 0);\n        unsigned char* upButtonFrmData = art_frame_data(upButtonFrm, 0, 0);\n\n        int downButtonFid = art_id(OBJ_TYPE_INTERFACE, buttonData->downFrmId, 0, 0, 0);\n        Art* downButtonFrm = art_ptr_lock(downButtonFid, &(buttonData->downFrmHandle));\n        if (downButtonFrm == NULL) {\n            gdCustomDestroyWin();\n            return -1;\n        }\n\n        unsigned char* downButtonFrmData = art_frame_data(downButtonFrm, 0, 0);\n\n        optionButton++;\n        gdialog_buttons[optionButton] = win_register_button(dialogueWindow,\n            buttonData->x,\n            buttonData->y,\n            width,\n            height,\n            -1,\n            -1,\n            -1,\n            buttonData->keyCode,\n            upButtonFrmData,\n            downButtonFrmData,\n            NULL,\n            BUTTON_FLAG_TRANSPARENT);\n        if (gdialog_buttons[optionButton] == -1) {\n            gdCustomDestroyWin();\n            return -1;\n        }\n\n        win_register_button_sound_func(gdialog_buttons[index], gsound_med_butt_press, gsound_med_butt_release);\n    }\n\n    custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE] = ai_get_burst_value(dialog_target);\n    custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE] = ai_get_run_away_value(dialog_target);\n    custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON] = ai_get_weapon_pref_value(dialog_target);\n    custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE] = ai_get_distance_pref_value(dialog_target);\n    custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO] = ai_get_attack_who_value(dialog_target);\n    custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE] = ai_get_chem_use_value(dialog_target);\n\n    dialogue_state = 13;\n\n    gdCustomUpdateInfo();\n\n    return 0;\n}\n\n// 0x449A10\nstatic void gdCustomDestroyWin()\n{\n    if (dialogueWindow == -1) {\n        return;\n    }\n\n    for (int index = 0; index < 9; index++) {\n        win_delete_button(gdialog_buttons[index]);\n        gdialog_buttons[index] = -1;\n    }\n\n    for (int index = 0; index < PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT; index++) {\n        GameDialogButtonData* buttonData = &(custom_button_info[index]);\n\n        if (buttonData->upFrmHandle != NULL) {\n            art_ptr_unlock(buttonData->upFrmHandle);\n            buttonData->upFrmHandle = NULL;\n        }\n\n        if (buttonData->downFrmHandle != NULL) {\n            art_ptr_unlock(buttonData->downFrmHandle);\n            buttonData->downFrmHandle = NULL;\n        }\n\n        if (buttonData->disabledFrmHandle != NULL) {\n            art_ptr_unlock(buttonData->disabledFrmHandle);\n            buttonData->disabledFrmHandle = NULL;\n        }\n    }\n\n    CacheEntry* backgroundFrmHandle;\n    // custom.frm - party member control interface\n    int fid = art_id(OBJ_TYPE_INTERFACE, 391, 0, 0, 0);\n    unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle);\n    if (backgroundFrmData != NULL) {\n        gdialog_scroll_subwin(dialogueWindow, 0, backgroundFrmData, win_get_buf(dialogueWindow), win_get_buf(dialogueBackWindow) + (GAME_DIALOG_WINDOW_WIDTH) * (480 - dialogue_subwin_len), dialogue_subwin_len, 0);\n        art_ptr_unlock(backgroundFrmHandle);\n    }\n\n    win_delete(dialogueWindow);\n    dialogueWindow = -1;\n\n    message_exit(&custom_msg_file);\n}\n\n// 0x449B3C\nstatic void gdCustom()\n{\n    bool done = false;\n    while (!done) {\n        unsigned int keyCode = get_input();\n        if (keyCode != -1) {\n            if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n                game_quit_with_confirm();\n            }\n\n            if (game_user_wants_to_quit != 0) {\n                break;\n            }\n\n            if (keyCode <= 5) {\n                gdCustomSelect(keyCode);\n                gdCustomUpdateInfo();\n            } else if (keyCode == KEY_RETURN || keyCode == KEY_ESCAPE) {\n                done = true;\n                dialogue_switch_mode = 8;\n                dialogue_state = 10;\n            }\n        }\n    }\n}\n\n// 0x449BB4\nstatic void gdCustomUpdateInfo()\n{\n    int oldFont = text_curr();\n    text_font(101);\n\n    unsigned char* windowBuffer = win_get_buf(dialogueWindow);\n    int windowWidth = win_width(dialogueWindow);\n\n    CacheEntry* backgroundHandle;\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 391, 0, 0, 0);\n    Art* background = art_ptr_lock(backgroundFid, &backgroundHandle);\n    if (background == NULL) {\n        return;\n    }\n\n    int backgroundWidth = art_frame_width(background, 0, 0);\n    int backgroundHeight = art_frame_length(background, 0, 0);\n    unsigned char* backgroundData = art_frame_data(background, 0, 0);\n    buf_to_buf(backgroundData, backgroundWidth, backgroundHeight, backgroundWidth, windowBuffer, GAME_DIALOG_WINDOW_WIDTH);\n\n    art_ptr_unlock(backgroundHandle);\n\n    MessageListItem messageListItem;\n    int num;\n    char* msg;\n\n    // BURST\n    if (custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE] == -1) {\n        // Not Applicable\n        num = 99;\n    } else {\n        debug_printf(\"\\nburst: %d\", custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE]);\n        num = custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE]].messageId;\n    }\n\n    msg = getmsg(&custom_msg_file, &messageListItem, num);\n    text_to_buf(windowBuffer + windowWidth * 20 + 232, msg, 248, windowWidth, colorTable[992]);\n\n    // RUN AWAY\n    msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE]].messageId);\n    text_to_buf(windowBuffer + windowWidth * 48 + 232, msg, 248, windowWidth, colorTable[992]);\n\n    // WEAPON PREF\n    msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON]].messageId);\n    text_to_buf(windowBuffer + windowWidth * 78 + 232, msg, 248, windowWidth, colorTable[992]);\n\n    // DISTANCE\n    msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE]].messageId);\n    text_to_buf(windowBuffer + windowWidth * 108 + 232, msg, 248, windowWidth, colorTable[992]);\n\n    // ATTACK WHO\n    msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO]].messageId);\n    text_to_buf(windowBuffer + windowWidth * 137 + 232, msg, 248, windowWidth, colorTable[992]);\n\n    // CHEM USE\n    msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE]].messageId);\n    text_to_buf(windowBuffer + windowWidth * 166 + 232, msg, 248, windowWidth, colorTable[992]);\n\n    win_draw(dialogueWindow);\n    text_font(oldFont);\n}\n\n// 0x449E64\nstatic void gdCustomSelectRedraw(unsigned char* dest, int pitch, int type, int selectedIndex)\n{\n    MessageListItem messageListItem;\n\n    text_font(101);\n\n    for (int index = 0; index < 6; index++) {\n        STRUCT_5189E4* ptr = &(custom_settings[type][index]);\n        if (ptr->messageId != -1) {\n            bool enabled = false;\n            switch (type) {\n            case PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE:\n                enabled = partyMemberHasAIBurstValue(dialog_target, ptr->value);\n                break;\n            case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE:\n                enabled = partyMemberHasAIRunAwayValue(dialog_target, ptr->value);\n                break;\n            case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON:\n                enabled = partyMemberHasAIWeaponPrefValue(dialog_target, ptr->value);\n                break;\n            case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE:\n                enabled = partyMemberHasAIDistancePrefValue(dialog_target, ptr->value);\n                break;\n            case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO:\n                enabled = partyMemberHasAIAttackWhoValue(dialog_target, ptr->value);\n                break;\n            case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE:\n                enabled = partyMemberHasAIChemUseValue(dialog_target, ptr->value);\n                break;\n            }\n\n            int color;\n            if (enabled) {\n                if (index == selectedIndex) {\n                    color = colorTable[32747];\n                } else {\n                    color = colorTable[992];\n                }\n            } else {\n                color = colorTable[15855];\n            }\n\n            const char* msg = getmsg(&custom_msg_file, &messageListItem, ptr->messageId);\n            text_to_buf(dest + pitch * (text_height() * index + 42) + 42, msg, pitch - 84, pitch, color);\n        }\n    }\n}\n\n// 0x449FC0\nstatic int gdCustomSelect(int a1)\n{\n    int oldFont = text_curr();\n\n    CacheEntry* backgroundFrmHandle;\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 419, 0, 0, 0);\n    Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle);\n    if (backgroundFrm == NULL) {\n        return -1;\n    }\n\n    int backgroundFrmWidth = art_frame_width(backgroundFrm, 0, 0);\n    int backgroundFrmHeight = art_frame_length(backgroundFrm, 0, 0);\n\n    int selectWindowX = (640 - backgroundFrmWidth) / 2;\n    int selectWindowY = (480 - backgroundFrmHeight) / 2;\n    int win = win_add(selectWindowX, selectWindowY, backgroundFrmWidth, backgroundFrmHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (win == -1) {\n        art_ptr_unlock(backgroundFrmHandle);\n        return -1;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(win);\n    unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0);\n    buf_to_buf(backgroundFrmData,\n        backgroundFrmWidth,\n        backgroundFrmHeight,\n        backgroundFrmWidth,\n        windowBuffer,\n        backgroundFrmWidth);\n\n    art_ptr_unlock(backgroundFrmHandle);\n\n    int btn1 = win_register_button(win, 70, 164, 14, 14, -1, -1, -1, KEY_RETURN, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (btn1 == -1) {\n        win_delete(win);\n        return -1;\n    }\n\n    int btn2 = win_register_button(win, 176, 163, 14, 14, -1, -1, -1, KEY_ESCAPE, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (btn2 == -1) {\n        win_delete(win);\n        return -1;\n    }\n\n    text_font(103);\n\n    MessageListItem messageListItem;\n    const char* msg;\n\n    msg = getmsg(&custom_msg_file, &messageListItem, a1);\n    text_to_buf(windowBuffer + backgroundFrmWidth * 15 + 40, msg, backgroundFrmWidth, backgroundFrmWidth, colorTable[18979]);\n\n    msg = getmsg(&custom_msg_file, &messageListItem, 10);\n    text_to_buf(windowBuffer + backgroundFrmWidth * 163 + 88, msg, backgroundFrmWidth, backgroundFrmWidth, colorTable[18979]);\n\n    msg = getmsg(&custom_msg_file, &messageListItem, 11);\n    text_to_buf(windowBuffer + backgroundFrmWidth * 162 + 193, msg, backgroundFrmWidth, backgroundFrmWidth, colorTable[18979]);\n\n    int value = custom_current_selected[a1];\n    gdCustomSelectRedraw(windowBuffer, backgroundFrmWidth, a1, value);\n    win_draw(win);\n\n    int minX = selectWindowX + 42;\n    int minY = selectWindowY + 42;\n    int maxX = selectWindowX + backgroundFrmWidth - 42;\n    int maxY = selectWindowY + backgroundFrmHeight - 42;\n\n    bool done = false;\n    unsigned int v53 = 0;\n    while (!done) {\n        int keyCode = get_input();\n        if (keyCode == -1) {\n            continue;\n        }\n\n        if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n            game_quit_with_confirm();\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        if (keyCode == KEY_RETURN) {\n            STRUCT_5189E4* ptr = &(custom_settings[a1][value]);\n            custom_current_selected[a1] = value;\n            gdCustomUpdateSetting(a1, ptr->value);\n            done = true;\n        } else if (keyCode == KEY_ESCAPE) {\n            done = true;\n        } else if (keyCode == -2) {\n            if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) {\n                continue;\n            }\n\n            if (!mouse_click_in(minX, minY, maxX, maxY)) {\n                continue;\n            }\n\n            int mouseX;\n            int mouseY;\n            mouse_get_position(&mouseX, &mouseY);\n\n            int lineHeight = text_height();\n            int newValue = (mouseY - minY) / lineHeight;\n            if (newValue >= 6) {\n                continue;\n            }\n\n            unsigned int timestamp = get_time();\n            if (newValue == value) {\n                if (elapsed_tocks(timestamp, v53) < 250) {\n                    custom_current_selected[a1] = newValue;\n                    gdCustomUpdateSetting(a1, newValue);\n                    done = true;\n                }\n            } else {\n                STRUCT_5189E4* ptr = &(custom_settings[a1][newValue]);\n                if (ptr->messageId != -1) {\n                    bool enabled = false;\n                    switch (a1) {\n                    case PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE:\n                        enabled = partyMemberHasAIBurstValue(dialog_target, ptr->value);\n                        break;\n                    case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE:\n                        enabled = partyMemberHasAIRunAwayValue(dialog_target, ptr->value);\n                        break;\n                    case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON:\n                        enabled = partyMemberHasAIWeaponPrefValue(dialog_target, ptr->value);\n                        break;\n                    case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE:\n                        enabled = partyMemberHasAIDistancePrefValue(dialog_target, ptr->value);\n                        break;\n                    case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO:\n                        enabled = partyMemberHasAIAttackWhoValue(dialog_target, ptr->value);\n                        break;\n                    case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE:\n                        enabled = partyMemberHasAIChemUseValue(dialog_target, ptr->value);\n                        break;\n                    }\n\n                    if (enabled) {\n                        value = newValue;\n                        gdCustomSelectRedraw(windowBuffer, backgroundFrmWidth, a1, newValue);\n                        win_draw(win);\n                    }\n                }\n            }\n            v53 = timestamp;\n        }\n    }\n\n    win_delete(win);\n    text_font(oldFont);\n    return 0;\n}\n\n// 0x44A4E0\nstatic void gdCustomUpdateSetting(int option, int value)\n{\n    switch (option) {\n    case PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE:\n        ai_set_burst_value(dialog_target, value);\n        break;\n    case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE:\n        ai_set_run_away_value(dialog_target, value);\n        break;\n    case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON:\n        ai_set_weapon_pref_value(dialog_target, value);\n        break;\n    case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE:\n        ai_set_distance_pref_value(dialog_target, value);\n        break;\n    case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO:\n        ai_set_attack_who_value(dialog_target, value);\n        break;\n    case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE:\n        ai_set_chem_use_value(dialog_target, value);\n        break;\n    }\n}\n\n// 0x44A52C\nstatic void gdialog_barter_pressed(int btn, int keyCode)\n{\n    if (PID_TYPE(dialog_target->pid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    Script* script;\n    if (scr_ptr(dialog_target->sid, &script) == -1) {\n        return;\n    }\n\n    Proto* proto;\n    proto_ptr(dialog_target->pid, &proto);\n    if (proto->critter.data.flags & CRITTER_BARTER) {\n        if (gdialog_speech_playing) {\n            if (soundPlaying(lip_info.sound)) {\n                gdialogFreeSpeech();\n            }\n        }\n\n        dialogue_switch_mode = 2;\n        dialogue_state = 4;\n\n        // NOTE: Uninline.\n        gdHide();\n    } else {\n        MessageListItem messageListItem;\n        // This person will not barter with you.\n        messageListItem.num = 903;\n        if (dialog_target_is_party) {\n            // This critter can't carry anything.\n            messageListItem.num = 913;\n        }\n\n        if (message_search(&proto_main_msg_file, &messageListItem)) {\n            gdialogDisplayMsg(messageListItem.text);\n        } else {\n            debug_printf(\"\\nError: gdialog: Can't find message!\");\n        }\n    }\n}\n\n// 0x44A62C\nstatic int gdialog_window_create()\n{\n    const int screenWidth = GAME_DIALOG_WINDOW_WIDTH;\n\n    if (gdialog_window_created) {\n        return -1;\n    }\n\n    for (int index = 0; index < 9; index++) {\n        gdialog_buttons[index] = -1;\n    }\n\n    CacheEntry* backgroundFrmHandle;\n    // 389 - di_talkp.frm - dialog screen subwindow (party members)\n    // 99 - di_talk.frm - dialog screen subwindow (NPC's)\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, dialog_target_is_party ? 389 : 99, 0, 0, 0);\n    Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle);\n    if (backgroundFrm == NULL) {\n        return -1;\n    }\n\n    unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0);\n    if (backgroundFrmData != NULL) {\n        dialogue_subwin_len = art_frame_length(backgroundFrm, 0, 0);\n\n        int dialogSubwindowX = 0;\n        int dialogSubwindowY = 480 - dialogue_subwin_len;\n        dialogueWindow = win_add(dialogSubwindowX, dialogSubwindowY, screenWidth, dialogue_subwin_len, 256, WINDOW_FLAG_0x02);\n        if (dialogueWindow != -1) {\n\n            unsigned char* v10 = win_get_buf(dialogueWindow);\n            unsigned char* v14 = win_get_buf(dialogueBackWindow);\n            // TODO: Not sure about offsets.\n            buf_to_buf(v14 + screenWidth * (GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len), screenWidth, dialogue_subwin_len, screenWidth, v10, screenWidth);\n\n            if (dialogue_just_started) {\n                win_draw(dialogueBackWindow);\n                gdialog_scroll_subwin(dialogueWindow, 1, backgroundFrmData, v10, 0, dialogue_subwin_len, -1);\n                dialogue_just_started = 0;\n            } else {\n                gdialog_scroll_subwin(dialogueWindow, 1, backgroundFrmData, v10, 0, dialogue_subwin_len, 0);\n            }\n\n            art_ptr_unlock(backgroundFrmHandle);\n\n            // BARTER/TRADE\n            gdialog_buttons[0] = win_register_button(dialogueWindow, 593, 41, 14, 14, -1, -1, -1, -1, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT);\n            if (gdialog_buttons[0] != -1) {\n                win_register_button_func(gdialog_buttons[0], NULL, NULL, NULL, gdialog_barter_pressed);\n                win_register_button_sound_func(gdialog_buttons[0], gsound_med_butt_press, gsound_med_butt_release);\n\n                // di_rest1.frm - dialog rest button up\n                int upFid = art_id(OBJ_TYPE_INTERFACE, 97, 0, 0, 0);\n                unsigned char* reviewButtonUpData = art_ptr_lock_data(upFid, 0, 0, &gdialog_review_up_key);\n                if (reviewButtonUpData != NULL) {\n                    // di_rest2.frm - dialog rest button down\n                    int downFid = art_id(OBJ_TYPE_INTERFACE, 98, 0, 0, 0);\n                    unsigned char* reivewButtonDownData = art_ptr_lock_data(downFid, 0, 0, &gdialog_review_down_key);\n                    if (reivewButtonDownData != NULL) {\n                        // REVIEW\n                        gdialog_buttons[1] = win_register_button(dialogueWindow, 13, 154, 51, 29, -1, -1, -1, -1, reviewButtonUpData, reivewButtonDownData, NULL, 0);\n                        if (gdialog_buttons[1] != -1) {\n                            win_register_button_func(gdialog_buttons[1], NULL, NULL, NULL, gdReviewPressed);\n                            win_register_button_sound_func(gdialog_buttons[1], gsound_red_butt_press, gsound_red_butt_release);\n\n                            if (!dialog_target_is_party) {\n                                gdialog_window_created = true;\n                                return 0;\n                            }\n\n                            // COMBAT CONTROL\n                            gdialog_buttons[2] = win_register_button(dialogueWindow, 593, 116, 14, 14, -1, -1, -1, -1, dialog_red_button_up_buf, dialog_red_button_down_buf, 0, BUTTON_FLAG_TRANSPARENT);\n                            if (gdialog_buttons[2] != -1) {\n                                win_register_button_func(gdialog_buttons[2], NULL, NULL, NULL, gdControlPressed);\n                                win_register_button_sound_func(gdialog_buttons[2], gsound_med_butt_press, gsound_med_butt_release);\n\n                                gdialog_window_created = true;\n                                return 0;\n                            }\n\n                            win_delete_button(gdialog_buttons[1]);\n                            gdialog_buttons[1] = -1;\n                        }\n\n                        art_ptr_unlock(gdialog_review_down_key);\n                    }\n\n                    art_ptr_unlock(gdialog_review_up_key);\n                }\n\n                win_delete_button(gdialog_buttons[0]);\n                gdialog_buttons[0] = -1;\n            }\n\n            win_delete(dialogueWindow);\n            dialogueWindow = -1;\n        }\n    }\n\n    art_ptr_unlock(backgroundFrmHandle);\n\n    return -1;\n}\n\n// 0x44A9D8\nstatic void gdialog_window_destroy()\n{\n    if (dialogueWindow == -1) {\n        return;\n    }\n\n    for (int index = 0; index < 9; index++) {\n        win_delete_button(gdialog_buttons[index]);\n        gdialog_buttons[index] = -1;\n    }\n\n    art_ptr_unlock(gdialog_review_down_key);\n    art_ptr_unlock(gdialog_review_up_key);\n\n    int offset = (GAME_DIALOG_WINDOW_WIDTH) * (480 - dialogue_subwin_len);\n    unsigned char* backgroundWindowBuffer = win_get_buf(dialogueBackWindow) + offset;\n\n    int frmId;\n    if (dialog_target_is_party) {\n        // di_talkp.frm - dialog screen subwindow (party members)\n        frmId = 389;\n    } else {\n        // di_talk.frm - dialog screen subwindow (NPC's)\n        frmId = 99;\n    }\n\n    CacheEntry* backgroundFrmHandle;\n    int fid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0);\n    unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle);\n    if (backgroundFrmData != NULL) {\n        unsigned char* windowBuffer = win_get_buf(dialogueWindow);\n        gdialog_scroll_subwin(dialogueWindow, 0, backgroundFrmData, windowBuffer, backgroundWindowBuffer, dialogue_subwin_len, 0);\n        art_ptr_unlock(backgroundFrmHandle);\n        win_delete(dialogueWindow);\n        gdialog_window_created = 0;\n        dialogueWindow = -1;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x44AAD8\nstatic int talk_to_create_background_window()\n{\n    dialogueBackWindow = win_add(0,\n        0,\n        scr_size.lrx - scr_size.ulx + 1,\n        GAME_DIALOG_WINDOW_HEIGHT,\n        256,\n        WINDOW_FLAG_0x02);\n\n    if (dialogueBackWindow != -1) {\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x44AB18\nstatic int talk_to_refresh_background_window()\n{\n    CacheEntry* backgroundFrmHandle;\n    // alltlk.frm - dialog screen background\n    int fid = art_id(OBJ_TYPE_INTERFACE, 103, 0, 0, 0);\n    unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle);\n    if (backgroundFrmData == NULL) {\n        return -1;\n    }\n\n    int windowWidth = GAME_DIALOG_WINDOW_WIDTH;\n    unsigned char* windowBuffer = win_get_buf(dialogueBackWindow);\n    buf_to_buf(backgroundFrmData, windowWidth, 480, windowWidth, windowBuffer, windowWidth);\n    art_ptr_unlock(backgroundFrmHandle);\n\n    if (!dialogue_just_started) {\n        win_draw(dialogueBackWindow);\n    }\n\n    return 0;\n}\n\n// 0x44ABA8\nstatic int talkToRefreshDialogWindowRect(Rect* rect)\n{\n    int frmId;\n    if (dialog_target_is_party) {\n        // di_talkp.frm - dialog screen subwindow (party members)\n        frmId = 389;\n    } else {\n        // di_talk.frm - dialog screen subwindow (NPC's)\n        frmId = 99;\n    }\n\n    CacheEntry* backgroundFrmHandle;\n    int fid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0);\n    unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle);\n    if (backgroundFrmData == NULL) {\n        return -1;\n    }\n\n    int offset = 640 * rect->uly + rect->ulx;\n\n    unsigned char* windowBuffer = win_get_buf(dialogueWindow);\n    buf_to_buf(backgroundFrmData + offset,\n        rect->lrx - rect->ulx,\n        rect->lry - rect->uly,\n        GAME_DIALOG_WINDOW_WIDTH,\n        windowBuffer + offset,\n        GAME_DIALOG_WINDOW_WIDTH);\n\n    art_ptr_unlock(backgroundFrmHandle);\n\n    win_draw_rect(dialogueWindow, rect);\n\n    return 0;\n}\n\n// 0x44AC68\nstatic void talk_to_translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, unsigned char* a9, unsigned char* a10)\n{\n    int srcStep = srcPitch - srcWidth;\n    int destStep = destPitch - srcWidth;\n\n    dest += destPitch * destY + destX;\n\n    for (int y = 0; y < srcHeight; y++) {\n        for (int x = 0; x < srcWidth; x++) {\n            unsigned char v1 = *src++;\n            if (v1 != 0) {\n                v1 = (256 - v1) >> 4;\n            }\n\n            unsigned char v15 = *dest;\n            *dest++ = a9[256 * v1 + v15];\n        }\n        src += srcStep;\n        dest += destStep;\n    }\n}\n\n// 0x44ACFC\nstatic void gdDisplayFrame(Art* headFrm, int frame)\n{\n    // 0x518BF4\n    static int totalHotx = 0;\n\n    if (dialogueWindow == -1) {\n        return;\n    }\n\n    if (headFrm != NULL) {\n        if (frame == 0) {\n            totalHotx = 0;\n        }\n\n        int backgroundFid = art_id(OBJ_TYPE_BACKGROUND, backgroundIndex, 0, 0, 0);\n\n        CacheEntry* backgroundHandle;\n        Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundHandle);\n        if (backgroundFrm == NULL) {\n            debug_printf(\"\\tError locking background in display...\\n\");\n        }\n\n        unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0);\n        if (backgroundFrmData != NULL) {\n            buf_to_buf(backgroundFrmData, 388, 200, 388, headWindowBuffer, GAME_DIALOG_WINDOW_WIDTH);\n        } else {\n            debug_printf(\"\\tError getting background data in display...\\n\");\n        }\n\n        art_ptr_unlock(backgroundHandle);\n\n        int width = art_frame_width(headFrm, frame, 0);\n        int height = art_frame_length(headFrm, frame, 0);\n        unsigned char* data = art_frame_data(headFrm, frame, 0);\n\n        int a3;\n        int v8;\n        art_frame_offset(headFrm, 0, &a3, &v8);\n\n        int a4;\n        int a5;\n        art_frame_hot(headFrm, frame, 0, &a4, &a5);\n\n        totalHotx += a4;\n        a3 += totalHotx;\n\n        if (data != NULL) {\n            int destWidth = GAME_DIALOG_WINDOW_WIDTH;\n            int destOffset = destWidth * (200 - height) + a3 + (388 - width) / 2;\n            if (destOffset + width * v8 > 0) {\n                destOffset += width * v8;\n            }\n\n            trans_buf_to_buf(\n                data,\n                width,\n                height,\n                width,\n                headWindowBuffer + destOffset,\n                destWidth);\n        } else {\n            debug_printf(\"\\tError getting head data in display...\\n\");\n        }\n    } else {\n        if (talk_need_to_center == 1) {\n            talk_need_to_center = 0;\n            tile_refresh_display();\n        }\n\n        unsigned char* src = win_get_buf(display_win);\n        buf_to_buf(\n            src + ((scr_size.lry - scr_size.uly + 1 - 332) / 2) * (GAME_DIALOG_WINDOW_WIDTH) + (GAME_DIALOG_WINDOW_WIDTH - 388) / 2,\n            388,\n            200,\n            scr_size.lrx - scr_size.ulx + 1,\n            headWindowBuffer,\n            GAME_DIALOG_WINDOW_WIDTH);\n    }\n\n    Rect v27;\n    v27.ulx = 126;\n    v27.uly = 14;\n    v27.lrx = 514;\n    v27.lry = 214;\n\n    unsigned char* dest = win_get_buf(dialogueBackWindow);\n\n    unsigned char* data1 = art_frame_data(upper_hi_fp, 0, 0);\n    talk_to_translucent_trans_buf_to_buf(data1, upper_hi_wid, upper_hi_len, upper_hi_wid, dest, 426, 15, GAME_DIALOG_WINDOW_WIDTH, light_BlendTable, light_GrayTable);\n\n    unsigned char* data2 = art_frame_data(lower_hi_fp, 0, 0);\n    talk_to_translucent_trans_buf_to_buf(data2, lower_hi_wid, lower_hi_len, lower_hi_wid, dest, 129, 214 - lower_hi_len - 2, GAME_DIALOG_WINDOW_WIDTH, dark_BlendTable, dark_GrayTable);\n\n    for (int index = 0; index < 8; ++index) {\n        Rect* rect = &(backgrndRects[index]);\n        int width = rect->lrx - rect->ulx;\n\n        trans_buf_to_buf(backgrndBufs[index],\n            width,\n            rect->lry - rect->uly,\n            width,\n            dest + (GAME_DIALOG_WINDOW_WIDTH) * rect->uly + rect->ulx,\n            GAME_DIALOG_WINDOW_WIDTH);\n    }\n\n    win_draw_rect(dialogueBackWindow, &v27);\n}\n\n// 0x44B080\nstatic void gdBlendTableInit()\n{\n    for (int color = 0; color < 256; color++) {\n        int r = (Color2RGB(color) & 0x7C00) >> 10;\n        int g = (Color2RGB(color) & 0x3E0) >> 5;\n        int b = Color2RGB(color) & 0x1F;\n        light_GrayTable[color] = ((r + 2 * g + 2 * b) / 10) >> 2;\n        dark_GrayTable[color] = ((r + g + b) / 10) >> 2;\n    }\n\n    light_GrayTable[0] = 0;\n    dark_GrayTable[0] = 0;\n\n    light_BlendTable = getColorBlendTable(colorTable[17969]);\n    dark_BlendTable = getColorBlendTable(colorTable[22187]);\n\n    // hilight1.frm - dialogue upper hilight\n    int upperHighlightFid = art_id(OBJ_TYPE_INTERFACE, 115, 0, 0, 0);\n    upper_hi_fp = art_ptr_lock(upperHighlightFid, &upper_hi_key);\n    upper_hi_wid = art_frame_width(upper_hi_fp, 0, 0);\n    upper_hi_len = art_frame_length(upper_hi_fp, 0, 0);\n\n    // hilight2.frm - dialogue lower hilight\n    int lowerHighlightFid = art_id(OBJ_TYPE_INTERFACE, 116, 0, 0, 0);\n    lower_hi_fp = art_ptr_lock(lowerHighlightFid, &lower_hi_key);\n    lower_hi_wid = art_frame_width(lower_hi_fp, 0, 0);\n    lower_hi_len = art_frame_length(lower_hi_fp, 0, 0);\n}\n\n// NOTE: Inlined.\n//\n// 0x44B1D4\nstatic void gdBlendTableExit()\n{\n    freeColorBlendTable(colorTable[17969]);\n    freeColorBlendTable(colorTable[22187]);\n\n    art_ptr_unlock(upper_hi_key);\n    art_ptr_unlock(lower_hi_key);\n}\n"
  },
  {
    "path": "src/game/gdialog.h",
    "content": "#ifndef FALLOUT_GAME_GDIALOG_H_\n#define FALLOUT_GAME_GDIALOG_H_\n\n#include <stdbool.h>\n\n#include \"game/art.h\"\n#include \"int/intrpret.h\"\n#include \"game/object_types.h\"\n\nextern unsigned char* light_BlendTable;\nextern unsigned char* dark_BlendTable;\nextern Object* dialog_target;\nextern bool dialog_target_is_party;\nextern int dialogue_head;\nextern int dialogue_scr_id;\n\nextern unsigned char light_GrayTable[256];\nextern unsigned char dark_GrayTable[256];\n\nint gdialogInit();\nint gdialogReset();\nint gdialogExit();\nbool gdialogActive();\nvoid gdialogEnter(Object* a1, int a2);\nvoid gdialogSystemEnter();\nvoid gdialogSetupSpeech(const char* a1);\nvoid gdialogFreeSpeech();\nint gdialogEnableBK();\nint gdialogDisableBK();\nint gdialogInitFromScript(int headFid, int reaction);\nint gdialogExitFromScript();\nvoid gdialogSetBackground(int a1);\nvoid gdialogDisplayMsg(char* msg);\nint gdialogStart();\nint gdialogSayMessage();\nint gdialogOption(int messageListId, int messageId, const char* a3, int reaction);\nint gdialogOptionStr(int messageListId, const char* text, const char* a3, int reaction);\nint gdialogOptionProc(int messageListId, int messageId, int proc, int reaction);\nint gdialogOptionProcStr(int messageListId, const char* text, int proc, int reaction);\nint gdialogReply(Program* a1, int a2, int a3);\nint gdialogReplyStr(Program* a1, int a2, const char* a3);\nint gdialogGo();\nvoid gdialogUpdatePartyStatus();\nvoid talk_to_critter_reacts(int a1);\nvoid gdialogSetBarterMod(int modifier);\nint gdActivateBarter(int modifier);\nvoid barter_end_to_talk_to();\n\n#endif /* FALLOUT_GAME_GDIALOG_H_ */\n"
  },
  {
    "path": "src/game/gmemory.c",
    "content": "#include \"game/gmemory.h\"\n\n#include \"plib/db/db.h\"\n#include \"plib/assoc/assoc.h\"\n#include \"plib/gnw/memory.h\"\n#include \"int/memdbg.h\"\n\n// NOTE: Unused.\n//\n// 0x44B200\nvoid* localmymalloc(size_t size)\n{\n    return mymalloc(size, __FILE__, __LINE__); // \"gmemory.c\", 22\n}\n\n// NOTE: Unused.\n//\n// 0x44B214\nvoid* localmyrealloc(void* ptr, size_t size)\n{\n    return myrealloc(ptr, size, __FILE__, __LINE__); // \"gmemory.c\", 26\n}\n\n// NOTE: Unused.\n//\n// 0x44B228\nvoid localmyfree(void* ptr)\n{\n    myfree(ptr, __FILE__, __LINE__); // \"gmemory.c\", 30\n}\n\n// NOTE: Unused.\n//\n// 0x44B23C\nchar* localmystrdup(const char* string)\n{\n    return mystrdup(string, __FILE__, __LINE__); // \"gmemory.c\", 34\n}\n\n// 0x44B250\nint gmemory_init()\n{\n    assoc_register_mem(mem_malloc, mem_realloc, mem_free);\n    db_register_mem(mem_malloc, mem_strdup, mem_free);\n    memoryRegisterAlloc(gmalloc, grealloc, gfree);\n\n    return 0;\n}\n\n// 0x44B294\nvoid* gmalloc(size_t size)\n{\n    return mem_malloc(size);\n}\n\n// 0x44B29C\nvoid* grealloc(void* ptr, size_t newSize)\n{\n    return mem_realloc(ptr, newSize);\n}\n\n// 0x44B2A4\nvoid gfree(void* ptr)\n{\n    mem_free(ptr);\n}\n"
  },
  {
    "path": "src/game/gmemory.h",
    "content": "#ifndef FALLOUT_GAME_GMEMORY_H_\n#define FALLOUT_GAME_GMEMORY_H_\n\n#include <stddef.h>\n\nvoid* localmymalloc(size_t size);\nvoid* localmyrealloc(void* ptr, size_t size);\nvoid localmyfree(void* ptr);\nchar* localmystrdup(const char* string);\nint gmemory_init();\nvoid* gmalloc(size_t size);\nvoid* grealloc(void* ptr, size_t newSize);\nvoid gfree(void* ptr);\n\n#endif /* FALLOUT_GAME_GMEMORY_H_ */\n"
  },
  {
    "path": "src/game/gmouse.c",
    "content": "#include \"game/gmouse.h\"\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"game/actions.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"game/object.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/skilldex.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/svga.h\"\n\ntypedef enum ScrollableDirections {\n    SCROLLABLE_W = 0x01,\n    SCROLLABLE_E = 0x02,\n    SCROLLABLE_N = 0x04,\n    SCROLLABLE_S = 0x08,\n} ScrollableDirections;\n\nstatic int gmouse_3d_init();\nstatic int gmouse_3d_reset();\nstatic void gmouse_3d_exit();\nstatic int gmouse_3d_lock_frames();\nstatic void gmouse_3d_unlock_frames();\nstatic int gmouse_3d_set_flat_fid(int fid, Rect* rect);\nstatic int gmouse_3d_reset_flat_fid(Rect* rect);\nstatic int gmouse_3d_move_to(int x, int y, int elevation, Rect* a4);\nstatic int gmouse_check_scrolling(int x, int y, int cursor);\nstatic int gmObjIsValidTarget(Object* object);\n\n// 0x518BF8\nstatic bool gmouse_initialized = false;\n\n// 0x518BFC\nstatic int gmouse_enabled = 0;\n\n// 0x518C00\nstatic int gmouse_mapper_mode = 0;\n\n// 0x518C04\nstatic int gmouse_click_to_scroll = 0;\n\n// 0x518C08\nstatic int gmouse_scrolling_enabled = 1;\n\n// 0x518C0C\nstatic int gmouse_current_cursor = MOUSE_CURSOR_NONE;\n\n// 0x518C10\nstatic CacheEntry* gmouse_current_cursor_key = INVALID_CACHE_ENTRY;\n\n// 0x518C14\nstatic int gmouse_cursor_nums[MOUSE_CURSOR_TYPE_COUNT] = {\n    266,\n    267,\n    268,\n    269,\n    270,\n    271,\n    272,\n    273,\n    274,\n    275,\n    276,\n    277,\n    330,\n    331,\n    329,\n    328,\n    332,\n    334,\n    333,\n    335,\n    279,\n    280,\n    281,\n    293,\n    310,\n    278,\n    295,\n};\n\n// 0x518C80\nstatic bool gmouse_3d_initialized = false;\n\n// 0x518C84\nstatic bool gmouse_3d_hover_test = false;\n\n// 0x518C88\nstatic unsigned int gmouse_3d_last_move_time = 0;\n\n// actmenu.frm\n// 0x518C8C\nstatic Art* gmouse_3d_menu_frame = NULL;\n\n// 0x518C90\nstatic CacheEntry* gmouse_3d_menu_frame_key = INVALID_CACHE_ENTRY;\n\n// 0x518C94\nstatic int gmouse_3d_menu_frame_width = 0;\n\n// 0x518C98\nstatic int gmouse_3d_menu_frame_height = 0;\n\n// 0x518C9C\nstatic int gmouse_3d_menu_frame_size = 0;\n\n// 0x518CA0\nstatic int gmouse_3d_menu_frame_hot_x = 0;\n\n// 0x518CA4\nstatic int gmouse_3d_menu_frame_hot_y = 0;\n\n// 0x518CA8\nstatic unsigned char* gmouse_3d_menu_frame_data = NULL;\n\n// actpick.frm\n// 0x518CAC\nstatic Art* gmouse_3d_pick_frame = NULL;\n\n// 0x518CB0\nstatic CacheEntry* gmouse_3d_pick_frame_key = INVALID_CACHE_ENTRY;\n\n// 0x518CB4\nstatic int gmouse_3d_pick_frame_width = 0;\n\n// 0x518CB8\nstatic int gmouse_3d_pick_frame_height = 0;\n\n// 0x518CBC\nstatic int gmouse_3d_pick_frame_size = 0;\n\n// 0x518CC0\nstatic int gmouse_3d_pick_frame_hot_x = 0;\n\n// 0x518CC4\nstatic int gmouse_3d_pick_frame_hot_y = 0;\n\n// 0x518CC8\nstatic unsigned char* gmouse_3d_pick_frame_data = NULL;\n\n// acttohit.frm\n// 0x518CCC\nstatic Art* gmouse_3d_to_hit_frame = NULL;\n\n// 0x518CD0\nstatic CacheEntry* gmouse_3d_to_hit_frame_key = INVALID_CACHE_ENTRY;\n\n// 0x518CD4\nstatic int gmouse_3d_to_hit_frame_width = 0;\n\n// 0x518CD8\nstatic int gmouse_3d_to_hit_frame_height = 0;\n\n// 0x518CDC\nstatic int gmouse_3d_to_hit_frame_size = 0;\n\n// 0x518CE0\nstatic unsigned char* gmouse_3d_to_hit_frame_data = NULL;\n\n// blank.frm\n// 0x518CE4\nstatic Art* gmouse_3d_hex_base_frame = NULL;\n\n// 0x518CE8\nstatic CacheEntry* gmouse_3d_hex_base_frame_key = INVALID_CACHE_ENTRY;\n\n// 0x518CEC\nstatic int gmouse_3d_hex_base_frame_width = 0;\n\n// 0x518CF0\nstatic int gmouse_3d_hex_base_frame_height = 0;\n\n// 0x518CF4\nstatic int gmouse_3d_hex_base_frame_size = 0;\n\n// 0x518CF8\nstatic unsigned char* gmouse_3d_hex_base_frame_data = NULL;\n\n// msef000.frm\n// 0x518CFC\nstatic Art* gmouse_3d_hex_frame = NULL;\n\n// 0x518D00\nstatic CacheEntry* gmouse_3d_hex_frame_key = INVALID_CACHE_ENTRY;\n\n// 0x518D04\nstatic int gmouse_3d_hex_frame_width = 0;\n\n// 0x518D08\nstatic int gmouse_3d_hex_frame_height = 0;\n\n// 0x518D0C\nstatic int gmouse_3d_hex_frame_size = 0;\n\n// 0x518D10\nstatic unsigned char* gmouse_3d_hex_frame_data = NULL;\n\n// 0x518D14\nstatic unsigned char gmouse_3d_menu_available_actions = 0;\n\n// 0x518D18\nstatic unsigned char* gmouse_3d_menu_actions_start = NULL;\n\n// 0x518D1C\nstatic unsigned char gmouse_3d_menu_current_action_index = 0;\n\n// 0x518D1E\nstatic short gmouse_3d_action_nums[GAME_MOUSE_ACTION_MENU_ITEM_COUNT] = {\n    253, // Cancel\n    255, // Drop\n    257, // Inventory\n    259, // Look\n    261, // Rotate\n    263, // Talk\n    265, // Use/Get\n    302, // Unload\n    304, // Skill\n    435, // Push\n};\n\n// 0x518D34\nstatic int gmouse_3d_modes_enabled = 1;\n\n// 0x518D38\nstatic int gmouse_3d_current_mode = GAME_MOUSE_MODE_MOVE;\n\n// 0x518D3C\nstatic int gmouse_3d_mode_nums[GAME_MOUSE_MODE_COUNT] = {\n    249,\n    250,\n    251,\n    293,\n    293,\n    293,\n    293,\n    293,\n    293,\n    293,\n    293,\n};\n\n// 0x518D68\nstatic int gmouse_skill_table[GAME_MOUSE_MODE_SKILL_COUNT] = {\n    SKILL_FIRST_AID,\n    SKILL_DOCTOR,\n    SKILL_LOCKPICK,\n    SKILL_STEAL,\n    SKILL_TRAPS,\n    SKILL_SCIENCE,\n    SKILL_REPAIR,\n};\n\n// 0x518D84\nstatic int gmouse_wait_cursor_frame = 0;\n\n// 0x518D88\nstatic unsigned int gmouse_wait_cursor_time = 0;\n\n// 0x518D8C\nstatic int gmouse_bk_last_cursor = -1;\n\n// 0x518D90\nstatic bool gmouse_3d_item_highlight = true;\n\n// 0x518D94\nstatic Object* outlined_object = NULL;\n\n// 0x518D98\nbool gmouse_clicked_on_edge = false;\n\n// 0x596C3C\nstatic int gmouse_3d_menu_frame_actions[GAME_MOUSE_ACTION_MENU_ITEM_COUNT];\n\n// 0x596C64\nstatic int gmouse_3d_last_mouse_x;\n\n// 0x596C68\nstatic int gmouse_3d_last_mouse_y;\n\n// blank.frm\n// 0x596C6C\nObject* obj_mouse;\n\n// msef000.frm\n// 0x596C70\nObject* obj_mouse_flat;\n\n// 0x44B2B0\nint gmouse_init()\n{\n    if (gmouse_initialized) {\n        return -1;\n    }\n\n    if (gmouse_3d_init() != 0) {\n        return -1;\n    }\n\n    gmouse_initialized = true;\n    gmouse_enabled = 1;\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    return 0;\n}\n\n// 0x44B2E8\nint gmouse_reset()\n{\n    if (!gmouse_initialized) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    if (gmouse_3d_reset() != 0) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    gmouse_enable();\n\n    gmouse_scrolling_enabled = 1;\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    gmouse_wait_cursor_frame = 0;\n    gmouse_wait_cursor_time = 0;\n    gmouse_clicked_on_edge = 0;\n\n    return 0;\n}\n\n// 0x44B3B8\nvoid gmouse_exit()\n{\n    if (!gmouse_initialized) {\n        return;\n    }\n\n    mouse_hide();\n\n    mouse_set_shape(NULL, 0, 0, 0, 0, 0, 0);\n\n    // NOTE: Uninline.\n    gmouse_3d_exit();\n\n    if (gmouse_current_cursor_key != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(gmouse_current_cursor_key);\n    }\n    gmouse_current_cursor_key = INVALID_CACHE_ENTRY;\n\n    gmouse_enabled = 0;\n    gmouse_initialized = false;\n    gmouse_current_cursor = -1;\n}\n\n// 0x44B454\nvoid gmouse_enable()\n{\n    if (!gmouse_enabled) {\n        gmouse_current_cursor = -1;\n        gmouse_set_cursor(MOUSE_CURSOR_NONE);\n        gmouse_scrolling_enabled = 1;\n        gmouse_enabled = 1;\n        gmouse_bk_last_cursor = -1;\n    }\n}\n\n// 0x44B48C\nvoid gmouse_disable(int a1)\n{\n    if (gmouse_enabled) {\n        gmouse_set_cursor(MOUSE_CURSOR_NONE);\n        gmouse_enabled = 0;\n\n        if (a1 & 1) {\n            gmouse_scrolling_enabled = 1;\n        } else {\n            gmouse_scrolling_enabled = 0;\n        }\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x44B4C4\nint gmouse_is_enabled()\n{\n    return gmouse_enabled;\n}\n\n// 0x44B4CC\nvoid gmouse_enable_scrolling()\n{\n    gmouse_scrolling_enabled = 1;\n}\n\n// 0x44B4D8\nvoid gmouse_disable_scrolling()\n{\n    gmouse_scrolling_enabled = 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x44B4E4\nint gmouse_scrolling_is_enabled()\n{\n    return gmouse_scrolling_enabled;\n}\n\n// NOTE: Unused.\n//\n// 0x44B4EC\nvoid gmouse_set_click_to_scroll(int a1)\n{\n    if (a1 != gmouse_click_to_scroll) {\n        gmouse_click_to_scroll = a1;\n        gmouse_clicked_on_edge = 0;\n    }\n}\n\n// 0x44B504\nint gmouse_get_click_to_scroll()\n{\n    return gmouse_click_to_scroll;\n}\n\n// 0x44B54C\nint gmouse_is_scrolling()\n{\n    int v1 = 0;\n\n    if (gmouse_scrolling_enabled) {\n        int x;\n        int y;\n        mouse_get_position(&x, &y);\n        if (x == scr_size.ulx || x == scr_size.lrx || y == scr_size.uly || y == scr_size.lry) {\n            switch (gmouse_current_cursor) {\n            case MOUSE_CURSOR_SCROLL_NW:\n            case MOUSE_CURSOR_SCROLL_N:\n            case MOUSE_CURSOR_SCROLL_NE:\n            case MOUSE_CURSOR_SCROLL_E:\n            case MOUSE_CURSOR_SCROLL_SE:\n            case MOUSE_CURSOR_SCROLL_S:\n            case MOUSE_CURSOR_SCROLL_SW:\n            case MOUSE_CURSOR_SCROLL_W:\n            case MOUSE_CURSOR_SCROLL_NW_INVALID:\n            case MOUSE_CURSOR_SCROLL_N_INVALID:\n            case MOUSE_CURSOR_SCROLL_NE_INVALID:\n            case MOUSE_CURSOR_SCROLL_E_INVALID:\n            case MOUSE_CURSOR_SCROLL_SE_INVALID:\n            case MOUSE_CURSOR_SCROLL_S_INVALID:\n            case MOUSE_CURSOR_SCROLL_SW_INVALID:\n            case MOUSE_CURSOR_SCROLL_W_INVALID:\n                v1 = 1;\n                break;\n            default:\n                return v1;\n            }\n        }\n    }\n\n    return v1;\n}\n\n// 0x44B684\nvoid gmouse_bk_process()\n{\n    // 0x596C74\n    static Object* last_object;\n\n    // 0x518D9C\n    static int last_tile = -1;\n\n    if (!gmouse_initialized) {\n        return;\n    }\n\n    int mouseX;\n    int mouseY;\n\n    if (gmouse_current_cursor >= FIRST_GAME_MOUSE_ANIMATED_CURSOR) {\n        mouse_info();\n\n        // NOTE: Uninline.\n        if (gmouse_scrolling_is_enabled()) {\n            mouse_get_position(&mouseX, &mouseY);\n            int oldMouseCursor = gmouse_current_cursor;\n\n            if (gmouse_check_scrolling(mouseX, mouseY, gmouse_current_cursor) == 0) {\n                switch (oldMouseCursor) {\n                case MOUSE_CURSOR_SCROLL_NW:\n                case MOUSE_CURSOR_SCROLL_N:\n                case MOUSE_CURSOR_SCROLL_NE:\n                case MOUSE_CURSOR_SCROLL_E:\n                case MOUSE_CURSOR_SCROLL_SE:\n                case MOUSE_CURSOR_SCROLL_S:\n                case MOUSE_CURSOR_SCROLL_SW:\n                case MOUSE_CURSOR_SCROLL_W:\n                case MOUSE_CURSOR_SCROLL_NW_INVALID:\n                case MOUSE_CURSOR_SCROLL_N_INVALID:\n                case MOUSE_CURSOR_SCROLL_NE_INVALID:\n                case MOUSE_CURSOR_SCROLL_E_INVALID:\n                case MOUSE_CURSOR_SCROLL_SE_INVALID:\n                case MOUSE_CURSOR_SCROLL_S_INVALID:\n                case MOUSE_CURSOR_SCROLL_SW_INVALID:\n                case MOUSE_CURSOR_SCROLL_W_INVALID:\n                    break;\n                default:\n                    gmouse_bk_last_cursor = oldMouseCursor;\n                    break;\n                }\n                return;\n            }\n\n            if (gmouse_bk_last_cursor != -1) {\n                gmouse_set_cursor(gmouse_bk_last_cursor);\n                gmouse_bk_last_cursor = -1;\n                return;\n            }\n        }\n\n        gmouse_set_cursor(gmouse_current_cursor);\n        return;\n    }\n\n    if (!gmouse_enabled) {\n        // NOTE: Uninline.\n        if (gmouse_scrolling_is_enabled()) {\n            mouse_get_position(&mouseX, &mouseY);\n            int oldMouseCursor = gmouse_current_cursor;\n\n            if (gmouse_check_scrolling(mouseX, mouseY, gmouse_current_cursor) == 0) {\n                switch (oldMouseCursor) {\n                case MOUSE_CURSOR_SCROLL_NW:\n                case MOUSE_CURSOR_SCROLL_N:\n                case MOUSE_CURSOR_SCROLL_NE:\n                case MOUSE_CURSOR_SCROLL_E:\n                case MOUSE_CURSOR_SCROLL_SE:\n                case MOUSE_CURSOR_SCROLL_S:\n                case MOUSE_CURSOR_SCROLL_SW:\n                case MOUSE_CURSOR_SCROLL_W:\n                case MOUSE_CURSOR_SCROLL_NW_INVALID:\n                case MOUSE_CURSOR_SCROLL_N_INVALID:\n                case MOUSE_CURSOR_SCROLL_NE_INVALID:\n                case MOUSE_CURSOR_SCROLL_E_INVALID:\n                case MOUSE_CURSOR_SCROLL_SE_INVALID:\n                case MOUSE_CURSOR_SCROLL_S_INVALID:\n                case MOUSE_CURSOR_SCROLL_SW_INVALID:\n                case MOUSE_CURSOR_SCROLL_W_INVALID:\n                    break;\n                default:\n                    gmouse_bk_last_cursor = oldMouseCursor;\n                    break;\n                }\n\n                return;\n            }\n\n            if (gmouse_bk_last_cursor != -1) {\n                gmouse_set_cursor(gmouse_bk_last_cursor);\n                gmouse_bk_last_cursor = -1;\n            }\n        }\n\n        return;\n    }\n\n    mouse_get_position(&mouseX, &mouseY);\n\n    int oldMouseCursor = gmouse_current_cursor;\n    if (gmouse_check_scrolling(mouseX, mouseY, MOUSE_CURSOR_NONE) == 0) {\n        switch (oldMouseCursor) {\n        case MOUSE_CURSOR_SCROLL_NW:\n        case MOUSE_CURSOR_SCROLL_N:\n        case MOUSE_CURSOR_SCROLL_NE:\n        case MOUSE_CURSOR_SCROLL_E:\n        case MOUSE_CURSOR_SCROLL_SE:\n        case MOUSE_CURSOR_SCROLL_S:\n        case MOUSE_CURSOR_SCROLL_SW:\n        case MOUSE_CURSOR_SCROLL_W:\n        case MOUSE_CURSOR_SCROLL_NW_INVALID:\n        case MOUSE_CURSOR_SCROLL_N_INVALID:\n        case MOUSE_CURSOR_SCROLL_NE_INVALID:\n        case MOUSE_CURSOR_SCROLL_E_INVALID:\n        case MOUSE_CURSOR_SCROLL_SE_INVALID:\n        case MOUSE_CURSOR_SCROLL_S_INVALID:\n        case MOUSE_CURSOR_SCROLL_SW_INVALID:\n        case MOUSE_CURSOR_SCROLL_W_INVALID:\n            break;\n        default:\n            gmouse_bk_last_cursor = oldMouseCursor;\n            break;\n        }\n        return;\n    }\n\n    if (gmouse_bk_last_cursor != -1) {\n        gmouse_set_cursor(gmouse_bk_last_cursor);\n        gmouse_bk_last_cursor = -1;\n    }\n\n    if (win_get_top_win(mouseX, mouseY) != display_win) {\n        if (gmouse_current_cursor == MOUSE_CURSOR_NONE) {\n            gmouse_3d_off();\n            gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n            if (gmouse_3d_current_mode >= 2 && !isInCombat()) {\n                gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE);\n            }\n        }\n        return;\n    }\n\n    // NOTE: Strange set of conditions and jumps. Not sure about this one.\n    switch (gmouse_current_cursor) {\n    case MOUSE_CURSOR_NONE:\n    case MOUSE_CURSOR_ARROW:\n    case MOUSE_CURSOR_SMALL_ARROW_UP:\n    case MOUSE_CURSOR_SMALL_ARROW_DOWN:\n    case MOUSE_CURSOR_CROSSHAIR:\n    case MOUSE_CURSOR_USE_CROSSHAIR:\n        if (gmouse_current_cursor != MOUSE_CURSOR_NONE) {\n            gmouse_set_cursor(MOUSE_CURSOR_NONE);\n        }\n\n        if ((obj_mouse_flat->flags & OBJECT_HIDDEN) != 0) {\n            gmouse_3d_on();\n        }\n\n        break;\n    }\n\n    Rect r1;\n    if (gmouse_3d_move_to(mouseX, mouseY, map_elevation, &r1) == 0) {\n        tile_refresh_rect(&r1, map_elevation);\n    }\n\n    if ((obj_mouse_flat->flags & OBJECT_HIDDEN) != 0 || gmouse_mapper_mode != 0) {\n        return;\n    }\n\n    unsigned int v3 = get_bk_time();\n    if (mouseX == gmouse_3d_last_mouse_x && mouseY == gmouse_3d_last_mouse_y) {\n        if (gmouse_3d_hover_test || elapsed_tocks(v3, gmouse_3d_last_move_time) < 250) {\n            return;\n        }\n\n        if (gmouse_3d_current_mode != GAME_MOUSE_MODE_MOVE) {\n            if (gmouse_3d_current_mode == GAME_MOUSE_MODE_ARROW) {\n                gmouse_3d_last_move_time = v3;\n                gmouse_3d_hover_test = true;\n\n                Object* pointedObject = object_under_mouse(-1, true, map_elevation);\n                if (pointedObject != NULL) {\n                    int primaryAction = -1;\n\n                    switch (FID_TYPE(pointedObject->fid)) {\n                    case OBJ_TYPE_ITEM:\n                        primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE;\n                        if (gmouse_3d_item_highlight) {\n                            Rect tmp;\n                            if (obj_outline_object(pointedObject, OUTLINE_TYPE_ITEM, &tmp) == 0) {\n                                tile_refresh_rect(&tmp, map_elevation);\n                                outlined_object = pointedObject;\n                            }\n                        }\n                        break;\n                    case OBJ_TYPE_CRITTER:\n                        if (pointedObject == obj_dude) {\n                            primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_ROTATE;\n                        } else {\n                            if (obj_action_can_talk_to(pointedObject)) {\n                                if (isInCombat()) {\n                                    primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;\n                                } else {\n                                    primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_TALK;\n                                }\n                            } else {\n                                if (critter_flag_check(pointedObject->pid, CRITTER_NO_STEAL)) {\n                                    primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;\n                                } else {\n                                    primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE;\n                                }\n                            }\n                        }\n                        break;\n                    case OBJ_TYPE_SCENERY:\n                        if (!obj_action_can_use(pointedObject)) {\n                            primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;\n                        } else {\n                            primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE;\n                        }\n                        break;\n                    case OBJ_TYPE_WALL:\n                        primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;\n                        break;\n                    }\n\n                    if (primaryAction != -1) {\n                        if (gmouse_3d_build_pick_frame(mouseX, mouseY, primaryAction, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99) == 0) {\n                            Rect tmp;\n                            int fid = art_id(OBJ_TYPE_INTERFACE, 282, 0, 0, 0);\n                            // NOTE: Uninline.\n                            if (gmouse_3d_set_flat_fid(fid, &tmp) == 0) {\n                                tile_refresh_rect(&tmp, map_elevation);\n                            }\n                        }\n                    }\n\n                    if (pointedObject != last_object) {\n                        last_object = pointedObject;\n                        obj_look_at(obj_dude, last_object);\n                    }\n                }\n            } else if (gmouse_3d_current_mode == GAME_MOUSE_MODE_CROSSHAIR) {\n                Object* pointedObject = object_under_mouse(OBJ_TYPE_CRITTER, false, map_elevation);\n                if (pointedObject == NULL) {\n                    pointedObject = object_under_mouse(-1, false, map_elevation);\n                    if (!gmObjIsValidTarget(pointedObject)) {\n                        pointedObject = NULL;\n                    }\n                }\n\n                if (pointedObject != NULL) {\n                    bool pointedObjectIsCritter = FID_TYPE(pointedObject->fid) == OBJ_TYPE_CRITTER;\n\n                    int combatLooks = 0;\n                    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, &combatLooks);\n                    if (combatLooks != 0) {\n                        if (obj_examine(obj_dude, pointedObject) == -1) {\n                            obj_look_at(obj_dude, pointedObject);\n                        }\n                    }\n\n                    int color;\n                    int accuracy;\n                    char formattedAccuracy[8];\n                    if (combat_to_hit(pointedObject, &accuracy)) {\n                        sprintf(formattedAccuracy, \"%d%%\", accuracy);\n\n                        if (pointedObjectIsCritter) {\n                            if (pointedObject->data.critter.combat.team != 0) {\n                                color = colorTable[32767];\n                            } else {\n                                color = colorTable[32495];\n                            }\n                        } else {\n                            color = colorTable[17969];\n                        }\n                    } else {\n                        sprintf(formattedAccuracy, \" %c \", 'X');\n\n                        if (pointedObjectIsCritter) {\n                            if (pointedObject->data.critter.combat.team != 0) {\n                                color = colorTable[31744];\n                            } else {\n                                color = colorTable[18161];\n                            }\n                        } else {\n                            color = colorTable[32239];\n                        }\n                    }\n\n                    if (gmouse_3d_build_to_hit_frame(formattedAccuracy, color) == 0) {\n                        Rect tmp;\n                        int fid = art_id(OBJ_TYPE_INTERFACE, 284, 0, 0, 0);\n                        // NOTE: Uninline.\n                        if (gmouse_3d_set_flat_fid(fid, &tmp) == 0) {\n                            tile_refresh_rect(&tmp, map_elevation);\n                        }\n                    }\n\n                    if (last_object != pointedObject) {\n                        last_object = pointedObject;\n                    }\n                } else {\n                    Rect tmp;\n                    if (gmouse_3d_reset_flat_fid(&tmp) == 0) {\n                        tile_refresh_rect(&tmp, map_elevation);\n                    }\n                }\n\n                gmouse_3d_last_move_time = v3;\n                gmouse_3d_hover_test = true;\n            }\n            return;\n        }\n\n        char formattedActionPoints[8];\n        int color;\n        int v6 = make_path(obj_dude, obj_dude->tile, obj_mouse_flat->tile, NULL, 1);\n        if (v6) {\n            if (!isInCombat()) {\n                formattedActionPoints[0] = '\\0';\n                color = colorTable[31744];\n            } else {\n                int v7 = critter_compute_ap_from_distance(obj_dude, v6);\n                int v8;\n                if (v7 - combat_free_move >= 0) {\n                    v8 = v7 - combat_free_move;\n                } else {\n                    v8 = 0;\n                }\n\n                if (v8 <= obj_dude->data.critter.combat.ap) {\n                    sprintf(formattedActionPoints, \"%d\", v8);\n                    color = colorTable[32767];\n                } else {\n                    sprintf(formattedActionPoints, \"%c\", 'X');\n                    color = colorTable[31744];\n                }\n            }\n        } else {\n            sprintf(formattedActionPoints, \"%c\", 'X');\n            color = colorTable[31744];\n        }\n\n        if (gmouse_3d_build_hex_frame(formattedActionPoints, color) == 0) {\n            Rect tmp;\n            obj_bound(obj_mouse_flat, &tmp);\n            tile_refresh_rect(&tmp, 0);\n        }\n\n        gmouse_3d_last_move_time = v3;\n        gmouse_3d_hover_test = true;\n        last_tile = obj_mouse_flat->tile;\n        return;\n    }\n\n    gmouse_3d_last_move_time = v3;\n    gmouse_3d_hover_test = false;\n    gmouse_3d_last_mouse_x = mouseX;\n    gmouse_3d_last_mouse_y = mouseY;\n\n    if (!gmouse_mapper_mode) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0);\n        gmouse_3d_set_fid(fid);\n    }\n\n    int v34 = 0;\n\n    Rect r2;\n    Rect r26;\n    if (gmouse_3d_reset_flat_fid(&r2) == 0) {\n        v34 |= 1;\n    }\n\n    if (outlined_object != NULL) {\n        if (obj_remove_outline(outlined_object, &r26) == 0) {\n            v34 |= 2;\n        }\n        outlined_object = NULL;\n    }\n\n    switch (v34) {\n    case 3:\n        rect_min_bound(&r2, &r26, &r2);\n        // FALLTHROUGH\n    case 1:\n        tile_refresh_rect(&r2, map_elevation);\n        break;\n    case 2:\n        tile_refresh_rect(&r26, map_elevation);\n        break;\n    }\n}\n\n// 0x44BFA8\nvoid gmouse_handle_event(int mouseX, int mouseY, int mouseState)\n{\n    if (!gmouse_initialized) {\n        return;\n    }\n\n    if (gmouse_current_cursor >= MOUSE_CURSOR_WAIT_PLANET) {\n        return;\n    }\n\n    if (!gmouse_enabled) {\n        return;\n    }\n\n    if (gmouse_clicked_on_edge) {\n        if (gmouse_get_click_to_scroll()) {\n            return;\n        }\n    }\n\n    if (!mouse_click_in(0, 0, scr_size.lrx - scr_size.ulx, scr_size.lry - scr_size.uly - 100)) {\n        return;\n    }\n\n    if ((mouseState & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) {\n        if ((mouseState & MOUSE_EVENT_RIGHT_BUTTON_REPEAT) == 0 && (obj_mouse_flat->flags & OBJECT_HIDDEN) == 0) {\n            gmouse_3d_toggle_mode();\n        }\n        return;\n    }\n\n    if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n        if (gmouse_3d_current_mode == GAME_MOUSE_MODE_MOVE) {\n            int actionPoints;\n            if (isInCombat()) {\n                actionPoints = combat_free_move + obj_dude->data.critter.combat.ap;\n            } else {\n                actionPoints = -1;\n            }\n\n            bool running;\n            configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, &running);\n\n            if (keys[DIK_LSHIFT] || keys[DIK_RSHIFT]) {\n                if (running) {\n                    dude_move(actionPoints);\n                    return;\n                }\n            } else {\n                if (!running) {\n                    dude_move(actionPoints);\n                    return;\n                }\n            }\n\n            dude_run(actionPoints);\n            return;\n        }\n\n        if (gmouse_3d_current_mode == GAME_MOUSE_MODE_ARROW) {\n            Object* v5 = object_under_mouse(-1, true, map_elevation);\n            if (v5 != NULL) {\n                switch (FID_TYPE(v5->fid)) {\n                case OBJ_TYPE_ITEM:\n                    action_get_an_object(obj_dude, v5);\n                    break;\n                case OBJ_TYPE_CRITTER:\n                    if (v5 == obj_dude) {\n                        if (FID_ANIM_TYPE(obj_dude->fid) == ANIM_STAND) {\n                            Rect a1;\n                            if (obj_inc_rotation(v5, &a1) == 0) {\n                                tile_refresh_rect(&a1, v5->elevation);\n                            }\n                        }\n                    } else {\n                        if (obj_action_can_talk_to(v5)) {\n                            if (isInCombat()) {\n                                if (obj_examine(obj_dude, v5) == -1) {\n                                    obj_look_at(obj_dude, v5);\n                                }\n                            } else {\n                                action_talk_to(obj_dude, v5);\n                            }\n                        } else {\n                            action_loot_container(obj_dude, v5);\n                        }\n                    }\n                    break;\n                case OBJ_TYPE_SCENERY:\n                    if (obj_action_can_use(v5)) {\n                        action_use_an_object(obj_dude, v5);\n                    } else {\n                        if (obj_examine(obj_dude, v5) == -1) {\n                            obj_look_at(obj_dude, v5);\n                        }\n                    }\n                    break;\n                case OBJ_TYPE_WALL:\n                    if (obj_examine(obj_dude, v5) == -1) {\n                        obj_look_at(obj_dude, v5);\n                    }\n                    break;\n                }\n            }\n            return;\n        }\n\n        if (gmouse_3d_current_mode == GAME_MOUSE_MODE_CROSSHAIR) {\n            Object* v7 = object_under_mouse(OBJ_TYPE_CRITTER, false, map_elevation);\n            if (v7 == NULL) {\n                v7 = object_under_mouse(-1, false, map_elevation);\n                if (!gmObjIsValidTarget(v7)) {\n                    v7 = NULL;\n                }\n            }\n\n            if (v7 != NULL) {\n                combat_attack_this(v7);\n                gmouse_3d_hover_test = true;\n                gmouse_3d_last_mouse_y = mouseY;\n                gmouse_3d_last_mouse_x = mouseX;\n                gmouse_3d_last_move_time = get_time() - 250;\n            }\n            return;\n        }\n\n        if (gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_CROSSHAIR) {\n            Object* object = object_under_mouse(-1, true, map_elevation);\n            if (object != NULL) {\n                Object* weapon;\n                if (intface_get_current_item(&weapon) != -1) {\n                    if (isInCombat()) {\n                        int hitMode = intface_is_item_right_hand()\n                            ? HIT_MODE_RIGHT_WEAPON_PRIMARY\n                            : HIT_MODE_LEFT_WEAPON_PRIMARY;\n\n                        int actionPointsRequired = item_mp_cost(obj_dude, hitMode, false);\n                        if (actionPointsRequired <= obj_dude->data.critter.combat.ap) {\n                            if (action_use_an_item_on_object(obj_dude, object, weapon) != -1) {\n                                int actionPoints = obj_dude->data.critter.combat.ap;\n                                if (actionPointsRequired > actionPoints) {\n                                    obj_dude->data.critter.combat.ap = 0;\n                                } else {\n                                    obj_dude->data.critter.combat.ap -= actionPointsRequired;\n                                }\n                                intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n                            }\n                        }\n                    } else {\n                        action_use_an_item_on_object(obj_dude, object, weapon);\n                    }\n                }\n            }\n            gmouse_set_cursor(MOUSE_CURSOR_NONE);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE);\n            return;\n        }\n\n        if (gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_FIRST_AID\n            || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_DOCTOR\n            || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_LOCKPICK\n            || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_STEAL\n            || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_TRAPS\n            || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_SCIENCE\n            || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_REPAIR) {\n            Object* object = object_under_mouse(-1, 1, map_elevation);\n            if (object == NULL || action_use_skill_on(obj_dude, object, gmouse_skill_table[gmouse_3d_current_mode - FIRST_GAME_MOUSE_MODE_SKILL]) != -1) {\n                gmouse_set_cursor(MOUSE_CURSOR_NONE);\n                gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE);\n            }\n            return;\n        }\n    }\n\n    if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) == MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT && gmouse_3d_current_mode == GAME_MOUSE_MODE_ARROW) {\n        Object* v16 = object_under_mouse(-1, true, map_elevation);\n        if (v16 != NULL) {\n            int actionMenuItemsCount = 0;\n            int actionMenuItems[6];\n            switch (FID_TYPE(v16->fid)) {\n            case OBJ_TYPE_ITEM:\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE;\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;\n                if (item_get_type(v16) == ITEM_TYPE_CONTAINER) {\n                    actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY;\n                    actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL;\n                }\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_CANCEL;\n                break;\n            case OBJ_TYPE_CRITTER:\n                if (v16 == obj_dude) {\n                    actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_ROTATE;\n                } else {\n                    if (obj_action_can_talk_to(v16)) {\n                        if (!isInCombat()) {\n                            actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_TALK;\n                        }\n                    } else {\n                        if (!critter_flag_check(v16->pid, CRITTER_NO_STEAL)) {\n                            actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE;\n                        }\n                    }\n\n                    if (action_can_be_pushed(obj_dude, v16)) {\n                        actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_PUSH;\n                    }\n                }\n\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY;\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL;\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_CANCEL;\n                break;\n            case OBJ_TYPE_SCENERY:\n                if (obj_action_can_use(v16)) {\n                    actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE;\n                }\n\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY;\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL;\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_CANCEL;\n                break;\n            case OBJ_TYPE_WALL:\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK;\n                if (obj_action_can_use(v16)) {\n                    actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY;\n                }\n                actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_CANCEL;\n                break;\n            }\n\n            if (gmouse_3d_build_menu_frame(mouseX, mouseY, actionMenuItems, actionMenuItemsCount, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99) == 0) {\n                Rect v43;\n                int fid = art_id(OBJ_TYPE_INTERFACE, 283, 0, 0, 0);\n                // NOTE: Uninline.\n                if (gmouse_3d_set_flat_fid(fid, &v43) == 0 && gmouse_3d_move_to(mouseX, mouseY, map_elevation, &v43) == 0) {\n                    tile_refresh_rect(&v43, map_elevation);\n                    map_disable_bk_processes();\n\n                    int v33 = mouseY;\n                    int actionIndex = 0;\n                    while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) {\n                        get_input();\n\n                        if (game_user_wants_to_quit != 0) {\n                            actionMenuItems[actionIndex] = 0;\n                        }\n\n                        int v48;\n                        int v47;\n                        mouse_get_position(&v48, &v47);\n\n                        if (abs(v47 - v33) > 10) {\n                            if (v33 >= v47) {\n                                actionIndex -= 1;\n                            } else {\n                                actionIndex += 1;\n                            }\n\n                            if (gmouse_3d_highlight_menu_frame(actionIndex) == 0) {\n                                tile_refresh_rect(&v43, map_elevation);\n                            }\n                            v33 = v47;\n                        }\n                    }\n\n                    map_enable_bk_processes();\n\n                    gmouse_3d_hover_test = false;\n                    gmouse_3d_last_mouse_x = mouseX;\n                    gmouse_3d_last_mouse_y = mouseY;\n                    gmouse_3d_last_move_time = get_time();\n\n                    mouse_set_position(mouseX, v33);\n\n                    if (gmouse_3d_reset_flat_fid(&v43) == 0) {\n                        tile_refresh_rect(&v43, map_elevation);\n                    }\n\n                    switch (actionMenuItems[actionIndex]) {\n                    case GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY:\n                        use_inventory_on(v16);\n                        break;\n                    case GAME_MOUSE_ACTION_MENU_ITEM_LOOK:\n                        if (obj_examine(obj_dude, v16) == -1) {\n                            obj_look_at(obj_dude, v16);\n                        }\n                        break;\n                    case GAME_MOUSE_ACTION_MENU_ITEM_ROTATE:\n                        if (obj_inc_rotation(v16, &v43) == 0) {\n                            tile_refresh_rect(&v43, v16->elevation);\n                        }\n                        break;\n                    case GAME_MOUSE_ACTION_MENU_ITEM_TALK:\n                        action_talk_to(obj_dude, v16);\n                        break;\n                    case GAME_MOUSE_ACTION_MENU_ITEM_USE:\n                        switch (FID_TYPE(v16->fid)) {\n                        case OBJ_TYPE_SCENERY:\n                            action_use_an_object(obj_dude, v16);\n                            break;\n                        case OBJ_TYPE_CRITTER:\n                            action_loot_container(obj_dude, v16);\n                            break;\n                        default:\n                            action_get_an_object(obj_dude, v16);\n                            break;\n                        }\n                        break;\n                    case GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL:\n                        if (1) {\n                            int skill = -1;\n\n                            int rc = skilldex_select();\n                            switch (rc) {\n                            case SKILLDEX_RC_SNEAK:\n                                action_skill_use(SKILL_SNEAK);\n                                break;\n                            case SKILLDEX_RC_LOCKPICK:\n                                skill = SKILL_LOCKPICK;\n                                break;\n                            case SKILLDEX_RC_STEAL:\n                                skill = SKILL_STEAL;\n                                break;\n                            case SKILLDEX_RC_TRAPS:\n                                skill = SKILL_TRAPS;\n                                break;\n                            case SKILLDEX_RC_FIRST_AID:\n                                skill = SKILL_FIRST_AID;\n                                break;\n                            case SKILLDEX_RC_DOCTOR:\n                                skill = SKILL_DOCTOR;\n                                break;\n                            case SKILLDEX_RC_SCIENCE:\n                                skill = SKILL_SCIENCE;\n                                break;\n                            case SKILLDEX_RC_REPAIR:\n                                skill = SKILL_REPAIR;\n                                break;\n                            }\n\n                            if (skill != -1) {\n                                action_use_skill_on(obj_dude, v16, skill);\n                            }\n                        }\n                        break;\n                    case GAME_MOUSE_ACTION_MENU_ITEM_PUSH:\n                        action_push_critter(obj_dude, v16);\n                        break;\n                    }\n                }\n            }\n        }\n    }\n}\n\n// 0x44C840\nint gmouse_set_cursor(int cursor)\n{\n    if (!gmouse_initialized) {\n        return -1;\n    }\n\n    if (cursor != MOUSE_CURSOR_ARROW && cursor == gmouse_current_cursor && (gmouse_current_cursor < 25 || gmouse_current_cursor >= 27)) {\n        return -1;\n    }\n\n    CacheEntry* mouseCursorFrmHandle;\n    int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_cursor_nums[cursor], 0, 0, 0);\n    Art* mouseCursorFrm = art_ptr_lock(fid, &mouseCursorFrmHandle);\n    if (mouseCursorFrm == NULL) {\n        return -1;\n    }\n\n    bool shouldUpdate = true;\n    int frame = 0;\n    if (cursor >= FIRST_GAME_MOUSE_ANIMATED_CURSOR) {\n        unsigned int tick = get_time();\n\n        if ((obj_mouse_flat->flags & OBJECT_HIDDEN) == 0) {\n            gmouse_3d_off();\n        }\n\n        unsigned int delay = 1000 / art_frame_fps(mouseCursorFrm);\n        if (elapsed_tocks(tick, gmouse_wait_cursor_time) < delay) {\n            shouldUpdate = false;\n        } else {\n            if (art_frame_max_frame(mouseCursorFrm) <= gmouse_wait_cursor_frame) {\n                gmouse_wait_cursor_frame = 0;\n            }\n\n            frame = gmouse_wait_cursor_frame;\n            gmouse_wait_cursor_time = tick;\n            gmouse_wait_cursor_frame++;\n        }\n    }\n\n    if (!shouldUpdate) {\n        return -1;\n    }\n\n    int width = art_frame_width(mouseCursorFrm, frame, 0);\n    int height = art_frame_length(mouseCursorFrm, frame, 0);\n\n    int offsetX;\n    int offsetY;\n    art_frame_offset(mouseCursorFrm, 0, &offsetX, &offsetY);\n\n    offsetX = width / 2 - offsetX;\n    offsetY = height - 1 - offsetY;\n\n    unsigned char* mouseCursorFrmData = art_frame_data(mouseCursorFrm, frame, 0);\n    if (mouse_set_shape(mouseCursorFrmData, width, height, width, offsetX, offsetY, 0) != 0) {\n        return -1;\n    }\n\n    if (gmouse_current_cursor_key != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(gmouse_current_cursor_key);\n    }\n\n    gmouse_current_cursor = cursor;\n    gmouse_current_cursor_key = mouseCursorFrmHandle;\n\n    return 0;\n}\n\n// 0x44C9E8\nint gmouse_get_cursor()\n{\n    return gmouse_current_cursor;\n}\n\n// NOTE: Unused.\n//\n// 0x44C9F0\nvoid gmouse_set_mapper_mode(int mode)\n{\n    gmouse_mapper_mode = mode;\n}\n\n// 0x44C9F8\nvoid gmouse_3d_enable_modes()\n{\n    gmouse_3d_modes_enabled = 1;\n}\n\n// NOTE: Unused.\n//\n// 0x44CA04\nvoid gmouse_3d_disable_modes()\n{\n    gmouse_3d_modes_enabled = 0;\n}\n\n// NOTE: Unused.\n//\n// 0x44CA10\nint gmouse_3d_modes_are_enabled()\n{\n    return gmouse_3d_modes_enabled;\n}\n\n// 0x44CA18\nvoid gmouse_3d_set_mode(int mode)\n{\n    if (!gmouse_initialized) {\n        return;\n    }\n\n    if (!gmouse_3d_modes_enabled) {\n        return;\n    }\n\n    if (mode == gmouse_3d_current_mode) {\n        return;\n    }\n\n    int fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0);\n    gmouse_3d_set_fid(fid);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[mode], 0, 0, 0);\n\n    Rect rect;\n    // NOTE: Uninline.\n    if (gmouse_3d_set_flat_fid(fid, &rect) == -1) {\n        return;\n    }\n\n    int mouseX;\n    int mouseY;\n    mouse_get_position(&mouseX, &mouseY);\n\n    Rect r2;\n    if (gmouse_3d_move_to(mouseX, mouseY, map_elevation, &r2) == 0) {\n        rect_min_bound(&rect, &r2, &rect);\n    }\n\n    int v5 = 0;\n    if (gmouse_3d_current_mode == GAME_MOUSE_MODE_CROSSHAIR) {\n        v5 = -1;\n    }\n\n    if (mode != 0) {\n        if (mode == GAME_MOUSE_MODE_CROSSHAIR) {\n            v5 = 1;\n        }\n\n        if (gmouse_3d_current_mode == 0) {\n            if (obj_turn_off_outline(obj_mouse_flat, &r2) == 0) {\n                rect_min_bound(&rect, &r2, &rect);\n            }\n        }\n    } else {\n        if (obj_turn_on_outline(obj_mouse_flat, &r2) == 0) {\n            rect_min_bound(&rect, &r2, &rect);\n        }\n    }\n\n    gmouse_3d_current_mode = mode;\n    gmouse_3d_hover_test = false;\n    gmouse_3d_last_move_time = get_time();\n\n    tile_refresh_rect(&rect, map_elevation);\n\n    switch (v5) {\n    case 1:\n        combat_outline_on();\n        break;\n    case -1:\n        combat_outline_off();\n        break;\n    }\n}\n\n// 0x44CB6C\nint gmouse_3d_get_mode()\n{\n    return gmouse_3d_current_mode;\n}\n\n// 0x44CB74\nvoid gmouse_3d_toggle_mode()\n{\n    int mode = (gmouse_3d_current_mode + 1) % 3;\n\n    if (isInCombat()) {\n        Object* item;\n        if (intface_get_current_item(&item) == 0) {\n            if (item != NULL && item_get_type(item) != ITEM_TYPE_WEAPON && mode == GAME_MOUSE_MODE_CROSSHAIR) {\n                mode = GAME_MOUSE_MODE_MOVE;\n            }\n        }\n    } else {\n        if (mode == GAME_MOUSE_MODE_CROSSHAIR) {\n            mode = GAME_MOUSE_MODE_MOVE;\n        }\n    }\n\n    gmouse_3d_set_mode(mode);\n}\n\n// 0x44CBD0\nvoid gmouse_3d_refresh()\n{\n    gmouse_3d_last_mouse_x = -1;\n    gmouse_3d_last_mouse_y = -1;\n    gmouse_3d_hover_test = false;\n    gmouse_3d_last_move_time = 0;\n    gmouse_bk_process();\n}\n\n// 0x44CBFC\nint gmouse_3d_set_fid(int fid)\n{\n    if (!gmouse_initialized) {\n        return -1;\n    }\n\n    if (!art_exists(fid)) {\n        return -1;\n    }\n\n    if (obj_mouse->fid == fid) {\n        return -1;\n    }\n\n    if (!gmouse_mapper_mode) {\n        return obj_change_fid(obj_mouse, fid, NULL);\n    }\n\n    int v1 = 0;\n\n    Rect oldRect;\n    if (obj_mouse->fid != -1) {\n        obj_bound(obj_mouse, &oldRect);\n        v1 |= 1;\n    }\n\n    int rc = -1;\n\n    Rect rect;\n    if (obj_change_fid(obj_mouse, fid, &rect) == 0) {\n        rc = 0;\n        v1 |= 2;\n    }\n\n    if ((obj_mouse_flat->flags & OBJECT_HIDDEN) == 0) {\n        if (v1 == 1) {\n            tile_refresh_rect(&oldRect, map_elevation);\n        } else if (v1 == 2) {\n            tile_refresh_rect(&rect, map_elevation);\n        } else if (v1 == 3) {\n            rect_min_bound(&oldRect, &rect, &oldRect);\n            tile_refresh_rect(&oldRect, map_elevation);\n        }\n    }\n\n    return rc;\n}\n\n// NOTE: Unused.\n//\n// 0x44CCF4\nint gmouse_3d_get_fid()\n{\n    if (gmouse_initialized) {\n        return obj_mouse->fid;\n    }\n\n    return -1;\n}\n\n// 0x44CD0C\nvoid gmouse_3d_reset_fid()\n{\n    int fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0);\n    gmouse_3d_set_fid(fid);\n}\n\n// 0x44CD2C\nvoid gmouse_3d_on()\n{\n    if (!gmouse_initialized) {\n        return;\n    }\n\n    int v2 = 0;\n\n    Rect rect1;\n    if (obj_turn_on(obj_mouse, &rect1) == 0) {\n        v2 |= 1;\n    }\n\n    Rect rect2;\n    if (obj_turn_on(obj_mouse_flat, &rect2) == 0) {\n        v2 |= 2;\n    }\n\n    Rect tmp;\n    if (gmouse_3d_current_mode != GAME_MOUSE_MODE_MOVE) {\n        if (obj_turn_off_outline(obj_mouse_flat, &tmp) == 0) {\n            if ((v2 & 2) != 0) {\n                rect_min_bound(&rect2, &tmp, &rect2);\n            } else {\n                memcpy(&rect2, &tmp, sizeof(rect2));\n                v2 |= 2;\n            }\n        }\n    }\n\n    if (gmouse_3d_reset_flat_fid(&tmp) == 0) {\n        if ((v2 & 2) != 0) {\n            rect_min_bound(&rect2, &tmp, &rect2);\n        } else {\n            memcpy(&rect2, &tmp, sizeof(rect2));\n            v2 |= 2;\n        }\n    }\n\n    if (v2 != 0) {\n        Rect* rect;\n        switch (v2) {\n        case 1:\n            rect = &rect1;\n            break;\n        case 2:\n            rect = &rect2;\n            break;\n        case 3:\n            rect_min_bound(&rect1, &rect2, &rect1);\n            rect = &rect1;\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        tile_refresh_rect(rect, map_elevation);\n    }\n\n    gmouse_3d_hover_test = false;\n    gmouse_3d_last_move_time = get_time() - 250;\n}\n\n// 0x44CE34\nvoid gmouse_3d_off()\n{\n    if (!gmouse_initialized) {\n        return;\n    }\n\n    int v1 = 0;\n\n    Rect rect1;\n    if (obj_turn_off(obj_mouse, &rect1) == 0) {\n        v1 |= 1;\n    }\n\n    Rect rect2;\n    if (obj_turn_off(obj_mouse_flat, &rect2) == 0) {\n        v1 |= 2;\n    }\n\n    if (v1 == 1) {\n        tile_refresh_rect(&rect1, map_elevation);\n    } else if (v1 == 2) {\n        tile_refresh_rect(&rect2, map_elevation);\n    } else if (v1 == 3) {\n        rect_min_bound(&rect1, &rect2, &rect1);\n        tile_refresh_rect(&rect1, map_elevation);\n    }\n}\n\n// 0x44CEB0\nbool gmouse_3d_is_on()\n{\n    return (obj_mouse_flat->flags & OBJECT_HIDDEN) == 0;\n}\n\n// 0x44CEC4\nObject* object_under_mouse(int objectType, bool a2, int elevation)\n{\n    int mouseX;\n    int mouseY;\n    mouse_get_position(&mouseX, &mouseY);\n\n    bool v13 = false;\n    if (objectType == -1) {\n        if (square_roof_intersect(mouseX, mouseY, elevation)) {\n            if (obj_intersects_with(obj_egg, mouseX, mouseY) == 0) {\n                v13 = true;\n            }\n        }\n    }\n\n    Object* v4 = NULL;\n    if (!v13) {\n        ObjectWithFlags* entries;\n        int count = obj_create_intersect_list(mouseX, mouseY, elevation, objectType, &entries);\n        for (int index = count - 1; index >= 0; index--) {\n            ObjectWithFlags* ptr = &(entries[index]);\n            if (a2 || obj_dude != ptr->object) {\n                v4 = ptr->object;\n                if ((ptr->flags & 0x01) != 0) {\n                    if ((ptr->flags & 0x04) == 0) {\n                        if (FID_TYPE(ptr->object->fid) != OBJ_TYPE_CRITTER || (ptr->object->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD)) == 0) {\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        if (count != 0) {\n            obj_delete_intersect_list(&entries);\n        }\n    }\n    return v4;\n}\n\n// 0x44CFA0\nint gmouse_3d_build_pick_frame(int x, int y, int menuItem, int width, int height)\n{\n    CacheEntry* menuItemFrmHandle;\n    int menuItemFid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_action_nums[menuItem], 0, 0, 0);\n    Art* menuItemFrm = art_ptr_lock(menuItemFid, &menuItemFrmHandle);\n    if (menuItemFrm == NULL) {\n        return -1;\n    }\n\n    CacheEntry* arrowFrmHandle;\n    int arrowFid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[GAME_MOUSE_MODE_ARROW], 0, 0, 0);\n    Art* arrowFrm = art_ptr_lock(arrowFid, &arrowFrmHandle);\n    if (arrowFrm == NULL) {\n        art_ptr_unlock(menuItemFrmHandle);\n        // FIXME: Why this is success?\n        return 0;\n    }\n\n    unsigned char* arrowFrmData = art_frame_data(arrowFrm, 0, 0);\n    int arrowFrmWidth = art_frame_width(arrowFrm, 0, 0);\n    int arrowFrmHeight = art_frame_length(arrowFrm, 0, 0);\n\n    unsigned char* menuItemFrmData = art_frame_data(menuItemFrm, 0, 0);\n    int menuItemFrmWidth = art_frame_width(menuItemFrm, 0, 0);\n    int menuItemFrmHeight = art_frame_length(menuItemFrm, 0, 0);\n\n    unsigned char* arrowFrmDest = gmouse_3d_pick_frame_data;\n    unsigned char* menuItemFrmDest = gmouse_3d_pick_frame_data;\n\n    gmouse_3d_pick_frame_hot_x = 0;\n    gmouse_3d_pick_frame_hot_y = 0;\n\n    gmouse_3d_pick_frame->xOffsets[0] = gmouse_3d_pick_frame_width / 2;\n    gmouse_3d_pick_frame->yOffsets[0] = gmouse_3d_pick_frame_height - 1;\n\n    int maxX = x + menuItemFrmWidth + arrowFrmWidth - 1;\n    int maxY = y + menuItemFrmHeight - 1;\n    int shiftY = maxY - height + 2;\n\n    if (maxX < width) {\n        menuItemFrmDest += arrowFrmWidth;\n        if (maxY >= height) {\n            gmouse_3d_pick_frame_hot_y = shiftY;\n            gmouse_3d_pick_frame->yOffsets[0] -= shiftY;\n            arrowFrmDest += gmouse_3d_pick_frame_width * shiftY;\n        }\n    } else {\n        art_ptr_unlock(arrowFrmHandle);\n\n        arrowFid = art_id(OBJ_TYPE_INTERFACE, 285, 0, 0, 0);\n        arrowFrm = art_ptr_lock(arrowFid, &arrowFrmHandle);\n        arrowFrmData = art_frame_data(arrowFrm, 0, 0);\n        arrowFrmDest += menuItemFrmWidth;\n\n        gmouse_3d_pick_frame->xOffsets[0] = -gmouse_3d_pick_frame->xOffsets[0];\n        gmouse_3d_pick_frame_hot_x += menuItemFrmWidth + arrowFrmWidth;\n\n        if (maxY >= height) {\n            gmouse_3d_pick_frame_hot_y += shiftY;\n            gmouse_3d_pick_frame->yOffsets[0] -= shiftY;\n\n            arrowFrmDest += gmouse_3d_pick_frame_width * shiftY;\n        }\n    }\n\n    memset(gmouse_3d_pick_frame_data, 0, gmouse_3d_pick_frame_size);\n\n    buf_to_buf(arrowFrmData, arrowFrmWidth, arrowFrmHeight, arrowFrmWidth, arrowFrmDest, gmouse_3d_pick_frame_width);\n    buf_to_buf(menuItemFrmData, menuItemFrmWidth, menuItemFrmHeight, menuItemFrmWidth, menuItemFrmDest, gmouse_3d_pick_frame_width);\n\n    art_ptr_unlock(arrowFrmHandle);\n    art_ptr_unlock(menuItemFrmHandle);\n\n    return 0;\n}\n\n// 0x44D200\nint gmouse_3d_pick_frame_hot(int* a1, int* a2)\n{\n    *a1 = gmouse_3d_pick_frame_hot_x;\n    *a2 = gmouse_3d_pick_frame_hot_y;\n    return 0;\n}\n\n// 0x44D214\nint gmouse_3d_build_menu_frame(int x, int y, const int* menuItems, int menuItemsLength, int width, int height)\n{\n    gmouse_3d_menu_actions_start = NULL;\n    gmouse_3d_menu_current_action_index = 0;\n    gmouse_3d_menu_available_actions = 0;\n\n    if (menuItems == NULL) {\n        return -1;\n    }\n\n    if (menuItemsLength == 0 || menuItemsLength >= GAME_MOUSE_ACTION_MENU_ITEM_COUNT) {\n        return -1;\n    }\n\n    CacheEntry* menuItemFrmHandles[GAME_MOUSE_ACTION_MENU_ITEM_COUNT];\n    Art* menuItemFrms[GAME_MOUSE_ACTION_MENU_ITEM_COUNT];\n\n    for (int index = 0; index < menuItemsLength; index++) {\n        int frmId = gmouse_3d_action_nums[menuItems[index]] & 0xFFFF;\n        if (index == 0) {\n            frmId -= 1;\n        }\n\n        int fid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0);\n\n        menuItemFrms[index] = art_ptr_lock(fid, &(menuItemFrmHandles[index]));\n        if (menuItemFrms[index] == NULL) {\n            while (--index >= 0) {\n                art_ptr_unlock(menuItemFrmHandles[index]);\n            }\n            return -1;\n        }\n    }\n\n    int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[GAME_MOUSE_MODE_ARROW], 0, 0, 0);\n    CacheEntry* arrowFrmHandle;\n    Art* arrowFrm = art_ptr_lock(fid, &arrowFrmHandle);\n    if (arrowFrm == NULL) {\n        // FIXME: Unlock arts.\n        return -1;\n    }\n\n    int arrowWidth = art_frame_width(arrowFrm, 0, 0);\n    int arrowHeight = art_frame_length(arrowFrm, 0, 0);\n\n    int menuItemWidth = art_frame_width(menuItemFrms[0], 0, 0);\n    int menuItemHeight = art_frame_length(menuItemFrms[0], 0, 0);\n\n    gmouse_3d_menu_frame_hot_x = 0;\n    gmouse_3d_menu_frame_hot_y = 0;\n\n    gmouse_3d_menu_frame->xOffsets[0] = gmouse_3d_menu_frame_width / 2;\n    gmouse_3d_menu_frame->yOffsets[0] = gmouse_3d_menu_frame_height - 1;\n\n    int v60 = y + menuItemsLength * menuItemHeight - 1;\n    int v24 = v60 - height + 2;\n    unsigned char* v22 = gmouse_3d_menu_frame_data;\n    unsigned char* v58 = v22;\n\n    unsigned char* arrowData;\n    if (x + arrowWidth + menuItemWidth - 1 < width) {\n        arrowData = art_frame_data(arrowFrm, 0, 0);\n        v58 = v22 + arrowWidth;\n        if (height <= v60) {\n            gmouse_3d_menu_frame_hot_y += v24;\n            v22 += gmouse_3d_menu_frame_width * v24;\n            gmouse_3d_menu_frame->yOffsets[0] -= v24;\n        }\n    } else {\n        // Mirrored arrow (from left to right).\n        fid = art_id(OBJ_TYPE_INTERFACE, 285, 0, 0, 0);\n        arrowFrm = art_ptr_lock(fid, &arrowFrmHandle);\n        arrowData = art_frame_data(arrowFrm, 0, 0);\n        gmouse_3d_menu_frame->xOffsets[0] = -gmouse_3d_menu_frame->xOffsets[0];\n        gmouse_3d_menu_frame_hot_x += menuItemWidth + arrowWidth;\n        if (v60 >= height) {\n            gmouse_3d_menu_frame_hot_y += v24;\n            gmouse_3d_menu_frame->yOffsets[0] -= v24;\n            v22 += gmouse_3d_menu_frame_width * v24;\n        }\n    }\n\n    memset(gmouse_3d_menu_frame_data, 0, gmouse_3d_menu_frame_size);\n    buf_to_buf(arrowData, arrowWidth, arrowHeight, arrowWidth, v22, gmouse_3d_pick_frame_width);\n\n    unsigned char* v38 = v58;\n    for (int index = 0; index < menuItemsLength; index++) {\n        unsigned char* data = art_frame_data(menuItemFrms[index], 0, 0);\n        buf_to_buf(data, menuItemWidth, menuItemHeight, menuItemWidth, v38, gmouse_3d_pick_frame_width);\n        v38 += gmouse_3d_menu_frame_width * menuItemHeight;\n    }\n\n    art_ptr_unlock(arrowFrmHandle);\n\n    for (int index = 0; index < menuItemsLength; index++) {\n        art_ptr_unlock(menuItemFrmHandles[index]);\n    }\n\n    memcpy(gmouse_3d_menu_frame_actions, menuItems, sizeof(*gmouse_3d_menu_frame_actions) * menuItemsLength);\n    gmouse_3d_menu_available_actions = menuItemsLength;\n    gmouse_3d_menu_actions_start = v58;\n\n    Sound* sound = gsound_load_sound(\"iaccuxx1\", NULL);\n    if (sound != NULL) {\n        gsound_play_sound(sound);\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x44D61C\nint gmouse_3d_menu_frame_hot(int* x, int* y)\n{\n    *x = gmouse_3d_menu_frame_hot_x;\n    *y = gmouse_3d_menu_frame_hot_y;\n    return 0;\n}\n\n// 0x44D630\nint gmouse_3d_highlight_menu_frame(int menuItemIndex)\n{\n    if (menuItemIndex < 0 || menuItemIndex >= gmouse_3d_menu_available_actions) {\n        return -1;\n    }\n\n    CacheEntry* handle;\n    int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_action_nums[gmouse_3d_menu_frame_actions[gmouse_3d_menu_current_action_index]], 0, 0, 0);\n    Art* art = art_ptr_lock(fid, &handle);\n    if (art == NULL) {\n        return -1;\n    }\n\n    int width = art_frame_width(art, 0, 0);\n    int height = art_frame_length(art, 0, 0);\n    unsigned char* data = art_frame_data(art, 0, 0);\n    buf_to_buf(data, width, height, width, gmouse_3d_menu_actions_start + gmouse_3d_menu_frame_width * height * gmouse_3d_menu_current_action_index, gmouse_3d_menu_frame_width);\n    art_ptr_unlock(handle);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_action_nums[gmouse_3d_menu_frame_actions[menuItemIndex]] - 1, 0, 0, 0);\n    art = art_ptr_lock(fid, &handle);\n    if (art == NULL) {\n        return -1;\n    }\n\n    data = art_frame_data(art, 0, 0);\n    buf_to_buf(data, width, height, width, gmouse_3d_menu_actions_start + gmouse_3d_menu_frame_width * height * menuItemIndex, gmouse_3d_menu_frame_width);\n    art_ptr_unlock(handle);\n\n    gmouse_3d_menu_current_action_index = menuItemIndex;\n\n    return 0;\n}\n\n// 0x44D774\nint gmouse_3d_build_to_hit_frame(const char* string, int color)\n{\n    CacheEntry* crosshairFrmHandle;\n    int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[GAME_MOUSE_MODE_CROSSHAIR], 0, 0, 0);\n    Art* crosshairFrm = art_ptr_lock(fid, &crosshairFrmHandle);\n    if (crosshairFrm == NULL) {\n        return -1;\n    }\n\n    memset(gmouse_3d_to_hit_frame_data, 0, gmouse_3d_to_hit_frame_size);\n\n    int crosshairFrmWidth = art_frame_width(crosshairFrm, 0, 0);\n    int crosshairFrmHeight = art_frame_length(crosshairFrm, 0, 0);\n    unsigned char* crosshairFrmData = art_frame_data(crosshairFrm, 0, 0);\n    buf_to_buf(crosshairFrmData,\n        crosshairFrmWidth,\n        crosshairFrmHeight,\n        crosshairFrmWidth,\n        gmouse_3d_to_hit_frame_data,\n        gmouse_3d_to_hit_frame_width);\n\n    int oldFont = text_curr();\n    text_font(101);\n\n    text_to_buf(gmouse_3d_to_hit_frame_data + gmouse_3d_to_hit_frame_width + crosshairFrmWidth + 1,\n        string,\n        gmouse_3d_to_hit_frame_width - crosshairFrmWidth,\n        gmouse_3d_to_hit_frame_width,\n        color);\n\n    buf_outline(gmouse_3d_to_hit_frame_data + crosshairFrmWidth,\n        gmouse_3d_to_hit_frame_width - crosshairFrmWidth,\n        gmouse_3d_to_hit_frame_height,\n        gmouse_3d_to_hit_frame_width,\n        colorTable[0]);\n\n    text_font(oldFont);\n\n    art_ptr_unlock(crosshairFrmHandle);\n\n    return 0;\n}\n\n// 0x44D878\nint gmouse_3d_build_hex_frame(const char* string, int color)\n{\n    memset(gmouse_3d_hex_frame_data, 0, gmouse_3d_hex_frame_width * gmouse_3d_hex_frame_height);\n\n    if (*string == '\\0') {\n        return 0;\n    }\n\n    int oldFont = text_curr();\n    text_font(101);\n\n    int length = text_width(string);\n    text_to_buf(gmouse_3d_hex_frame_data + gmouse_3d_hex_frame_width * (gmouse_3d_hex_frame_height - text_height()) / 2 + (gmouse_3d_hex_frame_width - length) / 2, string, gmouse_3d_hex_frame_width, gmouse_3d_hex_frame_width, color);\n\n    buf_outline(gmouse_3d_hex_frame_data, gmouse_3d_hex_frame_width, gmouse_3d_hex_frame_height, gmouse_3d_hex_frame_width, colorTable[0]);\n\n    text_font(oldFont);\n\n    int fid = art_id(OBJ_TYPE_INTERFACE, 1, 0, 0, 0);\n    gmouse_3d_set_fid(fid);\n\n    return 0;\n}\n\n// 0x44D954\nvoid gmouse_3d_synch_item_highlight()\n{\n    bool itemHighlight;\n    if (configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, &itemHighlight)) {\n        gmouse_3d_item_highlight = itemHighlight;\n    }\n}\n\n// 0x44D984\nstatic int gmouse_3d_init()\n{\n    int fid;\n\n    if (gmouse_3d_initialized) {\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0);\n    if (obj_new(&obj_mouse, fid, -1) != 0) {\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 1, 0, 0, 0);\n    if (obj_new(&obj_mouse_flat, fid, -1) != 0) {\n        return -1;\n    }\n\n    if (obj_outline_object(obj_mouse_flat, OUTLINE_PALETTED | OUTLINE_TYPE_2, NULL) != 0) {\n        return -1;\n    }\n\n    if (gmouse_3d_lock_frames() != 0) {\n        return -1;\n    }\n\n    obj_mouse->flags |= OBJECT_LIGHT_THRU;\n    obj_mouse->flags |= OBJECT_TEMPORARY;\n    obj_mouse->flags |= OBJECT_FLAG_0x400;\n    obj_mouse->flags |= OBJECT_SHOOT_THRU;\n    obj_mouse->flags |= OBJECT_NO_BLOCK;\n\n    obj_mouse_flat->flags |= OBJECT_FLAG_0x400;\n    obj_mouse_flat->flags |= OBJECT_TEMPORARY;\n    obj_mouse_flat->flags |= OBJECT_LIGHT_THRU;\n    obj_mouse_flat->flags |= OBJECT_SHOOT_THRU;\n    obj_mouse_flat->flags |= OBJECT_NO_BLOCK;\n\n    obj_toggle_flat(obj_mouse_flat, NULL);\n\n    int x;\n    int y;\n    mouse_get_position(&x, &y);\n\n    Rect v9;\n    gmouse_3d_move_to(x, y, map_elevation, &v9);\n\n    gmouse_3d_initialized = true;\n\n    gmouse_3d_synch_item_highlight();\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x44DAC0\nstatic int gmouse_3d_reset()\n{\n    if (!gmouse_3d_initialized) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    gmouse_3d_enable_modes();\n\n    // NOTE: Uninline.\n    gmouse_3d_reset_fid();\n\n    gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE);\n    gmouse_3d_on();\n\n    gmouse_3d_last_mouse_x = -1;\n    gmouse_3d_last_mouse_y = -1;\n    gmouse_3d_hover_test = false;\n    gmouse_3d_last_move_time = get_time();\n    gmouse_3d_synch_item_highlight();\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x44DB34\nstatic void gmouse_3d_exit()\n{\n    if (gmouse_3d_initialized) {\n        gmouse_3d_unlock_frames();\n\n        obj_mouse->flags &= ~OBJECT_TEMPORARY;\n        obj_mouse_flat->flags &= ~OBJECT_TEMPORARY;\n\n        obj_erase_object(obj_mouse, NULL);\n        obj_erase_object(obj_mouse_flat, NULL);\n\n        gmouse_3d_initialized = false;\n    }\n}\n\n// 0x44DB78\nstatic int gmouse_3d_lock_frames()\n{\n    int fid;\n\n    // actmenu.frm - action menu\n    fid = art_id(OBJ_TYPE_INTERFACE, 283, 0, 0, 0);\n    gmouse_3d_menu_frame = art_ptr_lock(fid, &gmouse_3d_menu_frame_key);\n    if (gmouse_3d_menu_frame == NULL) {\n        goto err;\n    }\n\n    // actpick.frm - action pick\n    fid = art_id(OBJ_TYPE_INTERFACE, 282, 0, 0, 0);\n    gmouse_3d_pick_frame = art_ptr_lock(fid, &gmouse_3d_pick_frame_key);\n    if (gmouse_3d_pick_frame == NULL) {\n        goto err;\n    }\n\n    // acttohit.frm - action to hit\n    fid = art_id(OBJ_TYPE_INTERFACE, 284, 0, 0, 0);\n    gmouse_3d_to_hit_frame = art_ptr_lock(fid, &gmouse_3d_to_hit_frame_key);\n    if (gmouse_3d_to_hit_frame == NULL) {\n        goto err;\n    }\n\n    // blank.frm - used be mset000.frm for top of bouncing mouse cursor\n    fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0);\n    gmouse_3d_hex_base_frame = art_ptr_lock(fid, &gmouse_3d_hex_base_frame_key);\n    if (gmouse_3d_hex_base_frame == NULL) {\n        goto err;\n    }\n\n    // msef000.frm - hex mouse cursor\n    fid = art_id(OBJ_TYPE_INTERFACE, 1, 0, 0, 0);\n    gmouse_3d_hex_frame = art_ptr_lock(fid, &gmouse_3d_hex_frame_key);\n    if (gmouse_3d_hex_frame == NULL) {\n        goto err;\n    }\n\n    gmouse_3d_menu_frame_width = art_frame_width(gmouse_3d_menu_frame, 0, 0);\n    gmouse_3d_menu_frame_height = art_frame_length(gmouse_3d_menu_frame, 0, 0);\n    gmouse_3d_menu_frame_size = gmouse_3d_menu_frame_width * gmouse_3d_menu_frame_height;\n    gmouse_3d_menu_frame_data = art_frame_data(gmouse_3d_menu_frame, 0, 0);\n\n    gmouse_3d_pick_frame_width = art_frame_width(gmouse_3d_pick_frame, 0, 0);\n    gmouse_3d_pick_frame_height = art_frame_length(gmouse_3d_pick_frame, 0, 0);\n    gmouse_3d_pick_frame_size = gmouse_3d_pick_frame_width * gmouse_3d_pick_frame_height;\n    gmouse_3d_pick_frame_data = art_frame_data(gmouse_3d_pick_frame, 0, 0);\n\n    gmouse_3d_to_hit_frame_width = art_frame_width(gmouse_3d_to_hit_frame, 0, 0);\n    gmouse_3d_to_hit_frame_height = art_frame_length(gmouse_3d_to_hit_frame, 0, 0);\n    gmouse_3d_to_hit_frame_size = gmouse_3d_to_hit_frame_width * gmouse_3d_to_hit_frame_height;\n    gmouse_3d_to_hit_frame_data = art_frame_data(gmouse_3d_to_hit_frame, 0, 0);\n\n    gmouse_3d_hex_base_frame_width = art_frame_width(gmouse_3d_hex_base_frame, 0, 0);\n    gmouse_3d_hex_base_frame_height = art_frame_length(gmouse_3d_hex_base_frame, 0, 0);\n    gmouse_3d_hex_base_frame_size = gmouse_3d_hex_base_frame_width * gmouse_3d_hex_base_frame_height;\n    gmouse_3d_hex_base_frame_data = art_frame_data(gmouse_3d_hex_base_frame, 0, 0);\n\n    gmouse_3d_hex_frame_width = art_frame_width(gmouse_3d_hex_frame, 0, 0);\n    gmouse_3d_hex_frame_height = art_frame_length(gmouse_3d_hex_frame, 0, 0);\n    gmouse_3d_hex_frame_size = gmouse_3d_hex_frame_width * gmouse_3d_hex_frame_height;\n    gmouse_3d_hex_frame_data = art_frame_data(gmouse_3d_hex_frame, 0, 0);\n\n    return 0;\n\nerr:\n\n    // NOTE: Original code is different. There is no call to this function.\n    // Instead it either use deep nesting or bunch of goto's to unwind\n    // locked frms from the point of failure.\n    gmouse_3d_unlock_frames();\n\n    return -1;\n}\n\n// 0x44DE44\nstatic void gmouse_3d_unlock_frames()\n{\n    if (gmouse_3d_hex_base_frame_key != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(gmouse_3d_hex_base_frame_key);\n    }\n    gmouse_3d_hex_base_frame = NULL;\n    gmouse_3d_hex_base_frame_key = INVALID_CACHE_ENTRY;\n\n    if (gmouse_3d_hex_frame_key != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(gmouse_3d_hex_frame_key);\n    }\n    gmouse_3d_hex_frame = NULL;\n    gmouse_3d_hex_frame_key = INVALID_CACHE_ENTRY;\n\n    if (gmouse_3d_to_hit_frame_key != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(gmouse_3d_to_hit_frame_key);\n    }\n    gmouse_3d_to_hit_frame = NULL;\n    gmouse_3d_to_hit_frame_key = INVALID_CACHE_ENTRY;\n\n    if (gmouse_3d_menu_frame_key != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(gmouse_3d_menu_frame_key);\n    }\n    gmouse_3d_menu_frame = NULL;\n    gmouse_3d_menu_frame_key = INVALID_CACHE_ENTRY;\n\n    if (gmouse_3d_pick_frame_key != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(gmouse_3d_pick_frame_key);\n    }\n\n    gmouse_3d_pick_frame = NULL;\n    gmouse_3d_pick_frame_key = INVALID_CACHE_ENTRY;\n\n    gmouse_3d_pick_frame_data = NULL;\n    gmouse_3d_pick_frame_width = 0;\n    gmouse_3d_pick_frame_height = 0;\n    gmouse_3d_pick_frame_size = 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x44DF1C\nstatic int gmouse_3d_set_flat_fid(int fid, Rect* rect)\n{\n    if (obj_change_fid(obj_mouse_flat, fid, rect) == 0) {\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x44DF40\nstatic int gmouse_3d_reset_flat_fid(Rect* rect)\n{\n    int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[gmouse_3d_current_mode], 0, 0, 0);\n    if (obj_mouse_flat->fid == fid) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    return gmouse_3d_set_flat_fid(fid, rect);\n}\n\n// 0x44DF94\nstatic int gmouse_3d_move_to(int x, int y, int elevation, Rect* a4)\n{\n    if (gmouse_mapper_mode == 0) {\n        if (gmouse_3d_current_mode != GAME_MOUSE_MODE_MOVE) {\n            int offsetX = 0;\n            int offsetY = 0;\n            CacheEntry* hexCursorFrmHandle;\n            Art* hexCursorFrm = art_ptr_lock(obj_mouse_flat->fid, &hexCursorFrmHandle);\n            if (hexCursorFrm != NULL) {\n                art_frame_offset(hexCursorFrm, 0, &offsetX, &offsetY);\n\n                int frameOffsetX;\n                int frameOffsetY;\n                art_frame_hot(hexCursorFrm, 0, 0, &frameOffsetX, &frameOffsetY);\n\n                offsetX += frameOffsetX;\n                offsetY += frameOffsetY;\n\n                art_ptr_unlock(hexCursorFrmHandle);\n            }\n\n            obj_move(obj_mouse_flat, x + offsetX, y + offsetY, elevation, a4);\n        } else {\n            int tile = tile_num(x, y, 0);\n            if (tile != -1) {\n                int screenX;\n                int screenY;\n\n                bool v1 = false;\n                Rect rect1;\n                if (tile_coord(tile, &screenX, &screenY, 0) == 0) {\n                    if (obj_move(obj_mouse, screenX + 16, screenY + 15, 0, &rect1) == 0) {\n                        v1 = true;\n                    }\n                }\n\n                Rect rect2;\n                if (obj_move_to_tile(obj_mouse_flat, tile, elevation, &rect2) == 0) {\n                    if (v1) {\n                        rect_min_bound(&rect1, &rect2, &rect1);\n                    } else {\n                        rectCopy(&rect1, &rect2);\n                    }\n\n                    rectCopy(a4, &rect1);\n                }\n            }\n        }\n        return 0;\n    }\n\n    int tile;\n    int x1 = 0;\n    int y1 = 0;\n\n    int fid = obj_mouse->fid;\n    if (FID_TYPE(fid) == OBJ_TYPE_TILE) {\n        int squareTile = square_num(x, y, elevation);\n        if (squareTile == -1) {\n            tile = HEX_GRID_WIDTH * (2 * (squareTile / SQUARE_GRID_WIDTH) + 1) + 2 * (squareTile % SQUARE_GRID_WIDTH) + 1;\n            x1 = -8;\n            y1 = 13;\n\n            char* executable;\n            config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, &executable);\n            if (stricmp(executable, \"mapper\") == 0) {\n                if (tile_roof_visible()) {\n                    if ((obj_dude->flags & OBJECT_HIDDEN) == 0) {\n                        y1 = -83;\n                    }\n                }\n            }\n        } else {\n            tile = -1;\n        }\n    } else {\n        tile = tile_num(x, y, elevation);\n    }\n\n    if (tile != -1) {\n        bool v1 = false;\n\n        Rect rect1;\n        Rect rect2;\n\n        if (obj_move_to_tile(obj_mouse, tile, elevation, &rect1) == 0) {\n            if (x1 != 0 || y1 != 0) {\n                if (obj_offset(obj_mouse, x1, y1, &rect2) == 0) {\n                    rect_min_bound(&rect1, &rect2, &rect1);\n                }\n            }\n            v1 = true;\n        }\n\n        if (gmouse_3d_current_mode != GAME_MOUSE_MODE_MOVE) {\n            int offsetX = 0;\n            int offsetY = 0;\n            CacheEntry* hexCursorFrmHandle;\n            Art* hexCursorFrm = art_ptr_lock(obj_mouse_flat->fid, &hexCursorFrmHandle);\n            if (hexCursorFrm != NULL) {\n                art_frame_offset(hexCursorFrm, 0, &offsetX, &offsetY);\n\n                int frameOffsetX;\n                int frameOffsetY;\n                art_frame_hot(hexCursorFrm, 0, 0, &frameOffsetX, &frameOffsetY);\n\n                offsetX += frameOffsetX;\n                offsetY += frameOffsetY;\n\n                art_ptr_unlock(hexCursorFrmHandle);\n            }\n\n            if (obj_move(obj_mouse_flat, x + offsetX, y + offsetY, elevation, &rect2) == 0) {\n                if (v1) {\n                    rect_min_bound(&rect1, &rect2, &rect1);\n                } else {\n                    rectCopy(&rect1, &rect2);\n                    v1 = true;\n                }\n            }\n        } else {\n            if (obj_move_to_tile(obj_mouse_flat, tile, elevation, &rect2) == 0) {\n                if (v1) {\n                    rect_min_bound(&rect1, &rect2, &rect1);\n                } else {\n                    rectCopy(&rect1, &rect2);\n                    v1 = true;\n                }\n            }\n        }\n\n        if (v1) {\n            rectCopy(a4, &rect1);\n        }\n    }\n\n    return 0;\n}\n\n// 0x44E42C\nstatic int gmouse_check_scrolling(int x, int y, int cursor)\n{\n    if (!gmouse_scrolling_enabled) {\n        return -1;\n    }\n\n    int flags = 0;\n\n    if (x <= scr_size.ulx) {\n        flags |= SCROLLABLE_W;\n    }\n\n    if (x >= scr_size.lrx) {\n        flags |= SCROLLABLE_E;\n    }\n\n    if (y <= scr_size.uly) {\n        flags |= SCROLLABLE_N;\n    }\n\n    if (y >= scr_size.lry) {\n        flags |= SCROLLABLE_S;\n    }\n\n    int dx = 0;\n    int dy = 0;\n\n    switch (flags) {\n    case SCROLLABLE_W:\n        dx = -1;\n        cursor = MOUSE_CURSOR_SCROLL_W;\n        break;\n    case SCROLLABLE_E:\n        dx = 1;\n        cursor = MOUSE_CURSOR_SCROLL_E;\n        break;\n    case SCROLLABLE_N:\n        dy = -1;\n        cursor = MOUSE_CURSOR_SCROLL_N;\n        break;\n    case SCROLLABLE_N | SCROLLABLE_W:\n        dx = -1;\n        dy = -1;\n        cursor = MOUSE_CURSOR_SCROLL_NW;\n        break;\n    case SCROLLABLE_N | SCROLLABLE_E:\n        dx = 1;\n        dy = -1;\n        cursor = MOUSE_CURSOR_SCROLL_NE;\n        break;\n    case SCROLLABLE_S:\n        dy = 1;\n        cursor = MOUSE_CURSOR_SCROLL_S;\n        break;\n    case SCROLLABLE_S | SCROLLABLE_W:\n        dx = -1;\n        dy = 1;\n        cursor = MOUSE_CURSOR_SCROLL_SW;\n        break;\n    case SCROLLABLE_S | SCROLLABLE_E:\n        dx = 1;\n        dy = 1;\n        cursor = MOUSE_CURSOR_SCROLL_SE;\n        break;\n    }\n\n    if (dx == 0 && dy == 0) {\n        return -1;\n    }\n\n    int rc = map_scroll(dx, dy);\n    switch (rc) {\n    case -1:\n        // Scrolling is blocked for whatever reason, upgrade cursor to\n        // appropriate blocked version.\n        cursor += 8;\n        // FALLTHROUGH\n    case 0:\n        gmouse_set_cursor(cursor);\n        break;\n    }\n\n    return 0;\n}\n\n// 0x44E544\nvoid gmouse_remove_item_outline(Object* object)\n{\n    if (outlined_object != NULL && outlined_object == object) {\n        Rect rect;\n        if (obj_remove_outline(object, &rect) == 0) {\n            tile_refresh_rect(&rect, map_elevation);\n        }\n        outlined_object = NULL;\n    }\n}\n\n// 0x44E580\nstatic int gmObjIsValidTarget(Object* object)\n{\n    if (object == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_SCENERY) {\n        return false;\n    }\n\n    Proto* proto;\n    if (proto_ptr(object->pid, &proto) == -1) {\n        return false;\n    }\n\n    return proto->scenery.type == SCENERY_TYPE_DOOR;\n}\n"
  },
  {
    "path": "src/game/gmouse.h",
    "content": "#ifndef FALLOUT_GAME_GMOUSE_H_\n#define FALLOUT_GAME_GMOUSE_H_\n\n#include <stdbool.h>\n\n#include \"game/object_types.h\"\n\ntypedef enum GameMouseMode {\n    GAME_MOUSE_MODE_MOVE,\n    GAME_MOUSE_MODE_ARROW,\n    GAME_MOUSE_MODE_CROSSHAIR,\n    GAME_MOUSE_MODE_USE_CROSSHAIR,\n    GAME_MOUSE_MODE_USE_FIRST_AID,\n    GAME_MOUSE_MODE_USE_DOCTOR,\n    GAME_MOUSE_MODE_USE_LOCKPICK,\n    GAME_MOUSE_MODE_USE_STEAL,\n    GAME_MOUSE_MODE_USE_TRAPS,\n    GAME_MOUSE_MODE_USE_SCIENCE,\n    GAME_MOUSE_MODE_USE_REPAIR,\n    GAME_MOUSE_MODE_COUNT,\n    FIRST_GAME_MOUSE_MODE_SKILL = GAME_MOUSE_MODE_USE_FIRST_AID,\n    GAME_MOUSE_MODE_SKILL_COUNT = GAME_MOUSE_MODE_COUNT - FIRST_GAME_MOUSE_MODE_SKILL,\n} GameMouseMode;\n\ntypedef enum GameMouseActionMenuItem {\n    GAME_MOUSE_ACTION_MENU_ITEM_CANCEL = 0,\n    GAME_MOUSE_ACTION_MENU_ITEM_DROP = 1,\n    GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY = 2,\n    GAME_MOUSE_ACTION_MENU_ITEM_LOOK = 3,\n    GAME_MOUSE_ACTION_MENU_ITEM_ROTATE = 4,\n    GAME_MOUSE_ACTION_MENU_ITEM_TALK = 5,\n    GAME_MOUSE_ACTION_MENU_ITEM_USE = 6,\n    GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD = 7,\n    GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL = 8,\n    GAME_MOUSE_ACTION_MENU_ITEM_PUSH = 9,\n    GAME_MOUSE_ACTION_MENU_ITEM_COUNT,\n} GameMouseActionMenuItem;\n\ntypedef enum MouseCursorType {\n    MOUSE_CURSOR_NONE,\n    MOUSE_CURSOR_ARROW,\n    MOUSE_CURSOR_SMALL_ARROW_UP,\n    MOUSE_CURSOR_SMALL_ARROW_DOWN,\n    MOUSE_CURSOR_SCROLL_NW,\n    MOUSE_CURSOR_SCROLL_N,\n    MOUSE_CURSOR_SCROLL_NE,\n    MOUSE_CURSOR_SCROLL_E,\n    MOUSE_CURSOR_SCROLL_SE,\n    MOUSE_CURSOR_SCROLL_S,\n    MOUSE_CURSOR_SCROLL_SW,\n    MOUSE_CURSOR_SCROLL_W,\n    MOUSE_CURSOR_SCROLL_NW_INVALID,\n    MOUSE_CURSOR_SCROLL_N_INVALID,\n    MOUSE_CURSOR_SCROLL_NE_INVALID,\n    MOUSE_CURSOR_SCROLL_E_INVALID,\n    MOUSE_CURSOR_SCROLL_SE_INVALID,\n    MOUSE_CURSOR_SCROLL_S_INVALID,\n    MOUSE_CURSOR_SCROLL_SW_INVALID,\n    MOUSE_CURSOR_SCROLL_W_INVALID,\n    MOUSE_CURSOR_CROSSHAIR,\n    MOUSE_CURSOR_PLUS,\n    MOUSE_CURSOR_DESTROY,\n    MOUSE_CURSOR_USE_CROSSHAIR,\n    MOUSE_CURSOR_WATCH,\n    MOUSE_CURSOR_WAIT_PLANET,\n    MOUSE_CURSOR_WAIT_WATCH,\n    MOUSE_CURSOR_TYPE_COUNT,\n    FIRST_GAME_MOUSE_ANIMATED_CURSOR = MOUSE_CURSOR_WAIT_PLANET,\n} MouseCursorType;\n\nextern bool gmouse_clicked_on_edge;\n\nextern Object* obj_mouse;\nextern Object* obj_mouse_flat;\n\nint gmouse_init();\nint gmouse_reset();\nvoid gmouse_exit();\nvoid gmouse_enable();\nvoid gmouse_disable(int a1);\nint gmouse_is_enabled();\nvoid gmouse_enable_scrolling();\nvoid gmouse_disable_scrolling();\nint gmouse_scrolling_is_enabled();\nvoid gmouse_set_click_to_scroll(int a1);\nint gmouse_get_click_to_scroll();\nint gmouse_is_scrolling();\nvoid gmouse_bk_process();\nvoid gmouse_handle_event(int mouseX, int mouseY, int mouseState);\nint gmouse_set_cursor(int cursor);\nint gmouse_get_cursor();\nvoid gmouse_set_mapper_mode(int mode);\nvoid gmouse_3d_enable_modes();\nvoid gmouse_3d_disable_modes();\nint gmouse_3d_modes_are_enabled();\nvoid gmouse_3d_set_mode(int a1);\nint gmouse_3d_get_mode();\nvoid gmouse_3d_toggle_mode();\nvoid gmouse_3d_refresh();\nint gmouse_3d_set_fid(int fid);\nint gmouse_3d_get_fid();\nvoid gmouse_3d_reset_fid();\nvoid gmouse_3d_on();\nvoid gmouse_3d_off();\nbool gmouse_3d_is_on();\nObject* object_under_mouse(int objectType, bool a2, int elevation);\nint gmouse_3d_build_pick_frame(int x, int y, int menuItem, int width, int height);\nint gmouse_3d_pick_frame_hot(int* a1, int* a2);\nint gmouse_3d_build_menu_frame(int x, int y, const int* menuItems, int menuItemsCount, int width, int height);\nint gmouse_3d_menu_frame_hot(int* x, int* y);\nint gmouse_3d_highlight_menu_frame(int menuItemIndex);\nint gmouse_3d_build_to_hit_frame(const char* string, int color);\nint gmouse_3d_build_hex_frame(const char* string, int color);\nvoid gmouse_3d_synch_item_highlight();\nvoid gmouse_remove_item_outline(Object* object);\n\n#endif /* FALLOUT_GAME_GMOUSE_H_ */\n"
  },
  {
    "path": "src/game/gmovie.c",
    "content": "#include \"game/gmovie.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"int/window.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"int/movie.h\"\n#include \"game/moviefx.h\"\n#include \"game/palette.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/svga.h\"\n\n#define GAME_MOVIE_WINDOW_WIDTH 640\n#define GAME_MOVIE_WINDOW_HEIGHT 480\n\nstatic char* gmovie_subtitle_func(char* movieFilePath);\n\n// 0x518DA0\nstatic const char* movie_list[MOVIE_COUNT] = {\n    \"iplogo.mve\",\n    \"intro.mve\",\n    \"elder.mve\",\n    \"vsuit.mve\",\n    \"afailed.mve\",\n    \"adestroy.mve\",\n    \"car.mve\",\n    \"cartucci.mve\",\n    \"timeout.mve\",\n    \"tanker.mve\",\n    \"enclave.mve\",\n    \"derrick.mve\",\n    \"artimer1.mve\",\n    \"artimer2.mve\",\n    \"artimer3.mve\",\n    \"artimer4.mve\",\n    \"credits.mve\",\n};\n\n// 0x518DE4\nstatic const char* subtitlePalList[MOVIE_COUNT] = {\n    NULL,\n    \"art\\\\cuts\\\\introsub.pal\",\n    \"art\\\\cuts\\\\eldersub.pal\",\n    NULL,\n    \"art\\\\cuts\\\\artmrsub.pal\",\n    NULL,\n    NULL,\n    NULL,\n    \"art\\\\cuts\\\\artmrsub.pal\",\n    NULL,\n    NULL,\n    NULL,\n    \"art\\\\cuts\\\\artmrsub.pal\",\n    \"art\\\\cuts\\\\artmrsub.pal\",\n    \"art\\\\cuts\\\\artmrsub.pal\",\n    \"art\\\\cuts\\\\artmrsub.pal\",\n    \"art\\\\cuts\\\\crdtssub.pal\",\n};\n\n// 0x518E28\nstatic bool gmMovieIsPlaying = false;\n\n// 0x518E2C\nstatic bool gmPaletteWasFaded = false;\n\n// 0x596C78\nstatic unsigned char gmovie_played_list[MOVIE_COUNT];\n\n// gmovie_init\n// 0x44E5C0\nint gmovie_init()\n{\n    int v1 = 0;\n    if (gsound_background_is_enabled()) {\n        v1 = gsound_background_volume_get();\n    }\n\n    movieSetVolume(v1);\n\n    movieSetSubtitleFunc(gmovie_subtitle_func);\n\n    memset(gmovie_played_list, 0, sizeof(gmovie_played_list));\n\n    gmMovieIsPlaying = false;\n    gmPaletteWasFaded = false;\n\n    return 0;\n}\n\n// 0x44E60C\nvoid gmovie_reset()\n{\n    memset(gmovie_played_list, 0, sizeof(gmovie_played_list));\n\n    gmMovieIsPlaying = false;\n    gmPaletteWasFaded = false;\n}\n\n// 0x44E638\nint gmovie_load(File* stream)\n{\n    if (db_fread(gmovie_played_list, sizeof(*gmovie_played_list), MOVIE_COUNT, stream) != MOVIE_COUNT) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x44E664\nint gmovie_save(File* stream)\n{\n    if (db_fwrite(gmovie_played_list, sizeof(*gmovie_played_list), MOVIE_COUNT, stream) != MOVIE_COUNT) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// gmovie_play\n// 0x44E690\nint gmovie_play(int movie, int flags)\n{\n    gmMovieIsPlaying = true;\n\n    const char* movieFileName = movie_list[movie];\n    debug_printf(\"\\nPlaying movie: %s\\n\", movieFileName);\n\n    char* language;\n    if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) {\n        debug_printf(\"\\ngmovie_play() - Error: Unable to determine language!\\n\");\n        gmMovieIsPlaying = false;\n        return -1;\n    }\n\n    char movieFilePath[MAX_PATH];\n    int movieFileSize;\n    bool movieFound = false;\n\n    if (stricmp(language, ENGLISH) != 0) {\n        sprintf(movieFilePath, \"art\\\\%s\\\\cuts\\\\%s\", language, movie_list[movie]);\n        movieFound = db_dir_entry(movieFilePath, &movieFileSize) == 0;\n    }\n\n    if (!movieFound) {\n        sprintf(movieFilePath, \"art\\\\cuts\\\\%s\", movie_list[movie]);\n        movieFound = db_dir_entry(movieFilePath, &movieFileSize) == 0;\n    }\n\n    if (!movieFound) {\n        debug_printf(\"\\ngmovie_play() - Error: Unable to open %s\\n\", movie_list[movie]);\n        gmMovieIsPlaying = false;\n        return -1;\n    }\n\n    if ((flags & GAME_MOVIE_FADE_IN) != 0) {\n        palette_fade_to(black_palette);\n        gmPaletteWasFaded = true;\n    }\n\n    int gameMovieWindowX = 0;\n    int gameMovieWindowY = 0;\n    int win = win_add(gameMovieWindowX,\n        gameMovieWindowY,\n        GAME_MOVIE_WINDOW_WIDTH,\n        GAME_MOVIE_WINDOW_HEIGHT,\n        0,\n        WINDOW_FLAG_0x10);\n    if (win == -1) {\n        gmMovieIsPlaying = false;\n        return -1;\n    }\n\n    if ((flags & GAME_MOVIE_STOP_MUSIC) != 0) {\n        gsound_background_stop();\n    } else if ((flags & GAME_MOVIE_PAUSE_MUSIC) != 0) {\n        gsound_background_pause();\n    }\n\n    win_draw(win);\n\n    bool subtitlesEnabled = false;\n    int v1 = 4;\n    configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &subtitlesEnabled);\n    if (subtitlesEnabled) {\n        char* subtitlesFilePath = gmovie_subtitle_func(movieFilePath);\n\n        int subtitlesFileSize;\n        if (db_dir_entry(subtitlesFilePath, &subtitlesFileSize) == 0) {\n            v1 = 12;\n        } else {\n            subtitlesEnabled = false;\n        }\n    }\n\n    movieSetFlags(v1);\n\n    int oldTextColor;\n    int oldFont;\n    if (subtitlesEnabled) {\n        const char* subtitlesPaletteFilePath;\n        if (subtitlePalList[movie] != NULL) {\n            subtitlesPaletteFilePath = subtitlePalList[movie];\n        } else {\n            subtitlesPaletteFilePath = \"art\\\\cuts\\\\subtitle.pal\";\n        }\n\n        loadColorTable(subtitlesPaletteFilePath);\n\n        oldTextColor = windowGetTextColor();\n        windowSetTextColor(1.0, 1.0, 1.0);\n\n        oldFont = text_curr();\n        windowSetFont(101);\n    }\n\n    bool cursorWasHidden = mouse_hidden();\n    if (cursorWasHidden) {\n        gmouse_set_cursor(MOUSE_CURSOR_NONE);\n        mouse_show();\n    }\n\n    while (mouse_get_buttons() != 0) {\n        mouse_info();\n    }\n\n    mouse_hide();\n    cycle_disable();\n\n    moviefx_start(movieFilePath);\n\n    zero_vid_mem();\n    movieRun(win, movieFilePath);\n\n    int v11 = 0;\n    int buttons;\n    do {\n        if (!moviePlaying() || game_user_wants_to_quit || get_input() != -1) {\n            break;\n        }\n\n        int x;\n        int y;\n        mouse_get_raw_state(&x, &y, &buttons);\n\n        v11 |= buttons;\n    } while (((v11 & 1) == 0 && (v11 & 2) == 0) || (buttons & 1) != 0 || (buttons & 2) != 0);\n\n    movieStop();\n    moviefx_stop();\n    movieUpdate();\n    palette_set_to(black_palette);\n\n    gmovie_played_list[movie] = 1;\n\n    cycle_enable();\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    if (!cursorWasHidden) {\n        mouse_show();\n    }\n\n    if (subtitlesEnabled) {\n        loadColorTable(\"color.pal\");\n\n        windowSetFont(oldFont);\n\n        float r = (float)((Color2RGB(oldTextColor) & 0x7C00) >> 10) / 31.0f;\n        float g = (float)((Color2RGB(oldTextColor) & 0x3E0) >> 5) / 31.0f;\n        float b = (float)(Color2RGB(oldTextColor) & 0x1F) / 31.0f;\n        windowSetTextColor(r, g, b);\n    }\n\n    win_delete(win);\n\n    if ((flags & GAME_MOVIE_PAUSE_MUSIC) != 0) {\n        gsound_background_unpause();\n    }\n\n    if ((flags & GAME_MOVIE_FADE_OUT) != 0) {\n        if (!subtitlesEnabled) {\n            loadColorTable(\"color.pal\");\n        }\n\n        palette_fade_to(cmap);\n        gmPaletteWasFaded = false;\n    }\n\n    gmMovieIsPlaying = false;\n    return 0;\n}\n\n// 0x44EAE4\nvoid gmPaletteFinish()\n{\n    if (gmPaletteWasFaded) {\n        palette_fade_to(cmap);\n        gmPaletteWasFaded = false;\n    }\n}\n\n// 0x44EB04\nbool gmovie_has_been_played(int movie)\n{\n    return gmovie_played_list[movie] == 1;\n}\n\n// 0x44EB14\nbool gmovieIsPlaying()\n{\n    return gmMovieIsPlaying;\n}\n\n// 0x44EB1C\nstatic char* gmovie_subtitle_func(char* movieFilePath)\n{\n    // 0x596C89\n    static char full_path[MAX_PATH];\n\n    char* language;\n    config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language);\n\n    char* path = movieFilePath;\n\n    char* separator = strrchr(path, '\\\\');\n    if (separator != NULL) {\n        path = separator + 1;\n    }\n\n    sprintf(full_path, \"text\\\\%s\\\\cuts\\\\%s\", language, path);\n\n    char* pch = strrchr(full_path, '.');\n    if (*pch != '\\0') {\n        *pch = '\\0';\n    }\n\n    strcpy(full_path + strlen(full_path), \".SVE\");\n\n    return full_path;\n}\n"
  },
  {
    "path": "src/game/gmovie.h",
    "content": "#ifndef FALLOUT_GAME_GMOVIE_H_\n#define FALLOUT_GAME_GMOVIE_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n\ntypedef enum GameMovieFlags {\n    GAME_MOVIE_FADE_IN = 0x01,\n    GAME_MOVIE_FADE_OUT = 0x02,\n    GAME_MOVIE_STOP_MUSIC = 0x04,\n    GAME_MOVIE_PAUSE_MUSIC = 0x08,\n} GameMovieFlags;\n\ntypedef enum GameMovie {\n    MOVIE_IPLOGO,\n    MOVIE_INTRO,\n    MOVIE_ELDER,\n    MOVIE_VSUIT,\n    MOVIE_AFAILED,\n    MOVIE_ADESTROY,\n    MOVIE_CAR,\n    MOVIE_CARTUCCI,\n    MOVIE_TIMEOUT,\n    MOVIE_TANKER,\n    MOVIE_ENCLAVE,\n    MOVIE_DERRICK,\n    MOVIE_ARTIMER1,\n    MOVIE_ARTIMER2,\n    MOVIE_ARTIMER3,\n    MOVIE_ARTIMER4,\n    MOVIE_CREDITS,\n    MOVIE_COUNT,\n} GameMovie;\n\nint gmovie_init();\nvoid gmovie_reset();\nint gmovie_load(File* stream);\nint gmovie_save(File* stream);\nint gmovie_play(int movie, int flags);\nvoid gmPaletteFinish();\nbool gmovie_has_been_played(int movie);\nbool gmovieIsPlaying();\n\n#endif /* FALLOUT_GAME_GMOVIE_H_ */\n"
  },
  {
    "path": "src/game/graphlib.c",
    "content": "#include \"game/graphlib.h\"\n\n#include <string.h>\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/memory.h\"\n\nstatic void InitTree();\nstatic void InsertNode(int a1);\nstatic void DeleteNode(int a1);\n\n// 0x596D90\nstatic unsigned char GreyTable[256];\n\n// 0x596E90\nint* dad;\n\n// 0x596E94\nint match_length;\n\n// 0x596E98\nint textsize;\n\n// 0x596E9C\nint* rson;\n\n// 0x596EA0\nint* lson;\n\n// 0x596EA4\nunsigned char* text_buf;\n\n// 0x596EA8\nint codesize;\n\n// 0x596EAC\nint match_position;\n\n// 0x44EBC0\nint HighRGB(int a1)\n{\n    // TODO: Some strange bit arithmetic.\n    int v1 = Color2RGB(a1);\n    int r = (v1 & 0x7C00) >> 10;\n    int g = (v1 & 0x3E0) >> 5;\n    int b = (v1 & 0x1F);\n\n    int result = g;\n    if (r > result) {\n        result = r;\n    }\n\n    result = result & 0xFF;\n    if (result <= b) {\n        result = b;\n    }\n\n    return result;\n}\n\n// 0x44F250\nint CompLZS(unsigned char* a1, unsigned char* a2, int a3)\n{\n    dad = NULL;\n    rson = NULL;\n    lson = NULL;\n    text_buf = NULL;\n\n    // NOTE: Original code is slightly different, it uses deep nesting or a\n    // bunch of gotos.\n    lson = (int*)mem_malloc(sizeof(*lson) * 4104);\n    rson = (int*)mem_malloc(sizeof(*rson) * 4376);\n    dad = (int*)mem_malloc(sizeof(*dad) * 4104);\n    text_buf = (unsigned char*)mem_malloc(sizeof(*text_buf) * 4122);\n\n    if (lson == NULL || rson == NULL || dad == NULL || text_buf == NULL) {\n        debug_printf(\"\\nGRAPHLIB: Error allocating compression buffers!\\n\");\n\n        if (dad != NULL) {\n            mem_free(dad);\n        }\n\n        if (rson != NULL) {\n            mem_free(rson);\n        }\n        if (lson != NULL) {\n            mem_free(lson);\n        }\n        if (text_buf != NULL) {\n            mem_free(text_buf);\n        }\n\n        return -1;\n    }\n\n    InitTree();\n\n    memset(text_buf, ' ', 4078);\n\n    int count = 0;\n    int v30 = 0;\n    for (int index = 4078; index < 4096; index++) {\n        text_buf[index] = *a1++;\n        int v8 = v30++;\n        if (v8 > a3) {\n            break;\n        }\n        count++;\n    }\n\n    textsize = count;\n\n    for (int index = 4077; index > 4059; index--) {\n        InsertNode(index);\n    }\n\n    InsertNode(4078);\n\n    unsigned char v29[32];\n    v29[1] = 0;\n\n    int v3 = 4078;\n    int v4 = 0;\n    int v10 = 0;\n    int v36 = 1;\n    unsigned char v41 = 1;\n    int rc = 0;\n    while (count != 0) {\n        if (count < match_length) {\n            match_length = count;\n        }\n\n        int v11 = v36 + 1;\n        if (match_length > 2) {\n            v29[v36 + 1] = match_position;\n            v29[v36 + 2] = ((match_length - 3) | ((match_position >> 4) & 0xF0));\n            v36 = v11 + 1;\n        } else {\n            match_length = 1;\n            v29[1] |= v41;\n            int v13 = v36++;\n            v29[v13 + 1] = text_buf[v3];\n        }\n\n        v41 *= 2;\n\n        if (v41 == 0) {\n            v11 = 0;\n            if (v36 != 0) {\n                for (;;) {\n                    v4++;\n                    *a2++ = v29[v11 + 1];\n                    if (v4 > a3) {\n                        rc = -1;\n                        break;\n                    }\n\n                    v11++;\n                    if (v11 >= v36) {\n                        break;\n                    }\n                }\n\n                if (rc == -1) {\n                    break;\n                }\n            }\n\n            codesize += v36;\n            v29[1] = 0;\n            v36 = 1;\n            v41 = 1;\n        }\n\n        int v16;\n        int v38 = match_length;\n        for (v16 = 0; v16 < v38; v16++) {\n            unsigned char v34 = *a1++;\n            int v17 = v30++;\n            if (v17 >= a3) {\n                break;\n            }\n\n            DeleteNode(v10);\n\n            unsigned char* v19 = text_buf + v10;\n            text_buf[v10] = v34;\n\n            if (v10 < 17) {\n                v19[4096] = v34;\n            }\n\n            v3 = (v3 + 1) & 0xFFF;\n            v10 = (v10 + 1) & 0xFFF;\n            InsertNode(v3);\n        }\n\n        for (; v16 < v38; v16++) {\n            DeleteNode(v10);\n            v3 = (v3 + 1) & 0xFFF;\n            v10 = (v10 + 1) & 0xFFF;\n            if (--count != 0) {\n                InsertNode(v3);\n            }\n        }\n    }\n\n    if (rc != -1) {\n        for (int v23 = 0; v23 < v36; v23++) {\n            v4++;\n            v10++;\n            *a2++ = v29[v23 + 1];\n            if (v10 > a3) {\n                rc = -1;\n                break;\n            }\n        }\n\n        codesize += v36;\n    }\n\n    mem_free(lson);\n    mem_free(rson);\n    mem_free(dad);\n    mem_free(text_buf);\n\n    if (rc == -1) {\n        v4 = -1;\n    }\n\n    return v4;\n}\n\n// 0x44F5F0\nstatic void InitTree()\n{\n    for (int index = 4097; index < 4353; index++) {\n        rson[index] = 4096;\n    }\n\n    for (int index = 0; index < 4096; index++) {\n        dad[index] = 4096;\n    }\n}\n\n// 0x44F63C\nstatic void InsertNode(int a1)\n{\n    lson[a1] = 4096;\n    rson[a1] = 4096;\n    match_length = 0;\n\n    unsigned char* v2 = text_buf + a1;\n\n    int v21 = 4097 + text_buf[a1];\n    int v5 = 1;\n    for (;;) {\n        int v6 = v21;\n        if (v5 < 0) {\n            if (lson[v6] == 4096) {\n                lson[v6] = a1;\n                dad[a1] = v21;\n                return;\n            }\n            v21 = lson[v6];\n        } else {\n            if (rson[v6] == 4096) {\n                rson[v6] = a1;\n                dad[a1] = v21;\n                return;\n            }\n            v21 = rson[v6];\n        }\n\n        int v9;\n        unsigned char* v10 = v2 + 1;\n        int v11 = v21 + 1;\n        for (v9 = 1; v9 < 18; v9++) {\n            v5 = *v10 - text_buf[v11];\n            if (v5 != 0) {\n                break;\n            }\n            v10++;\n            v11++;\n        }\n\n        if (v9 > match_length) {\n            match_length = v9;\n            match_position = v21;\n            if (v9 >= 18) {\n                break;\n            }\n        }\n    }\n\n    dad[a1] = dad[v21];\n    lson[a1] = lson[v21];\n    rson[a1] = rson[v21];\n\n    dad[lson[v21]] = a1;\n    dad[rson[v21]] = a1;\n\n    if (rson[dad[v21]] == v21) {\n        rson[dad[v21]] = a1;\n    } else {\n        lson[dad[v21]] = a1;\n    }\n\n    dad[v21] = 4096;\n}\n\n// 0x44F7EC\nstatic void DeleteNode(int a1)\n{\n    if (dad[a1] != 4096) {\n        int v5;\n        if (rson[a1] == 4096) {\n            v5 = lson[a1];\n        } else {\n            if (lson[a1] == 4096) {\n                v5 = rson[a1];\n            } else {\n                v5 = lson[a1];\n\n                if (rson[v5] != 4096) {\n                    do {\n                        v5 = rson[v5];\n                    } while (rson[v5] != 4096);\n\n                    rson[dad[v5]] = lson[v5];\n                    dad[lson[v5]] = dad[v5];\n                    lson[v5] = lson[a1];\n                    dad[lson[a1]] = v5;\n                }\n\n                rson[v5] = rson[a1];\n                dad[rson[a1]] = v5;\n            }\n        }\n\n        dad[v5] = dad[a1];\n\n        if (rson[dad[a1]] == a1) {\n            rson[dad[a1]] = v5;\n        } else {\n            lson[dad[a1]] = v5;\n        }\n        dad[a1] = 4096;\n    }\n}\n\n// 0x44F92C\nint DecodeLZS(unsigned char* src, unsigned char* dest, int length)\n{\n    text_buf = (unsigned char*)mem_malloc(sizeof(*text_buf) * 4122);\n    if (text_buf == NULL) {\n        debug_printf(\"\\nGRAPHLIB: Error allocating decompression buffer!\\n\");\n        return -1;\n    }\n\n    int v8 = 4078;\n    memset(text_buf, ' ', v8);\n\n    int v21 = 0;\n    int index = 0;\n    while (index < length) {\n        v21 >>= 1;\n        if ((v21 & 0x100) == 0) {\n            v21 = *src++;\n            v21 |= 0xFF00;\n        }\n\n        if ((v21 & 0x01) == 0) {\n            int v10 = *src++;\n            int v11 = *src++;\n\n            v10 |= (v11 & 0xF0) << 4;\n            v11 &= 0x0F;\n            v11 += 2;\n\n            for (int v16 = 0; v16 <= v11; v16++) {\n                int v17 = (v10 + v16) & 0xFFF;\n\n                unsigned char ch = text_buf[v17];\n                text_buf[v8] = ch;\n                *dest++ = ch;\n\n                v8 = (v8 + 1) & 0xFFF;\n\n                index++;\n                if (index >= length) {\n                    break;\n                }\n            }\n        } else {\n            unsigned char ch = *src++;\n            text_buf[v8] = ch;\n            *dest++ = ch;\n\n            v8 = (v8 + 1) & 0xFFF;\n\n            index++;\n        }\n    }\n\n    mem_free(text_buf);\n\n    return 0;\n}\n\n// 0x44FA78\nvoid InitGreyTable(int a1, int a2)\n{\n    if (a1 >= 0 && a2 <= 255) {\n        for (int index = a1; index <= a2; index++) {\n            // NOTE: The only way to explain so much calls to [Color2RGB] with\n            // the same repeated pattern is by the use of min/max macros.\n\n            int v1 = max((Color2RGB(index) & 0x7C00) >> 10, max((Color2RGB(index) & 0x3E0) >> 5, Color2RGB(index) & 0x1F));\n            int v2 = min((Color2RGB(index) & 0x7C00) >> 10, min((Color2RGB(index) & 0x3E0) >> 5, Color2RGB(index) & 0x1F));\n            int v3 = v1 + v2;\n            int v4 = (int)((double)v3 * 240.0 / 510.0);\n\n            int paletteIndex = ((v4 & 0xFF) << 10) | ((v4 & 0xFF) << 5) | (v4 & 0xFF);\n            GreyTable[index] = colorTable[paletteIndex];\n        }\n    }\n}\n\n// 0x44FC40\nvoid grey_buf(unsigned char* buffer, int width, int height, int pitch)\n{\n    unsigned char* ptr = buffer;\n    int skip = pitch - width;\n\n    for (int y = 0; y < height; y++) {\n        for (int x = 0; x < width; x++) {\n            unsigned char c = *ptr;\n            *ptr++ = GreyTable[c];\n        }\n        ptr += skip;\n    }\n}\n"
  },
  {
    "path": "src/game/graphlib.h",
    "content": "#ifndef FALLOUT_GAME_GRAPHLIB_H_\n#define FALLOUT_GAME_GRAPHLIB_H_\n\nextern int* dad;\nextern int match_length;\nextern int textsize;\nextern int* rson;\nextern int* lson;\nextern unsigned char* text_buf;\nextern int codesize;\nextern int match_position;\n\nint HighRGB(int a1);\nint CompLZS(unsigned char* a1, unsigned char* a2, int a3);\nint DecodeLZS(unsigned char* a1, unsigned char* a2, int a3);\nvoid InitGreyTable(int a1, int a2);\nvoid grey_buf(unsigned char* surface, int width, int height, int pitch);\n\n#endif /* FALLOUT_GAME_GRAPHLIB_H_ */\n"
  },
  {
    "path": "src/game/gsound.c",
    "content": "#include \"game/gsound.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"game/anim.h\"\n#include \"int/audio.h\"\n#include \"int/audiof.h\"\n#include \"game/combat.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/gconfig.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"int/movie.h\"\n#include \"game/object.h\"\n#include \"game/proto.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/sfxcache.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"game/worldmap.h\"\n\nstatic void gsound_bkg_proc();\nstatic int gsound_open(const char* fname, int access, ...);\nstatic long gsound_compressed_tell(int handle);\nstatic int gsound_write(int handle, const void* buf, unsigned int size);\nstatic int gsound_close(int handle);\nstatic int gsound_read(int handle, void* buf, unsigned int size);\nstatic long gsound_seek(int handle, long offset, int origin);\nstatic long gsound_tell(int handle);\nstatic long gsound_filesize(int handle);\nstatic bool gsound_compressed_query(char* filePath);\nstatic void gsound_internal_speech_callback(void* userData, int a2);\nstatic void gsound_internal_background_callback(void* userData, int a2);\nstatic void gsound_internal_effect_callback(void* userData, int a2);\nstatic int gsound_background_allocate(Sound** out_s, int a2, int a3);\nstatic int gsound_background_find_with_copy(char* dest, const char* src);\nstatic int gsound_background_find_dont_copy(char* dest, const char* src);\nstatic int gsound_speech_find_dont_copy(char* dest, const char* src);\nstatic void gsound_background_remove_last_copy();\nstatic int gsound_background_start();\nstatic int gsound_speech_start();\nstatic int gsound_get_music_path(char** out_value, const char* key);\nstatic Sound* gsound_get_sound_ready_for_effect();\nstatic bool gsound_file_exists_f(const char* fname);\nstatic int gsound_file_exists_db(const char* path);\nstatic int gsound_setup_paths();\n\n// TODO: Remove.\n// 0x5035BC\nchar _aSoundSfx[] = \"sound\\\\sfx\\\\\";\n\n// TODO: Remove.\n// 0x5035C8\nchar _aSoundMusic_0[] = \"sound\\\\music\\\\\";\n\n// TODO: Remove.\n// 0x5035D8\nchar _aSoundSpeech_0[] = \"sound\\\\speech\\\\\";\n\n// 0x518E30\nstatic bool gsound_initialized = false;\n\n// 0x518E34\nstatic bool gsound_debug = false;\n\n// 0x518E38\nstatic bool gsound_background_enabled = false;\n\n// 0x518E3C\nstatic int _gsound_background_df_vol = 0;\n\n// 0x518E40\nstatic int gsound_background_fade = 0;\n\n// 0x518E44\nstatic bool gsound_speech_enabled = false;\n\n// 0x518E48\nstatic bool gsound_sfx_enabled = false;\n\n// number of active effects (max 4)\n//\n// 0x518E4C\nstatic int gsound_active_effect_counter;\n\n// 0x518E50\nstatic Sound* gsound_background_tag = NULL;\n\n// 0x518E54\nstatic Sound* gsound_speech_tag = NULL;\n\n// 0x518E58\nstatic SoundEndCallback* gsound_background_callback_fp = NULL;\n\n// 0x518E5C\nstatic SoundEndCallback* gsound_speech_callback_fp = NULL;\n\n// 0x518E60\nstatic char snd_lookup_weapon_type[WEAPON_SOUND_EFFECT_COUNT] = {\n    'R', // Ready\n    'A', // Attack\n    'O', // Out of ammo\n    'F', // Firing\n    'H', // Hit\n};\n\n// 0x518E65\nstatic char snd_lookup_scenery_action[SCENERY_SOUND_EFFECT_COUNT] = {\n    'O', // Open\n    'C', // Close\n    'L', // Lock\n    'N', // Unlock\n    'U', // Use\n};\n\n// 0x518E6C\nstatic int background_storage_requested = -1;\n\n// 0x518E70\nstatic int background_loop_requested = -1;\n\n// 0x518E74\nstatic char sound_sfx_path[] = \"sound\\\\sfx\\\\\";\n\n// 0x518E78\nstatic char* sound_music_path1 = _aSoundMusic_0;\n\n// 0x518E7C\nstatic char* sound_music_path2 = _aSoundMusic_0;\n\n// 0x518E80\nstatic char* sound_speech_path = _aSoundSpeech_0;\n\n// 0x518E84\nstatic int master_volume = VOLUME_MAX;\n\n// 0x518E88\nstatic int background_volume = VOLUME_MAX;\n\n// 0x518E8C\nstatic int speech_volume = VOLUME_MAX;\n\n// 0x518E90\nstatic int sndfx_volume = VOLUME_MAX;\n\n// 0x518E94\nstatic int detectDevices = -1;\n\n// 0x596EB0\nstatic char background_fname_copied[MAX_PATH];\n\n// 0x596FB5\nstatic char sfx_file_name[13];\n\n// NOTE: I'm mot sure about it's size. Why not MAX_PATH?\n//\n// 0x596FC2\nstatic char background_fname_requested[270];\n\n// 0x44FC70\nint gsound_init()\n{\n    if (gsound_initialized) {\n        if (gsound_debug) {\n            debug_printf(\"Trying to initialize gsound twice.\\n\");\n        }\n        return -1;\n    }\n\n    bool initialize;\n    configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_INITIALIZE_KEY, &initialize);\n    if (!initialize) {\n        return 0;\n    }\n\n    configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEBUG_KEY, &gsound_debug);\n\n    if (gsound_debug) {\n        debug_printf(\"Initializing sound system...\");\n    }\n\n    if (gsound_get_music_path(&sound_music_path1, GAME_CONFIG_MUSIC_PATH1_KEY) != 0) {\n        return -1;\n    }\n\n    if (gsound_get_music_path(&sound_music_path2, GAME_CONFIG_MUSIC_PATH2_KEY) != 0) {\n        return -1;\n    }\n\n    if (strlen(sound_music_path1) > 247 || strlen(sound_music_path2) > 247) {\n        if (gsound_debug) {\n            debug_printf(\"Music paths way too long.\\n\");\n        }\n        return -1;\n    }\n\n    // gsound_setup_paths\n    if (gsound_setup_paths() != 0) {\n        return -1;\n    }\n\n    soundRegisterAlloc(mem_malloc, mem_realloc, mem_free);\n\n    // initialize direct sound\n    if (soundInit(detectDevices, 24, 0x8000, 0x8000, 22050) != 0) {\n        if (gsound_debug) {\n            debug_printf(\"failed!\\n\");\n        }\n\n        return -1;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"success.\\n\");\n    }\n\n    initAudiof(gsound_compressed_query);\n    initAudio(gsound_compressed_query);\n\n    int cacheSize;\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_CACHE_SIZE_KEY, &cacheSize);\n    if (cacheSize >= 0x40000) {\n        debug_printf(\"\\n!!! Config file needs adustment.  Please remove the \");\n        debug_printf(\"cache_size line and run fallout again.  This will reset \");\n        debug_printf(\"cache_size to the new default, which is expressed in K.\\n\");\n        return -1;\n    }\n\n    if (sfxc_init(cacheSize << 10, sound_sfx_path) != 0) {\n        if (gsound_debug) {\n            debug_printf(\"Unable to initialize sound effects cache.\\n\");\n        }\n    }\n\n    if (soundSetDefaultFileIO(gsound_open, gsound_close, gsound_read, gsound_write, gsound_seek, gsound_tell, gsound_filesize) != 0) {\n        if (gsound_debug) {\n            debug_printf(\"Failure setting sound I/O calls.\\n\");\n        }\n        return -1;\n    }\n\n    add_bk_process(gsound_bkg_proc);\n    gsound_initialized = true;\n\n    // SOUNDS\n    bool sounds = 0;\n    configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SOUNDS_KEY, &sounds);\n\n    if (gsound_debug) {\n        debug_printf(\"Sounds are \");\n    }\n\n    if (sounds) {\n        // NOTE: Uninline.\n        gsound_sfx_enable();\n    } else {\n        if (gsound_debug) {\n            debug_printf(\" not \");\n        }\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"on.\\n\");\n    }\n\n    // MUSIC\n    bool music = 0;\n    configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_KEY, &music);\n\n    if (gsound_debug) {\n        debug_printf(\"Music is \");\n    }\n\n    if (music) {\n        // NOTE: Uninline.\n        gsound_background_enable();\n    } else {\n        if (gsound_debug) {\n            debug_printf(\" not \");\n        }\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"on.\\n\");\n    }\n\n    // SPEEECH\n    bool speech = 0;\n    configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_KEY, &speech);\n\n    if (gsound_debug) {\n        debug_printf(\"Speech is \");\n    }\n\n    if (speech) {\n        // NOTE: Uninline.\n        gsound_speech_enable();\n    } else {\n        if (gsound_debug) {\n            debug_printf(\" not \");\n        }\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"on.\\n\");\n    }\n\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, &master_volume);\n    gsound_set_master_volume(master_volume);\n\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, &background_volume);\n    gsound_background_volume_set(background_volume);\n\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, &sndfx_volume);\n    gsound_set_sfx_volume(sndfx_volume);\n\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, &speech_volume);\n    gsound_speech_volume_set(speech_volume);\n\n    // NOTE: Uninline.\n    gsound_background_fade_set(0);\n    background_fname_requested[0] = '\\0';\n\n    return 0;\n}\n\n// 0x450164\nvoid gsound_reset()\n{\n    if (!gsound_initialized) {\n        return;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"Resetting sound system...\");\n    }\n\n    // NOTE: Uninline.\n    gsound_speech_stop();\n\n    if (_gsound_background_df_vol) {\n        // NOTE: Uninline.\n        gsound_background_enable();\n    }\n\n    gsound_background_stop();\n\n    // NOTE: Uninline.\n    gsound_background_fade_set(0);\n\n    soundFlushAllSounds();\n\n    sfxc_flush();\n\n    gsound_active_effect_counter = 0;\n\n    if (gsound_debug) {\n        debug_printf(\"done.\\n\");\n    }\n\n    return;\n}\n\n// 0x450244\nint gsound_exit()\n{\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    remove_bk_process(gsound_bkg_proc);\n\n    // NOTE: Uninline.\n    gsound_speech_stop();\n\n    gsound_background_stop();\n    gsound_background_remove_last_copy();\n    soundClose();\n    sfxc_exit();\n    audiofClose();\n    audioClose();\n\n    gsound_initialized = false;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4502BC\nvoid gsound_sfx_enable()\n{\n    if (gsound_initialized) {\n        gsound_sfx_enabled = true;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4502D0\nvoid gsound_sfx_disable()\n{\n    if (gsound_initialized) {\n        gsound_sfx_enabled = false;\n    }\n}\n\n// 0x4502E4\nint gsound_sfx_is_enabled()\n{\n    return gsound_sfx_enabled;\n}\n\n// 0x4502EC\nint gsound_set_master_volume(int volume)\n{\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    if (volume < VOLUME_MIN && volume > VOLUME_MAX) {\n        if (gsound_debug) {\n            debug_printf(\"Requested master volume out of range.\\n\");\n        }\n        return -1;\n    }\n\n    if (_gsound_background_df_vol && volume != 0 && gsound_background_volume_get() != 0) {\n        // NOTE: Uninline.\n        gsound_background_enable();\n        _gsound_background_df_vol = 0;\n    }\n\n    if (soundSetMasterVolume(volume) != 0) {\n        if (gsound_debug) {\n            debug_printf(\"Error setting master sound volume.\\n\");\n        }\n        return -1;\n    }\n\n    master_volume = volume;\n    if (gsound_background_enabled && volume == 0) {\n        // NOTE: Uninline.\n        gsound_background_disable();\n        _gsound_background_df_vol = 1;\n    }\n\n    return 0;\n}\n\n// 0x450410\nint gsound_get_master_volume()\n{\n    return master_volume;\n}\n\n// 0x450418\nint gsound_set_sfx_volume(int volume)\n{\n    if (!gsound_initialized || volume < VOLUME_MIN || volume > VOLUME_MAX) {\n        if (gsound_debug) {\n            debug_printf(\"Error setting sfx volume.\\n\");\n        }\n        return -1;\n    }\n\n    sndfx_volume = volume;\n\n    return 0;\n}\n\n// 0x450454\nint gsound_get_sfx_volume()\n{\n    return sndfx_volume;\n}\n\n// NOTE: Inlined.\n//\n// 0x45045C\nvoid gsound_background_disable()\n{\n    if (gsound_initialized) {\n        if (gsound_background_enabled) {\n            gsound_background_stop();\n            movieSetVolume(0);\n            gsound_background_enabled = false;\n        }\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x450488\nvoid gsound_background_enable()\n{\n    if (gsound_initialized) {\n        if (!gsound_background_enabled) {\n            movieSetVolume((int)(background_volume * 0.94));\n            gsound_background_enabled = true;\n            gsound_background_restart_last(12);\n        }\n    }\n}\n\n// 0x4504D4\nint gsound_background_is_enabled()\n{\n    return gsound_background_enabled;\n}\n\n// 0x4504DC\nvoid gsound_background_volume_set(int volume)\n{\n    if (!gsound_initialized) {\n        return;\n    }\n\n    if (volume < VOLUME_MIN || volume > VOLUME_MAX) {\n        if (gsound_debug) {\n            debug_printf(\"Requested background volume out of range.\\n\");\n        }\n        return;\n    }\n\n    background_volume = volume;\n\n    if (_gsound_background_df_vol) {\n        // NOTE: Uninline.\n        gsound_background_enable();\n        _gsound_background_df_vol = 0;\n    }\n\n    if (gsound_background_enabled) {\n        movieSetVolume((int)(volume * 0.94));\n    }\n\n    if (gsound_background_enabled) {\n        if (gsound_background_tag != NULL) {\n            soundVolume(gsound_background_tag, (int)(background_volume * 0.94));\n        }\n    }\n\n    if (gsound_background_enabled) {\n        if (volume == 0 || gsound_get_master_volume() == 0) {\n            // NOTE: Uninline.\n            gsound_background_disable();\n            _gsound_background_df_vol = 1;\n        }\n    }\n}\n\n// 0x450618\nint gsound_background_volume_get()\n{\n    return background_volume;\n}\n\n// 0x450620\nint gsound_background_volume_get_set(int volume)\n{\n    int oldMusicVolume;\n\n    // NOTE: Uninline.\n    oldMusicVolume = gsound_background_volume_get();\n    gsound_background_volume_set(volume);\n\n    return oldMusicVolume;\n}\n\n// NOTE: Inlined.\n//\n// 0x450630\nvoid gsound_background_fade_set(int value)\n{\n    gsound_background_fade = value;\n}\n\n// NOTE: Inlined.\n//\n// 0x450638\nint gsound_background_fade_get()\n{\n    return gsound_background_fade;\n}\n\n// NOTE: Unused.\n//\n// 0x450640\nint gsound_background_fade_get_set(int value)\n{\n    int oldValue;\n\n    // NOTE: Uninline.\n    oldValue = gsound_background_fade_get();\n\n    // NOTE: Uninline.\n    gsound_background_fade_set(value);\n\n    return oldValue;\n}\n\n// 0x450650\nvoid gsound_background_callback_set(SoundEndCallback* callback)\n{\n    gsound_background_callback_fp = callback;\n}\n\n// 0x450658\nSoundEndCallback* gsound_background_callback_get()\n{\n    return gsound_background_callback_fp;\n}\n\n// 0x450660\nSoundEndCallback* gsound_background_callback_get_set(SoundEndCallback* callback)\n{\n    SoundEndCallback* oldCallback;\n\n    // NOTE: Uninline.\n    oldCallback = gsound_background_callback_get();\n\n    // NOTE: Uninline.\n    gsound_background_callback_set(callback);\n\n    return oldCallback;\n}\n\n// NOTE: There are no references to this function.\n//\n// 0x450670\nint gsound_background_length_get()\n{\n    return soundLength(gsound_background_tag);\n}\n\n// [fileName] is base file name, without path and extension.\n//\n// 0x45067C\nint gsound_background_play(const char* fileName, int a2, int a3, int a4)\n{\n    int rc;\n\n    background_storage_requested = a3;\n    background_loop_requested = a4;\n\n    strcpy(background_fname_requested, fileName);\n\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    if (!gsound_background_enabled) {\n        return -1;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"Loading background sound file %s%s...\", fileName, \".acm\");\n    }\n\n    gsound_background_stop();\n\n    rc = gsound_background_allocate(&gsound_background_tag, a3, a4);\n    if (rc != 0) {\n        if (gsound_debug) {\n            debug_printf(\"failed because sound could not be allocated.\\n\");\n        }\n\n        gsound_background_tag = NULL;\n        return -1;\n    }\n\n    rc = soundSetFileIO(gsound_background_tag, audiofOpen, audiofCloseFile, audiofRead, NULL, audiofSeek, gsound_compressed_tell, audiofFileSize);\n    if (rc != 0) {\n        if (gsound_debug) {\n            debug_printf(\"failed because file IO could not be set for compression.\\n\");\n        }\n\n        soundDelete(gsound_background_tag);\n        gsound_background_tag = NULL;\n\n        return -1;\n    }\n\n    rc = soundSetChannel(gsound_background_tag, 3);\n    if (rc != 0) {\n        if (gsound_debug) {\n            debug_printf(\"failed because the channel could not be set.\\n\");\n        }\n\n        soundDelete(gsound_background_tag);\n        gsound_background_tag = NULL;\n\n        return -1;\n    }\n\n    char path[MAX_PATH + 1];\n    if (a3 == 13) {\n        rc = gsound_background_find_dont_copy(path, fileName);\n    } else if (a3 == 14) {\n        rc = gsound_background_find_with_copy(path, fileName);\n    }\n\n    if (rc != SOUND_NO_ERROR) {\n        if (gsound_debug) {\n            debug_printf(\"'failed because the file could not be found.\\n\");\n        }\n\n        soundDelete(gsound_background_tag);\n        gsound_background_tag = NULL;\n\n        return -1;\n    }\n\n    if (a4 == 16) {\n        rc = soundLoop(gsound_background_tag, 0xFFFF);\n        if (rc != SOUND_NO_ERROR) {\n            if (gsound_debug) {\n                debug_printf(\"failed because looping could not be set.\\n\");\n            }\n\n            soundDelete(gsound_background_tag);\n            gsound_background_tag = NULL;\n\n            return -1;\n        }\n    }\n\n    rc = soundSetCallback(gsound_background_tag, gsound_internal_background_callback, NULL);\n    if (rc != SOUND_NO_ERROR) {\n        if (gsound_debug) {\n            debug_printf(\"soundSetCallback failed for background sound\\n\");\n        }\n    }\n\n    if (a2 == 11) {\n        rc = soundSetReadLimit(gsound_background_tag, 0x40000);\n        if (rc != SOUND_NO_ERROR) {\n            if (gsound_debug) {\n                debug_printf(\"unable to set read limit \");\n            }\n        }\n    }\n\n    rc = soundLoad(gsound_background_tag, path);\n    if (rc != SOUND_NO_ERROR) {\n        if (gsound_debug) {\n            debug_printf(\"failed on call to soundLoad.\\n\");\n        }\n\n        soundDelete(gsound_background_tag);\n        gsound_background_tag = NULL;\n\n        return -1;\n    }\n\n    if (a2 != 11) {\n        rc = soundSetReadLimit(gsound_background_tag, 0x40000);\n        if (rc != 0) {\n            if (gsound_debug) {\n                debug_printf(\"unable to set read limit \");\n            }\n        }\n    }\n\n    if (a2 == 10) {\n        return 0;\n    }\n\n    rc = gsound_background_start();\n    if (rc != 0) {\n        if (gsound_debug) {\n            debug_printf(\"failed starting to play.\\n\");\n        }\n\n        soundDelete(gsound_background_tag);\n        gsound_background_tag = NULL;\n\n        return -1;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"succeeded.\\n\");\n    }\n\n    return 0;\n}\n\n// 0x450A08\nint gsound_background_play_level_music(const char* a1, int a2)\n{\n    return gsound_background_play(a1, a2, 14, 16);\n}\n\n// 0x450A1C\nint gsound_background_play_preloaded()\n{\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    if (!gsound_background_enabled) {\n        return -1;\n    }\n\n    if (gsound_background_tag == NULL) {\n        return -1;\n    }\n\n    if (soundPlaying(gsound_background_tag)) {\n        return -1;\n    }\n\n    if (soundPaused(gsound_background_tag)) {\n        return -1;\n    }\n\n    if (soundDone(gsound_background_tag)) {\n        return -1;\n    }\n\n    if (gsound_background_start() != 0) {\n        soundDelete(gsound_background_tag);\n        gsound_background_tag = NULL;\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x450AB4\nvoid gsound_background_stop()\n{\n    if (gsound_initialized && gsound_background_enabled && gsound_background_tag) {\n        if (gsound_background_fade) {\n            if (soundFade(gsound_background_tag, 2000, 0) == 0) {\n                gsound_background_tag = NULL;\n                return;\n            }\n        }\n\n        soundDelete(gsound_background_tag);\n        gsound_background_tag = NULL;\n    }\n}\n\n// 0x450B0C\nvoid gsound_background_restart_last(int value)\n{\n    if (background_fname_requested[0] != '\\0') {\n        if (gsound_background_play(background_fname_requested, value, background_storage_requested, background_loop_requested) != 0) {\n            if (gsound_debug)\n                debug_printf(\" background restart failed \");\n        }\n    }\n}\n\n// 0x450B50\nvoid gsound_background_pause()\n{\n    if (gsound_background_tag != NULL) {\n        soundPause(gsound_background_tag);\n    }\n}\n\n// 0x450B64\nvoid gsound_background_unpause()\n{\n    if (gsound_background_tag != NULL) {\n        soundUnpause(gsound_background_tag);\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x450B78\nvoid gsound_speech_disable()\n{\n    if (gsound_initialized) {\n        if (gsound_speech_enabled) {\n            gsound_speech_stop();\n            gsound_speech_enabled = false;\n        }\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x450BC0\nvoid gsound_speech_enable()\n{\n    if (gsound_initialized) {\n        if (!gsound_speech_enabled) {\n            gsound_speech_enabled = true;\n        }\n    }\n}\n\n// 0x450BE0\nint gsound_speech_is_enabled()\n{\n    return gsound_speech_enabled;\n}\n\n// 0x450BE8\nvoid gsound_speech_volume_set(int volume)\n{\n    if (!gsound_initialized) {\n        return;\n    }\n\n    if (volume < VOLUME_MIN || volume > VOLUME_MAX) {\n        if (gsound_debug) {\n            debug_printf(\"Requested speech volume out of range.\\n\");\n        }\n        return;\n    }\n\n    speech_volume = volume;\n\n    if (gsound_speech_enabled) {\n        if (gsound_speech_tag != NULL) {\n            soundVolume(gsound_speech_tag, (int)(volume * 0.69));\n        }\n    }\n}\n\n// 0x450C5C\nint gsound_speech_volume_get()\n{\n    return speech_volume;\n}\n\n// 0x450C64\nint gsound_speech_volume_get_set(int volume)\n{\n    int oldVolume = speech_volume;\n    gsound_speech_volume_set(volume);\n    return oldVolume;\n}\n\n// 0x450C74\nvoid gsound_speech_callback_set(SoundEndCallback* callback)\n{\n    gsound_speech_callback_fp = callback;\n}\n\n// 0x450C7C\nSoundEndCallback* gsound_speech_callback_get()\n{\n    return gsound_speech_callback_fp;\n}\n\n// 0x450C84\nSoundEndCallback* gsound_speech_callback_get_set(SoundEndCallback* callback)\n{\n    SoundEndCallback* oldCallback;\n\n    // NOTE: Uninline.\n    oldCallback = gsound_speech_callback_get();\n\n    // NOTE: Uninline.\n    gsound_speech_callback_set(callback);\n\n    return oldCallback;\n}\n\n// 0x450C94\nint gsound_speech_length_get()\n{\n    return soundLength(gsound_speech_tag);\n}\n\n// 0x450CA0\nint gsound_speech_play(const char* fname, int a2, int a3, int a4)\n{\n    char path[MAX_PATH + 1];\n\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    if (!gsound_speech_enabled) {\n        return -1;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"Loading speech sound file %s%s...\", fname, \".ACM\");\n    }\n\n    // uninline\n    gsound_speech_stop();\n\n    if (gsound_background_allocate(&gsound_speech_tag, a3, a4)) {\n        if (gsound_debug) {\n            debug_printf(\"failed because sound could not be allocated.\\n\");\n        }\n        gsound_speech_tag = NULL;\n        return -1;\n    }\n\n    if (soundSetFileIO(gsound_speech_tag, &audioOpen, &audioCloseFile, &audioRead, NULL, &audioSeek, &gsound_compressed_tell, &audioFileSize)) {\n        if (gsound_debug) {\n            debug_printf(\"failed because file IO could not be set for compression.\\n\");\n        }\n        soundDelete(gsound_speech_tag);\n        gsound_speech_tag = NULL;\n        return -1;\n    }\n\n    if (gsound_speech_find_dont_copy(path, fname)) {\n        if (gsound_debug) {\n            debug_printf(\"failed because the file could not be found.\\n\");\n        }\n        soundDelete(gsound_speech_tag);\n        gsound_speech_tag = NULL;\n        return -1;\n    }\n\n    if (a4 == 16) {\n        if (soundLoop(gsound_speech_tag, 0xFFFF)) {\n            if (gsound_debug) {\n                debug_printf(\"failed because looping could not be set.\\n\");\n            }\n            soundDelete(gsound_speech_tag);\n            gsound_speech_tag = NULL;\n            return -1;\n        }\n    }\n\n    if (soundSetCallback(gsound_speech_tag, gsound_internal_speech_callback, NULL)) {\n        if (gsound_debug) {\n            debug_printf(\"soundSetCallback failed for speech sound\\n\");\n        }\n    }\n\n    if (a2 == 11) {\n        if (soundSetReadLimit(gsound_speech_tag, 0x40000)) {\n            if (gsound_debug) {\n                debug_printf(\"unable to set read limit \");\n            }\n        }\n    }\n\n    if (soundLoad(gsound_speech_tag, path)) {\n        if (gsound_debug) {\n            debug_printf(\"failed on call to soundLoad.\\n\");\n        }\n        soundDelete(gsound_speech_tag);\n        gsound_speech_tag = NULL;\n        return -1;\n    }\n\n    if (a2 != 11) {\n        if (soundSetReadLimit(gsound_speech_tag, 0x40000)) {\n            if (gsound_debug) {\n                debug_printf(\"unable to set read limit \");\n            }\n        }\n    }\n\n    if (a2 == 10) {\n        return 0;\n    }\n\n    if (gsound_speech_start()) {\n        if (gsound_debug) {\n            debug_printf(\"failed starting to play.\\n\");\n        }\n        soundDelete(gsound_speech_tag);\n        gsound_speech_tag = NULL;\n        return -1;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"succeeded.\\n\");\n    }\n\n    return 0;\n}\n\n// 0x450F8C\nint gsound_speech_play_preloaded()\n{\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    if (!gsound_speech_enabled) {\n        return -1;\n    }\n\n    if (gsound_speech_tag == NULL) {\n        return -1;\n    }\n\n    if (soundPlaying(gsound_speech_tag)) {\n        return -1;\n    }\n\n    if (soundPaused(gsound_speech_tag)) {\n        return -1;\n    }\n\n    if (soundDone(gsound_speech_tag)) {\n        return -1;\n    }\n\n    if (gsound_speech_start() != 0) {\n        soundDelete(gsound_speech_tag);\n        gsound_speech_tag = NULL;\n\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x451024\nvoid gsound_speech_stop()\n{\n    if (gsound_initialized && gsound_speech_enabled) {\n        if (gsound_speech_tag != NULL) {\n            soundDelete(gsound_speech_tag);\n            gsound_speech_tag = NULL;\n        }\n    }\n}\n\n// 0x451054\nvoid gsound_speech_pause()\n{\n    if (gsound_speech_tag != NULL) {\n        soundPause(gsound_speech_tag);\n    }\n}\n\n// 0x451068\nvoid gsound_speech_unpause()\n{\n    if (gsound_speech_tag != NULL) {\n        soundUnpause(gsound_speech_tag);\n    }\n}\n\n// 0x45108C\nint gsound_play_sfx_file_volume(const char* a1, int a2)\n{\n    Sound* v1;\n\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    if (!gsound_sfx_enabled) {\n        return -1;\n    }\n\n    v1 = gsound_load_sound_volume(a1, NULL, a2);\n    if (v1 == NULL) {\n        return -1;\n    }\n\n    soundPlay(v1);\n\n    return 0;\n}\n\n// 0x4510DC\nSound* gsound_load_sound(const char* name, Object* object)\n{\n    if (!gsound_initialized) {\n        return NULL;\n    }\n\n    if (!gsound_sfx_enabled) {\n        return NULL;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"Loading sound file %s%s...\", name, \".ACM\");\n    }\n\n    if (gsound_active_effect_counter >= SOUND_EFFECTS_MAX_COUNT) {\n        if (gsound_debug) {\n            debug_printf(\"failed because there are already %d active effects.\\n\", gsound_active_effect_counter);\n        }\n\n        return NULL;\n    }\n\n    Sound* sound = gsound_get_sound_ready_for_effect();\n    if (sound == NULL) {\n        if (gsound_debug) {\n            debug_printf(\"failed.\\n\");\n        }\n\n        return NULL;\n    }\n\n    ++gsound_active_effect_counter;\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s%s\", sound_sfx_path, name, \".ACM\");\n\n    if (soundLoad(sound, path) == 0) {\n        if (gsound_debug) {\n            debug_printf(\"succeeded.\\n\");\n        }\n\n        return sound;\n    }\n\n    if (object != NULL) {\n        if (FID_TYPE(object->fid) == OBJ_TYPE_CRITTER && (name[0] == 'H' || name[0] == 'N')) {\n            char v9 = name[1];\n            if (v9 == 'A' || v9 == 'F' || v9 == 'M') {\n                if (v9 == 'A') {\n                    if (critterGetStat(object, STAT_GENDER)) {\n                        v9 = 'F';\n                    } else {\n                        v9 = 'M';\n                    }\n                }\n            }\n\n            sprintf(path, \"%sH%cXXXX%s%s\", sound_sfx_path, v9, name + 6, \".ACM\");\n\n            if (gsound_debug) {\n                debug_printf(\"tyring %s \", path + strlen(sound_sfx_path));\n            }\n\n            if (soundLoad(sound, path) == 0) {\n                if (gsound_debug) {\n                    debug_printf(\"succeeded (with alias).\\n\");\n                }\n\n                return sound;\n            }\n\n            if (v9 == 'F') {\n                sprintf(path, \"%sHMXXXX%s%s\", sound_sfx_path, name + 6, \".ACM\");\n\n                if (gsound_debug) {\n                    debug_printf(\"tyring %s \", path + strlen(sound_sfx_path));\n                }\n\n                if (soundLoad(sound, path) == 0) {\n                    if (gsound_debug) {\n                        debug_printf(\"succeeded (with male alias).\\n\");\n                    }\n\n                    return sound;\n                }\n            }\n        }\n    }\n\n    if (strncmp(name, \"MALIEU\", 6) == 0 || strncmp(name, \"MAMTN2\", 6) == 0) {\n        sprintf(path, \"%sMAMTNT%s%s\", sound_sfx_path, name + 6, \".ACM\");\n\n        if (gsound_debug) {\n            debug_printf(\"tyring %s \", path + strlen(sound_sfx_path));\n        }\n\n        if (soundLoad(sound, path) == 0) {\n            if (gsound_debug) {\n                debug_printf(\"succeeded (with alias).\\n\");\n            }\n\n            return sound;\n        }\n    }\n\n    --gsound_active_effect_counter;\n\n    soundDelete(sound);\n\n    if (gsound_debug) {\n        debug_printf(\"failed.\\n\");\n    }\n\n    return NULL;\n}\n\n// 0x45145C\nSound* gsound_load_sound_volume(const char* name, Object* object, int volume)\n{\n    Sound* sound = gsound_load_sound(name, object);\n\n    if (sound != NULL) {\n        soundVolume(sound, (volume * sndfx_volume) / VOLUME_MAX);\n    }\n\n    return sound;\n}\n\n// 0x45148C\nvoid gsound_delete_sfx(Sound* sound)\n{\n    if (!gsound_initialized) {\n        return;\n    }\n\n    if (!gsound_sfx_enabled) {\n        return;\n    }\n\n    if (soundPlaying(sound)) {\n        if (gsound_debug) {\n            debug_printf(\"Trying to manually delete a sound effect after it has started playing.\\n\");\n        }\n        return;\n    }\n\n    if (soundDelete(sound) != 0) {\n        if (gsound_debug) {\n            debug_printf(\"Unable to delete sound effect -- active effect counter may get out of sync.\\n\");\n        }\n        return;\n    }\n\n    --gsound_active_effect_counter;\n}\n\n// 0x4514F0\nint gsnd_anim_sound(Sound* sound, void* a2)\n{\n    if (!gsound_initialized) {\n        return 0;\n    }\n\n    if (!gsound_sfx_enabled) {\n        return 0;\n    }\n\n    if (sound == NULL) {\n        return 0;\n    }\n\n    soundPlay(sound);\n\n    return 0;\n}\n\n// 0x451510\nint gsound_play_sound(Sound* sound)\n{\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    if (!gsound_sfx_enabled) {\n        return -1;\n    }\n\n    if (sound == NULL) {\n        return -1;\n    }\n\n    soundPlay(sound);\n\n    return 0;\n}\n\n// Probably returns volume dependending on the distance between the specified\n// object and dude.\n//\n// 0x451534\nint gsound_compute_relative_volume(Object* obj)\n{\n    int type;\n    int v3;\n    Object* v7;\n    Rect v12;\n    Rect v14;\n    Rect iso_win_rect;\n    int distance;\n    int perception;\n\n    v3 = 0x7FFF;\n\n    if (obj) {\n        type = FID_TYPE(obj->fid);\n        if (type == 0 || type == 1 || type == 2) {\n            v7 = obj_top_environment(obj);\n            if (!v7) {\n                v7 = obj;\n            }\n\n            obj_bound(v7, &v14);\n\n            win_get_rect(display_win, &iso_win_rect);\n\n            if (rect_inside_bound(&v14, &iso_win_rect, &v12) == -1) {\n                distance = obj_dist(v7, obj_dude);\n                perception = critterGetStat(obj_dude, STAT_PERCEPTION);\n                if (distance > perception) {\n                    if (distance < 2 * perception) {\n                        v3 = 0x7FFF - 0x5554 * (distance - perception) / perception;\n                    } else {\n                        v3 = 0x2AAA;\n                    }\n                } else {\n                    v3 = 0x7FFF;\n                }\n            }\n        }\n    }\n\n    return v3;\n}\n\n// sfx_build_char_name\n// 0x451604\nchar* gsnd_build_character_sfx_name(Object* a1, int anim, int extra)\n{\n    char v7[13];\n    char v8;\n    char v9;\n\n    if (art_get_base_name(FID_TYPE(a1->fid), a1->fid & 0xFFF, v7) == -1) {\n        return NULL;\n    }\n\n    if (anim == ANIM_TAKE_OUT) {\n        if (art_get_code(anim, extra, &v8, &v9) == -1) {\n            return NULL;\n        }\n    } else {\n        if (art_get_code(anim, (a1->fid & 0xF000) >> 12, &v8, &v9) == -1) {\n            return NULL;\n        }\n    }\n\n    // TODO: Check.\n    if (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK) {\n        if (extra == CHARACTER_SOUND_EFFECT_PASS_OUT) {\n            v8 = 'Y';\n        } else if (extra == CHARACTER_SOUND_EFFECT_DIE) {\n            v8 = 'Z';\n        }\n    } else if ((anim == ANIM_THROW_PUNCH || anim == ANIM_KICK_LEG) && extra == CHARACTER_SOUND_EFFECT_CONTACT) {\n        v8 = 'Z';\n    }\n\n    sprintf(sfx_file_name, \"%s%c%c\", v7, v8, v9);\n    strupr(sfx_file_name);\n    return sfx_file_name;\n}\n\n// sfx_build_ambient_name\n// 0x4516F0\nchar* gsnd_build_ambient_sfx_name(const char* a1)\n{\n    sprintf(sfx_file_name, \"A%6s%1d\", a1, 1);\n    strupr(sfx_file_name);\n    return sfx_file_name;\n}\n\n// sfx_build_interface_name\n// 0x451718\nchar* gsnd_build_interface_sfx_name(const char* a1)\n{\n    sprintf(sfx_file_name, \"N%6s%1d\", a1, 1);\n    strupr(sfx_file_name);\n    return sfx_file_name;\n}\n\n// sfx_build_weapon_name\n// 0x451760\nchar* gsnd_build_weapon_sfx_name(int effectType, Object* weapon, int hitMode, Object* target)\n{\n    int v6;\n    char weaponSoundCode;\n    char effectTypeCode;\n    char materialCode;\n    Proto* proto;\n\n    weaponSoundCode = item_w_sound_id(weapon);\n    effectTypeCode = snd_lookup_weapon_type[effectType];\n\n    if (effectType != WEAPON_SOUND_EFFECT_READY\n        && effectType != WEAPON_SOUND_EFFECT_OUT_OF_AMMO) {\n        if (hitMode != HIT_MODE_LEFT_WEAPON_PRIMARY\n            && hitMode != HIT_MODE_RIGHT_WEAPON_PRIMARY\n            && hitMode != HIT_MODE_PUNCH) {\n            v6 = 2;\n        } else {\n            v6 = 1;\n        }\n    } else {\n        v6 = 1;\n    }\n\n    if (effectTypeCode != 'H' || target == NULL || item_w_is_grenade(weapon)) {\n        materialCode = 'X';\n    } else {\n        const int type = FID_TYPE(target->fid);\n        int material;\n        switch (type) {\n        case OBJ_TYPE_ITEM:\n            proto_ptr(target->pid, &proto);\n            material = proto->item.material;\n            break;\n        case OBJ_TYPE_SCENERY:\n            proto_ptr(target->pid, &proto);\n            material = proto->scenery.field_2C;\n            break;\n        case OBJ_TYPE_WALL:\n            proto_ptr(target->pid, &proto);\n            material = proto->wall.material;\n            break;\n        default:\n            material = -1;\n            break;\n        }\n\n        switch (material) {\n        case MATERIAL_TYPE_GLASS:\n        case MATERIAL_TYPE_METAL:\n        case MATERIAL_TYPE_PLASTIC:\n            materialCode = 'M';\n            break;\n        case MATERIAL_TYPE_WOOD:\n            materialCode = 'W';\n            break;\n        case MATERIAL_TYPE_DIRT:\n        case MATERIAL_TYPE_STONE:\n        case MATERIAL_TYPE_CEMENT:\n            materialCode = 'S';\n            break;\n        default:\n            materialCode = 'F';\n            break;\n        }\n    }\n\n    sprintf(sfx_file_name, \"W%c%c%1d%cXX%1d\", effectTypeCode, weaponSoundCode, v6, materialCode, 1);\n    strupr(sfx_file_name);\n    return sfx_file_name;\n}\n\n// sfx_build_scenery_name\n// 0x451898\nchar* gsnd_build_scenery_sfx_name(int actionType, int action, const char* name)\n{\n    char actionTypeCode = actionType == SOUND_EFFECT_ACTION_TYPE_PASSIVE ? 'P' : 'A';\n    char actionCode = snd_lookup_scenery_action[action];\n\n    sprintf(sfx_file_name, \"S%c%c%4s%1d\", actionTypeCode, actionCode, name, 1);\n    strupr(sfx_file_name);\n\n    return sfx_file_name;\n}\n\n// sfx_build_open_name\n// 0x4518D\nchar* gsnd_build_open_sfx_name(Object* object, int action)\n{\n    if (FID_TYPE(object->fid) == OBJ_TYPE_SCENERY) {\n        char scenerySoundId;\n        Proto* proto;\n        if (proto_ptr(object->pid, &proto) != -1) {\n            scenerySoundId = proto->scenery.field_34;\n        } else {\n            scenerySoundId = 'A';\n        }\n        sprintf(sfx_file_name, \"S%cDOORS%c\", snd_lookup_scenery_action[action], scenerySoundId);\n    } else {\n        Proto* proto;\n        proto_ptr(object->pid, &proto);\n        sprintf(sfx_file_name, \"I%cCNTNR%c\", snd_lookup_scenery_action[action], proto->item.field_80);\n    }\n    strupr(sfx_file_name);\n    return sfx_file_name;\n}\n\n// 0x451970\nvoid gsound_red_butt_press(int btn, int keyCode)\n{\n    gsound_play_sfx_file(\"ib1p1xx1\");\n}\n\n// 0x451978\nvoid gsound_red_butt_release(int btn, int keyCode)\n{\n    gsound_play_sfx_file(\"ib1lu1x1\");\n}\n\n// 0x451980\nvoid gsound_toggle_butt_press(int btn, int keyCode)\n{\n    gsound_play_sfx_file(\"toggle\");\n}\n\n// NOTE: Uncollapsed from 0x451980.\n//\n// 0x451980\nvoid gsound_toggle_butt_release(int btn, int keyCode)\n{\n    gsound_play_sfx_file(\"toggle\");\n}\n\n// 0x451988\nvoid gsound_med_butt_press(int btn, int keyCode)\n{\n    gsound_play_sfx_file(\"ib2p1xx1\");\n}\n\n// 0x451990\nvoid gsound_med_butt_release(int btn, int keyCode)\n{\n    gsound_play_sfx_file(\"ib2lu1x1\");\n}\n\n// 0x451998\nvoid gsound_lrg_butt_press(int btn, int keyCode)\n{\n    gsound_play_sfx_file(\"ib3p1xx1\");\n}\n\n// 0x4519A0\nvoid gsound_lrg_butt_release(int btn, int keyCode)\n{\n    gsound_play_sfx_file(\"ib3lu1x1\");\n}\n\n// 0x4519A8\nint gsound_play_sfx_file(const char* name)\n{\n    if (!gsound_initialized) {\n        return -1;\n    }\n\n    if (!gsound_sfx_enabled) {\n        return -1;\n    }\n\n    Sound* sound = gsound_load_sound(name, NULL);\n    if (sound == NULL) {\n        return -1;\n    }\n\n    soundPlay(sound);\n\n    return 0;\n}\n\n// 0x451A00\nstatic void gsound_bkg_proc()\n{\n    soundContinueAll();\n}\n\n// 0x451A08\nstatic int gsound_open(const char* fname, int flags, ...)\n{\n    if ((flags & 2) != 0) {\n        return -1;\n    }\n\n    File* stream = db_fopen(fname, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    return (int)stream;\n}\n\n// 0x451A1C\nstatic long gsound_compressed_tell(int fileHandle)\n{\n    return -1;\n}\n\n// NOTE: Uncollapsed 0x451A1C.\nstatic int gsound_write(int fileHandle, const void* buf, unsigned int size)\n{\n    return -1;\n}\n\n// 0x451A24\nstatic int gsound_close(int fileHandle)\n{\n    if (fileHandle == -1) {\n        return -1;\n    }\n\n    return db_fclose((File*)fileHandle);\n}\n\n// 0x451A30\nstatic int gsound_read(int fileHandle, void* buffer, unsigned int size)\n{\n    if (fileHandle == -1) {\n        return -1;\n    }\n\n    return db_fread(buffer, 1, size, (File*)fileHandle);\n}\n\n// 0x451A4C\nstatic long gsound_seek(int fileHandle, long offset, int origin)\n{\n    if (fileHandle == -1) {\n        return -1;\n    }\n\n    if (db_fseek((File*)fileHandle, offset, origin) != 0) {\n        return -1;\n    }\n\n    return db_ftell((File*)fileHandle);\n}\n\n// 0x451A70\nstatic long gsound_tell(int handle)\n{\n    if (handle == -1) {\n        return -1;\n    }\n\n    return db_ftell((File*)handle);\n}\n\n// 0x451A7C\nstatic long gsound_filesize(int handle)\n{\n    if (handle == -1) {\n        return -1;\n    }\n\n    return db_filelength((File*)handle);\n}\n\n// 0x451A88\nstatic bool gsound_compressed_query(char* filePath)\n{\n    return true;\n}\n\n// 0x451A90\nstatic void gsound_internal_speech_callback(void* userData, int a2)\n{\n    if (a2 == 1) {\n        gsound_speech_tag = NULL;\n\n        if (gsound_speech_callback_fp) {\n            gsound_speech_callback_fp();\n        }\n    }\n}\n\n// 0x451AB0\nstatic void gsound_internal_background_callback(void* userData, int a2)\n{\n    if (a2 == 1) {\n        gsound_background_tag = NULL;\n\n        if (gsound_background_callback_fp) {\n            gsound_background_callback_fp();\n        }\n    }\n}\n\n// 0x451AD0\nstatic void gsound_internal_effect_callback(void* userData, int a2)\n{\n    if (a2 == 1) {\n        --gsound_active_effect_counter;\n    }\n}\n\n// 0x451ADC\nstatic int gsound_background_allocate(Sound** soundPtr, int a2, int a3)\n{\n    int v5 = 10;\n    int v6 = 0;\n    if (a2 == 13) {\n        v6 |= 0x01;\n    } else if (a2 == 14) {\n        v6 |= 0x02;\n    }\n\n    if (a3 == 15) {\n        v6 |= 0x04;\n    } else if (a3 == 16) {\n        v5 = 42;\n    }\n\n    Sound* sound = soundAllocate(v6, v5);\n    if (sound == NULL) {\n        return -1;\n    }\n\n    *soundPtr = sound;\n\n    return 0;\n}\n\n// gsound_background_find_with_copy\n// 0x451B30\nstatic int gsound_background_find_with_copy(char* dest, const char* src)\n{\n    size_t len = strlen(src) + strlen(\".ACM\");\n    if (strlen(sound_music_path1) + len > MAX_PATH || strlen(sound_music_path2) + len > MAX_PATH) {\n        if (gsound_debug) {\n            debug_printf(\"Full background path too long.\\n\");\n        }\n\n        return -1;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\" finding background sound \");\n    }\n\n    char outPath[MAX_PATH];\n    sprintf(outPath, \"%s%s%s\", sound_music_path1, src, \".ACM\");\n    if (gsound_file_exists_f(outPath)) {\n        strncpy(dest, outPath, MAX_PATH);\n        dest[MAX_PATH] = '\\0';\n        return 0;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"by copy \");\n    }\n\n    gsound_background_remove_last_copy();\n\n    char inPath[MAX_PATH];\n    sprintf(inPath, \"%s%s%s\", sound_music_path2, src, \".ACM\");\n\n    FILE* inStream = fopen(inPath, \"rb\");\n    if (inStream == NULL) {\n        if (gsound_debug) {\n            debug_printf(\"Unable to find music file %s to copy down.\\n\", src);\n        }\n\n        return -1;\n    }\n\n    FILE* outStream = fopen(outPath, \"wb\");\n    if (outStream == NULL) {\n        if (gsound_debug) {\n            debug_printf(\"Unable to open music file %s for copying to.\", src);\n        }\n\n        fclose(inStream);\n\n        return -1;\n    }\n\n    void* buffer = mem_malloc(0x2000);\n    if (buffer == NULL) {\n        if (gsound_debug) {\n            debug_printf(\"Out of memory in gsound_background_find_with_copy.\\n\", src);\n        }\n\n        fclose(outStream);\n        fclose(inStream);\n\n        return -1;\n    }\n\n    bool err = false;\n    while (!feof(inStream)) {\n        size_t bytesRead = fread(buffer, 1, 0x2000, inStream);\n        if (bytesRead == 0) {\n            break;\n        }\n\n        if (fwrite(buffer, 1, bytesRead, outStream) != bytesRead) {\n            err = true;\n            break;\n        }\n    }\n\n    mem_free(buffer);\n    fclose(outStream);\n    fclose(inStream);\n\n    if (err) {\n        if (gsound_debug) {\n            debug_printf(\"Background sound file copy failed on write -- \");\n            debug_printf(\"likely out of disc space.\\n\");\n        }\n\n        return -1;\n    }\n\n    strcpy(background_fname_copied, src);\n\n    strncpy(dest, outPath, MAX_PATH);\n    dest[MAX_PATH] = '\\0';\n\n    return 0;\n}\n\n// 0x451E2C\nstatic int gsound_background_find_dont_copy(char* dest, const char* src)\n{\n    char path[MAX_PATH];\n    int len;\n\n    len = strlen(src) + strlen(\".ACM\");\n    if (strlen(sound_music_path1) + len > MAX_PATH || strlen(sound_music_path2) + len > MAX_PATH) {\n        if (gsound_debug) {\n            debug_printf(\"Full background path too long.\\n\");\n        }\n\n        return -1;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\" finding background sound \");\n    }\n\n    sprintf(path, \"%s%s%s\", sound_music_path1, src, \".ACM\");\n    if (gsound_file_exists_f(path)) {\n        strncpy(dest, path, MAX_PATH);\n        dest[MAX_PATH] = '\\0';\n        return 0;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"in 2nd path \");\n    }\n\n    sprintf(path, \"%s%s%s\", sound_music_path2, src, \".ACM\");\n    if (gsound_file_exists_f(path)) {\n        strncpy(dest, path, MAX_PATH);\n        dest[MAX_PATH] = '\\0';\n        return 0;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"-- find failed \");\n    }\n\n    return -1;\n}\n\n// 0x451F94\nstatic int gsound_speech_find_dont_copy(char* dest, const char* src)\n{\n    char path[MAX_PATH];\n\n    if (strlen(sound_speech_path) + strlen(\".acm\") > MAX_PATH) {\n        if (gsound_debug) {\n            // FIXME: The message is wrong (notes background path, but here\n            // we're dealing with speech path).\n            debug_printf(\"Full background path too long.\\n\");\n        }\n\n        return -1;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\" finding speech sound \");\n    }\n\n    sprintf(path, \"%s%s%s\", sound_speech_path, src, \".ACM\");\n\n    // NOTE: Uninline.\n    if (gsound_file_exists_db(path)) {\n        if (gsound_debug) {\n            debug_printf(\"-- find failed \");\n        }\n\n        return -1;\n    }\n\n    strncpy(dest, path, MAX_PATH);\n    dest[MAX_PATH] = '\\0';\n\n    return 0;\n}\n\n// delete old music file\n// 0x452088\nstatic void gsound_background_remove_last_copy()\n{\n    if (background_fname_copied[0] != '\\0') {\n        char path[MAX_PATH];\n        sprintf(path, \"%s%s%s\", \"sound\\\\music\\\\\", background_fname_copied, \".ACM\");\n        if (remove(path)) {\n            if (gsound_debug) {\n                debug_printf(\"Deleting old music file failed.\\n\");\n            }\n        }\n\n        background_fname_copied[0] = '\\0';\n    }\n}\n\n// 0x4520EC\nstatic int gsound_background_start()\n{\n    int result;\n\n    if (gsound_debug) {\n        debug_printf(\" playing \");\n    }\n\n    if (gsound_background_fade) {\n        soundVolume(gsound_background_tag, 1);\n        result = soundFade(gsound_background_tag, 2000, (int)(background_volume * 0.94));\n    } else {\n        soundVolume(gsound_background_tag, (int)(background_volume * 0.94));\n        result = soundPlay(gsound_background_tag);\n    }\n\n    if (result != 0) {\n        if (gsound_debug) {\n            debug_printf(\"Unable to play background sound.\\n\");\n        }\n\n        result = -1;\n    }\n\n    return result;\n}\n\n// 0x45219C\nstatic int gsound_speech_start()\n{\n    if (gsound_debug) {\n        debug_printf(\" playing \");\n    }\n\n    soundVolume(gsound_speech_tag, (int)(speech_volume * 0.69));\n\n    if (soundPlay(gsound_speech_tag) != 0) {\n        if (gsound_debug) {\n            debug_printf(\"Unable to play speech sound.\\n\");\n        }\n\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x452208\nstatic int gsound_get_music_path(char** out_value, const char* key)\n{\n    int len;\n    char* copy;\n    char* value;\n\n    config_get_string(&game_config, GAME_CONFIG_SOUND_KEY, key, out_value);\n\n    value = *out_value;\n    len = strlen(value);\n\n    if (value[len - 1] == '\\\\' || value[len - 1] == '/') {\n        return 0;\n    }\n\n    copy = (char*)mem_malloc(len + 2);\n    if (copy == NULL) {\n        if (gsound_debug) {\n            debug_printf(\"Out of memory in gsound_get_music_path.\\n\");\n        }\n        return -1;\n    }\n\n    strcpy(copy, value);\n    copy[len] = '\\\\';\n    copy[len + 1] = '\\0';\n\n    if (config_set_string(&game_config, GAME_CONFIG_SOUND_KEY, key, copy) != 1) {\n        if (gsound_debug) {\n            debug_printf(\"config_set_string failed in gsound_music_path.\\n\");\n        }\n\n        return -1;\n    }\n\n    if (config_get_string(&game_config, GAME_CONFIG_SOUND_KEY, key, out_value)) {\n        mem_free(copy);\n        return 0;\n    }\n\n    if (gsound_debug) {\n        debug_printf(\"config_get_string failed in gsound_music_path.\\n\");\n    }\n\n    return -1;\n}\n\n// 0x452378\nstatic Sound* gsound_get_sound_ready_for_effect()\n{\n    int rc;\n\n    Sound* sound = soundAllocate(5, 10);\n    if (sound == NULL) {\n        if (gsound_debug) {\n            debug_printf(\" Can't allocate sound for effect. \");\n        }\n\n        if (gsound_debug) {\n            debug_printf(\"soundAllocate returned: %d, %s\\n\", 0, soundError(0));\n        }\n\n        return NULL;\n    }\n\n    if (sfxc_is_initialized()) {\n        rc = soundSetFileIO(sound, sfxc_cached_open, sfxc_cached_close, sfxc_cached_read, sfxc_cached_write, sfxc_cached_seek, sfxc_cached_tell, sfxc_cached_file_size);\n    } else {\n        rc = soundSetFileIO(sound, audioOpen, audioCloseFile, audioRead, NULL, audioSeek, gsound_compressed_tell, audioFileSize);\n    }\n\n    if (rc != 0) {\n        if (gsound_debug) {\n            debug_printf(\"Can't set file IO on sound effect.\\n\");\n        }\n\n        if (gsound_debug) {\n            debug_printf(\"soundSetFileIO returned: %d, %s\\n\", rc, soundError(rc));\n        }\n\n        soundDelete(sound);\n\n        return NULL;\n    }\n\n    rc = soundSetCallback(sound, gsound_internal_effect_callback, NULL);\n    if (rc != 0) {\n        if (gsound_debug) {\n            debug_printf(\"failed because the callback could not be set.\\n\");\n        }\n\n        if (gsound_debug) {\n            debug_printf(\"soundSetCallback returned: %d, %s\\n\", rc, soundError(rc));\n        }\n\n        soundDelete(sound);\n\n        return NULL;\n    }\n\n    soundVolume(sound, sndfx_volume);\n\n    return sound;\n}\n\n// Check file for existence.\n//\n// 0x4524E0\nstatic bool gsound_file_exists_f(const char* fname)\n{\n    FILE* f = fopen(fname, \"rb\");\n    if (f == NULL) {\n        return false;\n    }\n\n    fclose(f);\n\n    return true;\n}\n\n// 0x4524FC\nstatic int gsound_file_exists_db(const char* path)\n{\n    int size;\n\n    return db_dir_entry(path, &size) == 0;\n}\n\n// 0x452518\nstatic int gsound_setup_paths()\n{\n    // TODO: Incomplete.\n\n    return 0;\n}\n\n// 0x452628\nint gsound_sfx_q_start()\n{\n    return gsound_sfx_q_process(0, NULL);\n}\n\n// 0x452634\nint gsound_sfx_q_process(Object* a1, void* data)\n{\n    // 0x518E98\n    static int lastTime = 0;\n\n    queue_clear_type(EVENT_TYPE_GSOUND_SFX_EVENT, NULL);\n\n    AmbientSoundEffectEvent* soundEffectEvent = (AmbientSoundEffectEvent*)data;\n    int ambientSoundEffectIndex = -1;\n    if (soundEffectEvent != NULL) {\n        ambientSoundEffectIndex = soundEffectEvent->ambientSoundEffectIndex;\n    } else {\n        if (wmSfxMaxCount() > 0) {\n            ambientSoundEffectIndex = wmSfxRollNextIdx();\n        }\n    }\n\n    AmbientSoundEffectEvent* nextSoundEffectEvent = (AmbientSoundEffectEvent*)mem_malloc(sizeof(*nextSoundEffectEvent));\n    if (nextSoundEffectEvent == NULL) {\n        return -1;\n    }\n\n    if (map_data.name[0] == '\\0') {\n        return 0;\n    }\n\n    int delay = 10 * roll_random(15, 20);\n    if (wmSfxMaxCount() > 0) {\n        nextSoundEffectEvent->ambientSoundEffectIndex = wmSfxRollNextIdx();\n        if (queue_add(delay, NULL, nextSoundEffectEvent, EVENT_TYPE_GSOUND_SFX_EVENT) == -1) {\n            return -1;\n        }\n    }\n\n    if (isInCombat()) {\n        ambientSoundEffectIndex = -1;\n    }\n\n    if (ambientSoundEffectIndex != -1) {\n        char* fileName;\n        if (wmSfxIdxName(ambientSoundEffectIndex, &fileName) == 0) {\n            int v7 = get_bk_time();\n            if (elapsed_tocks(v7, lastTime) >= 5000) {\n                if (gsound_play_sfx_file(fileName) == -1) {\n                    debug_printf(\"\\nGsound: playing ambient map sfx: %s.  FAILED\", fileName);\n                } else {\n                    debug_printf(\"\\nGsound: playing ambient map sfx: %s\", fileName);\n                }\n            }\n            lastTime = v7;\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/gsound.h",
    "content": "#ifndef FALLOUT_GAME_GSOUND_H_\n#define FALLOUT_GAME_GSOUND_H_\n\n#include <stdbool.h>\n\n#include \"game/object_types.h\"\n#include \"int/sound.h\"\n\ntypedef enum WeaponSoundEffect {\n    WEAPON_SOUND_EFFECT_READY,\n    WEAPON_SOUND_EFFECT_ATTACK,\n    WEAPON_SOUND_EFFECT_OUT_OF_AMMO,\n    WEAPON_SOUND_EFFECT_AMMO_FLYING,\n    WEAPON_SOUND_EFFECT_HIT,\n    WEAPON_SOUND_EFFECT_COUNT,\n} WeaponSoundEffect;\n\ntypedef enum SoundEffectActionType {\n    SOUND_EFFECT_ACTION_TYPE_ACTIVE,\n    SOUND_EFFECT_ACTION_TYPE_PASSIVE,\n} SoundEffectActionType;\n\ntypedef enum ScenerySoundEffect {\n    SCENERY_SOUND_EFFECT_OPEN,\n    SCENERY_SOUND_EFFECT_CLOSED,\n    SCENERY_SOUND_EFFECT_LOCKED,\n    SCENERY_SOUND_EFFECT_UNLOCKED,\n    SCENERY_SOUND_EFFECT_USED,\n    SCENERY_SOUND_EFFECT_COUNT,\n} ScenerySoundEffect;\n\ntypedef enum CharacterSoundEffect {\n    CHARACTER_SOUND_EFFECT_UNUSED,\n    CHARACTER_SOUND_EFFECT_KNOCKDOWN,\n    CHARACTER_SOUND_EFFECT_PASS_OUT,\n    CHARACTER_SOUND_EFFECT_DIE,\n    CHARACTER_SOUND_EFFECT_CONTACT,\n} CharacterSoundEffect;\n\ntypedef void(SoundEndCallback)();\n\nint gsound_init();\nvoid gsound_reset();\nint gsound_exit();\nvoid gsound_sfx_enable();\nvoid gsound_sfx_disable();\nint gsound_sfx_is_enabled();\nint gsound_set_master_volume(int value);\nint gsound_get_master_volume();\nint gsound_set_sfx_volume(int value);\nint gsound_get_sfx_volume();\nvoid gsound_background_disable();\nvoid gsound_background_enable();\nint gsound_background_is_enabled();\nvoid gsound_background_volume_set(int value);\nint gsound_background_volume_get();\nint gsound_background_volume_get_set(int a1);\nvoid gsound_background_fade_set(int value);\nint gsound_background_fade_get();\nint gsound_background_fade_get_set(int value);\nvoid gsound_background_callback_set(SoundEndCallback* callback);\nSoundEndCallback* gsound_background_callback_get();\nSoundEndCallback* gsound_background_callback_get_set(SoundEndCallback* callback);\nint gsound_background_length_get();\nint gsound_background_play(const char* fileName, int a2, int a3, int a4);\nint gsound_background_play_level_music(const char* a1, int a2);\nint gsound_background_play_preloaded();\nvoid gsound_background_stop();\nvoid gsound_background_restart_last(int value);\nvoid gsound_background_pause();\nvoid gsound_background_unpause();\nvoid gsound_speech_disable();\nvoid gsound_speech_enable();\nint gsound_speech_is_enabled();\nvoid gsound_speech_volume_set(int value);\nint gsound_speech_volume_get();\nint gsound_speech_volume_get_set(int volume);\nvoid gsound_speech_callback_set(SoundEndCallback* callback);\nSoundEndCallback* gsound_speech_callback_get();\nSoundEndCallback* gsound_speech_callback_get_set(SoundEndCallback* callback);\nint gsound_speech_length_get();\nint gsound_speech_play(const char* fname, int a2, int a3, int a4);\nint gsound_speech_play_preloaded();\nvoid gsound_speech_stop();\nvoid gsound_speech_pause();\nvoid gsound_speech_unpause();\nint gsound_play_sfx_file_volume(const char* a1, int a2);\nSound* gsound_load_sound(const char* name, Object* a2);\nSound* gsound_load_sound_volume(const char* a1, Object* a2, int a3);\nvoid gsound_delete_sfx(Sound* a1);\nint gsnd_anim_sound(Sound* sound, void* a2);\nint gsound_play_sound(Sound* a1);\nint gsound_compute_relative_volume(Object* obj);\nchar* gsnd_build_character_sfx_name(Object* a1, int anim, int extra);\nchar* gsnd_build_ambient_sfx_name(const char* a1);\nchar* gsnd_build_interface_sfx_name(const char* a1);\nchar* gsnd_build_weapon_sfx_name(int effectType, Object* weapon, int hitMode, Object* target);\nchar* gsnd_build_scenery_sfx_name(int actionType, int action, const char* name);\nchar* gsnd_build_open_sfx_name(Object* a1, int a2);\nvoid gsound_red_butt_press(int btn, int keyCode);\nvoid gsound_red_butt_release(int btn, int keyCode);\nvoid gsound_toggle_butt_press(int btn, int keyCode);\nvoid gsound_toggle_butt_release(int btn, int keyCode);\nvoid gsound_med_butt_press(int btn, int keyCode);\nvoid gsound_med_butt_release(int btn, int keyCode);\nvoid gsound_lrg_butt_press(int btn, int keyCode);\nvoid gsound_lrg_butt_release(int btn, int keyCode);\nint gsound_play_sfx_file(const char* name);\nint gsound_sfx_q_start();\nint gsound_sfx_q_process(Object* a1, void* a2);\n\n#endif /* FALLOUT_GAME_GSOUND_H_ */\n"
  },
  {
    "path": "src/game/gz.c",
    "content": "// NOTE: For unknown reason functions in this module use __stdcall instead\n// of regular __usercall.\n\n#include \"game/gz.h\"\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <zlib.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n// NOTE: Not present in debug symbols in `mapper2.exe`, but can be seen in OS X\n// binary.\n//\n// 0x452740\nint gzRealUncompressCopyReal_file(const char* existingFilePath, const char* newFilePath)\n{\n    FILE* stream = fopen(existingFilePath, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int magic[2];\n    magic[0] = fgetc(stream);\n    magic[1] = fgetc(stream);\n    fclose(stream);\n\n    if (magic[0] == 0x1F && magic[1] == 0x8B) {\n        gzFile inStream = gzopen(existingFilePath, \"rb\");\n        FILE* outStream = fopen(newFilePath, \"wb\");\n\n        if (inStream != NULL && outStream != NULL) {\n            for (;;) {\n                int ch = gzgetc(inStream);\n                if (ch == -1) {\n                    break;\n                }\n\n                fputc(ch, outStream);\n            }\n\n            gzclose(inStream);\n            fclose(outStream);\n        } else {\n            if (inStream != NULL) {\n                gzclose(inStream);\n            }\n\n            if (outStream != NULL) {\n                fclose(outStream);\n            }\n\n            return -1;\n        }\n    } else {\n        CopyFileA(existingFilePath, newFilePath, FALSE);\n    }\n\n    return 0;\n}\n\n// 0x452804\nint gzcompress_file(const char* existingFilePath, const char* newFilePath)\n{\n    FILE* inStream = fopen(existingFilePath, \"rb\");\n    if (inStream == NULL) {\n        return -1;\n    }\n\n    int magic[2];\n    magic[0] = fgetc(inStream);\n    magic[1] = fgetc(inStream);\n    rewind(inStream);\n\n    if (magic[0] == 0x1F && magic[1] == 0x8B) {\n        // Source file is already gzipped, there is no need to do anything\n        // besides copying.\n        fclose(inStream);\n        CopyFileA(existingFilePath, newFilePath, FALSE);\n    } else {\n        gzFile outStream = gzopen(newFilePath, \"wb\");\n        if (outStream == NULL) {\n            fclose(inStream);\n            return -1;\n        }\n\n        // Copy byte-by-byte.\n        for (;;) {\n            int ch = fgetc(inStream);\n            if (ch == -1) {\n                break;\n            }\n\n            gzputc(outStream, ch);\n        }\n\n        fclose(inStream);\n        gzclose(outStream);\n    }\n\n    return 0;\n}\n\n// TODO: Check, implementation looks odd.\n//\n// 0x4528B8\nint gzdecompress_file(const char* existingFilePath, const char* newFilePath)\n{\n    FILE* stream = fopen(existingFilePath, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int magic[2];\n    magic[0] = fgetc(stream);\n    magic[1] = fgetc(stream);\n    fclose(stream);\n\n    // TODO: Is it broken?\n    if (magic[0] != 0x1F || magic[1] != 0x8B) {\n        gzFile gzstream = gzopen(existingFilePath, \"rb\");\n        if (gzstream == NULL) {\n            return -1;\n        }\n\n        stream = fopen(newFilePath, \"wb\");\n        if (stream == NULL) {\n            gzclose(gzstream);\n            return -1;\n        }\n\n        while (1) {\n            int ch = gzgetc(gzstream);\n            if (ch == -1) {\n                break;\n            }\n\n            fputc(ch, stream);\n        }\n\n        gzclose(gzstream);\n        fclose(stream);\n    } else {\n        CopyFileA(existingFilePath, newFilePath, FALSE);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/gz.h",
    "content": "#ifndef FALLOUT_GAME_GZ_H_\n#define FALLOUT_GAME_GZ_H_\n\nint gzRealUncompressCopyReal_file(const char* existingFilePath, const char* newFilePath);\nint gzcompress_file(const char* existingFilePath, const char* newFilePath);\nint gzdecompress_file(const char* existingFilePath, const char* newFilePath);\n\n#endif /* FALLOUT_GAME_GZ_H_ */\n"
  },
  {
    "path": "src/game/heap.c",
    "content": "#include \"game/heap.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/memory.h\"\n\n#define HEAP_BLOCK_HEADER_GUARD (0xDEADC0DE)\n#define HEAP_BLOCK_FOOTER_GUARD (0xACDCACDC)\n\n#define HEAP_BLOCK_HEADER_SIZE (sizeof(HeapBlockHeader))\n#define HEAP_BLOCK_FOOTER_SIZE (sizeof(HeapBlockFooter))\n#define HEAP_BLOCK_OVERHEAD_SIZE (HEAP_BLOCK_HEADER_SIZE + HEAP_BLOCK_FOOTER_SIZE)\n\n// The initial length of [handles] array within [Heap].\n#define HEAP_HANDLES_INITIAL_LENGTH (64)\n\n// The initial length of [heap_free_list] array.\n#define HEAP_FREE_BLOCKS_INITIAL_LENGTH (128)\n\n// The initial length of [heap_moveable_list] array.\n#define HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH (64)\n\n// The initial length of [heap_subblock_list] array.\n#define HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH (64)\n\n// The initial length of [heap_fake_move_list] array.\n#define HEAP_RESERVED_FREE_BLOCK_INDEXES_INITIAL_LENGTH (64)\n\n// The minimum size of block for splitting.\n#define HEAP_BLOCK_MIN_SIZE (128 + HEAP_BLOCK_OVERHEAD_SIZE)\n\n#define HEAP_HANDLE_STATE_INVALID (-1)\n\n// The only allowed combination is LOCKED | SYSTEM.\ntypedef enum HeapBlockState {\n    HEAP_BLOCK_STATE_FREE = 0x00,\n    HEAP_BLOCK_STATE_MOVABLE = 0x01,\n    HEAP_BLOCK_STATE_LOCKED = 0x02,\n    HEAP_BLOCK_STATE_SYSTEM = 0x04,\n} HeapBlockState;\n\ntypedef struct HeapBlockHeader {\n    int guard;\n    int size;\n    unsigned int state;\n    int handle_index;\n} HeapBlockHeader;\n\ntypedef struct HeapBlockFooter {\n    int guard;\n} HeapBlockFooter;\n\ntypedef struct HeapMoveableExtent {\n    // Pointer to the first block in the extent.\n    unsigned char* data;\n\n    // Total number of free or moveable blocks in the extent.\n    int blocksLength;\n\n    // Number of moveable blocks in the extent.\n    int moveableBlocksLength;\n\n    // Total data size of blocks in the extent. This value does not include\n    // the size of blocks overhead.\n    int size;\n} HeapMoveableExtent;\n\nstatic_assert(sizeof(HeapBlockHeader) == 16, \"wrong size\");\nstatic_assert(sizeof(HeapBlockFooter) == 4, \"wrong size\");\nstatic_assert(sizeof(HeapMoveableExtent) == 16, \"wrong size\");\nstatic_assert(sizeof(HeapHandle) == 8, \"wrong size\");\nstatic_assert(sizeof(Heap) == 48, \"wrong size\");\n\nstatic bool heap_create_lists();\nstatic void heap_destroy_lists();\nstatic bool heap_init_handles(Heap* heap);\nstatic bool heap_exit_handles(Heap* heap);\nstatic bool heap_acquire_handle(Heap* heap, int* handleIndexPtr);\nstatic bool heap_release_handle(Heap* heap, int handleIndex);\nstatic bool heap_clear_handles(Heap* heap, HeapHandle* handles, unsigned int count);\nstatic bool heap_find_free_block(Heap* heap, int size, void** blockPtr, int a4);\nstatic bool heap_build_free_list(Heap* heap);\nstatic bool heap_sort_free_list(Heap* heap);\nstatic int heap_qsort_compare_free(const void* a1, const void* a2);\nstatic bool heap_build_moveable_list(Heap* heap, int* moveableExtentsLengthPtr, int* maxBlocksLengthPtr);\nstatic bool heap_sort_moveable_list(Heap* heap, size_t count);\nstatic int heap_qsort_compare_moveable(const void* a1, const void* a2);\nstatic bool heap_build_subblock_list(int extentIndex);\nstatic bool heap_sort_subblock_list(size_t count);\nstatic int heap_qsort_compare_subblock(const void* a1, const void* a2);\nstatic bool heap_build_fake_move_list(size_t count);\n\n// An array of pointers to free heap blocks.\n//\n// 0x518E9C\nstatic unsigned char** heap_free_list = NULL;\n\n// An array of moveable extents in heap.\n//\n// 0x518EA0\nstatic HeapMoveableExtent* heap_moveable_list = NULL;\n\n// An array of pointers to moveable heap blocks.\n//\n// 0x518EA4\nstatic unsigned char** heap_subblock_list = NULL;\n\n// An array of indexes into [heap_free_list] array to track which free blocks\n// were already reserved for subsequent moving.\n//\n// 0x518EA8\nstatic int* heap_fake_move_list = NULL;\n\n// The length of the [heap_free_list] array.\n//\n// 0x518EAC\nstatic int heap_free_list_size = 0;\n\n// The length of [heap_moveable_list] array.\n//\n// 0x518EB0\nstatic int heap_moveable_list_size = 0;\n\n// The length of [heap_subblock_list] array.\n//\n// 0x518EB4\nstatic int heap_subblock_list_size = 0;\n\n// The length of [heap_fake_move_list] array.\n//\n// 0x518EB8\nstatic size_t heap_fake_move_list_size = 0;\n\n// The number of heaps.\n//\n// This value is used to init/free internal temporary buffers\n// needed for any heap.\n//\n// 0x518EBC\nstatic int heap_count = 0;\n\n// 0x452974\nbool heap_init(Heap* heap, int a2)\n{\n    if (heap == NULL) {\n        return false;\n    }\n\n    if (heap_count == 0) {\n        if (!heap_create_lists()) {\n            return false;\n        }\n    }\n\n    memset(heap, 0, sizeof(*heap));\n\n    if (heap_init_handles(heap)) {\n        int size = (a2 >> 10) + a2;\n        heap->data = (unsigned char*)mem_malloc(size);\n        if (heap->data != NULL) {\n            heap->size = size;\n            heap->freeBlocks = 1;\n            heap->freeSize = heap->size - HEAP_BLOCK_OVERHEAD_SIZE;\n\n            HeapBlockHeader* blockHeader = (HeapBlockHeader*)heap->data;\n            blockHeader->guard = HEAP_BLOCK_HEADER_GUARD;\n            blockHeader->size = heap->freeSize;\n            blockHeader->state = 0;\n            blockHeader->handle_index = -1;\n\n            HeapBlockFooter* blockFooter = (HeapBlockFooter*)(heap->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n            blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD;\n\n            heap_count++;\n\n            return true;\n        }\n    }\n\n    if (heap_count == 0) {\n        heap_destroy_lists();\n    }\n\n    return false;\n}\n\n// 0x452A3C\nbool heap_exit(Heap* heap)\n{\n    if (heap == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < heap->handlesLength; index++) {\n        HeapHandle* handle = &(heap->handles[index]);\n        if (handle->state == 4 && handle->data != NULL) {\n            mem_free(handle->data);\n        }\n    }\n\n    // NOTE: Uninline.\n    heap_exit_handles(heap);\n\n    if (heap->data != NULL) {\n        mem_free(heap->data);\n    }\n\n    memset(heap, 0, sizeof(*heap));\n\n    heap_count--;\n    if (heap_count == 0) {\n        heap_destroy_lists();\n    }\n\n    return true;\n}\n\n// 0x452AD0\nbool heap_allocate(Heap* heap, int* handleIndexPtr, int size, int a4)\n{\n    HeapBlockHeader* blockHeader;\n    int state;\n    int blockSize;\n    HeapHandle* handle;\n\n    if (heap == NULL || handleIndexPtr == NULL || size == 0) {\n        goto err;\n    }\n\n    if (a4 != 0 && a4 != 1) {\n        a4 = 0;\n    }\n\n    void* block;\n    if (!heap_find_free_block(heap, size, &block, a4)) {\n        goto err;\n    }\n\n    blockHeader = (HeapBlockHeader*)block;\n    state = blockHeader->state;\n\n    int handleIndex;\n    if (!heap_acquire_handle(heap, &handleIndex)) {\n        goto err_no_handle;\n    }\n\n    blockSize = blockHeader->size;\n    handle = &(heap->handles[handleIndex]);\n\n    if (state == HEAP_BLOCK_STATE_SYSTEM) {\n        // Bind block to handle.\n        blockHeader->handle_index = handleIndex;\n\n        // Bind handle to block and mark it as system\n        handle->state = HEAP_BLOCK_STATE_SYSTEM;\n        handle->data = (unsigned char*)block;\n\n        // Update heap stats\n        heap->systemBlocks++;\n        heap->systemSize += size;\n\n        *handleIndexPtr = handleIndex;\n\n        return true;\n    }\n\n    if (state == HEAP_BLOCK_STATE_FREE) {\n        int remainingSize = blockSize - size;\n        if (remainingSize > HEAP_BLOCK_MIN_SIZE) {\n            // The block we've just found is big enough for splitting, first\n            // resize it to take just what was requested.\n            blockHeader->size = size;\n            blockSize = size;\n\n            //\n            HeapBlockFooter* blockFooter = (HeapBlockFooter*)((unsigned char*)block + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n            blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD;\n\n            // Obtain beginning of the next block.\n            unsigned char* nextBlock = (unsigned char*)block + blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE;\n\n            // Setup next block's header...\n            HeapBlockHeader* nextBlockHeader = (HeapBlockHeader*)nextBlock;\n            nextBlockHeader->guard = HEAP_BLOCK_HEADER_GUARD;\n            nextBlockHeader->size = remainingSize - HEAP_BLOCK_OVERHEAD_SIZE;\n            nextBlockHeader->state = HEAP_BLOCK_STATE_FREE;\n            nextBlockHeader->handle_index = -1;\n\n            // ... and footer.\n            HeapBlockFooter* nextBlockFooter = (HeapBlockFooter*)(nextBlock + nextBlockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n            nextBlockFooter->guard = HEAP_BLOCK_FOOTER_GUARD;\n\n            // Update heap stats\n            heap->freeBlocks++;\n            heap->freeSize -= HEAP_BLOCK_OVERHEAD_SIZE;\n        }\n\n        // Bind block to handle and mark it as moveable\n        blockHeader->state = HEAP_BLOCK_STATE_MOVABLE;\n        blockHeader->handle_index = handleIndex;\n\n        // Bind handle to block and mark it as moveable\n        handle->state = HEAP_BLOCK_STATE_MOVABLE;\n        handle->data = (unsigned char*)block;\n\n        // Update heap stats\n        heap->freeBlocks--;\n        heap->moveableBlocks++;\n        heap->freeSize -= blockSize;\n        heap->moveableSize += blockSize;\n\n        *handleIndexPtr = handleIndex;\n\n        return true;\n    }\n\n    // NOTE: Uninline.\n    heap_release_handle(heap, handleIndex);\n\n    debug_printf(\"Heap Error: Unknown block state during allocation.\\n\");\n\nerr_no_handle:\n\n    debug_printf(\"Heap Error: Could not acquire handle for new block.\\n\");\n    if (state == HEAP_BLOCK_STATE_SYSTEM) {\n        mem_free(block);\n    }\n\nerr:\n\n    debug_printf(\"Heap Warning: Could not allocate block of %d bytes.\\n\", size);\n    return false;\n}\n\n// 0x452CB4\nbool heap_deallocate(Heap* heap, int* handleIndexPtr)\n{\n    if (heap == NULL || handleIndexPtr == NULL) {\n        debug_printf(\"Heap Error: Could not deallocate block.\\n\");\n        return false;\n    }\n\n    int handleIndex = *handleIndexPtr;\n\n    HeapHandle* handle = &(heap->handles[handleIndex]);\n\n    HeapBlockHeader* blockHeader = (HeapBlockHeader*)handle->data;\n    if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) {\n        debug_printf(\"Heap Error: Bad guard begin detected during deallocate.\\n\");\n    }\n\n    HeapBlockFooter* blockFooter = (HeapBlockFooter*)(handle->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n    if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) {\n        debug_printf(\"Heap Error: Bad guard end detected during deallocate.\\n\");\n    }\n\n    if (handle->state != blockHeader->state) {\n        debug_printf(\"Heap Error: Mismatched block states detected during deallocate.\\n\");\n    }\n\n    if ((handle->state & HEAP_BLOCK_STATE_LOCKED) != 0) {\n        debug_printf(\"Heap Error: Attempt to deallocate locked block.\\n\");\n        return false;\n    }\n\n    int size = blockHeader->size;\n\n    if (handle->state == HEAP_BLOCK_STATE_MOVABLE) {\n        // Unbind block from handle and mark it as free.\n        blockHeader->handle_index = -1;\n        blockHeader->state = HEAP_BLOCK_STATE_FREE;\n\n        // Update heap stats\n        heap->freeBlocks++;\n        heap->moveableBlocks--;\n\n        heap->freeSize += size;\n        heap->moveableSize -= size;\n\n        // NOTE: Uninline.\n        heap_release_handle(heap, handleIndex);\n\n        return true;\n    }\n\n    if (handle->state == HEAP_BLOCK_STATE_SYSTEM) {\n        // Release system memory\n        mem_free(handle->data);\n\n        // Update heap stats\n        heap->systemBlocks--;\n        heap->systemSize -= size;\n\n        // NOTE: Uninline.\n        heap_release_handle(heap, handleIndex);\n\n        return true;\n    }\n\n    debug_printf(\"Heap Error: Unknown block state during deallocation.\\n\");\n    return false;\n}\n\n// 0x452DE0\nbool heap_lock(Heap* heap, int handleIndex, unsigned char** bufferPtr)\n{\n    if (heap == NULL) {\n        debug_printf(\"Heap Error: Could not lock block\");\n        return false;\n    }\n\n    HeapHandle* handle = &(heap->handles[handleIndex]);\n\n    HeapBlockHeader* blockHeader = (HeapBlockHeader*)handle->data;\n    if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) {\n        debug_printf(\"Heap Error: Bad guard begin detected during lock.\\n\");\n        return false;\n    }\n\n    HeapBlockFooter* blockFooter = (HeapBlockFooter*)(handle->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n    if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) {\n        debug_printf(\"Heap Error: Bad guard end detected during lock.\\n\");\n        return false;\n    }\n\n    if (handle->state != blockHeader->state) {\n        debug_printf(\"Heap Error: Mismatched block states detected during lock.\\n\");\n        return false;\n    }\n\n    if ((handle->state & HEAP_BLOCK_STATE_LOCKED) != 0) {\n        debug_printf(\"Heap Error: Attempt to lock a previously locked block.\");\n        return false;\n    }\n\n    if (handle->state == HEAP_BLOCK_STATE_MOVABLE) {\n        blockHeader->state = HEAP_BLOCK_STATE_LOCKED;\n        handle->state = HEAP_BLOCK_STATE_LOCKED;\n\n        heap->moveableBlocks--;\n        heap->lockedBlocks++;\n\n        int size = blockHeader->size;\n        heap->moveableSize -= size;\n        heap->lockedSize += size;\n\n        *bufferPtr = handle->data + HEAP_BLOCK_HEADER_SIZE;\n\n        return true;\n    }\n\n    if (handle->state == HEAP_BLOCK_STATE_SYSTEM) {\n        blockHeader->state |= HEAP_BLOCK_STATE_LOCKED;\n        handle->state |= HEAP_BLOCK_STATE_LOCKED;\n\n        *bufferPtr = handle->data + HEAP_BLOCK_HEADER_SIZE;\n\n        return true;\n    }\n\n    debug_printf(\"Heap Error: Unknown block state during lock.\\n\");\n    return false;\n}\n\n// 0x452EE4\nbool heap_unlock(Heap* heap, int handleIndex)\n{\n    if (heap == NULL) {\n        debug_printf(\"Heap Error: Could not unlock block.\\n\");\n        return false;\n    }\n\n    HeapHandle* handle = &(heap->handles[handleIndex]);\n\n    HeapBlockHeader* blockHeader = (HeapBlockHeader*)handle->data;\n    if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) {\n        debug_printf(\"Heap Error: Bad guard begin detected during unlock.\\n\");\n    }\n\n    HeapBlockFooter* blockFooter = (HeapBlockFooter*)(handle->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n    if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) {\n        debug_printf(\"Heap Error: Bad guard end detected during unlock.\\n\");\n    }\n\n    if (handle->state != blockHeader->state) {\n        debug_printf(\"Heap Error: Mismatched block states detected during unlock.\\n\");\n    }\n\n    if ((handle->state & HEAP_BLOCK_STATE_LOCKED) == 0) {\n        debug_printf(\"Heap Error: Attempt to unlock a previously unlocked block.\\n\");\n        debug_printf(\"Heap Error: Could not unlock block.\\n\");\n        return false;\n    }\n\n    if ((handle->state & HEAP_BLOCK_STATE_SYSTEM) != 0) {\n        blockHeader->state = HEAP_BLOCK_STATE_SYSTEM;\n        handle->state = HEAP_BLOCK_STATE_SYSTEM;\n        return true;\n    }\n\n    blockHeader->state = HEAP_BLOCK_STATE_MOVABLE;\n    handle->state = HEAP_BLOCK_STATE_MOVABLE;\n\n    heap->moveableBlocks++;\n    heap->lockedBlocks--;\n\n    int size = blockHeader->size;\n    heap->moveableSize += size;\n    heap->lockedSize -= size;\n\n    return true;\n}\n\n// 0x452FC4\nbool heap_validate(Heap* heap)\n{\n    debug_printf(\"Validating heap...\\n\");\n\n    int blocksCount = heap->freeBlocks + heap->moveableBlocks + heap->lockedBlocks;\n    unsigned char* ptr = heap->data;\n\n    int freeBlocks = 0;\n    int freeSize = 0;\n    int moveableBlocks = 0;\n    int moveableSize = 0;\n    int lockedBlocks = 0;\n    int lockedSize = 0;\n\n    for (int index = 0; index < blocksCount; index++) {\n        HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr;\n        if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) {\n            debug_printf(\"Bad guard begin detected during validate.\\n\");\n            return false;\n        }\n\n        HeapBlockFooter* blockFooter = (HeapBlockFooter*)(ptr + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n        if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) {\n            debug_printf(\"Bad guard end detected during validate.\\n\");\n            return false;\n        }\n\n        if (blockHeader->state == HEAP_BLOCK_STATE_FREE) {\n            freeBlocks++;\n            freeSize += blockHeader->size;\n        } else if (blockHeader->state == HEAP_BLOCK_STATE_MOVABLE) {\n            moveableBlocks++;\n            moveableSize += blockHeader->size;\n        } else if (blockHeader->state == HEAP_BLOCK_STATE_LOCKED) {\n            lockedBlocks++;\n            lockedSize += blockHeader->size;\n        }\n\n        if (index != blocksCount - 1) {\n            ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE;\n            if (ptr > (heap->data + heap->size)) {\n                debug_printf(\"Ran off end of heap during validate!\\n\");\n                return false;\n            }\n        }\n    }\n\n    if (freeBlocks != heap->freeBlocks) {\n        debug_printf(\"Invalid number of free blocks.\\n\");\n        return false;\n    }\n\n    if (freeSize != heap->freeSize) {\n        debug_printf(\"Invalid size of free blocks.\\n\");\n        return false;\n    }\n\n    if (moveableBlocks != heap->moveableBlocks) {\n        debug_printf(\"Invalid number of moveable blocks.\\n\");\n        return false;\n    }\n\n    if (moveableSize != heap->moveableSize) {\n        debug_printf(\"Invalid size of moveable blocks.\\n\");\n        return false;\n    }\n\n    if (lockedBlocks != heap->lockedBlocks) {\n        debug_printf(\"Invalid number of locked blocks.\\n\");\n        return false;\n    }\n\n    if (lockedSize != heap->lockedSize) {\n        debug_printf(\"Invalid size of locked blocks.\\n\");\n        return false;\n    }\n\n    debug_printf(\"Heap is O.K.\\n\");\n\n    int systemBlocks = 0;\n    int systemSize = 0;\n\n    for (int handleIndex = 0; handleIndex < heap->handlesLength; handleIndex++) {\n        HeapHandle* handle = &(heap->handles[handleIndex]);\n        if (handle->state != HEAP_HANDLE_STATE_INVALID && (handle->state & HEAP_BLOCK_STATE_SYSTEM) != 0) {\n            HeapBlockHeader* blockHeader = (HeapBlockHeader*)handle->data;\n            if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) {\n                debug_printf(\"Bad guard begin detected in system block during validate.\\n\");\n                return false;\n            }\n\n            HeapBlockFooter* blockFooter = (HeapBlockFooter*)(handle->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n            if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) {\n                debug_printf(\"Bad guard end detected in system block during validate.\\n\");\n                return false;\n            }\n\n            systemBlocks++;\n            systemSize += blockHeader->size;\n        }\n    }\n\n    if (systemBlocks != heap->systemBlocks) {\n        debug_printf(\"Invalid number of system blocks.\\n\");\n        return false;\n    }\n\n    if (systemSize != heap->systemSize) {\n        debug_printf(\"Invalid size of system blocks.\\n\");\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4532AC\nbool heap_stats(Heap* heap, char* dest)\n{\n    if (heap == NULL || dest == NULL) {\n        return false;\n    }\n\n    const char* format = \"[Heap]\\n\"\n                         \"Total free blocks: %d\\n\"\n                         \"Total free size: %d\\n\"\n                         \"Total moveable blocks: %d\\n\"\n                         \"Total moveable size: %d\\n\"\n                         \"Total locked blocks: %d\\n\"\n                         \"Total locked size: %d\\n\"\n                         \"Total system blocks: %d\\n\"\n                         \"Total system size: %d\\n\"\n                         \"Total handles: %d\\n\"\n                         \"Total heaps: %d\";\n\n    sprintf(dest, format,\n        heap->freeBlocks,\n        heap->freeSize,\n        heap->moveableBlocks,\n        heap->moveableSize,\n        heap->lockedBlocks,\n        heap->lockedSize,\n        heap->systemBlocks,\n        heap->systemSize,\n        heap->handlesLength,\n        heap_count);\n\n    return true;\n}\n\n// 0x453304\nstatic bool heap_create_lists()\n{\n    // NOTE: Original code is slightly different. It uses deep nesting or a\n    // bunch of goto's to free alloc'ed buffers one by one starting from where\n    // it has failed.\n    do {\n        heap_free_list = (unsigned char**)mem_malloc(sizeof(*heap_free_list) * HEAP_FREE_BLOCKS_INITIAL_LENGTH);\n        if (heap_free_list == NULL) {\n            break;\n        }\n\n        heap_free_list_size = HEAP_FREE_BLOCKS_INITIAL_LENGTH;\n\n        heap_moveable_list = (HeapMoveableExtent*)mem_malloc(sizeof(*heap_moveable_list) * HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH);\n        if (heap_moveable_list == NULL) {\n            break;\n        }\n        heap_moveable_list_size = HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH;\n\n        heap_subblock_list = (unsigned char**)mem_malloc(sizeof(*heap_subblock_list) * HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH);\n        if (heap_subblock_list == NULL) {\n            break;\n        }\n        heap_subblock_list_size = HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH;\n\n        heap_fake_move_list = (int*)mem_malloc(sizeof(*heap_fake_move_list) * HEAP_RESERVED_FREE_BLOCK_INDEXES_INITIAL_LENGTH);\n        if (heap_fake_move_list == NULL) {\n            break;\n        }\n        heap_fake_move_list_size = HEAP_RESERVED_FREE_BLOCK_INDEXES_INITIAL_LENGTH;\n\n        return true;\n    } while (0);\n\n    // NOTE: Original code frees them one by one without calling this function.\n    heap_destroy_lists();\n\n    return false;\n}\n\n// 0x4533A0\nstatic void heap_destroy_lists()\n{\n    if (heap_fake_move_list != NULL) {\n        mem_free(heap_fake_move_list);\n        heap_fake_move_list = NULL;\n    }\n    heap_fake_move_list_size = 0;\n\n    if (heap_subblock_list != NULL) {\n        mem_free(heap_subblock_list);\n        heap_subblock_list = NULL;\n    }\n    heap_subblock_list_size = 0;\n\n    if (heap_moveable_list != NULL) {\n        mem_free(heap_moveable_list);\n        heap_moveable_list = NULL;\n    }\n    heap_moveable_list_size = 0;\n\n    if (heap_free_list != NULL) {\n        mem_free(heap_free_list);\n        heap_free_list = NULL;\n    }\n    heap_free_list_size = 0;\n}\n\n// 0x453430\nstatic bool heap_init_handles(Heap* heap)\n{\n    heap->handles = (HeapHandle*)mem_malloc(sizeof(*heap->handles) * HEAP_HANDLES_INITIAL_LENGTH);\n    if (heap->handles != NULL) {\n        // NOTE: Uninline.\n        if (heap_clear_handles(heap, heap->handles, HEAP_HANDLES_INITIAL_LENGTH) == true) {\n            heap->handlesLength = HEAP_HANDLES_INITIAL_LENGTH;\n            return true;\n        }\n        debug_printf(\"Heap Error: Could not allocate handles.\\n\");\n        return false;\n    }\n\n    debug_printf(\"Heap Error : Could not initialize handles.\\n\");\n    return false;\n}\n\n// NOTE: Inlined.\n//\n// 0x453480\nstatic bool heap_exit_handles(Heap* heap)\n{\n    if (heap->handles == NULL) {\n        return false;\n    }\n\n    mem_free(heap->handles);\n    heap->handles = NULL;\n    heap->handlesLength = 0;\n\n    return true;\n}\n\n// 0x4534B0\nstatic bool heap_acquire_handle(Heap* heap, int* handleIndexPtr)\n{\n    // Loop thru already available handles and find first that is not currently\n    // used.\n    for (int index = 0; index < heap->handlesLength; index++) {\n        HeapHandle* handle = &(heap->handles[index]);\n        if (handle->state == HEAP_HANDLE_STATE_INVALID) {\n            *handleIndexPtr = index;\n            return true;\n        }\n    }\n\n    // If we're here the search above failed, we have to allocate more handles.\n    HeapHandle* handles = (HeapHandle*)mem_realloc(heap->handles, sizeof(*handles) * (heap->handlesLength + HEAP_HANDLES_INITIAL_LENGTH));\n    if (handles == NULL) {\n        return false;\n    }\n\n    heap->handles = handles;\n\n    // NOTE: Uninline.\n    heap_clear_handles(heap, &(heap->handles[heap->handlesLength]), HEAP_HANDLES_INITIAL_LENGTH);\n\n    *handleIndexPtr = heap->handlesLength;\n\n    heap->handlesLength += HEAP_HANDLES_INITIAL_LENGTH;\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x453538\nstatic bool heap_release_handle(Heap* heap, int handleIndex)\n{\n    heap->handles[handleIndex].state = HEAP_HANDLE_STATE_INVALID;\n    heap->handles[handleIndex].data = NULL;\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x453558\nstatic bool heap_clear_handles(Heap* heap, HeapHandle* handles, unsigned int count)\n{\n    unsigned int index;\n\n    for (index = 0; index < count; index++) {\n        handles[index].state = HEAP_HANDLE_STATE_INVALID;\n        handles[index].data = NULL;\n    }\n\n    return true;\n}\n\n// 0x453588\nstatic bool heap_find_free_block(Heap* heap, int size, void** blockPtr, int a4)\n{\n    unsigned char* biggestFreeBlock;\n    HeapBlockHeader* biggestFreeBlockHeader;\n    int biggestFreeBlockSize;\n    HeapMoveableExtent* extent;\n    int reservedFreeBlockIndex;\n    HeapBlockHeader* blockHeader;\n    HeapBlockFooter* blockFooter;\n\n    if (!heap_build_free_list(heap)) {\n        goto system;\n    }\n\n    if (size > heap->freeSize) {\n        goto system;\n    }\n\n    // NOTE: Uninline.\n    heap_sort_free_list(heap);\n\n    // Take last free block (the biggest one).\n    biggestFreeBlock = heap_free_list[heap->freeBlocks - 1];\n    biggestFreeBlockHeader = (HeapBlockHeader*)biggestFreeBlock;\n    biggestFreeBlockSize = biggestFreeBlockHeader->size;\n\n    // Make sure it can encompass new block of given size.\n    if (biggestFreeBlockSize >= size) {\n        // Now loop thru all free blocks and find the first one that's at least\n        // as large as what was required.\n        int index;\n        for (index = 0; index < heap->freeBlocks; index++) {\n            unsigned char* block = heap_free_list[index];\n            HeapBlockHeader* blockHeader = (HeapBlockHeader*)block;\n            if (blockHeader->size >= size) {\n                break;\n            }\n        }\n\n        *blockPtr = heap_free_list[index];\n        return true;\n    }\n\n    int moveableExtentsCount;\n    int maxBlocksCount;\n    if (!heap_build_moveable_list(heap, &moveableExtentsCount, &maxBlocksCount)) {\n        goto system;\n    }\n\n    // Ensure the length of [heap_fake_move_list] array is big enough\n    // to index all blocks for longest moveable extent.\n    // NOTE: Uninline.\n    if (!heap_build_fake_move_list(maxBlocksCount)) {\n        goto system;\n    }\n\n    // NOTE: Uninline.\n    heap_sort_moveable_list(heap, moveableExtentsCount);\n\n    if (moveableExtentsCount == 0) {\n        goto system;\n    }\n\n    // Loop thru moveable extents and find first one which is big enough for new\n    // block and for which we can move every block somewhere.\n    int extentIndex;\n    for (extentIndex = 0; extentIndex < moveableExtentsCount; extentIndex++) {\n        HeapMoveableExtent* extent = &(heap_moveable_list[extentIndex]);\n\n        // Calculate extent size including the size of the overhead. Exclude the\n        // size of one overhead for current block.\n        int extentSize = extent->size + HEAP_BLOCK_OVERHEAD_SIZE * extent->blocksLength - HEAP_BLOCK_OVERHEAD_SIZE;\n\n        // Make sure current extent is worth moving which means there will be\n        // enough size for new block of given size after moving current extent.\n        if (extentSize < size) {\n            continue;\n        }\n\n        if (!heap_build_subblock_list(extentIndex)) {\n            continue;\n        }\n\n        // Sort moveable blocks by size (smallest -> largest)\n        // NOTE: Uninline.\n        heap_sort_subblock_list(extent->moveableBlocksLength);\n\n        int reservedBlocksLength = 0;\n\n        // Loop thru sorted moveable blocks and build array of reservations.\n        for (int moveableBlockIndex = 0; moveableBlockIndex < extent->moveableBlocksLength; moveableBlockIndex++) {\n            // Grab current moveable block.\n            unsigned char* moveableBlock = heap_subblock_list[moveableBlockIndex];\n            HeapBlockHeader* moveableBlockHeader = (HeapBlockHeader*)moveableBlock;\n\n            // Make sure there is at least one free block that's big enough\n            // to encompass it.\n            if (biggestFreeBlockSize < moveableBlockHeader->size) {\n                continue;\n            }\n\n            // Loop thru sorted free blocks (smallest -> largest) and find\n            // first unreserved free block that can encompass current moveable\n            // block.\n            int freeBlockIndex;\n            for (freeBlockIndex = 0; freeBlockIndex < heap->freeBlocks; freeBlockIndex++) {\n                // Grab current free block.\n                unsigned char* freeBlock = heap_free_list[freeBlockIndex];\n                HeapBlockHeader* freeBlockHeader = (HeapBlockHeader*)freeBlock;\n\n                // Make sure it's size is enough for current moveable block.\n                if (freeBlockHeader->size < moveableBlockHeader->size) {\n                    continue;\n                }\n\n                // Make sure it's outside of the current extent, because free\n                // blocks inside it is already taken into account in\n                // `extentSize`.\n                if (freeBlock >= extent->data && freeBlock < extent->data + extentSize + HEAP_BLOCK_OVERHEAD_SIZE) {\n                    continue;\n                }\n\n                // Loop thru reserved free blocks to make to make sure we\n                // can take it.\n                int freeBlocksIndexesIndex;\n                for (freeBlocksIndexesIndex = 0; freeBlocksIndexesIndex < reservedBlocksLength; freeBlocksIndexesIndex++) {\n                    if (freeBlockIndex == heap_fake_move_list[freeBlocksIndexesIndex]) {\n                        // This free block was already reserved, there is no\n                        // need to continue.\n                        break;\n                    }\n                }\n\n                if (freeBlocksIndexesIndex == reservedBlocksLength) {\n                    // We've looked thru entire reserved free blocks array\n                    // and haven't found resevation. That means we can\n                    // reseve current free block, so stop further search.\n                    break;\n                }\n            }\n\n            if (freeBlockIndex == heap->freeBlocks) {\n                // We've looked thru entire free blocks array and haven't\n                // found suitable free block for current moveable block.\n                // Skip the rest of the search, since we want to move the\n                // entire extent and just found out that at least one block\n                // cannot be moved.\n                break;\n            }\n\n            // If we get this far, we've found suitable free block for\n            // current moveable block, save it for later usage.\n            heap_fake_move_list[reservedBlocksLength++] = freeBlockIndex;\n        }\n\n        if (reservedBlocksLength == extent->moveableBlocksLength) {\n            // We've reserved free block for every movable block in current\n            // extent.\n            break;\n        }\n    }\n\n    if (extentIndex == moveableExtentsCount) {\n        // We've looked thru entire moveable extents and haven't found one\n        // suitable for moving.\n        goto system;\n    }\n\n    extent = &(heap_moveable_list[extentIndex]);\n    reservedFreeBlockIndex = 0;\n    for (int moveableBlockIndex = 0; moveableBlockIndex < extent->moveableBlocksLength; moveableBlockIndex++) {\n        unsigned char* moveableBlock = heap_subblock_list[moveableBlockIndex];\n        HeapBlockHeader* moveableBlockHeader = (HeapBlockHeader*)moveableBlock;\n        int moveableBlockSize = moveableBlockHeader->size;\n        if (biggestFreeBlockSize < moveableBlockSize) {\n            continue;\n        }\n\n        unsigned char* freeBlock = heap_free_list[heap_fake_move_list[reservedFreeBlockIndex++]];\n        HeapBlockHeader* freeBlockHeader = (HeapBlockHeader*)freeBlock;\n        int freeBlockSize = freeBlockHeader->size;\n\n        memcpy(freeBlock, moveableBlock, moveableBlockSize + HEAP_BLOCK_OVERHEAD_SIZE);\n        heap->handles[freeBlockHeader->handle_index].data = freeBlock;\n\n        // Calculate remaining size of the free block after moving.\n        int remainingSize = freeBlockSize - moveableBlockSize;\n        if (remainingSize != 0) {\n            if (remainingSize < HEAP_BLOCK_MIN_SIZE) {\n                // The remaining size of the former free block is too small to\n                // become a new free block, merge it into the current one.\n                freeBlockHeader->size += remainingSize;\n                HeapBlockFooter* freeBlockFooter = (HeapBlockFooter*)(freeBlock + freeBlockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n                freeBlockFooter->guard = HEAP_BLOCK_FOOTER_GUARD;\n\n                // The remaining size of the free block was merged into moveable\n                // block, update heap stats accordingly.\n                heap->freeSize -= remainingSize;\n                heap->moveableSize += remainingSize;\n            } else {\n                // The remaining size is enough for a new block. The current\n                // block is already properly formatted - it's header and\n                // footer was copied from moveable block. Since this was a valid\n                // free block it also has it's footer already in place. So the\n                // only thing left is header.\n                unsigned char* nextFreeBlock = freeBlock + freeBlockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE;\n                HeapBlockHeader* nextFreeBlockHeader = (HeapBlockHeader*)nextFreeBlock;\n                nextFreeBlockHeader->state = HEAP_BLOCK_STATE_FREE;\n                nextFreeBlockHeader->handle_index = -1;\n                nextFreeBlockHeader->size = remainingSize - HEAP_BLOCK_OVERHEAD_SIZE;\n                nextFreeBlockHeader->guard = HEAP_BLOCK_HEADER_GUARD;\n\n                heap->freeBlocks++;\n                heap->freeSize -= HEAP_BLOCK_OVERHEAD_SIZE;\n            }\n        }\n    }\n\n    heap->freeBlocks -= extent->blocksLength - 1;\n    heap->freeSize += (extent->blocksLength - 1) * HEAP_BLOCK_OVERHEAD_SIZE;\n\n    // Create one free block from entire moveable extent.\n    blockHeader = (HeapBlockHeader*)extent->data;\n    blockHeader->guard = HEAP_BLOCK_HEADER_GUARD;\n    blockHeader->size = extent->size + (extent->blocksLength - 1) * HEAP_BLOCK_OVERHEAD_SIZE;\n    blockHeader->state = HEAP_BLOCK_STATE_FREE;\n    blockHeader->handle_index = -1;\n\n    blockFooter = (HeapBlockFooter*)(extent->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n    blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD;\n\n    *blockPtr = extent->data;\n\n    return true;\n\nsystem:\n\n    if (1) {\n        char stats[512];\n        if (heap_stats(heap, stats)) {\n            debug_printf(\"\\n%s\\n\", stats);\n        }\n\n        if (a4 == 0) {\n            debug_printf(\"Allocating block from system memory...\\n\");\n            unsigned char* block = (unsigned char*)mem_malloc(size + HEAP_BLOCK_OVERHEAD_SIZE);\n            if (block == NULL) {\n                debug_printf(\"fatal error: internal_malloc() failed in heap_find_free_block()!\\n\");\n                return false;\n            }\n\n            HeapBlockHeader* blockHeader = (HeapBlockHeader*)block;\n            blockHeader->guard = HEAP_BLOCK_HEADER_GUARD;\n            blockHeader->size = size;\n            blockHeader->state = HEAP_BLOCK_STATE_SYSTEM;\n            blockHeader->handle_index = -1;\n\n            HeapBlockFooter* blockFooter = (HeapBlockFooter*)(block + blockHeader->size + HEAP_BLOCK_HEADER_SIZE);\n            blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD;\n\n            *blockPtr = block;\n\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x453BC4\nstatic bool heap_build_free_list(Heap* heap)\n{\n    if (heap->freeBlocks == 0) {\n        return false;\n    }\n\n    if (heap->freeBlocks > heap_free_list_size) {\n        unsigned char** freeBlocks = (unsigned char**)mem_realloc(heap_free_list, sizeof(*freeBlocks) * heap->freeBlocks);\n        if (freeBlocks == NULL) {\n            return false;\n        }\n\n        heap_free_list = (unsigned char**)freeBlocks;\n        heap_free_list_size = heap->freeBlocks;\n    }\n\n    int blocksLength = heap->moveableBlocks + heap->freeBlocks + heap->lockedBlocks;\n\n    unsigned char* ptr = heap->data;\n\n    int freeBlockIndex = 0;\n    while (blocksLength != 0) {\n        if (freeBlockIndex >= heap->freeBlocks) {\n            break;\n        }\n\n        HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr;\n        if (blockHeader->state == HEAP_BLOCK_STATE_FREE) {\n            // Join consecutive free blocks if any.\n            while (blocksLength > 1) {\n                // Grab next block and check if's a free block.\n                HeapBlockHeader* nextBlockHeader = (HeapBlockHeader*)(ptr + blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE);\n                if (nextBlockHeader->state != HEAP_BLOCK_STATE_FREE) {\n                    break;\n                }\n\n                // Accumulate it's size plus size of the overhead in the main\n                // block.\n                blockHeader->size += nextBlockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE;\n\n                // Update heap stats, the free size increased because we've just\n                // remove overhead for one block.\n                heap->freeBlocks--;\n                heap->freeSize += HEAP_BLOCK_OVERHEAD_SIZE;\n\n                blocksLength--;\n            }\n\n            heap_free_list[freeBlockIndex++] = ptr;\n        }\n\n        // Move pointer to the header of the next block.\n        ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE;\n\n        blocksLength--;\n    }\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x453C9C\nstatic bool heap_sort_free_list(Heap* heap)\n{\n    if (heap->freeBlocks > 1) {\n        qsort(heap_free_list, heap->freeBlocks, sizeof(*heap_free_list), heap_qsort_compare_free);\n    }\n\n    return true;\n}\n\n// 0x453CC4\nstatic int heap_qsort_compare_free(const void* a1, const void* a2)\n{\n    HeapBlockHeader* header1 = *(HeapBlockHeader**)a1;\n    HeapBlockHeader* header2 = *(HeapBlockHeader**)a2;\n    return header1->size - header2->size;\n}\n\n// 0x453CD0\nstatic bool heap_build_moveable_list(Heap* heap, int* moveableExtentsLengthPtr, int* maxBlocksLengthPtr)\n{\n    // Calculate max number of extents. It's only possible when every\n    // free or moveable block is followed by locked block.\n    int maxExtentsCount = heap->moveableBlocks + heap->freeBlocks;\n    if (maxExtentsCount <= 2) {\n        debug_printf(\"<[couldn't build moveable list]>\\n\");\n        return false;\n    }\n\n    if (maxExtentsCount > heap_moveable_list_size) {\n        HeapMoveableExtent* moveableExtents = (HeapMoveableExtent*)mem_realloc(heap_moveable_list, sizeof(*heap_moveable_list) * maxExtentsCount);\n        if (moveableExtents == NULL) {\n            return false;\n        }\n\n        heap_moveable_list = moveableExtents;\n        heap_moveable_list_size = maxExtentsCount;\n    }\n\n    unsigned char* ptr = heap->data;\n    int blocksLength = heap->moveableBlocks + heap->freeBlocks + heap->lockedBlocks;\n    int maxBlocksLength = 0;\n    int extentIndex = 0;\n\n    while (blocksLength != 0) {\n        if (extentIndex >= maxExtentsCount) {\n            break;\n        }\n\n        HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr;\n        if (blockHeader->state == HEAP_BLOCK_STATE_FREE || blockHeader->state == HEAP_BLOCK_STATE_MOVABLE) {\n            HeapMoveableExtent* extent = &(heap_moveable_list[extentIndex++]);\n            extent->data = ptr;\n            extent->blocksLength = 1;\n            extent->moveableBlocksLength = 0;\n            extent->size = blockHeader->size;\n\n            if (blockHeader->state == HEAP_BLOCK_STATE_MOVABLE) {\n                extent->moveableBlocksLength = 1;\n            }\n\n            // Calculate moveable extent stats from consecutive blocks.\n            while (blocksLength > 1) {\n                // Grab next block and check if's a free or moveable block.\n                HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr;\n                HeapBlockHeader* nextBlockHeader = (HeapBlockHeader*)(ptr + blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE);\n                if (nextBlockHeader->state != HEAP_BLOCK_STATE_FREE && nextBlockHeader->state != HEAP_BLOCK_STATE_MOVABLE) {\n                    break;\n                }\n\n                // Update extent stats.\n                extent->blocksLength++;\n                extent->size += nextBlockHeader->size;\n\n                if (nextBlockHeader->state == HEAP_BLOCK_STATE_MOVABLE) {\n                    extent->moveableBlocksLength++;\n                }\n\n                // Move pointer to the beginning of the next block.\n                ptr += (blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE);\n\n                blocksLength--;\n            }\n\n            if (extent->blocksLength > maxBlocksLength) {\n                maxBlocksLength = extent->blocksLength;\n            }\n        }\n\n        // ptr might have been advanced during the loop above.\n        blockHeader = (HeapBlockHeader*)ptr;\n        ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE;\n\n        blocksLength--;\n    };\n\n    *moveableExtentsLengthPtr = extentIndex;\n    *maxBlocksLengthPtr = maxBlocksLength;\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x453E54\nstatic bool heap_sort_moveable_list(Heap* heap, size_t count)\n{\n    qsort(heap_moveable_list, count, sizeof(*heap_moveable_list), heap_qsort_compare_moveable);\n\n    return true;\n}\n\n// 0x453E74\nstatic int heap_qsort_compare_moveable(const void* a1, const void* a2)\n{\n    HeapMoveableExtent* v1 = (HeapMoveableExtent*)a1;\n    HeapMoveableExtent* v2 = (HeapMoveableExtent*)a2;\n    return v1->size - v2->size;\n}\n\n// Build list of pointers to moveable blocks in given extent.\n//\n// 0x453E80\nstatic bool heap_build_subblock_list(int extentIndex)\n{\n    HeapMoveableExtent* extent = &(heap_moveable_list[extentIndex]);\n    if (extent->moveableBlocksLength > heap_subblock_list_size) {\n        unsigned char** moveableBlocks = (unsigned char**)mem_realloc(heap_subblock_list, sizeof(*heap_subblock_list) * extent->moveableBlocksLength);\n        if (moveableBlocks == NULL) {\n            return false;\n        }\n\n        heap_subblock_list = moveableBlocks;\n        heap_subblock_list_size = extent->moveableBlocksLength;\n    }\n\n    unsigned char* ptr = extent->data;\n    int moveableBlockIndex = 0;\n    for (int index = 0; index < extent->blocksLength; index++) {\n        HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr;\n        if (blockHeader->state == HEAP_BLOCK_STATE_MOVABLE) {\n            heap_subblock_list[moveableBlockIndex++] = ptr;\n        }\n        ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE;\n    }\n\n    return moveableBlockIndex == extent->moveableBlocksLength;\n}\n\n// NOTE: Inlined.\n//\n// 0x453F24\nstatic bool heap_sort_subblock_list(size_t count)\n{\n    qsort(heap_subblock_list, count, sizeof(*heap_subblock_list), heap_qsort_compare_subblock);\n\n    return true;\n}\n\n// NOTE: Uncollapsed 0x453CC4.\nstatic int heap_qsort_compare_subblock(const void* a1, const void* a2)\n{\n    HeapBlockHeader* header1 = *(HeapBlockHeader**)a1;\n    HeapBlockHeader* header2 = *(HeapBlockHeader**)a2;\n    return header1->size - header2->size;\n}\n\n// NOTE: Inlined.\n//\n// 0x453F4C\nstatic bool heap_build_fake_move_list(size_t count)\n{\n    if (count > heap_fake_move_list_size) {\n        int* indexes = (int*)mem_realloc(heap_fake_move_list, sizeof(*heap_fake_move_list) * count);\n        if (indexes == NULL) {\n            return false;\n        }\n\n        heap_fake_move_list_size = count;\n        heap_fake_move_list = indexes;\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/game/heap.h",
    "content": "#ifndef FALLOUT_GAME_HEAP_H_\n#define FALLOUT_GAME_HEAP_H_\n\n#include <stdbool.h>\n\ntypedef struct HeapHandle {\n    unsigned int state;\n    unsigned char* data;\n} HeapHandle;\n\ntypedef struct Heap {\n    int size;\n    int freeBlocks;\n    int moveableBlocks;\n    int lockedBlocks;\n    int systemBlocks;\n    int handlesLength;\n    int freeSize;\n    int moveableSize;\n    int lockedSize;\n    int systemSize;\n    HeapHandle* handles;\n    unsigned char* data;\n} Heap;\n\nbool heap_init(Heap* heap, int a2);\nbool heap_exit(Heap* heap);\nbool heap_allocate(Heap* heap, int* handleIndexPtr, int size, int a3);\nbool heap_deallocate(Heap* heap, int* handleIndexPtr);\nbool heap_lock(Heap* heap, int handleIndex, unsigned char** bufferPtr);\nbool heap_unlock(Heap* heap, int handleIndex);\nbool heap_stats(Heap* heap, char* dest);\nbool heap_validate(Heap* heap);\n\n#endif /* FALLOUT_GAME_HEAP_H_ */\n"
  },
  {
    "path": "src/game/intface.c",
    "content": "#include \"game/intface.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"game/anim.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/config.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/endgame.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/item.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/proto_types.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define INDICATOR_BAR_X 0\n#define INDICATOR_BAR_Y 358\n\n// The width of connectors in the indicator box.\n//\n// There are male connectors on the left, and female connectors on the right.\n// When displaying series of boxes they appear to be plugged into a chain.\n#define INDICATOR_BOX_CONNECTOR_WIDTH 3\n\n// Minimum radiation amount to display RADIATED indicator.\n#define RADATION_INDICATOR_THRESHOLD 65\n\n// Minimum poison amount to display POISONED indicator.\n#define POISON_INDICATOR_THRESHOLD 0\n\n// The values of it's members are offsets to beginning of numbers in\n// numbers.frm.\ntypedef enum InterfaceNumbersColor {\n    INTERFACE_NUMBERS_COLOR_WHITE = 0,\n    INTERFACE_NUMBERS_COLOR_YELLOW = 120,\n    INTERFACE_NUMBERS_COLOR_RED = 240,\n} InterfaceNumbersColor;\n\n#define INDICATOR_BOX_WIDTH 130\n#define INDICATOR_BOX_HEIGHT 21\n\n// The maximum number of indicator boxes the indicator bar can display.\n//\n// For unknown reason this number is 6, even though there are only 5 different\n// indicator types. In addition to that, default screen width 640px cannot hold\n// 6 boxes 130px each.\n#define INDICATOR_SLOTS_COUNT (6)\n\n// Available indicators.\n//\n// Indicator boxes in the bar are displayed according to the order of this enum.\ntypedef enum Indicator {\n    INDICATOR_ADDICT,\n    INDICATOR_SNEAK,\n    INDICATOR_LEVEL,\n    INDICATOR_POISONED,\n    INDICATOR_RADIATED,\n    INDICATOR_COUNT,\n} Indicator;\n\n// Provides metadata about indicator boxes.\ntypedef struct IndicatorDescription {\n    // An identifier of title in `intrface.msg`.\n    int title;\n\n    // A flag denoting this box represents something harmful to the player. It\n    // affects color of the title.\n    bool isBad;\n\n    // Prerendered indicator data.\n    //\n    // This value is provided at runtime during indicator box initialization.\n    // It includes indicator box background with it's title positioned in the\n    // center and is green colored if indicator is good, or red otherwise, as\n    // denoted by [isBad] property.\n    unsigned char* data;\n} IndicatorDescription;\n\ntypedef struct InterfaceItemState {\n    Object* item;\n    unsigned char isDisabled;\n    unsigned char isWeapon;\n    int primaryHitMode;\n    int secondaryHitMode;\n    int action;\n    int itemFid;\n} InterfaceItemState;\n\nstatic int intface_init_items();\nstatic int intface_redraw_items();\nstatic int intface_redraw_items_callback(Object* a1, Object* a2);\nstatic int intface_change_fid_callback(Object* a1, Object* a2);\nstatic void intface_change_fid_animate(int previousWeaponAnimationCode, int weaponAnimationCode);\nstatic int intface_create_end_turn_button();\nstatic int intface_destroy_end_turn_button();\nstatic int intface_create_end_combat_button();\nstatic int intface_destroy_end_combat_button();\nstatic void intface_draw_ammo_lights(int x, int ratio);\nstatic int intface_item_reload();\nstatic void intface_rotate_numbers(int x, int y, int previousValue, int value, int offset, int delay);\nstatic int intface_fatal_error(int rc);\nstatic int construct_box_bar_win();\nstatic void deconstruct_box_bar_win();\nstatic void reset_box_bar_win();\nstatic int bbox_comp(const void* a, const void* b);\nstatic void draw_bboxes(int count);\nstatic bool add_bar_box(int indicator);\n\n// 0x518F08\nstatic bool insideInit = false;\n\n// 0x518F0C\nstatic bool intface_fid_is_changing = false;\n\n// 0x518F10\nstatic bool intfaceEnabled = false;\n\n// 0x518F14\nstatic bool intfaceHidden = false;\n\n// 0x518F18\nstatic int inventoryButton = -1;\n\n// 0x518F1C\nstatic CacheEntry* inventoryButtonUpKey = NULL;\n\n// 0x518F20\nstatic CacheEntry* inventoryButtonDownKey = NULL;\n\n// 0x518F24\nstatic int optionsButton = -1;\n\n// 0x518F28\nstatic CacheEntry* optionsButtonUpKey = NULL;\n\n// 0x518F2C\nstatic CacheEntry* optionsButtonDownKey = NULL;\n\n// 0x518F30\nstatic int skilldexButton = -1;\n\n// 0x518F34\nstatic CacheEntry* skilldexButtonUpKey = NULL;\n\n// 0x518F38\nstatic CacheEntry* skilldexButtonDownKey = NULL;\n\n// 0x518F3C\nstatic CacheEntry* skilldexButtonMaskKey = NULL;\n\n// 0x518F40\nstatic int automapButton = -1;\n\n// 0x518F44\nstatic CacheEntry* automapButtonUpKey = NULL;\n\n// 0x518F48\nstatic CacheEntry* automapButtonDownKey = NULL;\n\n// 0x518F4C\nstatic CacheEntry* automapButtonMaskKey = NULL;\n\n// 0x518F50\nstatic int pipboyButton = -1;\n\n// 0x518F54\nstatic CacheEntry* pipboyButtonUpKey = NULL;\n\n// 0x518F58\nstatic CacheEntry* pipboyButtonDownKey = NULL;\n\n// 0x518F5C\nstatic int characterButton = -1;\n\n// 0x518F60\nstatic CacheEntry* characterButtonUpKey = NULL;\n\n// 0x518F64\nstatic CacheEntry* characterButtonDownKey = NULL;\n\n// 0x518F68\nstatic int itemButton = -1;\n\n// 0x518F6C\nstatic CacheEntry* itemButtonUpKey = NULL;\n\n// 0x518F70\nstatic CacheEntry* itemButtonDownKey = NULL;\n\n// 0x518F74\nstatic CacheEntry* itemButtonDisabledKey = NULL;\n\n// 0x518F78\nstatic int itemCurrentItem = HAND_LEFT;\n\n// 0x518F7C\nstatic Rect itemButtonRect = { 267, 26, 455, 93 };\n\n// 0x518F8C\nstatic int toggleButton = -1;\n\n// 0x518F90\nstatic CacheEntry* toggleButtonUpKey = NULL;\n\n// 0x518F94\nstatic CacheEntry* toggleButtonDownKey = NULL;\n\n// 0x518F98\nstatic CacheEntry* toggleButtonMaskKey = NULL;\n\n// 0x518F9C\nstatic bool endWindowOpen = false;\n\n// Combat mode curtains rect.\n//\n// 0x518FA0\nstatic Rect endWindowRect = { 580, 38, 637, 96 };\n\n// 0x518FB0\nstatic int endTurnButton = -1;\n\n// 0x518FB4\nstatic CacheEntry* endTurnButtonUpKey = NULL;\n\n// 0x518FB8\nstatic CacheEntry* endTurnButtonDownKey = NULL;\n\n// 0x518FBC\nstatic int endCombatButton = -1;\n\n// 0x518FC0\nstatic CacheEntry* endCombatButtonUpKey = NULL;\n\n// 0x518FC4\nstatic CacheEntry* endCombatButtonDownKey = NULL;\n\n// 0x518FC8\nstatic unsigned char* moveLightGreen = NULL;\n\n// 0x518FCC\nstatic unsigned char* moveLightYellow = NULL;\n\n// 0x518FD0\nstatic unsigned char* moveLightRed = NULL;\n\n// 0x518FD4\nstatic Rect movePointRect = { 316, 14, 406, 19 };\n\n// 0x518FE4\nstatic unsigned char* numbersBuffer = NULL;\n\n// 0x518FE8\nstatic IndicatorDescription bbox[INDICATOR_COUNT] = {\n    { 102, true, NULL }, // ADDICT\n    { 100, false, NULL }, // SNEAK\n    { 101, false, NULL }, // LEVEL\n    { 103, true, NULL }, // POISONED\n    { 104, true, NULL }, // RADIATED\n};\n\n// 0x519024\nint interfaceWindow = -1;\n\n// 0x519028\nint bar_window = -1;\n\n// Each slot contains one of indicators or -1 if slot is empty.\n//\n// 0x5970E0\nstatic int bboxslot[INDICATOR_SLOTS_COUNT];\n\n// 0x5970F8\nstatic InterfaceItemState itemButtonItems[HAND_COUNT];\n\n// 0x597128\nstatic CacheEntry* moveLightYellowKey;\n\n// 0x59712C\nstatic CacheEntry* moveLightRedKey;\n\n// 0x597130\nstatic CacheEntry* numbersKey;\n\n// 0x597138\nstatic bool box_status_flag;\n\n// 0x59713C\nstatic unsigned char* toggleButtonUp;\n\n// 0x597140\nstatic CacheEntry* moveLightGreenKey;\n\n// 0x597144\nstatic unsigned char* endCombatButtonUp;\n\n// 0x597148\nstatic unsigned char* endCombatButtonDown;\n\n// 0x59714C\nstatic unsigned char* toggleButtonDown;\n\n// 0x597150\nstatic unsigned char* endTurnButtonDown;\n\n// 0x597154\nstatic unsigned char itemButtonDown[188 * 67];\n\n// 0x59A288\nstatic unsigned char* endTurnButtonUp;\n\n// 0x59A28C\nstatic unsigned char* toggleButtonMask;\n\n// 0x59A290\nstatic unsigned char* characterButtonUp;\n\n// 0x59A294\nstatic unsigned char* itemButtonUpBlank;\n\n// 0x59A298\nstatic unsigned char* itemButtonDisabled;\n\n// 0x59A29C\nstatic unsigned char* automapButtonDown;\n\n// 0x59A2A0\nstatic unsigned char* pipboyButtonUp;\n\n// 0x59A2A4\nstatic unsigned char* characterButtonDown;\n\n// 0x59A2A8\nstatic unsigned char* itemButtonDownBlank;\n\n// 0x59A2AC\nstatic unsigned char* pipboyButtonDown;\n\n// 0x59A2B0\nstatic unsigned char* automapButtonMask;\n\n// 0x59A2B4\nstatic unsigned char itemButtonUp[188 * 67];\n\n// 0x59D3E8\nstatic unsigned char* automapButtonUp;\n\n// 0x59D3EC\nstatic unsigned char* skilldexButtonMask;\n\n// 0x59D3F0\nstatic unsigned char* skilldexButtonDown;\n\n// 0x59D3F4\nstatic unsigned char* interfaceBuffer;\n\n// 0x59D3F8\nstatic unsigned char* inventoryButtonUp;\n\n// 0x59D3FC\nstatic unsigned char* optionsButtonUp;\n\n// 0x59D400\nstatic unsigned char* optionsButtonDown;\n\n// 0x59D404\nstatic unsigned char* skilldexButtonUp;\n\n// 0x59D408\nstatic unsigned char* inventoryButtonDown;\n\n// A slice of main interface background containing 10 shadowed action point\n// dots. In combat mode individual colored dots are rendered on top of this\n// background.\n//\n// This buffer is initialized once and does not change throughout the game.\n//\n// 0x59D40C\nstatic unsigned char movePointBackground[90 * 5];\n\n// 0x45D880\nint intface_init()\n{\n    int fid;\n    CacheEntry* backgroundFrmHandle;\n    unsigned char* backgroundFrmData;\n\n    if (interfaceWindow != -1) {\n        return -1;\n    }\n\n    insideInit = 1;\n\n    int interfaceBarWindowX = 0;\n    int interfaceBarWindowY = 480 - INTERFACE_BAR_HEIGHT - 1;\n\n    interfaceWindow = win_add(interfaceBarWindowX, interfaceBarWindowY, INTERFACE_BAR_WIDTH, INTERFACE_BAR_HEIGHT, colorTable[0], WINDOW_HIDDEN);\n    if (interfaceWindow == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    interfaceBuffer = win_get_buf(interfaceWindow);\n    if (interfaceBuffer == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 16, 0, 0, 0);\n    backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle);\n    if (backgroundFrmData == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    buf_to_buf(backgroundFrmData, INTERFACE_BAR_WIDTH, INTERFACE_BAR_HEIGHT - 1, INTERFACE_BAR_WIDTH, interfaceBuffer, 640);\n    art_ptr_unlock(backgroundFrmHandle);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 47, 0, 0, 0);\n    inventoryButtonUp = art_ptr_lock_data(fid, 0, 0, &inventoryButtonUpKey);\n    if (inventoryButtonUp == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 46, 0, 0, 0);\n    inventoryButtonDown = art_ptr_lock_data(fid, 0, 0, &inventoryButtonDownKey);\n    if (inventoryButtonDown == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    inventoryButton = win_register_button(interfaceWindow, 211, 41, 32, 21, -1, -1, -1, KEY_LOWERCASE_I, inventoryButtonUp, inventoryButtonDown, NULL, 0);\n    if (inventoryButton == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    win_register_button_sound_func(inventoryButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 18, 0, 0, 0);\n    optionsButtonUp = art_ptr_lock_data(fid, 0, 0, &optionsButtonUpKey);\n    if (optionsButtonUp == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 17, 0, 0, 0);\n    optionsButtonDown = art_ptr_lock_data(fid, 0, 0, &optionsButtonDownKey);\n    if (optionsButtonDown == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    optionsButton = win_register_button(interfaceWindow, 210, 62, 34, 34, -1, -1, -1, KEY_LOWERCASE_O, optionsButtonUp, optionsButtonDown, NULL, 0);\n    if (optionsButton == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    win_register_button_sound_func(optionsButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 6, 0, 0, 0);\n    skilldexButtonUp = art_ptr_lock_data(fid, 0, 0, &skilldexButtonUpKey);\n    if (skilldexButtonUp == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 7, 0, 0, 0);\n    skilldexButtonDown = art_ptr_lock_data(fid, 0, 0, &skilldexButtonDownKey);\n    if (skilldexButtonDown == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 6, 0, 0, 0);\n    skilldexButtonMask = art_ptr_lock_data(fid, 0, 0, &skilldexButtonMaskKey);\n    if (skilldexButtonMask == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    skilldexButton = win_register_button(interfaceWindow, 523, 6, 22, 21, -1, -1, -1, KEY_LOWERCASE_S, skilldexButtonUp, skilldexButtonDown, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (skilldexButton == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    win_register_button_mask(skilldexButton, skilldexButtonMask);\n    win_register_button_sound_func(skilldexButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 13, 0, 0, 0);\n    automapButtonUp = art_ptr_lock_data(fid, 0, 0, &automapButtonUpKey);\n    if (automapButtonUp == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 10, 0, 0, 0);\n    automapButtonDown = art_ptr_lock_data(fid, 0, 0, &automapButtonDownKey);\n    if (automapButtonDown == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 13, 0, 0, 0);\n    automapButtonMask = art_ptr_lock_data(fid, 0, 0, &automapButtonMaskKey);\n    if (automapButtonMask == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    automapButton = win_register_button(interfaceWindow, 526, 40, 41, 19, -1, -1, -1, KEY_TAB, automapButtonUp, automapButtonDown, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (automapButton == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    win_register_button_mask(automapButton, automapButtonMask);\n    win_register_button_sound_func(automapButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 59, 0, 0, 0);\n    pipboyButtonUp = art_ptr_lock_data(fid, 0, 0, &pipboyButtonUpKey);\n    if (pipboyButtonUp == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 58, 0, 0, 0);\n    pipboyButtonDown = art_ptr_lock_data(fid, 0, 0, &pipboyButtonDownKey);\n    if (pipboyButtonDown == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    pipboyButton = win_register_button(interfaceWindow, 526, 78, 41, 19, -1, -1, -1, KEY_LOWERCASE_P, pipboyButtonUp, pipboyButtonDown, NULL, 0);\n    if (pipboyButton == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    win_register_button_mask(pipboyButton, automapButtonMask);\n    win_register_button_sound_func(pipboyButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 57, 0, 0, 0);\n    characterButtonUp = art_ptr_lock_data(fid, 0, 0, &characterButtonUpKey);\n    if (characterButtonUp == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 56, 0, 0, 0);\n    characterButtonDown = art_ptr_lock_data(fid, 0, 0, &characterButtonDownKey);\n    if (characterButtonDown == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    characterButton = win_register_button(interfaceWindow, 526, 59, 41, 19, -1, -1, -1, KEY_LOWERCASE_C, characterButtonUp, characterButtonDown, NULL, 0);\n    if (characterButton == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    win_register_button_mask(characterButton, automapButtonMask);\n    win_register_button_sound_func(characterButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 32, 0, 0, 0);\n    itemButtonUpBlank = art_ptr_lock_data(fid, 0, 0, &itemButtonUpKey);\n    if (itemButtonUpBlank == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 31, 0, 0, 0);\n    itemButtonDownBlank = art_ptr_lock_data(fid, 0, 0, &itemButtonDownKey);\n    if (itemButtonDownBlank == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 73, 0, 0, 0);\n    itemButtonDisabled = art_ptr_lock_data(fid, 0, 0, &itemButtonDisabledKey);\n    if (itemButtonDisabled == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    memcpy(itemButtonUp, itemButtonUpBlank, sizeof(itemButtonUp));\n    memcpy(itemButtonDown, itemButtonDownBlank, sizeof(itemButtonDown));\n\n    itemButton = win_register_button(interfaceWindow, 267, 26, 188, 67, -1, -1, -1, -20, itemButtonUp, itemButtonDown, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (itemButton == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    win_register_right_button(itemButton, -1, KEY_LOWERCASE_N, NULL, NULL);\n    win_register_button_sound_func(itemButton, gsound_lrg_butt_press, gsound_lrg_butt_release);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 6, 0, 0, 0);\n    toggleButtonUp = art_ptr_lock_data(fid, 0, 0, &toggleButtonUpKey);\n    if (toggleButtonUp == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 7, 0, 0, 0);\n    toggleButtonDown = art_ptr_lock_data(fid, 0, 0, &toggleButtonDownKey);\n    if (toggleButtonDown == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 6, 0, 0, 0);\n    toggleButtonMask = art_ptr_lock_data(fid, 0, 0, &toggleButtonMaskKey);\n    if (toggleButtonMask == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    // Swap hands button\n    toggleButton = win_register_button(interfaceWindow, 218, 6, 22, 21, -1, -1, -1, KEY_LOWERCASE_B, toggleButtonUp, toggleButtonDown, NULL, BUTTON_FLAG_TRANSPARENT);\n    if (toggleButton == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    win_register_button_mask(toggleButton, toggleButtonMask);\n    win_register_button_sound_func(toggleButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 82, 0, 0, 0);\n    numbersBuffer = art_ptr_lock_data(fid, 0, 0, &numbersKey);\n    if (numbersBuffer == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 83, 0, 0, 0);\n    moveLightGreen = art_ptr_lock_data(fid, 0, 0, &moveLightGreenKey);\n    if (moveLightGreen == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 84, 0, 0, 0);\n    moveLightYellow = art_ptr_lock_data(fid, 0, 0, &moveLightYellowKey);\n    if (moveLightYellow == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 85, 0, 0, 0);\n    moveLightRed = art_ptr_lock_data(fid, 0, 0, &moveLightRedKey);\n    if (moveLightRed == NULL) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    buf_to_buf(interfaceBuffer + 640 * 14 + 316, 90, 5, 640, movePointBackground, 90);\n\n    if (construct_box_bar_win() == -1) {\n        // NOTE: Uninline.\n        return intface_fatal_error(-1);\n    }\n\n    itemCurrentItem = HAND_LEFT;\n\n    // NOTE: Uninline.\n    intface_init_items();\n\n    display_init();\n\n    intfaceEnabled = true;\n    insideInit = false;\n    intfaceHidden = 1;\n\n    return 0;\n}\n\n// 0x45E3D0\nvoid intface_reset()\n{\n    intface_enable();\n\n    // NOTE: Uninline.\n    intface_hide();\n\n    display_reset();\n\n    // NOTE: Uninline.\n    reset_box_bar_win();\n\n    itemCurrentItem = 0;\n}\n\n// 0x45E440\nvoid intface_exit()\n{\n    if (interfaceWindow != -1) {\n        display_exit();\n\n        if (moveLightRed != NULL) {\n            art_ptr_unlock(moveLightRedKey);\n            moveLightRed = NULL;\n        }\n\n        if (moveLightYellow != NULL) {\n            art_ptr_unlock(moveLightYellowKey);\n            moveLightYellow = NULL;\n        }\n\n        if (moveLightGreen != NULL) {\n            art_ptr_unlock(moveLightGreenKey);\n            moveLightGreen = NULL;\n        }\n\n        if (numbersBuffer != NULL) {\n            art_ptr_unlock(numbersKey);\n            numbersBuffer = NULL;\n        }\n\n        if (toggleButton != -1) {\n            win_delete_button(toggleButton);\n            toggleButton = -1;\n        }\n\n        if (toggleButtonMask != NULL) {\n            art_ptr_unlock(toggleButtonMaskKey);\n            toggleButtonMaskKey = NULL;\n            toggleButtonMask = NULL;\n        }\n\n        if (toggleButtonDown != NULL) {\n            art_ptr_unlock(toggleButtonDownKey);\n            toggleButtonDownKey = NULL;\n            toggleButtonDown = NULL;\n        }\n\n        if (toggleButtonUp != NULL) {\n            art_ptr_unlock(toggleButtonUpKey);\n            toggleButtonUpKey = NULL;\n            toggleButtonUp = NULL;\n        }\n\n        if (itemButton != -1) {\n            win_delete_button(itemButton);\n            itemButton = -1;\n        }\n\n        if (itemButtonDisabled != NULL) {\n            art_ptr_unlock(itemButtonDisabledKey);\n            itemButtonDisabledKey = NULL;\n            itemButtonDisabled = NULL;\n        }\n\n        if (itemButtonDownBlank != NULL) {\n            art_ptr_unlock(itemButtonDownKey);\n            itemButtonDownKey = NULL;\n            itemButtonDownBlank = NULL;\n        }\n\n        if (itemButtonUpBlank != NULL) {\n            art_ptr_unlock(itemButtonUpKey);\n            itemButtonUpKey = NULL;\n            itemButtonUpBlank = NULL;\n        }\n\n        if (characterButton != -1) {\n            win_delete_button(characterButton);\n            characterButton = -1;\n        }\n\n        if (characterButtonDown != NULL) {\n            art_ptr_unlock(characterButtonDownKey);\n            characterButtonDownKey = NULL;\n            characterButtonDown = NULL;\n        }\n\n        if (characterButtonUp != NULL) {\n            art_ptr_unlock(characterButtonUpKey);\n            characterButtonUpKey = NULL;\n            characterButtonUp = NULL;\n        }\n\n        if (pipboyButton != -1) {\n            win_delete_button(pipboyButton);\n            pipboyButton = -1;\n        }\n\n        if (pipboyButtonDown != NULL) {\n            art_ptr_unlock(pipboyButtonDownKey);\n            pipboyButtonDownKey = NULL;\n            pipboyButtonDown = NULL;\n        }\n\n        if (pipboyButtonUp != NULL) {\n            art_ptr_unlock(pipboyButtonUpKey);\n            pipboyButtonUpKey = NULL;\n            pipboyButtonUp = NULL;\n        }\n\n        if (automapButton != -1) {\n            win_delete_button(automapButton);\n            automapButton = -1;\n        }\n\n        if (automapButtonMask != NULL) {\n            art_ptr_unlock(automapButtonMaskKey);\n            automapButtonMaskKey = NULL;\n            automapButtonMask = NULL;\n        }\n\n        if (automapButtonDown != NULL) {\n            art_ptr_unlock(automapButtonDownKey);\n            automapButtonDownKey = NULL;\n            automapButtonDown = NULL;\n        }\n\n        if (automapButtonUp != NULL) {\n            art_ptr_unlock(automapButtonUpKey);\n            automapButtonUpKey = NULL;\n            automapButtonUp = NULL;\n        }\n\n        if (skilldexButton != -1) {\n            win_delete_button(skilldexButton);\n            skilldexButton = -1;\n        }\n\n        if (skilldexButtonMask != NULL) {\n            art_ptr_unlock(skilldexButtonMaskKey);\n            skilldexButtonMaskKey = NULL;\n            skilldexButtonMask = NULL;\n        }\n\n        if (skilldexButtonDown != NULL) {\n            art_ptr_unlock(skilldexButtonDownKey);\n            skilldexButtonDownKey = NULL;\n            skilldexButtonDown = NULL;\n        }\n\n        if (skilldexButtonUp != NULL) {\n            art_ptr_unlock(skilldexButtonUpKey);\n            skilldexButtonUpKey = NULL;\n            skilldexButtonUp = NULL;\n        }\n\n        if (optionsButton != -1) {\n            win_delete_button(optionsButton);\n            optionsButton = -1;\n        }\n\n        if (optionsButtonDown != NULL) {\n            art_ptr_unlock(optionsButtonDownKey);\n            optionsButtonDownKey = NULL;\n            optionsButtonDown = NULL;\n        }\n\n        if (optionsButtonUp != NULL) {\n            art_ptr_unlock(optionsButtonUpKey);\n            optionsButtonUpKey = NULL;\n            optionsButtonUp = NULL;\n        }\n\n        if (inventoryButton != -1) {\n            win_delete_button(inventoryButton);\n            inventoryButton = -1;\n        }\n\n        if (inventoryButtonDown != NULL) {\n            art_ptr_unlock(inventoryButtonDownKey);\n            inventoryButtonDownKey = NULL;\n            inventoryButtonDown = NULL;\n        }\n\n        if (inventoryButtonUp != NULL) {\n            art_ptr_unlock(inventoryButtonUpKey);\n            inventoryButtonUpKey = NULL;\n            inventoryButtonUp = NULL;\n        }\n\n        if (interfaceWindow != -1) {\n            win_delete(interfaceWindow);\n            interfaceWindow = -1;\n        }\n    }\n\n    deconstruct_box_bar_win();\n}\n\n// 0x45E860\nint intface_load(File* stream)\n{\n    if (interfaceWindow == -1) {\n        if (intface_init() == -1) {\n            return -1;\n        }\n    }\n\n    int interfaceBarEnabled;\n    if (db_freadInt(stream, &interfaceBarEnabled) == -1) return -1;\n\n    int v2;\n    if (db_freadInt(stream, &v2) == -1) return -1;\n\n    int interfaceCurrentHand;\n    if (db_freadInt(stream, &interfaceCurrentHand) == -1) return -1;\n\n    bool interfaceBarEndButtonsIsVisible;\n    if (fileReadBool(stream, &interfaceBarEndButtonsIsVisible) == -1) return -1;\n\n    if (!intfaceEnabled) {\n        intface_enable();\n    }\n\n    if (v2) {\n        // NOTE: Uninline.\n        intface_hide();\n    } else {\n        intface_show();\n    }\n\n    intface_update_hit_points(false);\n    intface_update_ac(false);\n\n    itemCurrentItem = interfaceCurrentHand;\n\n    intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n\n    if (interfaceBarEndButtonsIsVisible != endWindowOpen) {\n        if (interfaceBarEndButtonsIsVisible) {\n            intface_end_window_open(false);\n        } else {\n            intface_end_window_close(false);\n        }\n    }\n\n    if (!interfaceBarEnabled) {\n        intface_disable();\n    }\n\n    refresh_box_bar_win();\n\n    win_draw(interfaceWindow);\n\n    return 0;\n}\n\n// 0x45E988\nint intface_save(File* stream)\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    if (db_fwriteInt(stream, intfaceEnabled) == -1) return -1;\n    if (db_fwriteInt(stream, intfaceHidden) == -1) return -1;\n    if (db_fwriteInt(stream, itemCurrentItem) == -1) return -1;\n    if (db_fwriteInt(stream, endWindowOpen) == -1) return -1;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x45E9E0\nvoid intface_hide()\n{\n    if (interfaceWindow != -1) {\n        if (!intfaceHidden) {\n            win_hide(interfaceWindow);\n            intfaceHidden = 1;\n        }\n    }\n    refresh_box_bar_win();\n}\n\n// 0x45EA10\nvoid intface_show()\n{\n    if (interfaceWindow != -1) {\n        if (intfaceHidden) {\n            intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n            intface_update_hit_points(false);\n            intface_update_ac(false);\n            win_show(interfaceWindow);\n            intfaceHidden = false;\n        }\n    }\n    refresh_box_bar_win();\n}\n\n// NOTE: Unused.\n//\n// 0x45EA5C\nint intface_is_hidden()\n{\n    return intfaceHidden;\n}\n\n// 0x45EA64\nvoid intface_enable()\n{\n    if (!intfaceEnabled) {\n        win_enable_button(inventoryButton);\n        win_enable_button(optionsButton);\n        win_enable_button(skilldexButton);\n        win_enable_button(automapButton);\n        win_enable_button(pipboyButton);\n        win_enable_button(characterButton);\n\n        if (itemButtonItems[itemCurrentItem].isDisabled == 0) {\n            win_enable_button(itemButton);\n        }\n\n        win_enable_button(endTurnButton);\n        win_enable_button(endCombatButton);\n        display_enable();\n\n        intfaceEnabled = true;\n    }\n}\n\n// 0x45EAFC\nvoid intface_disable()\n{\n    if (intfaceEnabled) {\n        display_disable();\n        win_disable_button(inventoryButton);\n        win_disable_button(optionsButton);\n        win_disable_button(skilldexButton);\n        win_disable_button(automapButton);\n        win_disable_button(pipboyButton);\n        win_disable_button(characterButton);\n        if (itemButtonItems[itemCurrentItem].isDisabled == 0) {\n            win_disable_button(itemButton);\n        }\n        win_disable_button(endTurnButton);\n        win_disable_button(endCombatButton);\n        intfaceEnabled = false;\n    }\n}\n\n// 0x45EB90\nbool intface_is_enabled()\n{\n    return intfaceEnabled;\n}\n\n// 0x45EB98\nvoid intface_redraw()\n{\n    if (interfaceWindow != -1) {\n        intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n        intface_update_hit_points(false);\n        intface_update_ac(false);\n        refresh_box_bar_win();\n        win_draw(interfaceWindow);\n    }\n    refresh_box_bar_win();\n}\n\n// Render hit points.\n//\n// 0x45EBD8\nvoid intface_update_hit_points(bool animate)\n{\n    // Last hit points rendered in interface.\n    //\n    // Used to animate changes.\n    //\n    // 0x51902C\n    static int last_points = 0;\n\n    // Last color used to render hit points in interface.\n    //\n    // Used to animate changes.\n    //\n    // 0x519030\n    static int last_points_color = INTERFACE_NUMBERS_COLOR_RED;\n\n    if (interfaceWindow == -1) {\n        return;\n    }\n\n    int hp = critter_get_hits(obj_dude);\n    int maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n\n    int red = (int)((double)maxHp * 0.25);\n    int yellow = (int)((double)maxHp * 0.5);\n\n    int color;\n    if (hp < red) {\n        color = INTERFACE_NUMBERS_COLOR_RED;\n    } else if (hp < yellow) {\n        color = INTERFACE_NUMBERS_COLOR_YELLOW;\n    } else {\n        color = INTERFACE_NUMBERS_COLOR_WHITE;\n    }\n\n    int v1[4];\n    int v2[3];\n    int count = 1;\n\n    v1[0] = last_points;\n    v2[0] = last_points_color;\n\n    if (last_points_color != color) {\n        if (hp >= last_points) {\n            if (last_points < red && hp >= red) {\n                v1[count] = red;\n                v2[count] = INTERFACE_NUMBERS_COLOR_YELLOW;\n                count += 1;\n            }\n\n            if (last_points < yellow && hp >= yellow) {\n                v1[count] = yellow;\n                v2[count] = INTERFACE_NUMBERS_COLOR_WHITE;\n                count += 1;\n            }\n        } else {\n            if (last_points >= yellow && hp < yellow) {\n                v1[count] = yellow;\n                v2[count] = INTERFACE_NUMBERS_COLOR_YELLOW;\n                count += 1;\n            }\n\n            if (last_points >= red && hp < red) {\n                v1[count] = red;\n                v2[count] = INTERFACE_NUMBERS_COLOR_RED;\n                count += 1;\n            }\n        }\n    }\n\n    v1[count] = hp;\n\n    if (animate) {\n        int delay = 250 / (abs(last_points - hp) + 1);\n        for (int index = 0; index < count; index++) {\n            intface_rotate_numbers(473, 40, v1[index], v1[index + 1], v2[index], delay);\n        }\n    } else {\n        intface_rotate_numbers(473, 40, last_points, hp, color, 0);\n    }\n\n    last_points = hp;\n    last_points_color = color;\n}\n\n// Render armor class.\n//\n// 0x45EDA8\nvoid intface_update_ac(bool animate)\n{\n    // Last armor class rendered in interface.\n    //\n    // Used to animate changes.\n    //\n    // 0x519034\n    static int last_ac = 0;\n\n    int armorClass = critterGetStat(obj_dude, STAT_ARMOR_CLASS);\n\n    int delay = 0;\n    if (animate) {\n        delay = 250 / (abs(last_ac - armorClass) + 1);\n    }\n\n    intface_rotate_numbers(473, 75, last_ac, armorClass, 0, delay);\n\n    last_ac = armorClass;\n}\n\n// 0x45EE0C\nvoid intface_update_move_points(int actionPointsLeft, int bonusActionPoints)\n{\n    unsigned char* frmData;\n\n    if (interfaceWindow == -1) {\n        return;\n    }\n\n    buf_to_buf(movePointBackground, 90, 5, 90, interfaceBuffer + 14 * 640 + 316, 640);\n\n    if (actionPointsLeft == -1) {\n        frmData = moveLightRed;\n        actionPointsLeft = 10;\n        bonusActionPoints = 0;\n    } else {\n        frmData = moveLightGreen;\n\n        if (actionPointsLeft < 0) {\n            actionPointsLeft = 0;\n        }\n\n        if (actionPointsLeft > 10) {\n            actionPointsLeft = 10;\n        }\n\n        if (bonusActionPoints >= 0) {\n            if (actionPointsLeft + bonusActionPoints > 10) {\n                bonusActionPoints = 10 - actionPointsLeft;\n            }\n        } else {\n            bonusActionPoints = 0;\n        }\n    }\n\n    int index;\n    for (index = 0; index < actionPointsLeft; index++) {\n        buf_to_buf(frmData, 5, 5, 5, interfaceBuffer + 14 * 640 + 316 + index * 9, 640);\n    }\n\n    for (; index < (actionPointsLeft + bonusActionPoints); index++) {\n        buf_to_buf(moveLightYellow, 5, 5, 5, interfaceBuffer + 14 * 640 + 316 + index * 9, 640);\n    }\n\n    if (!insideInit) {\n        win_draw_rect(interfaceWindow, &movePointRect);\n    }\n}\n\n// 0x45EF6C\nint intface_get_attack(int* hitMode, bool* aiming)\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    *aiming = false;\n\n    switch (itemButtonItems[itemCurrentItem].action) {\n    case INTERFACE_ITEM_ACTION_PRIMARY_AIMING:\n        *aiming = true;\n        // FALLTHROUGH\n    case INTERFACE_ITEM_ACTION_PRIMARY:\n        *hitMode = itemButtonItems[itemCurrentItem].primaryHitMode;\n        return 0;\n    case INTERFACE_ITEM_ACTION_SECONDARY_AIMING:\n        *aiming = true;\n        // FALLTHROUGH\n    case INTERFACE_ITEM_ACTION_SECONDARY:\n        *hitMode = itemButtonItems[itemCurrentItem].secondaryHitMode;\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x45EFEC\nint intface_update_items(bool animated, int leftItemAction, int rightItemAction)\n{\n    if (map_bk_processes_are_disabled()) {\n        animated = false;\n    }\n\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    Object* oldCurrentItem = itemButtonItems[itemCurrentItem].item;\n\n    InterfaceItemState* leftItemState = &(itemButtonItems[HAND_LEFT]);\n    Object* item1 = inven_left_hand(obj_dude);\n    if (item1 == leftItemState->item && leftItemState->item != NULL) {\n        if (leftItemState->item != NULL) {\n            leftItemState->isDisabled = item_grey(item1);\n            leftItemState->itemFid = item_inv_fid(item1);\n        }\n    } else {\n        leftItemState->item = item1;\n        if (item1 != NULL) {\n            leftItemState->isDisabled = item_grey(item1);\n            leftItemState->primaryHitMode = HIT_MODE_LEFT_WEAPON_PRIMARY;\n            leftItemState->secondaryHitMode = HIT_MODE_LEFT_WEAPON_SECONDARY;\n            leftItemState->isWeapon = item_get_type(item1) == ITEM_TYPE_WEAPON;\n\n            if (leftItemAction == INTERFACE_ITEM_ACTION_DEFAULT) {\n                if (leftItemState->isWeapon != 0) {\n                    leftItemState->action = INTERFACE_ITEM_ACTION_PRIMARY;\n                } else {\n                    leftItemState->action = INTERFACE_ITEM_ACTION_USE;\n                }\n            } else {\n                leftItemState->action = leftItemAction;\n            }\n\n            leftItemState->itemFid = item_inv_fid(item1);\n        } else {\n            leftItemState->isDisabled = 0;\n            leftItemState->isWeapon = 1;\n            leftItemState->action = INTERFACE_ITEM_ACTION_PRIMARY;\n            leftItemState->itemFid = -1;\n\n            int unarmed = skill_level(obj_dude, SKILL_UNARMED);\n            int agility = critterGetStat(obj_dude, STAT_AGILITY);\n            int strength = critterGetStat(obj_dude, STAT_STRENGTH);\n            int level = stat_pc_get(PC_STAT_LEVEL);\n\n            if (unarmed > 99 && agility > 6 && strength > 4 && level > 8) {\n                leftItemState->primaryHitMode = HIT_MODE_HAYMAKER;\n            } else if (unarmed > 74 && agility > 5 && strength > 4 && level > 5) {\n                leftItemState->primaryHitMode = HIT_MODE_HAMMER_PUNCH;\n            } else if (unarmed > 54 && agility > 5) {\n                leftItemState->primaryHitMode = HIT_MODE_STRONG_PUNCH;\n            } else {\n                leftItemState->primaryHitMode = HIT_MODE_PUNCH;\n            }\n\n            if (unarmed > 129 && agility > 6 && strength > 4 && level > 15) {\n                leftItemState->secondaryHitMode = HIT_MODE_PIERCING_STRIKE;\n            } else if (unarmed > 114 && agility > 6 && strength > 4 && level > 11) {\n                leftItemState->secondaryHitMode = HIT_MODE_PALM_STRIKE;\n            } else if (unarmed > 74 && agility > 6 && strength > 4 && level > 4) {\n                leftItemState->secondaryHitMode = HIT_MODE_JAB;\n            } else {\n                leftItemState->secondaryHitMode = HIT_MODE_PUNCH;\n            }\n        }\n    }\n\n    InterfaceItemState* rightItemState = &(itemButtonItems[HAND_RIGHT]);\n\n    Object* item2 = inven_right_hand(obj_dude);\n    if (item2 == rightItemState->item && rightItemState->item != NULL) {\n        if (rightItemState->item != NULL) {\n            rightItemState->isDisabled = item_grey(rightItemState->item);\n            rightItemState->itemFid = item_inv_fid(rightItemState->item);\n        }\n    } else {\n        rightItemState->item = item2;\n\n        if (item2 != NULL) {\n            rightItemState->isDisabled = item_grey(item2);\n            rightItemState->primaryHitMode = HIT_MODE_RIGHT_WEAPON_PRIMARY;\n            rightItemState->secondaryHitMode = HIT_MODE_RIGHT_WEAPON_SECONDARY;\n            rightItemState->isWeapon = item_get_type(item2) == ITEM_TYPE_WEAPON;\n\n            if (rightItemAction == INTERFACE_ITEM_ACTION_DEFAULT) {\n                if (rightItemState->isWeapon != 0) {\n                    rightItemState->action = INTERFACE_ITEM_ACTION_PRIMARY;\n                } else {\n                    rightItemState->action = INTERFACE_ITEM_ACTION_USE;\n                }\n            } else {\n                rightItemState->action = rightItemAction;\n            }\n            rightItemState->itemFid = item_inv_fid(item2);\n        } else {\n            rightItemState->isDisabled = 0;\n            rightItemState->isWeapon = 1;\n            rightItemState->action = INTERFACE_ITEM_ACTION_PRIMARY;\n            rightItemState->itemFid = -1;\n\n            int unarmed = skill_level(obj_dude, SKILL_UNARMED);\n            int agility = critterGetStat(obj_dude, STAT_AGILITY);\n            int strength = critterGetStat(obj_dude, STAT_STRENGTH);\n            int level = stat_pc_get(PC_STAT_LEVEL);\n\n            if (unarmed > 79 && agility > 5 && strength > 5 && level > 8) {\n                rightItemState->primaryHitMode = HIT_MODE_POWER_KICK;\n            } else if (unarmed > 59 && agility > 5 && level > 5) {\n                rightItemState->primaryHitMode = HIT_MODE_SNAP_KICK;\n            } else if (unarmed > 39 && agility > 5) {\n                rightItemState->primaryHitMode = HIT_MODE_STRONG_KICK;\n            } else {\n                rightItemState->primaryHitMode = HIT_MODE_KICK;\n            }\n\n            if (unarmed > 124 && agility > 7 && strength > 5 && level > 14) {\n                rightItemState->secondaryHitMode = HIT_MODE_PIERCING_KICK;\n            } else if (unarmed > 99 && agility > 6 && strength > 5 && level > 11) {\n                rightItemState->secondaryHitMode = HIT_MODE_HOOK_KICK;\n            } else if (unarmed > 59 && agility > 6 && strength > 5 && level > 5) {\n                rightItemState->secondaryHitMode = HIT_MODE_HIP_KICK;\n            } else {\n                rightItemState->secondaryHitMode = HIT_MODE_KICK;\n            }\n        }\n    }\n\n    if (animated) {\n        Object* newCurrentItem = itemButtonItems[itemCurrentItem].item;\n        if (newCurrentItem != oldCurrentItem) {\n            int animationCode = 0;\n            if (newCurrentItem != NULL) {\n                if (item_get_type(newCurrentItem) == ITEM_TYPE_WEAPON) {\n                    animationCode = item_w_anim_code(newCurrentItem);\n                }\n            }\n\n            intface_change_fid_animate((obj_dude->fid & 0xF000) >> 12, animationCode);\n\n            return 0;\n        }\n    }\n\n    intface_redraw_items();\n\n    return 0;\n}\n\n// 0x45F404\nint intface_toggle_items(bool animated)\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    itemCurrentItem = 1 - itemCurrentItem;\n\n    if (animated) {\n        Object* item = itemButtonItems[itemCurrentItem].item;\n        int animationCode = 0;\n        if (item != NULL) {\n            if (item_get_type(item) == ITEM_TYPE_WEAPON) {\n                animationCode = item_w_anim_code(item);\n            }\n        }\n\n        intface_change_fid_animate((obj_dude->fid & 0xF000) >> 12, animationCode);\n    } else {\n        intface_redraw_items();\n    }\n\n    int mode = gmouse_3d_get_mode();\n    if (mode == GAME_MOUSE_MODE_CROSSHAIR || mode == GAME_MOUSE_MODE_USE_CROSSHAIR) {\n        gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE);\n    }\n\n    return 0;\n}\n\n// 0x45F4B4\nint intface_get_item_states(int* leftItemAction, int* rightItemAction)\n{\n    *leftItemAction = itemButtonItems[HAND_LEFT].action;\n    *rightItemAction = itemButtonItems[HAND_RIGHT].action;\n    return 0;\n}\n\n// 0x45F4E0\nint intface_toggle_item_state()\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    InterfaceItemState* itemState = &(itemButtonItems[itemCurrentItem]);\n\n    int oldAction = itemState->action;\n    if (itemState->isWeapon != 0) {\n        bool done = false;\n        while (!done) {\n            itemState->action++;\n            switch (itemState->action) {\n            case INTERFACE_ITEM_ACTION_PRIMARY:\n                done = true;\n                break;\n            case INTERFACE_ITEM_ACTION_PRIMARY_AIMING:\n                if (item_w_called_shot(obj_dude, itemState->primaryHitMode)) {\n                    done = true;\n                }\n                break;\n            case INTERFACE_ITEM_ACTION_SECONDARY:\n                if (itemState->secondaryHitMode != HIT_MODE_PUNCH\n                    && itemState->secondaryHitMode != HIT_MODE_KICK\n                    && item_w_subtype(itemState->item, itemState->secondaryHitMode) != ATTACK_TYPE_NONE) {\n                    done = true;\n                }\n                break;\n            case INTERFACE_ITEM_ACTION_SECONDARY_AIMING:\n                if (itemState->secondaryHitMode != HIT_MODE_PUNCH\n                    && itemState->secondaryHitMode != HIT_MODE_KICK\n                    && item_w_subtype(itemState->item, itemState->secondaryHitMode) != ATTACK_TYPE_NONE\n                    && item_w_called_shot(obj_dude, itemState->secondaryHitMode)) {\n                    done = true;\n                }\n                break;\n            case INTERFACE_ITEM_ACTION_RELOAD:\n                if (item_w_max_ammo(itemState->item) != item_w_curr_ammo(itemState->item)) {\n                    done = true;\n                }\n                break;\n            case INTERFACE_ITEM_ACTION_COUNT:\n                itemState->action = INTERFACE_ITEM_ACTION_USE;\n                break;\n            }\n        }\n    }\n\n    if (oldAction != itemState->action) {\n        intface_redraw_items();\n    }\n\n    return 0;\n}\n\n// 0x45F5EC\nvoid intface_use_item()\n{\n    if (interfaceWindow == -1) {\n        return;\n    }\n\n    InterfaceItemState* ptr = &(itemButtonItems[itemCurrentItem]);\n\n    if (ptr->isWeapon != 0) {\n        if (ptr->action == INTERFACE_ITEM_ACTION_RELOAD) {\n            if (isInCombat()) {\n                int hitMode = itemCurrentItem == HAND_LEFT\n                    ? HIT_MODE_LEFT_WEAPON_RELOAD\n                    : HIT_MODE_RIGHT_WEAPON_RELOAD;\n\n                int actionPointsRequired = item_mp_cost(obj_dude, hitMode, false);\n                if (actionPointsRequired <= obj_dude->data.critter.combat.ap) {\n                    if (intface_item_reload() == 0) {\n                        if (actionPointsRequired > obj_dude->data.critter.combat.ap) {\n                            obj_dude->data.critter.combat.ap = 0;\n                        } else {\n                            obj_dude->data.critter.combat.ap -= actionPointsRequired;\n                        }\n                        intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n                    }\n                }\n            } else {\n                intface_item_reload();\n            }\n        } else {\n            gmouse_set_cursor(MOUSE_CURSOR_CROSSHAIR);\n            gmouse_3d_set_mode(GAME_MOUSE_MODE_CROSSHAIR);\n            if (!isInCombat()) {\n                combat(NULL);\n            }\n        }\n    } else if (proto_action_can_use_on(ptr->item->pid)) {\n        gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR);\n        gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_CROSSHAIR);\n    } else if (obj_action_can_use(ptr->item)) {\n        if (isInCombat()) {\n            int actionPointsRequired = item_mp_cost(obj_dude, ptr->secondaryHitMode, false);\n            if (actionPointsRequired <= obj_dude->data.critter.combat.ap) {\n                obj_use_item(obj_dude, ptr->item);\n                intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n                if (actionPointsRequired > obj_dude->data.critter.combat.ap) {\n                    obj_dude->data.critter.combat.ap = 0;\n                } else {\n                    obj_dude->data.critter.combat.ap -= actionPointsRequired;\n                }\n\n                intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n            }\n        } else {\n            obj_use_item(obj_dude, ptr->item);\n            intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n        }\n    }\n}\n\n// 0x45F7FC\nint intface_is_item_right_hand()\n{\n    return itemCurrentItem;\n}\n\n// 0x45F804\nint intface_get_current_item(Object** itemPtr)\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    *itemPtr = itemButtonItems[itemCurrentItem].item;\n\n    return 0;\n}\n\n// 0x45F838\nint intface_update_ammo_lights()\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    InterfaceItemState* p = &(itemButtonItems[itemCurrentItem]);\n\n    int ratio = 0;\n\n    if (p->isWeapon != 0) {\n        // calls sub_478674 twice, probably because if min/max kind macro\n        int maximum = item_w_max_ammo(p->item);\n        if (maximum > 0) {\n            int current = item_w_curr_ammo(p->item);\n            ratio = (int)((double)current / (double)maximum * 70.0);\n        }\n    } else {\n        if (item_get_type(p->item) == ITEM_TYPE_MISC) {\n            // calls sub_4793D0 twice, probably because if min/max kind macro\n            int maximum = item_m_max_charges(p->item);\n            if (maximum > 0) {\n                int current = item_m_curr_charges(p->item);\n                ratio = (int)((double)current / (double)maximum * 70.0);\n            }\n        }\n    }\n\n    intface_draw_ammo_lights(463, ratio);\n\n    return 0;\n}\n\n// 0x45F96C\nvoid intface_end_window_open(bool animated)\n{\n    if (interfaceWindow == -1) {\n        return;\n    }\n\n    if (endWindowOpen) {\n        return;\n    }\n\n    int fid = art_id(OBJ_TYPE_INTERFACE, 104, 0, 0, 0);\n    CacheEntry* handle;\n    Art* art = art_ptr_lock(fid, &handle);\n    if (art == NULL) {\n        return;\n    }\n\n    int frameCount = art_frame_max_frame(art);\n    gsound_play_sfx_file(\"iciboxx1\");\n\n    if (animated) {\n        unsigned int delay = 1000 / art_frame_fps(art);\n        int time = 0;\n        int frame = 0;\n        while (frame < frameCount) {\n            if (elapsed_time(time) >= delay) {\n                unsigned char* src = art_frame_data(art, frame, 0);\n                if (src != NULL) {\n                    buf_to_buf(src, 57, 58, 57, interfaceBuffer + 640 * 38 + 580, 640);\n                    win_draw_rect(interfaceWindow, &endWindowRect);\n                }\n\n                time = get_time();\n                frame++;\n            }\n            gmouse_bk_process();\n        }\n    } else {\n        unsigned char* src = art_frame_data(art, frameCount - 1, 0);\n        buf_to_buf(src, 57, 58, 57, interfaceBuffer + 640 * 38 + 580, 640);\n        win_draw_rect(interfaceWindow, &endWindowRect);\n    }\n\n    art_ptr_unlock(handle);\n\n    endWindowOpen = true;\n    intface_create_end_turn_button();\n    intface_create_end_combat_button();\n    intface_end_buttons_disable();\n}\n\n// 0x45FAC0\nvoid intface_end_window_close(bool animated)\n{\n    if (interfaceWindow == -1) {\n        return;\n    }\n\n    if (!endWindowOpen) {\n        return;\n    }\n\n    int fid = art_id(OBJ_TYPE_INTERFACE, 104, 0, 0, 0);\n    CacheEntry* handle;\n    Art* art = art_ptr_lock(fid, &handle);\n    if (art == NULL) {\n        return;\n    }\n\n    intface_destroy_end_turn_button();\n    intface_destroy_end_combat_button();\n    gsound_play_sfx_file(\"icibcxx1\");\n\n    if (animated) {\n        unsigned int delay = 1000 / art_frame_fps(art);\n        unsigned int time = 0;\n        int frame = art_frame_max_frame(art);\n\n        while (frame != 0) {\n            if (elapsed_time(time) >= delay) {\n                unsigned char* src = art_frame_data(art, frame - 1, 0);\n                unsigned char* dest = interfaceBuffer + 640 * 38 + 580;\n                if (src != NULL) {\n                    buf_to_buf(src, 57, 58, 57, dest, 640);\n                    win_draw_rect(interfaceWindow, &endWindowRect);\n                }\n\n                time = get_time();\n                frame--;\n            }\n            gmouse_bk_process();\n        }\n    } else {\n        unsigned char* dest = interfaceBuffer + 640 * 38 + 580;\n        unsigned char* src = art_frame_data(art, 0, 0);\n        buf_to_buf(src, 57, 58, 57, dest, 640);\n        win_draw_rect(interfaceWindow, &endWindowRect);\n    }\n\n    art_ptr_unlock(handle);\n    endWindowOpen = false;\n}\n\n// 0x45FC04\nvoid intface_end_buttons_enable()\n{\n    if (endWindowOpen) {\n        win_enable_button(endTurnButton);\n        win_enable_button(endCombatButton);\n\n        // endltgrn.frm - green lights around end turn/combat window\n        int lightsFid = art_id(OBJ_TYPE_INTERFACE, 109, 0, 0, 0);\n        CacheEntry* lightsFrmHandle;\n        unsigned char* lightsFrmData = art_ptr_lock_data(lightsFid, 0, 0, &lightsFrmHandle);\n        if (lightsFrmData == NULL) {\n            return;\n        }\n\n        gsound_play_sfx_file(\"icombat2\");\n        trans_buf_to_buf(lightsFrmData, 57, 58, 57, interfaceBuffer + 38 * 640 + 580, 640);\n        win_draw_rect(interfaceWindow, &endWindowRect);\n\n        art_ptr_unlock(lightsFrmHandle);\n    }\n}\n\n// 0x45FC98\nvoid intface_end_buttons_disable()\n{\n    if (endWindowOpen) {\n        win_disable_button(endTurnButton);\n        win_disable_button(endCombatButton);\n\n        CacheEntry* lightsFrmHandle;\n        // endltred.frm - red lights around end turn/combat window\n        int lightsFid = art_id(OBJ_TYPE_INTERFACE, 110, 0, 0, 0);\n        unsigned char* lightsFrmData = art_ptr_lock_data(lightsFid, 0, 0, &lightsFrmHandle);\n        if (lightsFrmData == NULL) {\n            return;\n        }\n\n        gsound_play_sfx_file(\"icombat1\");\n        trans_buf_to_buf(lightsFrmData, 57, 58, 57, interfaceBuffer + 38 * 640 + 580, 640);\n        win_draw_rect(interfaceWindow, &endWindowRect);\n\n        art_ptr_unlock(lightsFrmHandle);\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x45FD2C\nstatic int intface_init_items()\n{\n    // FIXME: For unknown reason these values initialized with -1. It's never\n    // checked for -1, so I have no explanation for this.\n    itemButtonItems[HAND_LEFT].item = (Object*)-1;\n    itemButtonItems[HAND_RIGHT].item = (Object*)-1;\n\n    return 0;\n}\n\n// 0x45FD88\nstatic int intface_redraw_items()\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    win_enable_button(itemButton);\n\n    InterfaceItemState* itemState = &(itemButtonItems[itemCurrentItem]);\n    int actionPoints = -1;\n\n    if (itemState->isDisabled == 0) {\n        memcpy(itemButtonUp, itemButtonUpBlank, sizeof(itemButtonUp));\n        memcpy(itemButtonDown, itemButtonDownBlank, sizeof(itemButtonDown));\n\n        if (itemState->isWeapon == 0) {\n            int fid;\n            if (proto_action_can_use_on(itemState->item->pid)) {\n                // USE ON\n                fid = art_id(OBJ_TYPE_INTERFACE, 294, 0, 0, 0);\n            } else if (obj_action_can_use(itemState->item)) {\n                // USE\n                fid = art_id(OBJ_TYPE_INTERFACE, 292, 0, 0, 0);\n            } else {\n                fid = -1;\n            }\n\n            if (fid != -1) {\n                CacheEntry* useTextFrmHandle;\n                Art* useTextFrm = art_ptr_lock(fid, &useTextFrmHandle);\n                if (useTextFrm != NULL) {\n                    int width = art_frame_width(useTextFrm, 0, 0);\n                    int height = art_frame_length(useTextFrm, 0, 0);\n                    unsigned char* data = art_frame_data(useTextFrm, 0, 0);\n                    trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * 7 + 181 - width, 188);\n                    dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, 181 - width + 1, 5, 188, 59641);\n                    art_ptr_unlock(useTextFrmHandle);\n                }\n\n                actionPoints = item_mp_cost(obj_dude, itemState->primaryHitMode, false);\n            }\n        } else {\n            int primaryFid = -1;\n            int bullseyeFid = -1;\n            int hitMode = -1;\n\n            // NOTE: This value is decremented at 0x45FEAC, probably to build\n            // jump table.\n            switch (itemState->action) {\n            case INTERFACE_ITEM_ACTION_PRIMARY_AIMING:\n                bullseyeFid = art_id(OBJ_TYPE_INTERFACE, 288, 0, 0, 0);\n                // FALLTHROUGH\n            case INTERFACE_ITEM_ACTION_PRIMARY:\n                hitMode = itemState->primaryHitMode;\n                break;\n            case INTERFACE_ITEM_ACTION_SECONDARY_AIMING:\n                bullseyeFid = art_id(OBJ_TYPE_INTERFACE, 288, 0, 0, 0);\n                // FALLTHROUGH\n            case INTERFACE_ITEM_ACTION_SECONDARY:\n                hitMode = itemState->secondaryHitMode;\n                break;\n            case INTERFACE_ITEM_ACTION_RELOAD:\n                actionPoints = item_mp_cost(obj_dude, itemCurrentItem == HAND_LEFT ? HIT_MODE_LEFT_WEAPON_RELOAD : HIT_MODE_RIGHT_WEAPON_RELOAD, false);\n                primaryFid = art_id(OBJ_TYPE_INTERFACE, 291, 0, 0, 0);\n                break;\n            }\n\n            if (bullseyeFid != -1) {\n                CacheEntry* bullseyeFrmHandle;\n                Art* bullseyeFrm = art_ptr_lock(bullseyeFid, &bullseyeFrmHandle);\n                if (bullseyeFrm != NULL) {\n                    int width = art_frame_width(bullseyeFrm, 0, 0);\n                    int height = art_frame_length(bullseyeFrm, 0, 0);\n                    unsigned char* data = art_frame_data(bullseyeFrm, 0, 0);\n                    trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * (60 - height) + (181 - width), 188);\n\n                    int v9 = 60 - height - 2;\n                    if (v9 < 0) {\n                        v9 = 0;\n                        height -= 2;\n                    }\n\n                    dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, 181 - width + 1, v9, 188, 59641);\n                    art_ptr_unlock(bullseyeFrmHandle);\n                }\n            }\n\n            if (hitMode != -1) {\n                actionPoints = item_w_mp_cost(obj_dude, hitMode, bullseyeFid != -1);\n\n                int id;\n                int anim = item_w_anim(obj_dude, hitMode);\n                switch (anim) {\n                case ANIM_THROW_PUNCH:\n                    switch (hitMode) {\n                    case HIT_MODE_STRONG_PUNCH:\n                        id = 432; // strong punch\n                        break;\n                    case HIT_MODE_HAMMER_PUNCH:\n                        id = 425; // hammer punch\n                        break;\n                    case HIT_MODE_HAYMAKER:\n                        id = 428; // lightning punch\n                        break;\n                    case HIT_MODE_JAB:\n                        id = 421; // chop punch\n                        break;\n                    case HIT_MODE_PALM_STRIKE:\n                        id = 423; // dragon punch\n                        break;\n                    case HIT_MODE_PIERCING_STRIKE:\n                        id = 424; // force punch\n                        break;\n                    default:\n                        id = 42; // punch\n                        break;\n                    }\n                    break;\n                case ANIM_KICK_LEG:\n                    switch (hitMode) {\n                    case HIT_MODE_STRONG_KICK:\n                        id = 430; // skick.frm - strong kick text\n                        break;\n                    case HIT_MODE_SNAP_KICK:\n                        id = 431; // snapkick.frm - snap kick text\n                        break;\n                    case HIT_MODE_POWER_KICK:\n                        id = 429; // cm_pwkck.frm - roundhouse kick text\n                        break;\n                    case HIT_MODE_HIP_KICK:\n                        id = 426; // hipk.frm - kip kick text\n                        break;\n                    case HIT_MODE_HOOK_KICK:\n                        id = 427; // cm_hookk.frm - jump kick text\n                        break;\n                    case HIT_MODE_PIERCING_KICK: // cm_prckk.frm - death blossom kick text\n                        id = 422;\n                        break;\n                    default:\n                        id = 41; // kick.frm - kick text\n                        break;\n                    }\n                    break;\n                case ANIM_THROW_ANIM:\n                    id = 117; // throw\n                    break;\n                case ANIM_THRUST_ANIM:\n                    id = 45; // thrust\n                    break;\n                case ANIM_SWING_ANIM:\n                    id = 44; // swing\n                    break;\n                case ANIM_FIRE_SINGLE:\n                    id = 43; // single\n                    break;\n                case ANIM_FIRE_BURST:\n                case ANIM_FIRE_CONTINUOUS:\n                    id = 40; // burst\n                    break;\n                }\n\n                primaryFid = art_id(OBJ_TYPE_INTERFACE, id, 0, 0, 0);\n            }\n\n            if (primaryFid != -1) {\n                CacheEntry* primaryFrmHandle;\n                Art* primaryFrm = art_ptr_lock(primaryFid, &primaryFrmHandle);\n                if (primaryFrm != NULL) {\n                    int width = art_frame_width(primaryFrm, 0, 0);\n                    int height = art_frame_length(primaryFrm, 0, 0);\n                    unsigned char* data = art_frame_data(primaryFrm, 0, 0);\n                    trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * 7 + 181 - width, 188);\n                    dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, 181 - width + 1, 5, 188, 59641);\n                    art_ptr_unlock(primaryFrmHandle);\n                }\n            }\n        }\n    }\n\n    if (actionPoints >= 0 && actionPoints < 10) {\n        // movement point text\n        int fid = art_id(OBJ_TYPE_INTERFACE, 289, 0, 0, 0);\n\n        CacheEntry* handle;\n        Art* art = art_ptr_lock(fid, &handle);\n        if (art != NULL) {\n            int width = art_frame_width(art, 0, 0);\n            int height = art_frame_length(art, 0, 0);\n            unsigned char* data = art_frame_data(art, 0, 0);\n\n            trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * (60 - height) + 7, 188);\n\n            int v29 = 60 - height - 2;\n            if (v29 < 0) {\n                v29 = 0;\n                height -= 2;\n            }\n\n            dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, 7 + 1, v29, 188, 59641);\n            art_ptr_unlock(handle);\n\n            int offset = width + 7;\n\n            // movement point numbers - ten numbers 0 to 9, each 10 pixels wide.\n            fid = art_id(OBJ_TYPE_INTERFACE, 290, 0, 0, 0);\n            art = art_ptr_lock(fid, &handle);\n            if (art != NULL) {\n                width = art_frame_width(art, 0, 0);\n                height = art_frame_length(art, 0, 0);\n                data = art_frame_data(art, 0, 0);\n\n                trans_buf_to_buf(data + actionPoints * 10, 10, height, width, itemButtonUp + 188 * (60 - height) + 7 + offset, 188);\n\n                int v40 = 60 - height - 2;\n                if (v40 < 0) {\n                    v40 = 0;\n                    height -= 2;\n                }\n                dark_trans_buf_to_buf(data + actionPoints * 10, 10, height, width, itemButtonDown, offset + 7 + 1, v40, 188, 59641);\n\n                art_ptr_unlock(handle);\n            }\n        }\n    } else {\n        memcpy(itemButtonUp, itemButtonDisabled, sizeof(itemButtonUp));\n        memcpy(itemButtonDown, itemButtonDisabled, sizeof(itemButtonDown));\n    }\n\n    if (itemState->itemFid != -1) {\n        CacheEntry* itemFrmHandle;\n        Art* itemFrm = art_ptr_lock(itemState->itemFid, &itemFrmHandle);\n        if (itemFrm != NULL) {\n            int width = art_frame_width(itemFrm, 0, 0);\n            int height = art_frame_length(itemFrm, 0, 0);\n            unsigned char* data = art_frame_data(itemFrm, 0, 0);\n\n            int v46 = (188 - width) / 2;\n            int v47 = (67 - height) / 2 - 2;\n\n            trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * ((67 - height) / 2) + v46, 188);\n\n            if (v47 < 0) {\n                v47 = 0;\n                height -= 2;\n            }\n\n            dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, v46 + 1, v47, 188, 63571);\n            art_ptr_unlock(itemFrmHandle);\n        }\n    }\n\n    if (!insideInit) {\n        intface_update_ammo_lights();\n\n        win_draw_rect(interfaceWindow, &itemButtonRect);\n\n        if (itemState->isDisabled != 0) {\n            win_disable_button(itemButton);\n        } else {\n            win_enable_button(itemButton);\n        }\n    }\n\n    return 0;\n}\n\n// 0x460658\nstatic int intface_redraw_items_callback(Object* a1, Object* a2)\n{\n    intface_redraw_items();\n    return 0;\n}\n\n// 0x460660\nstatic int intface_change_fid_callback(Object* a1, Object* a2)\n{\n    intface_fid_is_changing = false;\n    return 0;\n}\n\n// 0x46066C\nstatic void intface_change_fid_animate(int previousWeaponAnimationCode, int weaponAnimationCode)\n{\n    intface_fid_is_changing = true;\n\n    register_clear(obj_dude);\n    register_begin(ANIMATION_REQUEST_RESERVED);\n    register_object_light(obj_dude, 4, 0);\n\n    if (previousWeaponAnimationCode != 0) {\n        const char* sfx = gsnd_build_character_sfx_name(obj_dude, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);\n        register_object_play_sfx(obj_dude, sfx, 0);\n        register_object_animate(obj_dude, ANIM_PUT_AWAY, 0);\n    }\n\n    register_object_must_call(NULL, NULL, intface_redraw_items_callback, -1);\n\n    Object* item = itemButtonItems[itemCurrentItem].item;\n    if (item != NULL && item->lightDistance > 4) {\n        register_object_light(obj_dude, item->lightDistance, 0);\n    }\n\n    if (weaponAnimationCode != 0) {\n        register_object_take_out(obj_dude, weaponAnimationCode, -1);\n    } else {\n        int fid = art_id(OBJ_TYPE_CRITTER, obj_dude->fid & 0xFFF, ANIM_STAND, 0, obj_dude->rotation + 1);\n        register_object_change_fid(obj_dude, fid, -1);\n    }\n\n    register_object_must_call(NULL, NULL, intface_change_fid_callback, -1);\n\n    if (register_end() == -1) {\n        return;\n    }\n\n    bool interfaceBarWasEnabled = intfaceEnabled;\n\n    intface_disable();\n    gmouse_disable(0);\n\n    gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH);\n\n    while (intface_fid_is_changing) {\n        if (game_user_wants_to_quit) {\n            break;\n        }\n\n        get_input();\n    }\n\n    gmouse_set_cursor(MOUSE_CURSOR_NONE);\n\n    gmouse_enable();\n\n    if (interfaceBarWasEnabled) {\n        intface_enable();\n    }\n}\n\n// 0x4607E0\nstatic int intface_create_end_turn_button()\n{\n    int fid;\n\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    if (!endWindowOpen) {\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 105, 0, 0, 0);\n    endTurnButtonUp = art_ptr_lock_data(fid, 0, 0, &endTurnButtonUpKey);\n    if (endTurnButtonUp == NULL) {\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 106, 0, 0, 0);\n    endTurnButtonDown = art_ptr_lock_data(fid, 0, 0, &endTurnButtonDownKey);\n    if (endTurnButtonDown == NULL) {\n        return -1;\n    }\n\n    endTurnButton = win_register_button(interfaceWindow, 590, 43, 38, 22, -1, -1, -1, 32, endTurnButtonUp, endTurnButtonDown, NULL, 0);\n    if (endTurnButton == -1) {\n        return -1;\n    }\n\n    win_register_button_disable(endTurnButton, endTurnButtonUp, endTurnButtonUp, endTurnButtonUp);\n    win_register_button_sound_func(endTurnButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    return 0;\n}\n\n// 0x4608C4\nstatic int intface_destroy_end_turn_button()\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    if (endTurnButton != -1) {\n        win_delete_button(endTurnButton);\n        endTurnButton = -1;\n    }\n\n    if (endTurnButtonDown) {\n        art_ptr_unlock(endTurnButtonDownKey);\n        endTurnButtonDownKey = NULL;\n        endTurnButtonDown = NULL;\n    }\n\n    if (endTurnButtonUp) {\n        art_ptr_unlock(endTurnButtonUpKey);\n        endTurnButtonUpKey = NULL;\n        endTurnButtonUp = NULL;\n    }\n\n    return 0;\n}\n\n// 0x460940\nstatic int intface_create_end_combat_button()\n{\n    int fid;\n\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    if (!endWindowOpen) {\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 107, 0, 0, 0);\n    endCombatButtonUp = art_ptr_lock_data(fid, 0, 0, &endCombatButtonUpKey);\n    if (endCombatButtonUp == NULL) {\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 108, 0, 0, 0);\n    endCombatButtonDown = art_ptr_lock_data(fid, 0, 0, &endCombatButtonDownKey);\n    if (endCombatButtonDown == NULL) {\n        return -1;\n    }\n\n    endCombatButton = win_register_button(interfaceWindow, 590, 65, 38, 22, -1, -1, -1, 13, endCombatButtonUp, endCombatButtonDown, NULL, 0);\n    if (endCombatButton == -1) {\n        return -1;\n    }\n\n    win_register_button_disable(endCombatButton, endCombatButtonUp, endCombatButtonUp, endCombatButtonUp);\n    win_register_button_sound_func(endCombatButton, gsound_med_butt_press, gsound_med_butt_release);\n\n    return 0;\n}\n\n// 0x460A24\nstatic int intface_destroy_end_combat_button()\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    if (endCombatButton != -1) {\n        win_delete_button(endCombatButton);\n        endCombatButton = -1;\n    }\n\n    if (endCombatButtonDown != NULL) {\n        art_ptr_unlock(endCombatButtonDownKey);\n        endCombatButtonDownKey = NULL;\n        endCombatButtonDown = NULL;\n    }\n\n    if (endCombatButtonUp != NULL) {\n        art_ptr_unlock(endCombatButtonUpKey);\n        endCombatButtonUpKey = NULL;\n        endCombatButtonUp = NULL;\n    }\n\n    return 0;\n}\n\n// 0x460AA0\nstatic void intface_draw_ammo_lights(int x, int ratio)\n{\n    if ((ratio & 1) != 0) {\n        ratio -= 1;\n    }\n\n    unsigned char* dest = interfaceBuffer + 640 * 26 + x;\n\n    for (int index = 70; index > ratio; index--) {\n        *dest = 14;\n        dest += 640;\n    }\n\n    while (ratio > 0) {\n        *dest = 196;\n        dest += 640;\n\n        *dest = 14;\n        dest += 640;\n\n        ratio -= 2;\n    }\n\n    if (!insideInit) {\n        Rect rect;\n        rect.ulx = x;\n        rect.uly = 26;\n        rect.lrx = x + 1;\n        rect.lry = 26 + 70;\n        win_draw_rect(interfaceWindow, &rect);\n    }\n}\n\n// 0x460B20\nstatic int intface_item_reload()\n{\n    if (interfaceWindow == -1) {\n        return -1;\n    }\n\n    bool v0 = false;\n    while (item_w_try_reload(obj_dude, itemButtonItems[itemCurrentItem].item) != -1) {\n        v0 = true;\n    }\n\n    intface_toggle_item_state();\n    intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n\n    if (!v0) {\n        return -1;\n    }\n\n    const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, itemButtonItems[itemCurrentItem].item, HIT_MODE_RIGHT_WEAPON_PRIMARY, NULL);\n    gsound_play_sfx_file(sfx);\n\n    return 0;\n}\n\n// Renders hit points.\n//\n// [delay] is an animation delay.\n// [previousValue] is only meaningful for animation.\n// [offset] = 0 - grey, 120 - yellow, 240 - red.\n//\n// 0x460BA0\nstatic void intface_rotate_numbers(int x, int y, int previousValue, int value, int offset, int delay)\n{\n    if (value > 999) {\n        value = 999;\n    } else if (value < -999) {\n        value = -999;\n    }\n\n    unsigned char* numbers = numbersBuffer + offset;\n    unsigned char* dest = interfaceBuffer + 640 * y;\n\n    unsigned char* downSrc = numbers + 90;\n    unsigned char* upSrc = numbers + 99;\n    unsigned char* minusSrc = numbers + 108;\n    unsigned char* plusSrc = numbers + 114;\n\n    unsigned char* signDest = dest + x;\n    unsigned char* hundredsDest = dest + x + 6;\n    unsigned char* tensDest = dest + x + 6 + 9;\n    unsigned char* onesDest = dest + x + 6 + 9 * 2;\n\n    int normalizedSign;\n    int normalizedValue;\n    if (insideInit || delay == 0) {\n        normalizedSign = value >= 0 ? 1 : -1;\n        normalizedValue = abs(value);\n    } else {\n        normalizedSign = previousValue >= 0 ? 1 : -1;\n        normalizedValue = previousValue;\n    }\n\n    int ones = normalizedValue % 10;\n    int tens = (normalizedValue / 10) % 10;\n    int hundreds = normalizedValue / 100;\n\n    buf_to_buf(numbers + 9 * hundreds, 9, 17, 360, hundredsDest, 640);\n    buf_to_buf(numbers + 9 * tens, 9, 17, 360, tensDest, 640);\n    buf_to_buf(numbers + 9 * ones, 9, 17, 360, onesDest, 640);\n    buf_to_buf(normalizedSign >= 0 ? plusSrc : minusSrc, 6, 17, 360, signDest, 640);\n\n    if (!insideInit) {\n        Rect numbersRect = { x, y, x + 33, y + 17 };\n        win_draw_rect(interfaceWindow, &numbersRect);\n        if (delay != 0) {\n            int change = value - previousValue >= 0 ? 1 : -1;\n            int v14 = previousValue >= 0 ? 1 : -1;\n            int v49 = change * v14;\n            while (previousValue != value) {\n                if ((hundreds | tens | ones) == 0) {\n                    v49 = 1;\n                }\n\n                buf_to_buf(upSrc, 9, 17, 360, onesDest, 640);\n                mouse_info();\n                gmouse_bk_process();\n                block_for_tocks(delay);\n                win_draw_rect(interfaceWindow, &numbersRect);\n\n                ones += v49;\n\n                if (ones > 9 || ones < 0) {\n                    buf_to_buf(upSrc, 9, 17, 360, tensDest, 640);\n                    mouse_info();\n                    gmouse_bk_process();\n                    block_for_tocks(delay);\n                    win_draw_rect(interfaceWindow, &numbersRect);\n\n                    tens += v49;\n                    ones -= 10 * v49;\n                    if (tens == 10 || tens == -1) {\n                        buf_to_buf(upSrc, 9, 17, 360, hundredsDest, 640);\n                        mouse_info();\n                        gmouse_bk_process();\n                        block_for_tocks(delay);\n                        win_draw_rect(interfaceWindow, &numbersRect);\n\n                        hundreds += v49;\n                        tens -= 10 * v49;\n                        if (hundreds == 10 || hundreds == -1) {\n                            hundreds -= 10 * v49;\n                        }\n\n                        buf_to_buf(downSrc, 9, 17, 360, hundredsDest, 640);\n                        mouse_info();\n                        gmouse_bk_process();\n                        block_for_tocks(delay);\n                        win_draw_rect(interfaceWindow, &numbersRect);\n                    }\n\n                    buf_to_buf(downSrc, 9, 17, 360, tensDest, 640);\n                    block_for_tocks(delay);\n                    win_draw_rect(interfaceWindow, &numbersRect);\n                }\n\n                buf_to_buf(downSrc, 9, 17, 360, onesDest, 640);\n                mouse_info();\n                gmouse_bk_process();\n                block_for_tocks(delay);\n                win_draw_rect(interfaceWindow, &numbersRect);\n\n                previousValue += change;\n\n                buf_to_buf(numbers + 9 * hundreds, 9, 17, 360, hundredsDest, 640);\n                buf_to_buf(numbers + 9 * tens, 9, 17, 360, tensDest, 640);\n                buf_to_buf(numbers + 9 * ones, 9, 17, 360, onesDest, 640);\n\n                buf_to_buf(previousValue >= 0 ? plusSrc : minusSrc, 6, 17, 360, signDest, 640);\n                mouse_info();\n                gmouse_bk_process();\n                block_for_tocks(delay);\n                win_draw_rect(interfaceWindow, &numbersRect);\n            }\n        }\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x461128\nstatic int intface_fatal_error(int rc)\n{\n    intface_exit();\n\n    return rc;\n}\n\n// 0x461134\nstatic int construct_box_bar_win()\n{\n    int oldFont = text_curr();\n\n    if (bar_window != -1) {\n        return 0;\n    }\n\n    MessageList messageList;\n    MessageListItem messageListItem;\n    int rc = 0;\n    if (!message_init(&messageList)) {\n        rc = -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"intrface.msg\");\n\n    if (rc != -1) {\n        if (!message_load(&messageList, path)) {\n            rc = -1;\n        }\n    }\n\n    if (rc == -1) {\n        debug_printf(\"\\nINTRFACE: Error indicator box messages! **\\n\");\n        return -1;\n    }\n\n    CacheEntry* indicatorBoxFrmHandle;\n    int width;\n    int height;\n    int indicatorBoxFid = art_id(OBJ_TYPE_INTERFACE, 126, 0, 0, 0);\n    unsigned char* indicatorBoxFrmData = art_lock(indicatorBoxFid, &indicatorBoxFrmHandle, &width, &height);\n    if (indicatorBoxFrmData == NULL) {\n        debug_printf(\"\\nINTRFACE: Error initializing indicator box graphics! **\\n\");\n        message_exit(&messageList);\n        return -1;\n    }\n\n    for (int index = 0; index < INDICATOR_COUNT; index++) {\n        IndicatorDescription* indicatorDescription = &(bbox[index]);\n\n        indicatorDescription->data = (unsigned char*)mem_malloc(INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT);\n        if (indicatorDescription->data == NULL) {\n            debug_printf(\"\\nINTRFACE: Error initializing indicator box graphics! **\");\n\n            while (--index >= 0) {\n                mem_free(bbox[index].data);\n            }\n\n            message_exit(&messageList);\n            art_ptr_unlock(indicatorBoxFrmHandle);\n\n            return -1;\n        }\n    }\n\n    text_font(101);\n\n    for (int index = 0; index < INDICATOR_COUNT; index++) {\n        IndicatorDescription* indicator = &(bbox[index]);\n\n        char text[1024];\n        strcpy(text, getmsg(&messageList, &messageListItem, indicator->title));\n\n        int color = indicator->isBad ? colorTable[31744] : colorTable[992];\n\n        memcpy(indicator->data, indicatorBoxFrmData, INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT);\n\n        // NOTE: For unknown reason it uses 24 as a height of the box to center\n        // the title. One explanation is that these boxes were redesigned, but\n        // this value was not changed. On the other hand 24 is\n        // [INDICATOR_BOX_HEIGHT] + [INDICATOR_BOX_CONNECTOR_WIDTH]. Maybe just\n        // a coincidence. I guess we'll never find out.\n        int y = (24 - text_height()) / 2;\n        int x = (INDICATOR_BOX_WIDTH - text_width(text)) / 2;\n        text_to_buf(indicator->data + INDICATOR_BOX_WIDTH * y + x, text, INDICATOR_BOX_WIDTH, INDICATOR_BOX_WIDTH, color);\n    }\n\n    box_status_flag = true;\n    refresh_box_bar_win();\n\n    message_exit(&messageList);\n    art_ptr_unlock(indicatorBoxFrmHandle);\n    text_font(oldFont);\n\n    return 0;\n}\n\n// 0x461454\nstatic void deconstruct_box_bar_win()\n{\n    if (bar_window != -1) {\n        win_delete(bar_window);\n        bar_window = -1;\n    }\n\n    for (int index = 0; index < INDICATOR_COUNT; index++) {\n        IndicatorDescription* indicatorBoxDescription = &(bbox[index]);\n        if (indicatorBoxDescription->data != NULL) {\n            mem_free(indicatorBoxDescription->data);\n            indicatorBoxDescription->data = NULL;\n        }\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4614A0\nstatic void reset_box_bar_win()\n{\n    if (bar_window != -1) {\n        win_delete(bar_window);\n        bar_window = -1;\n    }\n\n    box_status_flag = true;\n}\n\n// Updates indicator bar.\n//\n// 0x4614CC\nint refresh_box_bar_win()\n{\n    if (interfaceWindow != -1 && box_status_flag && !intfaceHidden) {\n        for (int index = 0; index < INDICATOR_SLOTS_COUNT; index++) {\n            bboxslot[index] = -1;\n        }\n\n        int count = 0;\n\n        if (is_pc_flag(DUDE_STATE_SNEAKING)) {\n            if (add_bar_box(INDICATOR_SNEAK)) {\n                ++count;\n            }\n        }\n\n        if (is_pc_flag(DUDE_STATE_LEVEL_UP_AVAILABLE)) {\n            if (add_bar_box(INDICATOR_LEVEL)) {\n                ++count;\n            }\n        }\n\n        if (is_pc_flag(DUDE_STATE_ADDICTED)) {\n            if (add_bar_box(INDICATOR_ADDICT)) {\n                ++count;\n            }\n        }\n\n        if (critter_get_poison(obj_dude) > POISON_INDICATOR_THRESHOLD) {\n            if (add_bar_box(INDICATOR_POISONED)) {\n                ++count;\n            }\n        }\n\n        if (critter_get_rads(obj_dude) > RADATION_INDICATOR_THRESHOLD) {\n            if (add_bar_box(INDICATOR_RADIATED)) {\n                ++count;\n            }\n        }\n\n        if (count > 1) {\n            qsort(bboxslot, count, sizeof(*bboxslot), bbox_comp);\n        }\n\n        if (bar_window != -1) {\n            win_delete(bar_window);\n            bar_window = -1;\n        }\n\n        if (count != 0) {\n            bar_window = win_add(INDICATOR_BAR_X,\n                INDICATOR_BAR_Y,\n                (INDICATOR_BOX_WIDTH - INDICATOR_BOX_CONNECTOR_WIDTH) * count,\n                INDICATOR_BOX_HEIGHT,\n                colorTable[0],\n                0);\n            draw_bboxes(count);\n            win_draw(bar_window);\n        }\n\n        return count;\n    }\n\n    if (bar_window != -1) {\n        win_delete(bar_window);\n        bar_window = -1;\n    }\n\n    return 0;\n}\n\n// 0x461624\nstatic int bbox_comp(const void* a, const void* b)\n{\n    int indicatorBox1 = *(int*)a;\n    int indicatorBox2 = *(int*)b;\n\n    if (indicatorBox1 == indicatorBox2) {\n        return 0;\n    } else if (indicatorBox1 < indicatorBox2) {\n        return -1;\n    } else {\n        return 1;\n    }\n}\n\n// Renders indicator boxes into the indicator bar window.\n//\n// 0x461648\nstatic void draw_bboxes(int count)\n{\n    if (bar_window == -1) {\n        return;\n    }\n\n    if (count == 0) {\n        return;\n    }\n\n    int windowWidth = win_width(bar_window);\n    unsigned char* windowBuffer = win_get_buf(bar_window);\n\n    // The initial number of connections is 2 - one is first box to the screen\n    // boundary, the other is female socket (initially empty). Every displayed\n    // box adds one more connection (it is \"plugged\" into previous box and\n    // exposes it's own empty female socket).\n    int connections = 2;\n\n    // The width of displayed indicator boxes as if there were no connections.\n    int unconnectedIndicatorsWidth = 0;\n\n    // The X offset to display next box.\n    int x = 0;\n\n    // The first box is connected to the screen boundary, so we have to clamp\n    // male connectors on the left.\n    int connectorWidthCompensation = INDICATOR_BOX_CONNECTOR_WIDTH;\n\n    for (int index = 0; index < count; index++) {\n        int indicator = bboxslot[index];\n        IndicatorDescription* indicatorDescription = &(bbox[indicator]);\n\n        trans_buf_to_buf(indicatorDescription->data + connectorWidthCompensation,\n            INDICATOR_BOX_WIDTH - connectorWidthCompensation,\n            INDICATOR_BOX_HEIGHT,\n            INDICATOR_BOX_WIDTH,\n            windowBuffer + x, windowWidth);\n\n        connectorWidthCompensation = 0;\n\n        unconnectedIndicatorsWidth += INDICATOR_BOX_WIDTH;\n        x = unconnectedIndicatorsWidth - INDICATOR_BOX_CONNECTOR_WIDTH * connections;\n        connections++;\n    }\n}\n\n// Adds indicator to the indicator bar.\n//\n// Returns `true` if indicator was added, or `false` if there is no available\n// space in the indicator bar.\n//\n// 0x4616F0\nstatic bool add_bar_box(int indicator)\n{\n    for (int index = 0; index < INDICATOR_SLOTS_COUNT; index++) {\n        if (bboxslot[index] == -1) {\n            bboxslot[index] = indicator;\n            return true;\n        }\n    }\n\n    debug_printf(\"\\nINTRFACE: no free bar box slots!\\n\");\n\n    return false;\n}\n\n// 0x461740\nbool enable_box_bar_win()\n{\n    bool oldIsVisible = box_status_flag;\n    box_status_flag = true;\n\n    refresh_box_bar_win();\n\n    return oldIsVisible;\n}\n\n// 0x461760\nbool disable_box_bar_win()\n{\n    bool oldIsVisible = box_status_flag;\n    box_status_flag = false;\n\n    refresh_box_bar_win();\n\n    return oldIsVisible;\n}\n"
  },
  {
    "path": "src/game/intface.h",
    "content": "#ifndef FALLOUT_GAME_INTFACE_H_\n#define FALLOUT_GAME_INTFACE_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/object_types.h\"\n\n#define INTERFACE_BAR_WIDTH 640\n#define INTERFACE_BAR_HEIGHT 100\n\ntypedef enum Hand {\n    // Item1 (Punch)\n    HAND_LEFT,\n    // Item2 (Kick)\n    HAND_RIGHT,\n    HAND_COUNT,\n} Hand;\n\ntypedef enum InterfaceItemAction {\n    INTERFACE_ITEM_ACTION_DEFAULT = -1,\n    INTERFACE_ITEM_ACTION_USE,\n    INTERFACE_ITEM_ACTION_PRIMARY,\n    INTERFACE_ITEM_ACTION_PRIMARY_AIMING,\n    INTERFACE_ITEM_ACTION_SECONDARY,\n    INTERFACE_ITEM_ACTION_SECONDARY_AIMING,\n    INTERFACE_ITEM_ACTION_RELOAD,\n    INTERFACE_ITEM_ACTION_COUNT,\n} InterfaceItemAction;\n\nextern int interfaceWindow;\nextern int bar_window;\n\nint intface_init();\nvoid intface_reset();\nvoid intface_exit();\nint intface_load(File* stream);\nint intface_save(File* stream);\nvoid intface_hide();\nvoid intface_show();\nint intface_is_hidden();\nvoid intface_enable();\nvoid intface_disable();\nbool intface_is_enabled();\nvoid intface_redraw();\nvoid intface_update_hit_points(bool animate);\nvoid intface_update_ac(bool animate);\nvoid intface_update_move_points(int actionPointsLeft, int bonusActionPoints);\nint intface_get_attack(int* hitMode, bool* aiming);\nint intface_update_items(bool animated, int leftItemAction, int rightItemAction);\nint intface_toggle_items(bool animated);\nint intface_get_item_states(int* leftItemAction, int* rightItemAction);\nint intface_toggle_item_state();\nvoid intface_use_item();\nint intface_is_item_right_hand();\nint intface_get_current_item(Object** itemPtr);\nint intface_update_ammo_lights();\nvoid intface_end_window_open(bool animated);\nvoid intface_end_window_close(bool animated);\nvoid intface_end_buttons_enable();\nvoid intface_end_buttons_disable();\nint refresh_box_bar_win();\nbool enable_box_bar_win();\nbool disable_box_bar_win();\n\n#endif /* FALLOUT_GAME_INTFACE_H_ */\n"
  },
  {
    "path": "src/game/inventry.c",
    "content": "#include \"game/inventry.h\"\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/actions.h\"\n#include \"game/anim.h\"\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/bmpdlog.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/dialog.h\"\n#include \"game/display.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gdialog.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/light.h\"\n#include \"game/map.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/roll.h\"\n#include \"game/reaction.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/svga.h\"\n\n#define INVENTORY_WINDOW_X 80\n#define INVENTORY_WINDOW_Y 0\n\n#define INVENTORY_TRADE_WINDOW_X 80\n#define INVENTORY_TRADE_WINDOW_Y 290\n#define INVENTORY_TRADE_WINDOW_WIDTH 480\n#define INVENTORY_TRADE_WINDOW_HEIGHT 180\n\n#define INVENTORY_LARGE_SLOT_WIDTH 90\n#define INVENTORY_LARGE_SLOT_HEIGHT 61\n\n#define INVENTORY_SLOT_WIDTH 64\n#define INVENTORY_SLOT_HEIGHT 48\n\n#define INVENTORY_LEFT_HAND_SLOT_X 154\n#define INVENTORY_LEFT_HAND_SLOT_Y 286\n#define INVENTORY_LEFT_HAND_SLOT_MAX_X (INVENTORY_LEFT_HAND_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH)\n#define INVENTORY_LEFT_HAND_SLOT_MAX_Y (INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT)\n\n#define INVENTORY_RIGHT_HAND_SLOT_X 245\n#define INVENTORY_RIGHT_HAND_SLOT_Y 286\n#define INVENTORY_RIGHT_HAND_SLOT_MAX_X (INVENTORY_RIGHT_HAND_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH)\n#define INVENTORY_RIGHT_HAND_SLOT_MAX_Y (INVENTORY_RIGHT_HAND_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT)\n\n#define INVENTORY_ARMOR_SLOT_X 154\n#define INVENTORY_ARMOR_SLOT_Y 183\n#define INVENTORY_ARMOR_SLOT_MAX_X (INVENTORY_ARMOR_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH)\n#define INVENTORY_ARMOR_SLOT_MAX_Y (INVENTORY_ARMOR_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT)\n\n#define INVENTORY_TRADE_LEFT_SCROLLER_X 29\n#define INVENTORY_TRADE_RIGHT_SCROLLER_X 395\n\n#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_X 165\n#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X 250\n\n#define INVENTORY_TRADE_OUTER_SCROLLER_Y 35\n#define INVENTORY_TRADE_INNER_SCROLLER_Y 20\n\n#define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X 165\n#define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y 10\n#define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X 0\n#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y 10\n#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X 250\n#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y 10\n#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X 395\n#define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y 10\n#define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_LOOT_LEFT_SCROLLER_X 176\n#define INVENTORY_LOOT_LEFT_SCROLLER_Y 37\n#define INVENTORY_LOOT_LEFT_SCROLLER_MAX_X (INVENTORY_LOOT_LEFT_SCROLLER_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_LOOT_RIGHT_SCROLLER_X 297\n#define INVENTORY_LOOT_RIGHT_SCROLLER_Y 37\n#define INVENTORY_LOOT_RIGHT_SCROLLER_MAX_X (INVENTORY_LOOT_RIGHT_SCROLLER_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_SCROLLER_X 44\n#define INVENTORY_SCROLLER_Y 35\n#define INVENTORY_SCROLLER_MAX_X (INVENTORY_SCROLLER_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_BODY_VIEW_WIDTH 60\n#define INVENTORY_BODY_VIEW_HEIGHT 100\n\n#define INVENTORY_PC_BODY_VIEW_X 176\n#define INVENTORY_PC_BODY_VIEW_Y 37\n#define INVENTORY_PC_BODY_VIEW_MAX_X (INVENTORY_PC_BODY_VIEW_X + INVENTORY_BODY_VIEW_WIDTH)\n#define INVENTORY_PC_BODY_VIEW_MAX_Y (INVENTORY_PC_BODY_VIEW_Y + INVENTORY_BODY_VIEW_HEIGHT)\n\n#define INVENTORY_LOOT_RIGHT_BODY_VIEW_X 422\n#define INVENTORY_LOOT_RIGHT_BODY_VIEW_Y 35\n\n#define INVENTORY_LOOT_LEFT_BODY_VIEW_X 44\n#define INVENTORY_LOOT_LEFT_BODY_VIEW_Y 35\n\n// NOTE: CE uses relative coordinates for hit testing for which coordinates\n// above is enough. However RE requires separate sets of coordinates as it\n// performs hit tests in screen coordinates.\n\n#define INVENTORY_LEFT_HAND_SLOT_ABS_X (INVENTORY_WINDOW_X + INVENTORY_LEFT_HAND_SLOT_X)\n#define INVENTORY_LEFT_HAND_SLOT_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_LEFT_HAND_SLOT_Y)\n#define INVENTORY_LEFT_HAND_SLOT_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_LEFT_HAND_SLOT_MAX_X)\n#define INVENTORY_LEFT_HAND_SLOT_ABS_MAX_Y (INVENTORY_WINDOW_Y + INVENTORY_LEFT_HAND_SLOT_MAX_Y)\n\n#define INVENTORY_RIGHT_HAND_SLOT_ABS_X (INVENTORY_WINDOW_X + INVENTORY_RIGHT_HAND_SLOT_X)\n#define INVENTORY_RIGHT_HAND_SLOT_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_RIGHT_HAND_SLOT_Y)\n#define INVENTORY_RIGHT_HAND_SLOT_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_RIGHT_HAND_SLOT_MAX_X)\n#define INVENTORY_RIGHT_HAND_SLOT_ABS_MAX_Y (INVENTORY_WINDOW_Y + INVENTORY_RIGHT_HAND_SLOT_MAX_Y)\n\n#define INVENTORY_ARMOR_SLOT_ABS_X (INVENTORY_WINDOW_X + INVENTORY_ARMOR_SLOT_X)\n#define INVENTORY_ARMOR_SLOT_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_ARMOR_SLOT_Y)\n#define INVENTORY_ARMOR_SLOT_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_ARMOR_SLOT_MAX_X)\n#define INVENTORY_ARMOR_SLOT_ABS_MAX_Y (INVENTORY_WINDOW_Y + INVENTORY_ARMOR_SLOT_MAX_Y)\n\n#define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X)\n#define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_Y (INVENTORY_TRADE_WINDOW_Y + 10 + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y)\n#define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_MAX_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X)\n#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_Y (INVENTORY_TRADE_WINDOW_Y + 10 + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y)\n#define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_MAX_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X)\n#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_Y (INVENTORY_TRADE_WINDOW_Y + 10 + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y)\n#define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_MAX_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X)\n#define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_Y (INVENTORY_TRADE_WINDOW_Y + 10 + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y)\n#define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_MAX_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH)\n\n#define INVENTORY_LOOT_LEFT_SCROLLER_ABS_X (INVENTORY_WINDOW_X + INVENTORY_LOOT_LEFT_SCROLLER_X)\n#define INVENTORY_LOOT_LEFT_SCROLLER_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_LOOT_LEFT_SCROLLER_Y)\n#define INVENTORY_LOOT_LEFT_SCROLLER_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_LOOT_LEFT_SCROLLER_MAX_X)\n\n#define INVENTORY_LOOT_RIGHT_SCROLLER_ABS_X (INVENTORY_WINDOW_X + INVENTORY_LOOT_RIGHT_SCROLLER_X)\n#define INVENTORY_LOOT_RIGHT_SCROLLER_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_LOOT_RIGHT_SCROLLER_Y)\n#define INVENTORY_LOOT_RIGHT_SCROLLER_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_LOOT_RIGHT_SCROLLER_MAX_X)\n\n#define INVENTORY_SCROLLER_ABS_X (INVENTORY_WINDOW_X + INVENTORY_SCROLLER_X)\n#define INVENTORY_SCROLLER_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_SCROLLER_Y)\n#define INVENTORY_SCROLLER_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_SCROLLER_MAX_X)\n\n#define INVENTORY_PC_BODY_VIEW_ABS_X (INVENTORY_WINDOW_X + INVENTORY_PC_BODY_VIEW_X)\n#define INVENTORY_PC_BODY_VIEW_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_PC_BODY_VIEW_Y)\n#define INVENTORY_PC_BODY_VIEW_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_PC_BODY_VIEW_MAX_X)\n#define INVENTORY_PC_BODY_VIEW_ABS_MAX_Y (INVENTORY_WINDOW_Y + INVENTORY_PC_BODY_VIEW_MAX_Y)\n\n#define INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY (1000U / ROTATION_COUNT)\n\ntypedef void(InventoryPrintItemDescriptionHandler)(char* string);\n\ntypedef enum InventoryArrowFrm {\n    INVENTORY_ARROW_FRM_LEFT_ARROW_UP,\n    INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN,\n    INVENTORY_ARROW_FRM_RIGHT_ARROW_UP,\n    INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN,\n    INVENTORY_ARROW_FRM_COUNT,\n} InventoryArrowFrm;\n\ntypedef struct InventoryWindowConfiguration {\n    int field_0; // artId\n    int width;\n    int height;\n    int x;\n    int y;\n} InventoryWindowDescription;\n\ntypedef struct InventoryCursorData {\n    Art* frm;\n    unsigned char* frmData;\n    int width;\n    int height;\n    int offsetX;\n    int offsetY;\n    CacheEntry* frmHandle;\n} InventoryCursorData;\n\nstatic int inventry_msg_load();\nstatic int inventry_msg_unload();\nstatic void display_inventory_info(Object* item, int quantity, unsigned char* dest, int pitch, bool a5);\nstatic void inven_update_lighting(Object* a1);\nstatic int barter_compute_value(Object* a1, Object* a2);\nstatic int barter_attempt_transaction(Object* a1, Object* a2, Object* a3, Object* a4);\nstatic void barter_move_inventory(Object* a1, int quantity, int a3, int a4, Object* a5, Object* a6, bool a7);\nstatic void barter_move_from_table_inventory(Object* a1, int quantity, int a3, Object* a4, Object* a5, bool a6);\nstatic void display_table_inventories(int win, Object* a2, Object* a3, int a4);\nstatic int do_move_timer(int inventoryWindowType, Object* item, int a3);\nstatic int setup_move_timer_win(int inventoryWindowType, Object* item);\nstatic int exit_move_timer_win(int inventoryWindowType);\n\n// The number of items to show in scroller.\n//\n// 0x519054\nstatic int inven_cur_disp = 6;\n\n// 0x519058\nstatic Object* inven_dude = NULL;\n\n// Probably fid of armor to display in inventory dialog.\n//\n// 0x51905C\nstatic int inven_pid = -1;\n\n// 0x519060\nstatic bool inven_is_initialized = false;\n\n// 0x519064\nstatic int inven_display_msg_line = 1;\n\n// 0x519068\nstatic InventoryWindowDescription iscr_data[INVENTORY_WINDOW_TYPE_COUNT] = {\n    { 48, 499, 377, 80, 0 },\n    { 113, 292, 376, 80, 0 },\n    { 114, 537, 376, 80, 0 },\n    { 111, 480, 180, 80, 290 },\n    { 305, 259, 162, 140, 80 },\n    { 305, 259, 162, 140, 80 },\n};\n\n// 0x5190E0\nstatic bool dropped_explosive = false;\n\n// 0x5190E4\nstatic int inven_scroll_up_bid = -1;\n\n// 0x5190E8\nstatic int inven_scroll_dn_bid = -1;\n\n// 0x5190EC\nstatic int loot_scroll_up_bid = -1;\n\n// 0x5190F0\nstatic int loot_scroll_dn_bid = -1;\n\n// 0x59E79C\nstatic CacheEntry* mt_key[8];\n\n// 0x59E7BC\nCacheEntry* ikey[OFF_59E7BC_COUNT];\n\n// 0x59E7EC\nstatic int target_stack_offset[10];\n\n// inventory.msg\n//\n// 0x59E814\nstatic MessageList inventry_message_file;\n\n// 0x59E81C\nstatic Object* target_stack[10];\n\n// 0x59E844\nstatic int stack_offset[10];\n\n// 0x59E86C\nstatic Object* stack[10];\n\n// 0x59E894\nstatic int mt_wid;\n\n// 0x59E898\nstatic int barter_mod;\n\n// 0x59E89C\nstatic int btable_offset;\n\n// 0x59E8A0\nstatic int ptable_offset;\n\n// 0x59E8A4\nstatic Inventory* ptable_pud;\n\n// 0x59E8A8\nstatic InventoryCursorData imdata[INVENTORY_WINDOW_CURSOR_COUNT];\n\n// 0x59E934\nstatic Object* ptable;\n\n// 0x59E938\nstatic InventoryPrintItemDescriptionHandler* display_msg;\n\n// 0x59E93C\nstatic int im_value;\n\n// 0x59E940\nstatic int immode;\n\n// 0x59E944\nstatic Object* btable;\n\n// 0x59E948\nstatic int target_curr_stack;\n\n// 0x59E94C\nstatic Inventory* btable_pud;\n\n// 0x59E950\nstatic bool inven_ui_was_disabled;\n\n// 0x59E954\nstatic Object* i_worn;\n\n// 0x59E958\nstatic Object* i_lhand;\n\n// Rotating character's fid.\n//\n// 0x59E95C\nstatic int i_fid;\n\n// 0x59E960\nstatic Inventory* pud;\n\n// 0x59E964\nstatic int i_wid;\n\n// item2\n// 0x59E968\nstatic Object* i_rhand;\n\n// 0x59E96C\nstatic int curr_stack;\n\n// 0x59E970\nstatic int i_wid_max_y;\n\n// 0x59E974\nstatic int i_wid_max_x;\n\n// 0x59E978\nstatic Inventory* target_pud;\n\n// 0x59E97C\nstatic int barter_back_win;\n\n// NOTE: Unused.\n//\n// 0x46E718\nvoid inven_set_dude(Object* obj, int pid)\n{\n    inven_dude = obj;\n    inven_pid = pid;\n}\n\n// 0x46E724\nvoid inven_reset_dude()\n{\n    inven_dude = obj_dude;\n    inven_pid = 0x1000000;\n}\n\n// 0x46E73C\nstatic int inventry_msg_load()\n{\n    char path[MAX_PATH];\n\n    if (!message_init(&inventry_message_file))\n        return -1;\n\n    sprintf(path, \"%s%s\", msg_path, \"inventry.msg\");\n    if (!message_load(&inventry_message_file, path))\n        return -1;\n\n    return 0;\n}\n\n// 0x46E7A0\nstatic int inventry_msg_unload()\n{\n    message_exit(&inventry_message_file);\n    return 0;\n}\n\n// 0x46E7B0\nvoid handle_inventory()\n{\n    if (isInCombat()) {\n        if (combat_whose_turn() != inven_dude) {\n            return;\n        }\n    }\n\n    if (inven_init() == -1) {\n        return;\n    }\n\n    if (isInCombat()) {\n        if (inven_dude == obj_dude) {\n            int actionPointsRequired = 4 - 2 * perk_level(inven_dude, PERK_QUICK_POCKETS);\n            if (actionPointsRequired > 0 && actionPointsRequired > obj_dude->data.critter.combat.ap) {\n                // You don't have enough action points to use inventory\n                MessageListItem messageListItem;\n                messageListItem.num = 19;\n                if (message_search(&inventry_message_file, &messageListItem)) {\n                    display_print(messageListItem.text);\n                }\n\n                // NOTE: Uninline.\n                inven_exit();\n\n                return;\n            }\n\n            if (actionPointsRequired > 0) {\n                if (actionPointsRequired > obj_dude->data.critter.combat.ap) {\n                    obj_dude->data.critter.combat.ap = 0;\n                } else {\n                    obj_dude->data.critter.combat.ap -= actionPointsRequired;\n                }\n                intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n            }\n        }\n    }\n\n    Object* oldArmor = inven_worn(inven_dude);\n    bool isoWasEnabled = setup_inventory(INVENTORY_WINDOW_TYPE_NORMAL);\n    register_clear(inven_dude);\n    display_stats();\n    display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL);\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n\n    for (;;) {\n        int keyCode = get_input();\n\n        if (keyCode == KEY_ESCAPE) {\n            break;\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL);\n\n        if (game_state() == GAME_STATE_5) {\n            break;\n        }\n\n        if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X) {\n            game_quit_with_confirm();\n        } else if (keyCode == KEY_HOME) {\n            stack_offset[curr_stack] = 0;\n            display_inventory(0, -1, INVENTORY_WINDOW_TYPE_NORMAL);\n        } else if (keyCode == KEY_ARROW_UP) {\n            if (stack_offset[curr_stack] > 0) {\n                stack_offset[curr_stack] -= 1;\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL);\n            }\n        } else if (keyCode == KEY_PAGE_UP) {\n            stack_offset[curr_stack] -= inven_cur_disp;\n            if (stack_offset[curr_stack] < 0) {\n                stack_offset[curr_stack] = 0;\n            }\n            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL);\n        } else if (keyCode == KEY_END) {\n            stack_offset[curr_stack] = pud->length - inven_cur_disp;\n            if (stack_offset[curr_stack] < 0) {\n                stack_offset[curr_stack] = 0;\n            }\n            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL);\n        } else if (keyCode == KEY_ARROW_DOWN) {\n            if (inven_cur_disp + stack_offset[curr_stack] < pud->length) {\n                stack_offset[curr_stack] += 1;\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL);\n            }\n        } else if (keyCode == KEY_PAGE_DOWN) {\n            int v12 = inven_cur_disp + stack_offset[curr_stack];\n            int v13 = v12 + inven_cur_disp;\n            stack_offset[curr_stack] = v12;\n            int v14 = pud->length;\n            if (v13 >= pud->length) {\n                int v15 = v14 - inven_cur_disp;\n                stack_offset[curr_stack] = v14 - inven_cur_disp;\n                if (v15 < 0) {\n                    stack_offset[curr_stack] = 0;\n                }\n            }\n            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL);\n        } else if (keyCode == 2500) {\n            container_exit(keyCode, INVENTORY_WINDOW_TYPE_NORMAL);\n        } else {\n            if ((mouse_get_buttons() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) {\n                if (immode == INVENTORY_WINDOW_CURSOR_HAND) {\n                    inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW);\n                } else if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n                    display_stats();\n                    win_draw(i_wid);\n                }\n            } else if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n                if (keyCode >= 1000 && keyCode <= 1008) {\n                    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                        inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_NORMAL);\n                    } else {\n                        inven_pickup(keyCode, stack_offset[curr_stack]);\n                    }\n                }\n            }\n        }\n    }\n\n    inven_dude = stack[0];\n    adjust_fid();\n\n    if (inven_dude == obj_dude) {\n        Rect rect;\n        obj_change_fid(inven_dude, i_fid, &rect);\n        tile_refresh_rect(&rect, inven_dude->elevation);\n    }\n\n    Object* newArmor = inven_worn(inven_dude);\n    if (inven_dude == obj_dude) {\n        if (oldArmor != newArmor) {\n            intface_update_ac(true);\n        }\n    }\n\n    exit_inventory(isoWasEnabled);\n\n    // NOTE: Uninline.\n    inven_exit();\n\n    if (inven_dude == obj_dude) {\n        intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n    }\n}\n\n// 0x46EC90\nbool setup_inventory(int inventoryWindowType)\n{\n    dropped_explosive = 0;\n    curr_stack = 0;\n    stack_offset[0] = 0;\n    inven_cur_disp = 6;\n    pud = &(inven_dude->data.inventory);\n    stack[0] = inven_dude;\n\n    if (inventoryWindowType <= INVENTORY_WINDOW_TYPE_LOOT) {\n        InventoryWindowDescription* windowDescription = &(iscr_data[inventoryWindowType]);\n        int inventoryWindowX = INVENTORY_WINDOW_X;\n        int inventoryWindowY = INVENTORY_WINDOW_Y;\n        i_wid = win_add(inventoryWindowX,\n            inventoryWindowY,\n            windowDescription->width,\n            windowDescription->height,\n            257,\n            WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n        i_wid_max_x = windowDescription->width + inventoryWindowX;\n        i_wid_max_y = windowDescription->height + inventoryWindowY;\n\n        unsigned char* dest = win_get_buf(i_wid);\n        int backgroundFid = art_id(OBJ_TYPE_INTERFACE, windowDescription->field_0, 0, 0, 0);\n\n        CacheEntry* backgroundFrmHandle;\n        unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n        if (backgroundFrmData != NULL) {\n            buf_to_buf(backgroundFrmData, windowDescription->width, windowDescription->height, windowDescription->width, dest, windowDescription->width);\n            art_ptr_unlock(backgroundFrmHandle);\n        }\n\n        display_msg = display_print;\n    } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        if (barter_back_win == -1) {\n            exit(1);\n        }\n\n        inven_cur_disp = 3;\n\n        int tradeWindowX = INVENTORY_TRADE_WINDOW_X;\n        int tradeWindowY = INVENTORY_TRADE_WINDOW_Y;\n        i_wid = win_add(tradeWindowX, tradeWindowY, INVENTORY_TRADE_WINDOW_WIDTH, INVENTORY_TRADE_WINDOW_HEIGHT, 257, 0);\n        i_wid_max_x = tradeWindowX + INVENTORY_TRADE_WINDOW_WIDTH;\n        i_wid_max_y = tradeWindowY + INVENTORY_TRADE_WINDOW_HEIGHT;\n\n        unsigned char* dest = win_get_buf(i_wid);\n        unsigned char* src = win_get_buf(barter_back_win);\n        buf_to_buf(src + INVENTORY_TRADE_WINDOW_X, INVENTORY_TRADE_WINDOW_WIDTH, INVENTORY_TRADE_WINDOW_HEIGHT, scr_size.lrx - scr_size.ulx + 1, dest, INVENTORY_TRADE_WINDOW_WIDTH);\n\n        display_msg = gdialogDisplayMsg;\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n        // Create invsibile buttons representing character's inventory item\n        // slots.\n        for (int index = 0; index < inven_cur_disp; index++) {\n            int btn = win_register_button(i_wid, INVENTORY_LOOT_LEFT_SCROLLER_X, INVENTORY_SLOT_HEIGHT * (inven_cur_disp - index - 1) + INVENTORY_LOOT_LEFT_SCROLLER_Y, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 999 + inven_cur_disp - index, -1, 999 + inven_cur_disp - index, -1, NULL, NULL, NULL, 0);\n            if (btn != -1) {\n                win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n            }\n        }\n\n        int eventCode = 2005;\n        int y = INVENTORY_SLOT_HEIGHT * 5 + INVENTORY_LOOT_LEFT_SCROLLER_Y;\n\n        // Create invisible buttons representing container's inventory item\n        // slots. For unknown reason it loops backwards and it's size is\n        // hardcoded at 6 items.\n        //\n        // Original code is slightly different. It loops until y reaches -11,\n        // which is a bit awkward for a loop. Probably result of some\n        // optimization.\n        for (int index = 0; index < 6; index++) {\n            int btn = win_register_button(i_wid, INVENTORY_LOOT_RIGHT_SCROLLER_X, y, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, eventCode, -1, eventCode, -1, NULL, NULL, NULL, 0);\n            if (btn != -1) {\n                win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n            }\n\n            eventCode -= 1;\n            y -= INVENTORY_SLOT_HEIGHT;\n        }\n    } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        int y1 = INVENTORY_TRADE_OUTER_SCROLLER_Y;\n        int y2 = INVENTORY_TRADE_INNER_SCROLLER_Y;\n\n        for (int index = 0; index < inven_cur_disp; index++) {\n            int btn;\n\n            // Invsibile button representing left inventory slot.\n            btn = win_register_button(i_wid, INVENTORY_TRADE_LEFT_SCROLLER_X, y1, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 1000 + index, -1, 1000 + index, -1, NULL, NULL, NULL, 0);\n            if (btn != -1) {\n                win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n            }\n\n            // Invisible button representing right inventory slot.\n            btn = win_register_button(i_wid, INVENTORY_TRADE_RIGHT_SCROLLER_X, y1, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2000 + index, -1, 2000 + index, -1, NULL, NULL, NULL, 0);\n            if (btn != -1) {\n                win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n            }\n\n            // Invisible button representing left suggested slot.\n            btn = win_register_button(i_wid, INVENTORY_TRADE_INNER_LEFT_SCROLLER_X, y2, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2300 + index, -1, 2300 + index, -1, NULL, NULL, NULL, 0);\n            if (btn != -1) {\n                win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n            }\n\n            // Invisible button representing right suggested slot.\n            btn = win_register_button(i_wid, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X, y2, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2400 + index, -1, 2400 + index, -1, NULL, NULL, NULL, 0);\n            if (btn != -1) {\n                win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n            }\n\n            y1 += INVENTORY_SLOT_HEIGHT;\n            y2 += INVENTORY_SLOT_HEIGHT;\n        }\n    } else {\n        // Create invisible buttons representing item slots.\n        for (int index = 0; index < inven_cur_disp; index++) {\n            int btn = win_register_button(i_wid,\n                INVENTORY_SCROLLER_X,\n                INVENTORY_SLOT_HEIGHT * (inven_cur_disp - index - 1) + INVENTORY_SCROLLER_Y,\n                INVENTORY_SLOT_WIDTH,\n                INVENTORY_SLOT_HEIGHT,\n                999 + inven_cur_disp - index,\n                -1,\n                999 + inven_cur_disp - index,\n                -1,\n                NULL,\n                NULL,\n                NULL,\n                0);\n            if (btn != -1) {\n                win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n            }\n        }\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) {\n        int btn;\n\n        // Item2 slot\n        btn = win_register_button(i_wid, INVENTORY_RIGHT_HAND_SLOT_X, INVENTORY_RIGHT_HAND_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 1006, -1, 1006, -1, NULL, NULL, NULL, 0);\n        if (btn != -1) {\n            win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n        }\n\n        // Item1 slot\n        btn = win_register_button(i_wid, INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LEFT_HAND_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 1007, -1, 1007, -1, NULL, NULL, NULL, 0);\n        if (btn != -1) {\n            win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n        }\n\n        // Armor slot\n        btn = win_register_button(i_wid, INVENTORY_ARMOR_SLOT_X, INVENTORY_ARMOR_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 1008, -1, 1008, -1, NULL, NULL, NULL, 0);\n        if (btn != -1) {\n            win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL);\n        }\n    }\n\n    memset(ikey, 0, sizeof(ikey));\n\n    int fid;\n    int btn;\n    unsigned char* buttonUpData;\n    unsigned char* buttonDownData;\n    unsigned char* buttonDisabledData;\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n    buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[0]));\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n    buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[1]));\n\n    if (buttonUpData != NULL && buttonDownData != NULL) {\n        btn = -1;\n        switch (inventoryWindowType) {\n        case INVENTORY_WINDOW_TYPE_NORMAL:\n            // Done button\n            btn = win_register_button(i_wid, 437, 329, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT);\n            break;\n        case INVENTORY_WINDOW_TYPE_USE_ITEM_ON:\n            // Cancel button\n            btn = win_register_button(i_wid, 233, 328, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT);\n            break;\n        case INVENTORY_WINDOW_TYPE_LOOT:\n            // Done button\n            btn = win_register_button(i_wid, 476, 331, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT);\n            break;\n        }\n\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n        }\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        // TODO: Figure out why it building fid in chain.\n\n        // Large arrow up (normal).\n        fid = art_id(OBJ_TYPE_INTERFACE, 100, 0, 0, 0);\n        fid = art_id(OBJ_TYPE_INTERFACE, fid, 0, 0, 0);\n        buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[2]));\n\n        // Large arrow up (pressed).\n        fid = art_id(OBJ_TYPE_INTERFACE, 101, 0, 0, 0);\n        fid = art_id(OBJ_TYPE_INTERFACE, fid, 0, 0, 0);\n        buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[3]));\n\n        if (buttonUpData != NULL && buttonDownData != NULL) {\n            // Left inventory up button.\n            btn = win_register_button(i_wid, 109, 56, 23, 24, -1, -1, KEY_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n\n            // Right inventory up button.\n            btn = win_register_button(i_wid, 342, 56, 23, 24, -1, -1, KEY_CTRL_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n        }\n    } else {\n        // Large up arrow (normal).\n        fid = art_id(OBJ_TYPE_INTERFACE, 49, 0, 0, 0);\n        buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[2]));\n\n        // Large up arrow (pressed).\n        fid = art_id(OBJ_TYPE_INTERFACE, 50, 0, 0, 0);\n        buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[3]));\n\n        // Large up arrow (disabled).\n        fid = art_id(OBJ_TYPE_INTERFACE, 53, 0, 0, 0);\n        buttonDisabledData = art_ptr_lock_data(fid, 0, 0, &(ikey[4]));\n\n        if (buttonUpData != NULL && buttonDownData != NULL && buttonDisabledData != NULL) {\n            if (inventoryWindowType != INVENTORY_WINDOW_TYPE_TRADE) {\n                // Left inventory up button.\n                inven_scroll_up_bid = win_register_button(i_wid, 128, 39, 22, 23, -1, -1, KEY_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0);\n                if (inven_scroll_up_bid != -1) {\n                    win_register_button_disable(inven_scroll_up_bid, buttonDisabledData, buttonDisabledData, buttonDisabledData);\n                    win_register_button_sound_func(inven_scroll_up_bid, gsound_red_butt_press, gsound_red_butt_release);\n                    win_disable_button(inven_scroll_up_bid);\n                }\n            }\n\n            if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n                // Right inventory up button.\n                loot_scroll_up_bid = win_register_button(i_wid, 379, 39, 22, 23, -1, -1, KEY_CTRL_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0);\n                if (loot_scroll_up_bid != -1) {\n                    win_register_button_disable(loot_scroll_up_bid, buttonDisabledData, buttonDisabledData, buttonDisabledData);\n                    win_register_button_sound_func(loot_scroll_up_bid, gsound_red_butt_press, gsound_red_butt_release);\n                    win_disable_button(loot_scroll_up_bid);\n                }\n            }\n        }\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        // Large dialog down button (normal)\n        fid = art_id(OBJ_TYPE_INTERFACE, 93, 0, 0, 0);\n        fid = art_id(OBJ_TYPE_INTERFACE, fid, 0, 0, 0);\n        buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[5]));\n\n        // Dialog down button (pressed)\n        fid = art_id(OBJ_TYPE_INTERFACE, 94, 0, 0, 0);\n        fid = art_id(OBJ_TYPE_INTERFACE, fid, 0, 0, 0);\n        buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[6]));\n\n        if (buttonUpData != NULL && buttonDownData != NULL) {\n            // Left inventory down button.\n            btn = win_register_button(i_wid, 109, 82, 24, 25, -1, -1, KEY_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n\n            // Right inventory down button\n            btn = win_register_button(i_wid, 342, 82, 24, 25, -1, -1, KEY_CTRL_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n\n            // Invisible button representing left character.\n            win_register_button(barter_back_win, 15, 25, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2500, -1, NULL, NULL, NULL, 0);\n\n            // Invisible button representing right character.\n            win_register_button(barter_back_win, 560, 25, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2501, -1, NULL, NULL, NULL, 0);\n        }\n    } else {\n        // Large arrow down (normal).\n        fid = art_id(OBJ_TYPE_INTERFACE, 51, 0, 0, 0);\n        buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[5]));\n\n        // Large arrow down (pressed).\n        fid = art_id(OBJ_TYPE_INTERFACE, 52, 0, 0, 0);\n        buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[6]));\n\n        // Large arrow down (disabled).\n        fid = art_id(OBJ_TYPE_INTERFACE, 54, 0, 0, 0);\n        buttonDisabledData = art_ptr_lock_data(fid, 0, 0, &(ikey[7]));\n\n        if (buttonUpData != NULL && buttonDownData != NULL && buttonDisabledData != NULL) {\n            // Left inventory down button.\n            inven_scroll_dn_bid = win_register_button(i_wid, 128, 62, 22, 23, -1, -1, KEY_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0);\n            win_register_button_sound_func(inven_scroll_dn_bid, gsound_red_butt_press, gsound_red_butt_release);\n            win_register_button_disable(inven_scroll_dn_bid, buttonDisabledData, buttonDisabledData, buttonDisabledData);\n            win_disable_button(inven_scroll_dn_bid);\n\n            if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n                // Invisible button representing left character.\n                win_register_button(i_wid, INVENTORY_LOOT_LEFT_BODY_VIEW_X, INVENTORY_LOOT_LEFT_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2500, -1, NULL, NULL, NULL, 0);\n\n                // Right inventory down button.\n                loot_scroll_dn_bid = win_register_button(i_wid, 379, 62, 22, 23, -1, -1, KEY_CTRL_ARROW_DOWN, -1, buttonUpData, buttonDownData, 0, 0);\n                if (loot_scroll_dn_bid != -1) {\n                    win_register_button_sound_func(loot_scroll_dn_bid, gsound_red_butt_press, gsound_red_butt_release);\n                    win_register_button_disable(loot_scroll_dn_bid, buttonDisabledData, buttonDisabledData, buttonDisabledData);\n                    win_disable_button(loot_scroll_dn_bid);\n                }\n\n                // Invisible button representing right character.\n                win_register_button(i_wid, INVENTORY_LOOT_RIGHT_BODY_VIEW_X, INVENTORY_LOOT_RIGHT_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2501, -1, NULL, NULL, NULL, 0);\n            } else {\n                // Invisible button representing character (in inventory and use on dialogs).\n                win_register_button(i_wid, INVENTORY_PC_BODY_VIEW_X, INVENTORY_PC_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2500, -1, NULL, NULL, NULL, 0);\n            }\n        }\n    }\n\n    if (inventoryWindowType != INVENTORY_WINDOW_TYPE_TRADE) {\n        if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n            if (!gIsSteal) {\n                // Take all button (normal)\n                fid = art_id(OBJ_TYPE_INTERFACE, 436, 0, 0, 0);\n                buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[8]));\n\n                // Take all button (pressed)\n                fid = art_id(OBJ_TYPE_INTERFACE, 437, 0, 0, 0);\n                buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[9]));\n\n                if (buttonUpData != NULL && buttonDownData != NULL) {\n                    // Take all button.\n                    btn = win_register_button(i_wid, 432, 204, 39, 41, -1, -1, KEY_UPPERCASE_A, -1, buttonUpData, buttonDownData, NULL, 0);\n                    if (btn != -1) {\n                        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n                    }\n                }\n            }\n        }\n    } else {\n        // Inventory button up (normal)\n        fid = art_id(OBJ_TYPE_INTERFACE, 49, 0, 0, 0);\n        buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[8]));\n\n        // Inventory button up (pressed)\n        fid = art_id(OBJ_TYPE_INTERFACE, 50, 0, 0, 0);\n        buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[9]));\n\n        if (buttonUpData != NULL && buttonDownData != NULL) {\n            // Left offered inventory up button.\n            btn = win_register_button(i_wid, 128, 113, 22, 23, -1, -1, KEY_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n\n            // Right offered inventory up button.\n            btn = win_register_button(i_wid, 333, 113, 22, 23, -1, -1, KEY_CTRL_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n        }\n\n        // Inventory button down (normal)\n        fid = art_id(OBJ_TYPE_INTERFACE, 51, 0, 0, 0);\n        buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[8]));\n\n        // Inventory button down (pressed).\n        fid = art_id(OBJ_TYPE_INTERFACE, 52, 0, 0, 0);\n        buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[9]));\n\n        if (buttonUpData != NULL && buttonDownData != NULL) {\n            // Left offered inventory down button.\n            btn = win_register_button(i_wid, 128, 136, 22, 23, -1, -1, KEY_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n\n            // Right offered inventory down button.\n            btn = win_register_button(i_wid, 333, 136, 22, 23, -1, -1, KEY_CTRL_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n        }\n    }\n\n    i_rhand = NULL;\n    i_worn = NULL;\n    i_lhand = NULL;\n\n    for (int index = 0; index < pud->length; index++) {\n        InventoryItem* inventoryItem = &(pud->items[index]);\n        Object* item = inventoryItem->item;\n        if ((item->flags & OBJECT_IN_LEFT_HAND) != 0) {\n            if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) {\n                i_rhand = item;\n            }\n            i_lhand = item;\n        } else if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) {\n            i_rhand = item;\n        } else if ((item->flags & OBJECT_WORN) != 0) {\n            i_worn = item;\n        }\n    }\n\n    if (i_lhand != NULL) {\n        item_remove_mult(inven_dude, i_lhand, 1);\n    }\n\n    if (i_rhand != NULL && i_rhand != i_lhand) {\n        item_remove_mult(inven_dude, i_rhand, 1);\n    }\n\n    if (i_worn != NULL) {\n        item_remove_mult(inven_dude, i_worn, 1);\n    }\n\n    adjust_fid();\n\n    bool isoWasEnabled = map_disable_bk_processes();\n\n    gmouse_disable(0);\n\n    return isoWasEnabled;\n}\n\n// 0x46FBD8\nvoid exit_inventory(bool shouldEnableIso)\n{\n    inven_dude = stack[0];\n\n    if (i_lhand != NULL) {\n        i_lhand->flags |= OBJECT_IN_LEFT_HAND;\n        if (i_lhand == i_rhand) {\n            i_lhand->flags |= OBJECT_IN_RIGHT_HAND;\n        }\n\n        item_add_force(inven_dude, i_lhand, 1);\n    }\n\n    if (i_rhand != NULL && i_rhand != i_lhand) {\n        i_rhand->flags |= OBJECT_IN_RIGHT_HAND;\n        item_add_force(inven_dude, i_rhand, 1);\n    }\n\n    if (i_worn != NULL) {\n        i_worn->flags |= OBJECT_WORN;\n        item_add_force(inven_dude, i_worn, 1);\n    }\n\n    i_rhand = NULL;\n    i_worn = NULL;\n    i_lhand = NULL;\n\n    for (int index = 0; index < OFF_59E7BC_COUNT; index++) {\n        art_ptr_unlock(ikey[index]);\n    }\n\n    if (shouldEnableIso) {\n        map_enable_bk_processes();\n    }\n\n    win_delete(i_wid);\n\n    gmouse_enable();\n\n    if (dropped_explosive) {\n        Attack v1;\n        combat_ctd_init(&v1, obj_dude, NULL, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);\n        v1.attackerFlags = DAM_HIT;\n        v1.tile = obj_dude->tile;\n        compute_explosion_on_extras(&v1, 0, 0, 1);\n\n        Object* v2 = NULL;\n        for (int index = 0; index < v1.extrasLength; index++) {\n            Object* critter = v1.extras[index];\n            if (critter != obj_dude\n                && critter->data.critter.combat.team != obj_dude->data.critter.combat.team\n                && stat_result(critter, STAT_PERCEPTION, 0, NULL) >= ROLL_SUCCESS) {\n                critter_set_who_hit_me(critter, obj_dude);\n\n                if (v2 == NULL) {\n                    v2 = critter;\n                }\n            }\n        }\n\n        if (v2 != NULL) {\n            if (!isInCombat()) {\n                STRUCT_664980 v3;\n                v3.attacker = v2;\n                v3.defender = obj_dude;\n                v3.actionPointsBonus = 0;\n                v3.accuracyBonus = 0;\n                v3.damageBonus = 0;\n                v3.minDamage = 0;\n                v3.maxDamage = INT_MAX;\n                v3.field_1C = 0;\n                scripts_request_combat(&v3);\n            }\n        }\n\n        dropped_explosive = false;\n    }\n}\n\n// 0x46FDF4\nvoid display_inventory(int a1, int a2, int inventoryWindowType)\n{\n    unsigned char* windowBuffer = win_get_buf(i_wid);\n    int pitch;\n\n    int v49 = 0;\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) {\n        pitch = 499;\n\n        int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0);\n\n        CacheEntry* backgroundFrmHandle;\n        unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n        if (backgroundFrmData != NULL) {\n            // Clear scroll view background.\n            buf_to_buf(backgroundFrmData + pitch * 35 + 44, INVENTORY_SLOT_WIDTH, inven_cur_disp * INVENTORY_SLOT_HEIGHT, pitch, windowBuffer + pitch * 35 + 44, pitch);\n\n            // Clear armor button background.\n            buf_to_buf(backgroundFrmData + pitch * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, pitch);\n\n            if (i_lhand != NULL && i_lhand == i_rhand) {\n                // Clear item1.\n                int itemBackgroundFid = art_id(OBJ_TYPE_INTERFACE, 32, 0, 0, 0);\n\n                CacheEntry* itemBackgroundFrmHandle;\n                Art* itemBackgroundFrm = art_ptr_lock(itemBackgroundFid, &itemBackgroundFrmHandle);\n                if (itemBackgroundFrm != NULL) {\n                    unsigned char* data = art_frame_data(itemBackgroundFrm, 0, 0);\n                    int width = art_frame_width(itemBackgroundFrm, 0, 0);\n                    int height = art_frame_length(itemBackgroundFrm, 0, 0);\n                    buf_to_buf(data, width, height, width, windowBuffer + pitch * 284 + 152, pitch);\n                    art_ptr_unlock(itemBackgroundFrmHandle);\n                }\n            } else {\n                // Clear both items in one go.\n                buf_to_buf(backgroundFrmData + pitch * INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH * 2, INVENTORY_LARGE_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LEFT_HAND_SLOT_X, pitch);\n            }\n\n            art_ptr_unlock(backgroundFrmHandle);\n        }\n    } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON) {\n        pitch = 292;\n\n        int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 113, 0, 0, 0);\n\n        CacheEntry* backgroundFrmHandle;\n        unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n        if (backgroundFrmData != NULL) {\n            // Clear scroll view background.\n            buf_to_buf(backgroundFrmData + pitch * 35 + 44, 64, inven_cur_disp * 48, pitch, windowBuffer + pitch * 35 + 44, pitch);\n            art_ptr_unlock(backgroundFrmHandle);\n        }\n    } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n        pitch = 537;\n\n        int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 114, 0, 0, 0);\n\n        CacheEntry* backgroundFrmHandle;\n        unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n        if (backgroundFrmData != NULL) {\n            // Clear scroll view background.\n            buf_to_buf(backgroundFrmData + pitch * 37 + 176, 64, inven_cur_disp * 48, pitch, windowBuffer + pitch * 37 + 176, pitch);\n            art_ptr_unlock(backgroundFrmHandle);\n        }\n    } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        pitch = 480;\n\n        windowBuffer = win_get_buf(i_wid);\n\n        buf_to_buf(win_get_buf(barter_back_win) + 35 * (scr_size.lrx - scr_size.ulx + 1) + 100, 64, 48 * inven_cur_disp, scr_size.lrx - scr_size.ulx + 1, windowBuffer + pitch * 35 + 20, pitch);\n        v49 = -20;\n    } else {\n        assert(false && \"Should be unreachable\");\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL\n        || inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON\n        || inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n        if (inven_scroll_up_bid != -1) {\n            if (a1 <= 0) {\n                win_disable_button(inven_scroll_up_bid);\n            } else {\n                win_enable_button(inven_scroll_up_bid);\n            }\n        }\n\n        if (inven_scroll_dn_bid != -1) {\n            if (pud->length - a1 <= inven_cur_disp) {\n                win_disable_button(inven_scroll_dn_bid);\n            } else {\n                win_enable_button(inven_scroll_dn_bid);\n            }\n        }\n    }\n\n    int y = 0;\n    for (int v19 = 0; v19 + a1 < pud->length && v19 < inven_cur_disp; v19 += 1) {\n        int v21 = v19 + a1 + 1;\n\n        int width;\n        int offset;\n        if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n            offset = pitch * (y + 39) + 26;\n            width = 59;\n        } else {\n            if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n                offset = pitch * (y + 41) + 180;\n            } else {\n                offset = pitch * (y + 39) + 48;\n            }\n            width = 56;\n        }\n\n        InventoryItem* inventoryItem = &(pud->items[pud->length - v21]);\n\n        int inventoryFid = item_inv_fid(inventoryItem->item);\n        scale_art(inventoryFid, windowBuffer + offset, width, 40, pitch);\n\n        if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n            offset = pitch * (y + 41) + 180 + v49;\n        } else {\n            offset = pitch * (y + 39) + 48 + v49;\n        }\n\n        display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, v19 == a2);\n\n        y += 48;\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) {\n        if (i_rhand != NULL) {\n            int width = i_rhand == i_lhand ? INVENTORY_LARGE_SLOT_WIDTH * 2 : INVENTORY_LARGE_SLOT_WIDTH;\n            int inventoryFid = item_inv_fid(i_rhand);\n            scale_art(inventoryFid, windowBuffer + 499 * INVENTORY_RIGHT_HAND_SLOT_Y + INVENTORY_RIGHT_HAND_SLOT_X, width, INVENTORY_LARGE_SLOT_HEIGHT, 499);\n        }\n\n        if (i_lhand != NULL && i_lhand != i_rhand) {\n            int inventoryFid = item_inv_fid(i_lhand);\n            scale_art(inventoryFid, windowBuffer + 499 * INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 499);\n        }\n\n        if (i_worn != NULL) {\n            int inventoryFid = item_inv_fid(i_worn);\n            scale_art(inventoryFid, windowBuffer + 499 * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 499);\n        }\n    }\n\n    win_draw(i_wid);\n}\n\n// Render inventory item.\n//\n// [a1] is likely an index of the first visible item in the scrolling view.\n// [a2] is likely an index of selected item or moving item (it decreases displayed number of items in inner functions).\n//\n// 0x47036C\nvoid display_target_inventory(int a1, int a2, Inventory* inventory, int inventoryWindowType)\n{\n    unsigned char* windowBuffer = win_get_buf(i_wid);\n\n    int pitch;\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n        pitch = 537;\n\n        int fid = art_id(OBJ_TYPE_INTERFACE, 114, 0, 0, 0);\n\n        CacheEntry* handle;\n        unsigned char* data = art_ptr_lock_data(fid, 0, 0, &handle);\n        if (data != NULL) {\n            buf_to_buf(data + 537 * 37 + 297, 64, 48 * inven_cur_disp, 537, windowBuffer + 537 * 37 + 297, 537);\n            art_ptr_unlock(handle);\n        }\n    } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        pitch = 480;\n\n        unsigned char* src = win_get_buf(barter_back_win);\n        buf_to_buf(src + (scr_size.lrx - scr_size.ulx + 1) * 35 + 475, 64, 48 * inven_cur_disp, scr_size.lrx - scr_size.ulx + 1, windowBuffer + 480 * 35 + 395, 480);\n    } else {\n        assert(false && \"Should be unreachable\");\n    }\n\n    int y = 0;\n    for (int index = 0; index < inven_cur_disp; index++) {\n        int v27 = a1 + index;\n        if (v27 >= inventory->length) {\n            break;\n        }\n\n        int offset;\n        if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n            offset = pitch * (y + 41) + 301;\n        } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n            offset = pitch * (y + 39) + 397;\n        } else {\n            assert(false && \"Should be unreachable\");\n        }\n\n        InventoryItem* inventoryItem = &(inventory->items[inventory->length - (v27 + 1)]);\n        int inventoryFid = item_inv_fid(inventoryItem->item);\n        scale_art(inventoryFid, windowBuffer + offset, 56, 40, pitch);\n        display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, index == a2);\n\n        y += 48;\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n        if (loot_scroll_up_bid != -1) {\n            if (a1 <= 0) {\n                win_disable_button(loot_scroll_up_bid);\n            } else {\n                win_enable_button(loot_scroll_up_bid);\n            }\n        }\n\n        if (loot_scroll_dn_bid != -1) {\n            if (inventory->length - a1 <= inven_cur_disp) {\n                win_disable_button(loot_scroll_dn_bid);\n            } else {\n                win_enable_button(loot_scroll_dn_bid);\n            }\n        }\n    }\n}\n\n// Renders inventory item quantity.\n//\n// 0x4705A0\nstatic void display_inventory_info(Object* item, int quantity, unsigned char* dest, int pitch, bool a5)\n{\n    int oldFont = text_curr();\n    text_font(101);\n\n    char formattedText[12];\n\n    // NOTE: Original code is slightly different and probably used goto.\n    bool draw = false;\n\n    if (item_get_type(item) == ITEM_TYPE_AMMO) {\n        int ammoQuantity = item_w_max_ammo(item) * (quantity - 1);\n\n        if (!a5) {\n            ammoQuantity += item_w_curr_ammo(item);\n        }\n\n        if (ammoQuantity > 99999) {\n            ammoQuantity = 99999;\n        }\n\n        sprintf(formattedText, \"x%d\", ammoQuantity);\n        draw = true;\n    } else {\n        if (quantity > 1) {\n            int v9 = quantity;\n            if (a5) {\n                v9 -= 1;\n            }\n\n            // NOTE: Checking for quantity twice probably means inlined function\n            // or some macro expansion.\n            if (quantity > 1) {\n                if (v9 > 99999) {\n                    v9 = 99999;\n                }\n\n                sprintf(formattedText, \"x%d\", v9);\n                draw = true;\n            }\n        }\n    }\n\n    if (draw) {\n        text_to_buf(dest, formattedText, 80, pitch, colorTable[32767]);\n    }\n\n    text_font(oldFont);\n}\n\n// 0x470650\nvoid display_body(int fid, int inventoryWindowType)\n{\n    // 0x5190F4\n    static unsigned int ticker = 0;\n\n    // 0x5190F8\n    static int curr_rot = 0;\n\n    if (elapsed_time(ticker) < INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY) {\n        return;\n    }\n\n    curr_rot += 1;\n\n    if (curr_rot == ROTATION_COUNT) {\n        curr_rot = 0;\n    }\n\n    int rotations[2];\n    if (fid == -1) {\n        rotations[0] = curr_rot;\n        rotations[1] = ROTATION_SE;\n    } else {\n        rotations[0] = ROTATION_SW;\n        rotations[1] = target_stack[target_curr_stack]->rotation;\n    }\n\n    int fids[2] = {\n        i_fid,\n        fid,\n    };\n\n    for (int index = 0; index < 2; index += 1) {\n        int fid = fids[index];\n        if (fid == -1) {\n            continue;\n        }\n\n        CacheEntry* handle;\n        Art* art = art_ptr_lock(fid, &handle);\n        if (art == NULL) {\n            continue;\n        }\n\n        int frame = 0;\n        if (index == 1) {\n            frame = art_frame_max_frame(art) - 1;\n        }\n\n        int rotation = rotations[index];\n\n        unsigned char* frameData = art_frame_data(art, frame, rotation);\n\n        int framePitch = art_frame_width(art, frame, rotation);\n        int frameWidth = min(framePitch, INVENTORY_BODY_VIEW_WIDTH);\n\n        int frameHeight = art_frame_length(art, frame, rotation);\n        if (frameHeight > INVENTORY_BODY_VIEW_HEIGHT) {\n            frameHeight = INVENTORY_BODY_VIEW_HEIGHT;\n        }\n\n        int win;\n        Rect rect;\n        CacheEntry* backrgroundFrmHandle;\n        if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n            unsigned char* windowBuffer = win_get_buf(barter_back_win);\n            int windowPitch = win_width(barter_back_win);\n\n            if (index == 1) {\n                rect.ulx = 560;\n                rect.uly = 25;\n            } else {\n                rect.ulx = 15;\n                rect.uly = 25;\n            }\n\n            rect.lrx = rect.ulx + INVENTORY_BODY_VIEW_WIDTH - 1;\n            rect.lry = rect.uly + INVENTORY_BODY_VIEW_HEIGHT - 1;\n\n            int frmId = dialog_target_is_party ? 420 : 111;\n            int backgroundFid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0);\n\n            unsigned char* src = art_ptr_lock_data(backgroundFid, 0, 0, &backrgroundFrmHandle);\n            if (src != NULL) {\n                buf_to_buf(src + rect.uly * (scr_size.lrx - scr_size.ulx + 1) + rect.ulx,\n                    INVENTORY_BODY_VIEW_WIDTH,\n                    INVENTORY_BODY_VIEW_HEIGHT,\n                    scr_size.lrx - scr_size.ulx + 1,\n                    windowBuffer + windowPitch * rect.uly + rect.ulx,\n                    windowPitch);\n            }\n\n            trans_buf_to_buf(frameData, frameWidth, frameHeight, framePitch,\n                windowBuffer + windowPitch * (rect.uly + (INVENTORY_BODY_VIEW_HEIGHT - frameHeight) / 2) + (INVENTORY_BODY_VIEW_WIDTH - frameWidth) / 2 + rect.ulx,\n                windowPitch);\n\n            win = barter_back_win;\n        } else {\n            unsigned char* windowBuffer = win_get_buf(i_wid);\n            int windowPitch = win_width(i_wid);\n\n            if (index == 1) {\n                if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n                    rect.ulx = 426;\n                    rect.uly = 39;\n                } else {\n                    rect.ulx = 297;\n                    rect.uly = 37;\n                }\n            } else {\n                if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) {\n                    rect.ulx = 48;\n                    rect.uly = 39;\n                } else {\n                    rect.ulx = 176;\n                    rect.uly = 37;\n                }\n            }\n\n            rect.lrx = rect.ulx + INVENTORY_BODY_VIEW_WIDTH - 1;\n            rect.lry = rect.uly + INVENTORY_BODY_VIEW_HEIGHT - 1;\n\n            int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 114, 0, 0, 0);\n            unsigned char* src = art_ptr_lock_data(backgroundFid, 0, 0, &backrgroundFrmHandle);\n            if (src != NULL) {\n                buf_to_buf(src + 537 * rect.uly + rect.ulx,\n                    INVENTORY_BODY_VIEW_WIDTH,\n                    INVENTORY_BODY_VIEW_HEIGHT,\n                    537,\n                    windowBuffer + windowPitch * rect.uly + rect.ulx,\n                    windowPitch);\n            }\n\n            trans_buf_to_buf(frameData, frameWidth, frameHeight, framePitch,\n                windowBuffer + windowPitch * (rect.uly + (INVENTORY_BODY_VIEW_HEIGHT - frameHeight) / 2) + (INVENTORY_BODY_VIEW_WIDTH - frameWidth) / 2 + rect.ulx,\n                windowPitch);\n\n            win = i_wid;\n        }\n        win_draw_rect(win, &rect);\n\n        art_ptr_unlock(backrgroundFrmHandle);\n        art_ptr_unlock(handle);\n    }\n\n    ticker = get_time();\n}\n\n// 0x470A2C\nint inven_init()\n{\n    // 0x5190FC\n    static int num[INVENTORY_WINDOW_CURSOR_COUNT] = {\n        286, // pointing hand\n        250, // action arrow\n        282, // action pick\n        283, // action menu\n        266, // blank\n    };\n\n    if (inventry_msg_load() == -1) {\n        return -1;\n    }\n\n    inven_ui_was_disabled = game_ui_is_disabled();\n\n    if (inven_ui_was_disabled) {\n        game_ui_enable();\n    }\n\n    gmouse_3d_off();\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    int index;\n    for (index = 0; index < INVENTORY_WINDOW_CURSOR_COUNT; index++) {\n        InventoryCursorData* cursorData = &(imdata[index]);\n\n        int fid = art_id(OBJ_TYPE_INTERFACE, num[index], 0, 0, 0);\n        Art* frm = art_ptr_lock(fid, &(cursorData->frmHandle));\n        if (frm == NULL) {\n            break;\n        }\n\n        cursorData->frm = frm;\n        cursorData->frmData = art_frame_data(frm, 0, 0);\n        cursorData->width = art_frame_width(frm, 0, 0);\n        cursorData->height = art_frame_length(frm, 0, 0);\n        art_frame_hot(frm, 0, 0, &(cursorData->offsetX), &(cursorData->offsetY));\n    }\n\n    if (index != INVENTORY_WINDOW_CURSOR_COUNT) {\n        for (; index >= 0; index--) {\n            art_ptr_unlock(imdata[index].frmHandle);\n        }\n\n        if (inven_ui_was_disabled) {\n            game_ui_disable(0);\n        }\n\n        message_exit(&inventry_message_file);\n\n        return -1;\n    }\n\n    inven_is_initialized = true;\n    im_value = -1;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x470B8C\nvoid inven_exit()\n{\n    for (int index = 0; index < INVENTORY_WINDOW_CURSOR_COUNT; index++) {\n        art_ptr_unlock(imdata[index].frmHandle);\n    }\n\n    if (inven_ui_was_disabled) {\n        game_ui_disable(0);\n    }\n\n    // NOTE: Uninline.\n    inventry_msg_unload();\n\n    inven_is_initialized = 0;\n}\n\n// 0x470BCC\nvoid inven_set_mouse(int cursor)\n{\n    immode = cursor;\n\n    if (cursor != INVENTORY_WINDOW_CURSOR_ARROW || im_value == -1) {\n        InventoryCursorData* cursorData = &(imdata[cursor]);\n        mouse_set_shape(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0);\n    } else {\n        inven_hover_on(-1, im_value);\n    }\n}\n\n// 0x470C2C\nvoid inven_hover_on(int btn, int keyCode)\n{\n    // 0x519110\n    static Object* last_target = NULL;\n\n    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n        int x;\n        int y;\n        mouse_get_position(&x, &y);\n\n        Object* a2a = NULL;\n        if (inven_from_button(keyCode, &a2a, NULL, NULL) != 0) {\n            gmouse_3d_build_pick_frame(x, y, 3, i_wid_max_x, i_wid_max_y);\n\n            int v5 = 0;\n            int v6 = 0;\n            gmouse_3d_pick_frame_hot(&v5, &v6);\n\n            InventoryCursorData* cursorData = &(imdata[INVENTORY_WINDOW_CURSOR_PICK]);\n            mouse_set_shape(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, v5, v6, 0);\n\n            if (a2a != last_target) {\n                obj_look_at_func(stack[0], a2a, display_msg);\n            }\n        } else {\n            InventoryCursorData* cursorData = &(imdata[INVENTORY_WINDOW_CURSOR_ARROW]);\n            mouse_set_shape(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0);\n        }\n\n        last_target = a2a;\n    }\n\n    im_value = keyCode;\n}\n\n// 0x470D1C\nvoid inven_hover_off(int btn, int keyCode)\n{\n    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n        InventoryCursorData* cursorData = &(imdata[INVENTORY_WINDOW_CURSOR_ARROW]);\n        mouse_set_shape(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0);\n    }\n\n    im_value = -1;\n}\n\n// 0x470D5C\nstatic void inven_update_lighting(Object* a1)\n{\n    if (obj_dude == inven_dude) {\n        int lightDistance;\n        if (a1 != NULL && a1->lightDistance > 4) {\n            lightDistance = a1->lightDistance;\n        } else {\n            lightDistance = 4;\n        }\n\n        Rect rect;\n        obj_set_light(inven_dude, lightDistance, 0x10000, &rect);\n        tile_refresh_rect(&rect, map_elevation);\n    }\n}\n\n// 0x470DB8\nvoid inven_pickup(int keyCode, int a2)\n{\n    Object* a1a;\n    Object** v29 = NULL;\n    int count = inven_from_button(keyCode, &a1a, &v29, NULL);\n    if (count == 0) {\n        return;\n    }\n\n    int v3 = -1;\n    Object* v39 = NULL;\n    Rect rect;\n\n    switch (keyCode) {\n    case 1006:\n        rect.ulx = 245;\n        rect.uly = 286;\n        if (inven_dude == obj_dude && intface_is_item_right_hand() != HAND_LEFT) {\n            v39 = a1a;\n        }\n        break;\n    case 1007:\n        rect.ulx = 154;\n        rect.uly = 286;\n        if (inven_dude == obj_dude && intface_is_item_right_hand() == HAND_LEFT) {\n            v39 = a1a;\n        }\n        break;\n    case 1008:\n        rect.ulx = 154;\n        rect.uly = 183;\n        break;\n    default:\n        // NOTE: Original code a little bit different, this code path\n        // is only for key codes below 1006.\n        v3 = keyCode - 1000;\n        rect.ulx = 44;\n        rect.uly = 48 * v3 + 35;\n        break;\n    }\n\n    if (v3 == -1 || pud->items[a2 + v3].quantity <= 1) {\n        unsigned char* windowBuffer = win_get_buf(i_wid);\n        if (i_rhand != i_lhand || a1a != i_lhand) {\n            int height;\n            int width;\n            if (v3 == -1) {\n                height = INVENTORY_LARGE_SLOT_HEIGHT;\n                width = INVENTORY_LARGE_SLOT_WIDTH;\n            } else {\n                height = INVENTORY_SLOT_HEIGHT;\n                width = INVENTORY_SLOT_WIDTH;\n            }\n\n            CacheEntry* backgroundFrmHandle;\n            int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0);\n            unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n            if (backgroundFrmData != NULL) {\n                buf_to_buf(backgroundFrmData + 499 * rect.uly + rect.ulx, width, height, 499, windowBuffer + 499 * rect.uly + rect.ulx, 499);\n                art_ptr_unlock(backgroundFrmHandle);\n            }\n\n            rect.lrx = rect.ulx + width - 1;\n            rect.lry = rect.uly + height - 1;\n        } else {\n            CacheEntry* backgroundFrmHandle;\n            int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0);\n            unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n            if (backgroundFrmData != NULL) {\n                buf_to_buf(backgroundFrmData + 499 * 286 + 154, 180, 61, 499, windowBuffer + 499 * 286 + 154, 499);\n                art_ptr_unlock(backgroundFrmHandle);\n            }\n\n            rect.ulx = 154;\n            rect.uly = 286;\n            rect.lrx = rect.ulx + 180 - 1;\n            rect.lry = rect.uly + 61 - 1;\n        }\n        win_draw_rect(i_wid, &rect);\n    } else {\n        display_inventory(a2, v3, INVENTORY_WINDOW_TYPE_NORMAL);\n    }\n\n    CacheEntry* itemInventoryFrmHandle;\n    int itemInventoryFid = item_inv_fid(a1a);\n    Art* itemInventoryFrm = art_ptr_lock(itemInventoryFid, &itemInventoryFrmHandle);\n    if (itemInventoryFrm != NULL) {\n        int width = art_frame_width(itemInventoryFrm, 0, 0);\n        int height = art_frame_length(itemInventoryFrm, 0, 0);\n        unsigned char* itemInventoryFrmData = art_frame_data(itemInventoryFrm, 0, 0);\n        mouse_set_shape(itemInventoryFrmData, width, height, width, width / 2, height / 2, 0);\n        gsound_play_sfx_file(\"ipickup1\");\n    }\n\n    if (v39 != NULL) {\n        inven_update_lighting(NULL);\n    }\n\n    do {\n        get_input();\n        display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL);\n    } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0);\n\n    if (itemInventoryFrm != NULL) {\n        art_ptr_unlock(itemInventoryFrmHandle);\n        gsound_play_sfx_file(\"iputdown\");\n    }\n\n    if (mouse_click_in(INVENTORY_SCROLLER_ABS_X, INVENTORY_SCROLLER_ABS_Y, INVENTORY_SCROLLER_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_SCROLLER_ABS_Y)) {\n        int x;\n        int y;\n        mouse_get_position(&x, &y);\n\n        int v18 = (y - 39) / 48 + a2;\n        if (v18 < pud->length) {\n            Object* v19 = pud->items[v18].item;\n            if (v19 != a1a) {\n                // TODO: Needs checking usage of v19\n                if (item_get_type(v19) == ITEM_TYPE_CONTAINER) {\n                    if (drop_into_container(v19, a1a, v3, v29, count) == 0) {\n                        v3 = 0;\n                    }\n                } else {\n                    if (drop_ammo_into_weapon(v19, a1a, v29, count, keyCode) == 0) {\n                        v3 = 0;\n                    }\n                }\n            }\n        }\n\n        if (v3 == -1) {\n            // TODO: Holy shit, needs refactoring.\n            *v29 = NULL;\n            if (item_add_force(inven_dude, a1a, 1)) {\n                *v29 = a1a;\n            } else if (v29 == &i_worn) {\n                adjust_ac(stack[0], a1a, NULL);\n            } else if (i_rhand == i_lhand) {\n                i_lhand = NULL;\n                i_rhand = NULL;\n            }\n        }\n    } else if (mouse_click_in(INVENTORY_LEFT_HAND_SLOT_ABS_X, INVENTORY_LEFT_HAND_SLOT_ABS_Y, INVENTORY_LEFT_HAND_SLOT_ABS_MAX_X, INVENTORY_LEFT_HAND_SLOT_ABS_MAX_Y)) {\n        if (i_lhand != NULL && item_get_type(i_lhand) == ITEM_TYPE_CONTAINER && i_lhand != a1a) {\n            drop_into_container(i_lhand, a1a, v3, v29, count);\n        } else if (i_lhand == NULL || drop_ammo_into_weapon(i_lhand, a1a, v29, count, keyCode)) {\n            switch_hand(a1a, &i_lhand, v29, keyCode);\n        }\n    } else if (mouse_click_in(INVENTORY_RIGHT_HAND_SLOT_ABS_X, INVENTORY_RIGHT_HAND_SLOT_ABS_Y, INVENTORY_RIGHT_HAND_SLOT_ABS_MAX_X, INVENTORY_RIGHT_HAND_SLOT_ABS_MAX_Y)) {\n        if (i_rhand != NULL && item_get_type(i_rhand) == ITEM_TYPE_CONTAINER && i_rhand != a1a) {\n            drop_into_container(i_rhand, a1a, v3, v29, count);\n        } else if (i_rhand == NULL || drop_ammo_into_weapon(i_rhand, a1a, v29, count, keyCode)) {\n            switch_hand(a1a, &i_rhand, v29, v3);\n        }\n    } else if (mouse_click_in(INVENTORY_ARMOR_SLOT_ABS_X, INVENTORY_ARMOR_SLOT_ABS_Y, INVENTORY_ARMOR_SLOT_ABS_MAX_X, INVENTORY_ARMOR_SLOT_ABS_MAX_Y)) {\n        if (item_get_type(a1a) == ITEM_TYPE_ARMOR) {\n            Object* v21 = i_worn;\n            int v22 = 0;\n            if (v3 != -1) {\n                item_remove_mult(inven_dude, a1a, 1);\n            }\n\n            if (i_worn != NULL) {\n                if (v29 != NULL) {\n                    *v29 = i_worn;\n                } else {\n                    i_worn = NULL;\n                    v22 = item_add_force(inven_dude, v21, 1);\n                }\n            } else {\n                if (v29 != NULL) {\n                    *v29 = i_worn;\n                }\n            }\n\n            if (v22 != 0) {\n                i_worn = v21;\n                if (v3 != -1) {\n                    item_add_force(inven_dude, a1a, 1);\n                }\n            } else {\n                adjust_ac(stack[0], v21, a1a);\n                i_worn = a1a;\n            }\n        }\n    } else if (mouse_click_in(INVENTORY_PC_BODY_VIEW_ABS_X, INVENTORY_PC_BODY_VIEW_ABS_Y, INVENTORY_PC_BODY_VIEW_ABS_MAX_X, INVENTORY_PC_BODY_VIEW_ABS_MAX_Y)) {\n        if (curr_stack != 0) {\n            // TODO: Check this curr_stack - 1, not sure.\n            drop_into_container(stack[curr_stack - 1], a1a, v3, v29, count);\n        }\n    }\n\n    adjust_fid();\n    display_stats();\n    display_inventory(a2, -1, INVENTORY_WINDOW_TYPE_NORMAL);\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n    if (inven_dude == obj_dude) {\n        Object* item;\n        if (intface_is_item_right_hand() == HAND_LEFT) {\n            item = inven_left_hand(inven_dude);\n        } else {\n            item = inven_right_hand(inven_dude);\n        }\n\n        if (item != NULL) {\n            inven_update_lighting(item);\n        }\n    }\n}\n\n// 0x4714E0\nvoid switch_hand(Object* a1, Object** a2, Object** a3, int a4)\n{\n    if (*a2 != NULL) {\n        if (item_get_type(*a2) == ITEM_TYPE_WEAPON && item_get_type(a1) == ITEM_TYPE_AMMO) {\n            return;\n        }\n\n        if (a3 != NULL && (a3 != &i_worn || item_get_type(*a2) == ITEM_TYPE_ARMOR)) {\n            if (a3 == &i_worn) {\n                adjust_ac(stack[0], i_worn, *a2);\n            }\n            *a3 = *a2;\n        } else {\n            if (a4 != -1) {\n                item_remove_mult(inven_dude, a1, 1);\n            }\n\n            Object* itemToAdd = *a2;\n            *a2 = NULL;\n            if (item_add_force(inven_dude, itemToAdd, 1) != 0) {\n                item_add_force(inven_dude, a1, 1);\n                return;\n            }\n\n            a4 = -1;\n\n            if (a3 != NULL) {\n                if (a3 == &i_worn) {\n                    adjust_ac(stack[0], i_worn, NULL);\n                }\n                *a3 = NULL;\n            }\n        }\n    } else {\n        if (a3 != NULL) {\n            if (a3 == &i_worn) {\n                adjust_ac(stack[0], i_worn, NULL);\n            }\n            *a3 = NULL;\n        }\n    }\n\n    *a2 = a1;\n\n    if (a4 != -1) {\n        item_remove_mult(inven_dude, a1, 1);\n    }\n}\n\n// This function removes armor bonuses and effects granted by [oldArmor] and\n// adds appropriate bonuses and effects granted by [newArmor]. Both [oldArmor]\n// and [newArmor] can be NULL.\n//\n// 0x4715F8\nvoid adjust_ac(Object* critter, Object* oldArmor, Object* newArmor)\n{\n    int armorClassBonus = stat_get_bonus(critter, STAT_ARMOR_CLASS);\n    int oldArmorClass = item_ar_ac(oldArmor);\n    int newArmorClass = item_ar_ac(newArmor);\n    stat_set_bonus(critter, STAT_ARMOR_CLASS, armorClassBonus - oldArmorClass + newArmorClass);\n\n    int damageResistanceStat = STAT_DAMAGE_RESISTANCE;\n    int damageThresholdStat = STAT_DAMAGE_THRESHOLD;\n    for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType += 1) {\n        int damageResistanceBonus = stat_get_bonus(critter, damageResistanceStat);\n        int oldArmorDamageResistance = item_ar_dr(oldArmor, damageType);\n        int newArmorDamageResistance = item_ar_dr(newArmor, damageType);\n        stat_set_bonus(critter, damageResistanceStat, damageResistanceBonus - oldArmorDamageResistance + newArmorDamageResistance);\n\n        int damageThresholdBonus = stat_get_bonus(critter, damageThresholdStat);\n        int oldArmorDamageThreshold = item_ar_dt(oldArmor, damageType);\n        int newArmorDamageThreshold = item_ar_dt(newArmor, damageType);\n        stat_set_bonus(critter, damageThresholdStat, damageThresholdBonus - oldArmorDamageThreshold + newArmorDamageThreshold);\n\n        damageResistanceStat += 1;\n        damageThresholdStat += 1;\n    }\n\n    if (isPartyMember(critter)) {\n        if (oldArmor != NULL) {\n            int perk = item_ar_perk(oldArmor);\n            perk_remove_effect(critter, perk);\n        }\n\n        if (newArmor != NULL) {\n            int perk = item_ar_perk(newArmor);\n            perk_add_effect(critter, perk);\n        }\n    }\n}\n\n// 0x4716E8\nvoid adjust_fid()\n{\n    int fid;\n    if (FID_TYPE(inven_dude->fid) == OBJ_TYPE_CRITTER) {\n        Proto* proto;\n\n        int v0 = art_vault_guy_num;\n\n        if (proto_ptr(inven_pid, &proto) == -1) {\n            v0 = proto->fid & 0xFFF;\n        }\n\n        if (i_worn != NULL) {\n            proto_ptr(i_worn->pid, &proto);\n            if (critterGetStat(inven_dude, STAT_GENDER) == GENDER_FEMALE) {\n                v0 = proto->item.data.armor.femaleFid;\n            } else {\n                v0 = proto->item.data.armor.maleFid;\n            }\n\n            if (v0 == -1) {\n                v0 = art_vault_guy_num;\n            }\n        }\n\n        int animationCode = 0;\n        if (intface_is_item_right_hand()) {\n            if (i_rhand != NULL) {\n                proto_ptr(i_rhand->pid, &proto);\n                if (proto->item.type == ITEM_TYPE_WEAPON) {\n                    animationCode = proto->item.data.weapon.animationCode;\n                }\n            }\n        } else {\n            if (i_lhand != NULL) {\n                proto_ptr(i_lhand->pid, &proto);\n                if (proto->item.type == ITEM_TYPE_WEAPON) {\n                    animationCode = proto->item.data.weapon.animationCode;\n                }\n            }\n        }\n\n        fid = art_id(OBJ_TYPE_CRITTER, v0, 0, animationCode, 0);\n    } else {\n        fid = inven_dude->fid;\n    }\n\n    i_fid = fid;\n}\n\n// 0x4717E4\nvoid use_inventory_on(Object* a1)\n{\n    if (inven_init() == -1) {\n        return;\n    }\n\n    bool isoWasEnabled = setup_inventory(INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n    display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n    for (;;) {\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        display_body(-1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n\n        int keyCode = get_input();\n        switch (keyCode) {\n        case KEY_HOME:\n            stack_offset[curr_stack] = 0;\n            display_inventory(0, -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n            break;\n        case KEY_ARROW_UP:\n            if (stack_offset[curr_stack] > 0) {\n                stack_offset[curr_stack] -= 1;\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n            }\n            break;\n        case KEY_PAGE_UP:\n            stack_offset[curr_stack] -= inven_cur_disp;\n            if (stack_offset[curr_stack] < 0) {\n                stack_offset[curr_stack] = 0;\n                display_inventory(stack_offset[curr_stack], -1, 1);\n            }\n            break;\n        case KEY_END:\n            stack_offset[curr_stack] = pud->length - inven_cur_disp;\n            if (stack_offset[curr_stack] < 0) {\n                stack_offset[curr_stack] = 0;\n            }\n            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n            break;\n        case KEY_ARROW_DOWN:\n            if (stack_offset[curr_stack] + inven_cur_disp < pud->length) {\n                stack_offset[curr_stack] += 1;\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n            }\n            break;\n        case KEY_PAGE_DOWN:\n            stack_offset[curr_stack] += inven_cur_disp;\n            if (stack_offset[curr_stack] + inven_cur_disp >= pud->length) {\n                stack_offset[curr_stack] = pud->length - inven_cur_disp;\n                if (stack_offset[curr_stack] < 0) {\n                    stack_offset[curr_stack] = 0;\n                }\n            }\n            display_inventory(stack_offset[curr_stack], -1, 1);\n            break;\n        case 2500:\n            container_exit(keyCode, INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n            break;\n        default:\n            if ((mouse_get_buttons() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) {\n                if (immode == INVENTORY_WINDOW_CURSOR_HAND) {\n                    inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW);\n                } else {\n                    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n                }\n            } else if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n                if (keyCode >= 1000 && keyCode < 1000 + inven_cur_disp) {\n                    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                        inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_USE_ITEM_ON);\n                    } else {\n                        int inventoryItemIndex = pud->length - (stack_offset[curr_stack] + keyCode - 1000 + 1);\n                        if (inventoryItemIndex < pud->length) {\n                            InventoryItem* inventoryItem = &(pud->items[inventoryItemIndex]);\n                            if (isInCombat()) {\n                                if (obj_dude->data.critter.combat.ap >= 2) {\n                                    if (action_use_an_item_on_object(obj_dude, a1, inventoryItem->item) != -1) {\n                                        int actionPoints = obj_dude->data.critter.combat.ap;\n                                        if (actionPoints < 2) {\n                                            obj_dude->data.critter.combat.ap = 0;\n                                        } else {\n                                            obj_dude->data.critter.combat.ap = actionPoints - 2;\n                                        }\n                                        intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n                                    }\n                                }\n                            } else {\n                                action_use_an_item_on_object(obj_dude, a1, inventoryItem->item);\n                            }\n                            keyCode = KEY_ESCAPE;\n                        } else {\n                            keyCode = -1;\n                        }\n                    }\n                }\n            }\n        }\n\n        if (keyCode == KEY_ESCAPE) {\n            break;\n        }\n    }\n\n    exit_inventory(isoWasEnabled);\n\n    // NOTE: Uninline.\n    inven_exit();\n}\n\n// 0x471B70\nObject* inven_right_hand(Object* critter)\n{\n    int i;\n    Inventory* inventory;\n    Object* item;\n\n    if (i_rhand != NULL && critter == inven_dude) {\n        return i_rhand;\n    }\n\n    inventory = &(critter->data.inventory);\n    for (i = 0; i < inventory->length; i++) {\n        item = inventory->items[i].item;\n        if (item->flags & OBJECT_IN_RIGHT_HAND) {\n            return item;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x471BBC\nObject* inven_left_hand(Object* critter)\n{\n    int i;\n    Inventory* inventory;\n    Object* item;\n\n    if (i_lhand != NULL && critter == inven_dude) {\n        return i_lhand;\n    }\n\n    inventory = &(critter->data.inventory);\n    for (i = 0; i < inventory->length; i++) {\n        item = inventory->items[i].item;\n        if (item->flags & OBJECT_IN_LEFT_HAND) {\n            return item;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x471C08\nObject* inven_worn(Object* critter)\n{\n    int i;\n    Inventory* inventory;\n    Object* item;\n\n    if (i_worn != NULL && critter == inven_dude) {\n        return i_worn;\n    }\n\n    inventory = &(critter->data.inventory);\n    for (i = 0; i < inventory->length; i++) {\n        item = inventory->items[i].item;\n        if (item->flags & OBJECT_WORN) {\n            return item;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x471CA0\nObject* inven_pid_is_carried(Object* obj, int pid)\n{\n    Inventory* inventory = &(obj->data.inventory);\n\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        if (inventoryItem->item->pid == pid) {\n            return inventoryItem->item;\n        }\n\n        Object* found = inven_pid_is_carried(inventoryItem->item, pid);\n        if (found != NULL) {\n            return found;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x471CDC\nint inven_pid_quantity_carried(Object* object, int pid)\n{\n    int quantity = 0;\n\n    Inventory* inventory = &(object->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        if (inventoryItem->item->pid == pid) {\n            quantity += inventoryItem->quantity;\n        }\n\n        quantity += inven_pid_quantity_carried(inventoryItem->item, pid);\n    }\n\n    return quantity;\n}\n\n// Renders character's summary of SPECIAL stats, equipped armor bonuses,\n// and weapon's damage/range.\n//\n// 0x471D5C\nvoid display_stats()\n{\n    // 0x46E6D0\n    static const int v56[7] = {\n        STAT_CURRENT_HIT_POINTS,\n        STAT_ARMOR_CLASS,\n        STAT_DAMAGE_THRESHOLD,\n        STAT_DAMAGE_THRESHOLD_LASER,\n        STAT_DAMAGE_THRESHOLD_FIRE,\n        STAT_DAMAGE_THRESHOLD_PLASMA,\n        STAT_DAMAGE_THRESHOLD_EXPLOSION,\n    };\n\n    // 0x46E6EC\n    static const int v57[7] = {\n        STAT_MAXIMUM_HIT_POINTS,\n        -1,\n        STAT_DAMAGE_RESISTANCE,\n        STAT_DAMAGE_RESISTANCE_LASER,\n        STAT_DAMAGE_RESISTANCE_FIRE,\n        STAT_DAMAGE_RESISTANCE_PLASMA,\n        STAT_DAMAGE_RESISTANCE_EXPLOSION,\n    };\n\n    char formattedText[80];\n\n    int oldFont = text_curr();\n    text_font(101);\n\n    unsigned char* windowBuffer = win_get_buf(i_wid);\n\n    int fid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0);\n\n    CacheEntry* backgroundHandle;\n    unsigned char* backgroundData = art_ptr_lock_data(fid, 0, 0, &backgroundHandle);\n    if (backgroundData != NULL) {\n        buf_to_buf(backgroundData + 499 * 44 + 297, 152, 188, 499, windowBuffer + 499 * 44 + 297, 499);\n    }\n    art_ptr_unlock(backgroundHandle);\n\n    // Render character name.\n    const char* critterName = critter_name(stack[0]);\n    text_to_buf(windowBuffer + 499 * 44 + 297, critterName, 80, 499, colorTable[992]);\n\n    draw_line(windowBuffer,\n        499,\n        297,\n        3 * text_height() / 2 + 44,\n        440,\n        3 * text_height() / 2 + 44,\n        colorTable[992]);\n\n    MessageListItem messageListItem;\n\n    int offset = 499 * 2 * text_height() + 499 * 44 + 297;\n    for (int stat = 0; stat < 7; stat++) {\n        messageListItem.num = stat;\n        if (message_search(&inventry_message_file, &messageListItem)) {\n            text_to_buf(windowBuffer + offset, messageListItem.text, 80, 499, colorTable[992]);\n        }\n\n        int value = critterGetStat(stack[0], stat);\n        sprintf(formattedText, \"%d\", value);\n        text_to_buf(windowBuffer + offset + 24, formattedText, 80, 499, colorTable[992]);\n\n        offset += 499 * text_height();\n    }\n\n    offset -= 499 * 7 * text_height();\n\n    for (int index = 0; index < 7; index += 1) {\n        messageListItem.num = 7 + index;\n        if (message_search(&inventry_message_file, &messageListItem)) {\n            text_to_buf(windowBuffer + offset + 40, messageListItem.text, 80, 499, colorTable[992]);\n        }\n\n        if (v57[index] == -1) {\n            int value = critterGetStat(stack[0], v56[index]);\n            sprintf(formattedText, \"   %d\", value);\n        } else {\n            int value1 = critterGetStat(stack[0], v56[index]);\n            int value2 = critterGetStat(stack[0], v57[index]);\n            const char* format = index != 0 ? \"%d/%d%%\" : \"%d/%d\";\n            sprintf(formattedText, format, value1, value2);\n        }\n\n        text_to_buf(windowBuffer + offset + 104, formattedText, 80, 499, colorTable[992]);\n\n        offset += 499 * text_height();\n    }\n\n    draw_line(windowBuffer, 499, 297, 18 * text_height() / 2 + 48, 440, 18 * text_height() / 2 + 48, colorTable[992]);\n    draw_line(windowBuffer, 499, 297, 26 * text_height() / 2 + 48, 440, 26 * text_height() / 2 + 48, colorTable[992]);\n\n    Object* itemsInHands[2] = {\n        i_lhand,\n        i_rhand,\n    };\n\n    const int hitModes[2] = {\n        HIT_MODE_LEFT_WEAPON_PRIMARY,\n        HIT_MODE_RIGHT_WEAPON_PRIMARY,\n    };\n\n    offset += 499 * text_height();\n\n    for (int index = 0; index < 2; index += 1) {\n        Object* item = itemsInHands[index];\n        if (item == NULL) {\n            formattedText[0] = '\\0';\n\n            // No item\n            messageListItem.num = 14;\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                text_to_buf(windowBuffer + offset, messageListItem.text, 120, 499, colorTable[992]);\n            }\n\n            offset += 499 * text_height();\n\n            // Unarmed dmg:\n            messageListItem.num = 24;\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                // TODO: Figure out why it uses STAT_MELEE_DAMAGE instead of\n                // STAT_UNARMED_DAMAGE.\n                int damage = critterGetStat(stack[0], STAT_MELEE_DAMAGE) + 2;\n                sprintf(formattedText, \"%s 1-%d\", messageListItem.text, damage);\n            }\n\n            text_to_buf(windowBuffer + offset, formattedText, 120, 499, colorTable[992]);\n\n            offset += 3 * 499 * text_height();\n            continue;\n        }\n\n        const char* itemName = item_name(item);\n        text_to_buf(windowBuffer + offset, itemName, 140, 499, colorTable[992]);\n\n        offset += 499 * text_height();\n\n        int itemType = item_get_type(item);\n        if (itemType != ITEM_TYPE_WEAPON) {\n            if (itemType == ITEM_TYPE_ARMOR) {\n                // (Not worn)\n                messageListItem.num = 18;\n                if (message_search(&inventry_message_file, &messageListItem)) {\n                    text_to_buf(windowBuffer + offset, messageListItem.text, 120, 499, colorTable[992]);\n                }\n            }\n\n            offset += 3 * 499 * text_height();\n            continue;\n        }\n\n        int range = item_w_range(stack[0], hitModes[index]);\n\n        int damageMin;\n        int damageMax;\n        item_w_damage_min_max(item, &damageMin, &damageMax);\n\n        int attackType = item_w_subtype(item, hitModes[index]);\n\n        formattedText[0] = '\\0';\n\n        int meleeDamage;\n        if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) {\n            meleeDamage = critterGetStat(stack[0], STAT_MELEE_DAMAGE);\n        } else {\n            meleeDamage = 0;\n        }\n\n        messageListItem.num = 15; // Dmg:\n        if (message_search(&inventry_message_file, &messageListItem)) {\n            if (attackType != 4 && range <= 1) {\n                sprintf(formattedText, \"%s %d-%d\", messageListItem.text, damageMin, damageMax + meleeDamage);\n            } else {\n                MessageListItem rangeMessageListItem;\n                rangeMessageListItem.num = 16; // Rng:\n                if (message_search(&inventry_message_file, &rangeMessageListItem)) {\n                    sprintf(formattedText, \"%s %d-%d   %s %d\", messageListItem.text, damageMin, damageMax + meleeDamage, rangeMessageListItem.text, range);\n                }\n            }\n\n            text_to_buf(windowBuffer + offset, formattedText, 140, 499, colorTable[992]);\n        }\n\n        offset += 499 * text_height();\n\n        if (item_w_max_ammo(item) > 0) {\n            int ammoTypePid = item_w_ammo_pid(item);\n\n            formattedText[0] = '\\0';\n\n            messageListItem.num = 17; // Ammo:\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                if (ammoTypePid != -1) {\n                    if (item_w_curr_ammo(item) != 0) {\n                        const char* ammoName = proto_name(ammoTypePid);\n                        int capacity = item_w_max_ammo(item);\n                        int quantity = item_w_curr_ammo(item);\n                        sprintf(formattedText, \"%s %d/%d %s\", messageListItem.text, quantity, capacity, ammoName);\n                    } else {\n                        int capacity = item_w_max_ammo(item);\n                        int quantity = item_w_curr_ammo(item);\n                        sprintf(formattedText, \"%s %d/%d\", messageListItem.text, quantity, capacity);\n                    }\n                }\n            } else {\n                int capacity = item_w_max_ammo(item);\n                int quantity = item_w_curr_ammo(item);\n                sprintf(formattedText, \"%s %d/%d\", messageListItem.text, quantity, capacity);\n            }\n\n            text_to_buf(windowBuffer + offset, formattedText, 140, 499, colorTable[992]);\n        }\n\n        offset += 2 * 499 * text_height();\n    }\n\n    // Total wt:\n    messageListItem.num = 20;\n    if (message_search(&inventry_message_file, &messageListItem)) {\n        if (PID_TYPE(stack[0]->pid) == OBJ_TYPE_CRITTER) {\n            int carryWeight = critterGetStat(stack[0], STAT_CARRY_WEIGHT);\n            int inventoryWeight = item_total_weight(stack[0]);\n            sprintf(formattedText, \"%s %d/%d\", messageListItem.text, inventoryWeight, carryWeight);\n\n            int color = colorTable[992];\n            if (critterIsOverloaded(stack[0])) {\n                color = colorTable[31744];\n            }\n\n            text_to_buf(windowBuffer + offset + 15, formattedText, 120, 499, color);\n        } else {\n            int inventoryWeight = item_total_weight(stack[0]);\n            sprintf(formattedText, \"%s %d\", messageListItem.text, inventoryWeight);\n\n            text_to_buf(windowBuffer + offset + 30, formattedText, 80, 499, colorTable[992]);\n        }\n    }\n\n    text_font(oldFont);\n}\n\n// Finds next item of given [itemType] (can be -1 which means any type of\n// item).\n//\n// The [index] is used to control where to continue the search from, -1 - from\n// the beginning.\n//\n// 0x472698\nObject* inven_find_type(Object* obj, int itemType, int* indexPtr)\n{\n    int dummy = -1;\n    if (indexPtr == NULL) {\n        indexPtr = &dummy;\n    }\n\n    *indexPtr += 1;\n\n    Inventory* inventory = &(obj->data.inventory);\n\n    // TODO: Refactor with for loop.\n    if (*indexPtr >= inventory->length) {\n        return NULL;\n    }\n\n    while (itemType != -1 && item_get_type(inventory->items[*indexPtr].item) != itemType) {\n        *indexPtr += 1;\n\n        if (*indexPtr >= inventory->length) {\n            return NULL;\n        }\n    }\n\n    return inventory->items[*indexPtr].item;\n}\n\n// 0x4726EC\nObject* inven_find_id(Object* obj, int id)\n{\n    if (obj->id == id) {\n        return obj;\n    }\n\n    Inventory* inventory = &(obj->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        Object* item = inventoryItem->item;\n        if (item->id == id) {\n            return item;\n        }\n\n        if (item_get_type(item) == ITEM_TYPE_CONTAINER) {\n            item = inven_find_id(item, id);\n            if (item != NULL) {\n                return item;\n            }\n        }\n    }\n\n    return NULL;\n}\n\n// 0x472740\nObject* inven_index_ptr(Object* obj, int a2)\n{\n    Inventory* inventory;\n\n    inventory = &(obj->data.inventory);\n\n    if (a2 < 0 || a2 >= inventory->length) {\n        return NULL;\n    }\n\n    return inventory->items[a2].item;\n}\n\n// inven_wield\n// 0x472758\nint inven_wield(Object* a1, Object* a2, int a3)\n{\n    return invenWieldFunc(a1, a2, a3, true);\n}\n\n// 0x472768\nint invenWieldFunc(Object* critter, Object* item, int a3, bool a4)\n{\n    if (a4) {\n        if (!map_bk_processes_are_disabled()) {\n            register_begin(ANIMATION_REQUEST_RESERVED);\n        }\n    }\n\n    int itemType = item_get_type(item);\n    if (itemType == ITEM_TYPE_ARMOR) {\n        Object* armor = inven_worn(critter);\n        if (armor != NULL) {\n            armor->flags &= ~OBJECT_WORN;\n        }\n\n        item->flags |= OBJECT_WORN;\n\n        int baseFrmId;\n        if (critterGetStat(critter, STAT_GENDER) == GENDER_FEMALE) {\n            baseFrmId = item_ar_female_fid(item);\n        } else {\n            baseFrmId = item_ar_male_fid(item);\n        }\n\n        if (baseFrmId == -1) {\n            baseFrmId = 1;\n        }\n\n        if (critter == obj_dude) {\n            if (!map_bk_processes_are_disabled()) {\n                int fid = art_id(OBJ_TYPE_CRITTER, baseFrmId, 0, (critter->fid & 0xF000) >> 12, critter->rotation + 1);\n                register_object_change_fid(critter, fid, 0);\n            }\n        } else {\n            adjust_ac(critter, armor, item);\n        }\n    } else {\n        int hand;\n        if (critter == obj_dude) {\n            hand = intface_is_item_right_hand();\n        } else {\n            hand = HAND_RIGHT;\n        }\n\n        int weaponAnimationCode = item_w_anim_code(item);\n        int hitModeAnimationCode = item_w_anim_weap(item, HIT_MODE_RIGHT_WEAPON_PRIMARY);\n        int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, hitModeAnimationCode, weaponAnimationCode, critter->rotation + 1);\n        if (!art_exists(fid)) {\n            debug_printf(\"\\ninven_wield failed!  ERROR ERROR ERROR!\");\n            return -1;\n        }\n\n        Object* v17;\n        if (a3) {\n            v17 = inven_right_hand(critter);\n            item->flags |= OBJECT_IN_RIGHT_HAND;\n        } else {\n            v17 = inven_left_hand(critter);\n            item->flags |= OBJECT_IN_LEFT_HAND;\n        }\n\n        Rect rect;\n        if (v17 != NULL) {\n            v17->flags &= ~OBJECT_IN_ANY_HAND;\n\n            if (v17->pid == PROTO_ID_LIT_FLARE) {\n                int lightIntensity;\n                int lightDistance;\n                if (critter == obj_dude) {\n                    lightIntensity = LIGHT_LEVEL_MAX;\n                    lightDistance = 4;\n                } else {\n                    Proto* proto;\n                    if (proto_ptr(critter->pid, &proto) == -1) {\n                        return -1;\n                    }\n\n                    lightDistance = proto->lightDistance;\n                    lightIntensity = proto->lightIntensity;\n                }\n\n                obj_set_light(critter, lightDistance, lightIntensity, &rect);\n            }\n        }\n\n        if (item->pid == PROTO_ID_LIT_FLARE) {\n            int lightDistance = item->lightDistance;\n            if (lightDistance < critter->lightDistance) {\n                lightDistance = critter->lightDistance;\n            }\n\n            int lightIntensity = item->lightIntensity;\n            if (lightIntensity < critter->lightIntensity) {\n                lightIntensity = critter->lightIntensity;\n            }\n\n            obj_set_light(critter, lightDistance, lightIntensity, &rect);\n            tile_refresh_rect(&rect, map_elevation);\n        }\n\n        if (item_get_type(item) == ITEM_TYPE_WEAPON) {\n            weaponAnimationCode = item_w_anim_code(item);\n        } else {\n            weaponAnimationCode = 0;\n        }\n\n        if (hand == a3) {\n            if ((critter->fid & 0xF000) >> 12 != 0) {\n                if (a4) {\n                    if (!map_bk_processes_are_disabled()) {\n                        const char* soundEffectName = gsnd_build_character_sfx_name(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);\n                        register_object_play_sfx(critter, soundEffectName, 0);\n                        register_object_animate(critter, ANIM_PUT_AWAY, 0);\n                    }\n                }\n            }\n\n            if (a4 && !map_bk_processes_are_disabled()) {\n                if (weaponAnimationCode != 0) {\n                    register_object_take_out(critter, weaponAnimationCode, -1);\n                } else {\n                    int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1);\n                    register_object_change_fid(critter, fid, -1);\n                }\n            } else {\n                int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, weaponAnimationCode, critter->rotation + 1);\n                dude_stand(critter, critter->rotation, fid);\n            }\n        }\n    }\n\n    if (a4) {\n        if (!map_bk_processes_are_disabled()) {\n            return register_end();\n        }\n    }\n\n    return 0;\n}\n\n// inven_unwield\n// 0x472A54\nint inven_unwield(Object* critter_obj, int a2)\n{\n    return invenUnwieldFunc(critter_obj, a2, 1);\n}\n\n// 0x472A64\nint invenUnwieldFunc(Object* obj, int a2, int a3)\n{\n    int v6;\n    Object* item_obj;\n    int fid;\n\n    if (obj == obj_dude) {\n        v6 = intface_is_item_right_hand();\n    } else {\n        v6 = 1;\n    }\n\n    if (a2) {\n        item_obj = inven_right_hand(obj);\n    } else {\n        item_obj = inven_left_hand(obj);\n    }\n\n    if (item_obj) {\n        item_obj->flags &= ~OBJECT_IN_ANY_HAND;\n    }\n\n    if (v6 == a2 && ((obj->fid & 0xF000) >> 12) != 0) {\n        if (a3 && !map_bk_processes_are_disabled()) {\n            register_begin(ANIMATION_REQUEST_RESERVED);\n\n            const char* sfx = gsnd_build_character_sfx_name(obj, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED);\n            register_object_play_sfx(obj, sfx, 0);\n\n            register_object_animate(obj, ANIM_PUT_AWAY, 0);\n\n            fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, 0, 0, obj->rotation + 1);\n            register_object_change_fid(obj, fid, -1);\n\n            return register_end();\n        }\n\n        fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, 0, 0, obj->rotation + 1);\n        dude_stand(obj, obj->rotation, fid);\n    }\n\n    return 0;\n}\n\n// 0x472B54\nint inven_from_button(int keyCode, Object** a2, Object*** a3, Object** a4)\n{\n    Object** v6;\n    Object* v7;\n    Object* v8;\n    int quantity = 0;\n\n    switch (keyCode) {\n    case 1006:\n        v6 = &i_rhand;\n        v7 = stack[0];\n        v8 = i_rhand;\n        break;\n    case 1007:\n        v6 = &i_lhand;\n        v7 = stack[0];\n        v8 = i_lhand;\n        break;\n    case 1008:\n        v6 = &i_worn;\n        v7 = stack[0];\n        v8 = i_worn;\n        break;\n    default:\n        v6 = NULL;\n        v7 = NULL;\n        v8 = NULL;\n\n        InventoryItem* inventoryItem;\n        if (keyCode < 2000) {\n            int index = stack_offset[curr_stack] + keyCode - 1000;\n            if (index >= pud->length) {\n                break;\n            }\n\n            inventoryItem = &(pud->items[pud->length - (index + 1)]);\n            v8 = inventoryItem->item;\n            v7 = stack[curr_stack];\n        } else if (keyCode < 2300) {\n            int index = target_stack_offset[target_curr_stack] + keyCode - 2000;\n            if (index >= target_pud->length) {\n                break;\n            }\n\n            inventoryItem = &(target_pud->items[target_pud->length - (index + 1)]);\n            v8 = inventoryItem->item;\n            v7 = target_stack[target_curr_stack];\n        } else if (keyCode < 2400) {\n            int index = ptable_offset + keyCode - 2300;\n            if (index >= ptable_pud->length) {\n                break;\n            }\n\n            inventoryItem = &(ptable_pud->items[ptable_pud->length - (index + 1)]);\n            v8 = inventoryItem->item;\n            v7 = ptable;\n        } else {\n            int index = btable_offset + keyCode - 2400;\n            if (index >= btable_pud->length) {\n                break;\n            }\n\n            inventoryItem = &(btable_pud->items[btable_pud->length - (index + 1)]);\n            v8 = inventoryItem->item;\n            v7 = btable;\n        }\n\n        quantity = inventoryItem->quantity;\n    }\n\n    if (a3 != NULL) {\n        *a3 = v6;\n    }\n\n    if (a2 != NULL) {\n        *a2 = v8;\n    }\n\n    if (a4 != NULL) {\n        *a4 = v7;\n    }\n\n    if (quantity == 0 && v8 != NULL) {\n        quantity = 1;\n    }\n\n    return quantity;\n}\n\n// Displays item description.\n//\n// The [string] is mutated in the process replacing spaces back and forth\n// for word wrapping purposes.\n//\n// inven_display_msg\n// 0x472D24\nvoid inven_display_msg(char* string)\n{\n    int oldFont = text_curr();\n    text_font(101);\n\n    unsigned char* windowBuffer = win_get_buf(i_wid);\n    windowBuffer += 499 * 44 + 297;\n\n    char* c = string;\n    while (c != NULL && *c != '\\0') {\n        inven_display_msg_line += 1;\n        if (inven_display_msg_line > 17) {\n            debug_printf(\"\\nError: inven_display_msg: out of bounds!\");\n            return;\n        }\n\n        char* space = NULL;\n        if (text_width(c) > 152) {\n            // Look for next space.\n            space = c + 1;\n            while (*space != '\\0' && *space != ' ') {\n                space += 1;\n            }\n\n            if (*space == '\\0') {\n                // This was the last line containing very long word. Text\n                // drawing routine will silently truncate it after reaching\n                // desired length.\n                text_to_buf(windowBuffer + 499 * inven_display_msg_line * text_height(), c, 152, 499, colorTable[992]);\n                return;\n            }\n\n            char* nextSpace = space + 1;\n            while (true) {\n                while (*nextSpace != '\\0' && *nextSpace != ' ') {\n                    nextSpace += 1;\n                }\n\n                if (*nextSpace == '\\0') {\n                    break;\n                }\n\n                // Break string and measure it.\n                *nextSpace = '\\0';\n                if (text_width(c) >= 152) {\n                    // Next space is too far to fit in one line. Restore next\n                    // space's character and stop.\n                    *nextSpace = ' ';\n                    break;\n                }\n\n                space = nextSpace;\n\n                // Restore next space's character and continue looping from the\n                // next character.\n                *nextSpace = ' ';\n                nextSpace += 1;\n            }\n\n            if (*space == ' ') {\n                *space = '\\0';\n            }\n        }\n\n        if (text_width(c) > 152) {\n            debug_printf(\"\\nError: inven_display_msg: word too long!\");\n            return;\n        }\n\n        text_to_buf(windowBuffer + 499 * inven_display_msg_line * text_height(), c, 152, 499, colorTable[992]);\n\n        if (space != NULL) {\n            c = space + 1;\n            if (*space == '\\0') {\n                *space = ' ';\n            }\n        } else {\n            c = NULL;\n        }\n    }\n\n    text_font(oldFont);\n}\n\n// Examines inventory item.\n//\n// 0x472EB8\nvoid inven_obj_examine_func(Object* critter, Object* item)\n{\n    int oldFont = text_curr();\n    text_font(101);\n\n    unsigned char* windowBuffer = win_get_buf(i_wid);\n\n    // Clear item description area.\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0);\n\n    CacheEntry* handle;\n    unsigned char* backgroundData = art_ptr_lock_data(backgroundFid, 0, 0, &handle);\n    if (backgroundData != NULL) {\n        buf_to_buf(backgroundData + 499 * 44 + 297, 152, 188, 499, windowBuffer + 499 * 44 + 297, 499);\n    }\n    art_ptr_unlock(handle);\n\n    // Reset item description lines counter.\n    inven_display_msg_line = 0;\n\n    // Render item's name.\n    char* itemName = object_name(item);\n    inven_display_msg(itemName);\n\n    // Increment line counter to accomodate separator below.\n    inven_display_msg_line += 1;\n\n    int lineHeight = text_height();\n\n    // Draw separator.\n    draw_line(windowBuffer,\n        499,\n        297,\n        3 * lineHeight / 2 + 49,\n        440,\n        3 * lineHeight / 2 + 49,\n        colorTable[992]);\n\n    // Examine item.\n    obj_examine_func(critter, item, inven_display_msg);\n\n    // Add weight if neccessary.\n    int weight = item_weight(item);\n    if (weight != 0) {\n        MessageListItem messageListItem;\n        messageListItem.num = 540;\n\n        if (weight == 1) {\n            messageListItem.num = 541;\n        }\n\n        if (!message_search(&proto_main_msg_file, &messageListItem)) {\n            debug_printf(\"\\nError: Couldn't find message!\");\n        }\n\n        char formattedText[40];\n        sprintf(formattedText, messageListItem.text, weight);\n        inven_display_msg(formattedText);\n    }\n\n    text_font(oldFont);\n}\n\n// 0x47304C\nvoid inven_action_cursor(int keyCode, int inventoryWindowType)\n{\n    // 0x519114\n    static int act_use[4] = {\n        GAME_MOUSE_ACTION_MENU_ITEM_LOOK,\n        GAME_MOUSE_ACTION_MENU_ITEM_USE,\n        GAME_MOUSE_ACTION_MENU_ITEM_DROP,\n        GAME_MOUSE_ACTION_MENU_ITEM_CANCEL,\n    };\n\n    // 0x519124\n    static int act_no_use[3] = {\n        GAME_MOUSE_ACTION_MENU_ITEM_LOOK,\n        GAME_MOUSE_ACTION_MENU_ITEM_DROP,\n        GAME_MOUSE_ACTION_MENU_ITEM_CANCEL,\n    };\n\n    // 0x519130\n    static int act_just_use[3] = {\n        GAME_MOUSE_ACTION_MENU_ITEM_LOOK,\n        GAME_MOUSE_ACTION_MENU_ITEM_USE,\n        GAME_MOUSE_ACTION_MENU_ITEM_CANCEL,\n    };\n\n    // 0x51913C\n    static int act_nothing[2] = {\n        GAME_MOUSE_ACTION_MENU_ITEM_LOOK,\n        GAME_MOUSE_ACTION_MENU_ITEM_CANCEL,\n    };\n\n    // 0x519144\n    static int act_weap[4] = {\n        GAME_MOUSE_ACTION_MENU_ITEM_LOOK,\n        GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD,\n        GAME_MOUSE_ACTION_MENU_ITEM_DROP,\n        GAME_MOUSE_ACTION_MENU_ITEM_CANCEL,\n    };\n\n    // 0x519154\n    static int act_weap2[3] = {\n        GAME_MOUSE_ACTION_MENU_ITEM_LOOK,\n        GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD,\n        GAME_MOUSE_ACTION_MENU_ITEM_CANCEL,\n    };\n\n    Object* item;\n    Object** v43;\n    Object* v41;\n\n    int v56 = inven_from_button(keyCode, &item, &v43, &v41);\n    if (v56 == 0) {\n        return;\n    }\n\n    int itemType = item_get_type(item);\n\n    int mouseState;\n    do {\n        get_input();\n\n        if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) {\n            display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL);\n        }\n\n        mouseState = mouse_get_buttons();\n        if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n            if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) {\n                obj_look_at_func(stack[0], item, display_msg);\n            } else {\n                inven_obj_examine_func(stack[0], item);\n            }\n            win_draw(i_wid);\n            return;\n        }\n    } while ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) != MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT);\n\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_BLANK);\n\n    unsigned char* windowBuffer = win_get_buf(i_wid);\n\n    int x;\n    int y;\n    mouse_get_position(&x, &y);\n\n    int actionMenuItemsLength;\n    const int* actionMenuItems;\n    if (itemType == ITEM_TYPE_WEAPON && item_w_can_unload(item)) {\n        if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL && obj_top_environment(item) != obj_dude) {\n            actionMenuItemsLength = 3;\n            actionMenuItems = act_weap2;\n        } else {\n            actionMenuItemsLength = 4;\n            actionMenuItems = act_weap;\n        }\n    } else {\n        if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) {\n            if (obj_top_environment(item) != obj_dude) {\n                if (itemType == ITEM_TYPE_CONTAINER) {\n                    actionMenuItemsLength = 3;\n                    actionMenuItems = act_just_use;\n                } else {\n                    actionMenuItemsLength = 2;\n                    actionMenuItems = act_nothing;\n                }\n            } else {\n                if (itemType == ITEM_TYPE_CONTAINER) {\n                    actionMenuItemsLength = 4;\n                    actionMenuItems = act_use;\n                } else {\n                    actionMenuItemsLength = 3;\n                    actionMenuItems = act_no_use;\n                }\n            }\n        } else {\n            if (itemType == ITEM_TYPE_CONTAINER && v43 != NULL) {\n                actionMenuItemsLength = 3;\n                actionMenuItems = act_no_use;\n            } else {\n                if (obj_action_can_use(item) || proto_action_can_use_on(item->pid)) {\n                    actionMenuItemsLength = 4;\n                    actionMenuItems = act_use;\n                } else {\n                    actionMenuItemsLength = 3;\n                    actionMenuItems = act_no_use;\n                }\n            }\n        }\n    }\n\n    InventoryWindowDescription* windowDescription = &(iscr_data[inventoryWindowType]);\n    gmouse_3d_build_menu_frame(x, y, actionMenuItems, actionMenuItemsLength,\n        windowDescription->width + windowDescription->x,\n        windowDescription->height + windowDescription->y);\n\n    InventoryCursorData* cursorData = &(imdata[INVENTORY_WINDOW_CURSOR_MENU]);\n\n    int offsetX;\n    int offsetY;\n    art_frame_offset(cursorData->frm, 0, &offsetX, &offsetY);\n\n    Rect rect;\n    rect.ulx = x - windowDescription->x - cursorData->width / 2 + offsetX;\n    rect.uly = y - windowDescription->y - cursorData->height + 1 + offsetY;\n    rect.lrx = rect.ulx + cursorData->width - 1;\n    rect.lry = rect.uly + cursorData->height - 1;\n\n    int menuButtonHeight = cursorData->height;\n    if (rect.uly + menuButtonHeight > windowDescription->height) {\n        menuButtonHeight = windowDescription->height - rect.uly;\n    }\n\n    int btn = win_register_button(i_wid,\n        rect.ulx,\n        rect.uly,\n        cursorData->width,\n        menuButtonHeight,\n        -1,\n        -1,\n        -1,\n        -1,\n        cursorData->frmData,\n        cursorData->frmData,\n        0,\n        BUTTON_FLAG_TRANSPARENT);\n    win_draw_rect(i_wid, &rect);\n\n    int menuItemIndex = 0;\n    int previousMouseY = y;\n    while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) {\n        get_input();\n\n        if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) {\n            display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL);\n        }\n\n        int x;\n        int y;\n        mouse_get_position(&x, &y);\n        if (y - previousMouseY > 10 || previousMouseY - y > 10) {\n            if (y >= previousMouseY || menuItemIndex <= 0) {\n                if (previousMouseY < y && menuItemIndex < actionMenuItemsLength - 1) {\n                    menuItemIndex++;\n                }\n            } else {\n                menuItemIndex--;\n            }\n            gmouse_3d_highlight_menu_frame(menuItemIndex);\n            win_draw_rect(i_wid, &rect);\n            previousMouseY = y;\n        }\n    }\n\n    win_delete_button(btn);\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        unsigned char* src = win_get_buf(barter_back_win);\n        int pitch = scr_size.lrx - scr_size.ulx + 1;\n        buf_to_buf(src + pitch * rect.uly + rect.ulx + 80,\n            cursorData->width,\n            menuButtonHeight,\n            pitch,\n            windowBuffer + windowDescription->width * rect.uly + rect.ulx,\n            windowDescription->width);\n    } else {\n        int backgroundFid = art_id(OBJ_TYPE_INTERFACE, windowDescription->field_0, 0, 0, 0);\n        CacheEntry* backgroundFrmHandle;\n        unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n        buf_to_buf(backgroundFrmData + windowDescription->width * rect.uly + rect.ulx,\n            cursorData->width,\n            menuButtonHeight,\n            windowDescription->width,\n            windowBuffer + windowDescription->width * rect.uly + rect.ulx,\n            windowDescription->width);\n        art_ptr_unlock(backgroundFrmHandle);\n    }\n\n    mouse_set_position(x, y);\n\n    display_inventory(stack_offset[curr_stack], -1, inventoryWindowType);\n\n    int actionMenuItem = actionMenuItems[menuItemIndex];\n    switch (actionMenuItem) {\n    case GAME_MOUSE_ACTION_MENU_ITEM_DROP:\n        if (v43 != NULL) {\n            if (v43 == &i_worn) {\n                adjust_ac(stack[0], item, NULL);\n            }\n            item_add_force(v41, item, 1);\n            v56 = 1;\n            *v43 = NULL;\n        }\n\n        if (item->pid == PROTO_ID_MONEY) {\n            if (v56 > 1) {\n                v56 = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, v56);\n            } else {\n                v56 = 1;\n            }\n\n            if (v56 > 0) {\n                if (v56 == 1) {\n                    item_caps_set_amount(item, 1);\n                    obj_drop(v41, item);\n                } else {\n                    if (item_remove_mult(v41, item, v56 - 1) == 0) {\n                        Object* a2;\n                        if (inven_from_button(keyCode, &a2, &v43, &v41) != 0) {\n                            item_caps_set_amount(a2, v56);\n                            obj_drop(v41, a2);\n                        } else {\n                            item_add_force(v41, item, v56 - 1);\n                        }\n                    }\n                }\n            }\n        } else if (item->pid == PROTO_ID_DYNAMITE_II || item->pid == PROTO_ID_PLASTIC_EXPLOSIVES_II) {\n            dropped_explosive = 1;\n            obj_drop(v41, item);\n        } else {\n            if (v56 > 1) {\n                v56 = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, v56);\n\n                for (int index = 0; index < v56; index++) {\n                    if (inven_from_button(keyCode, &item, &v43, &v41) != 0) {\n                        obj_drop(v41, item);\n                    }\n                }\n            } else {\n                obj_drop(v41, item);\n            }\n        }\n        break;\n    case GAME_MOUSE_ACTION_MENU_ITEM_LOOK:\n        if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) {\n            obj_examine_func(stack[0], item, display_msg);\n        } else {\n            inven_obj_examine_func(stack[0], item);\n        }\n        break;\n    case GAME_MOUSE_ACTION_MENU_ITEM_USE:\n        switch (itemType) {\n        case ITEM_TYPE_CONTAINER:\n            container_enter(keyCode, inventoryWindowType);\n            break;\n        case ITEM_TYPE_DRUG:\n            if (item_d_take_drug(stack[0], item)) {\n                if (v43 != NULL) {\n                    *v43 = NULL;\n                } else {\n                    item_remove_mult(v41, item, 1);\n                }\n\n                obj_connect(item, obj_dude->tile, obj_dude->elevation, NULL);\n                obj_destroy(item);\n            }\n            intface_update_hit_points(true);\n            break;\n        case ITEM_TYPE_WEAPON:\n        case ITEM_TYPE_MISC:\n            if (v43 == NULL) {\n                item_remove_mult(v41, item, 1);\n            }\n\n            int v21;\n            if (obj_action_can_use(item)) {\n                v21 = protinst_use_item(stack[0], item);\n            } else {\n                v21 = protinst_use_item_on(stack[0], stack[0], item);\n            }\n\n            if (v21 == 1) {\n                if (v43 != NULL) {\n                    *v43 = NULL;\n                }\n\n                obj_connect(item, obj_dude->tile, obj_dude->elevation, NULL);\n                obj_destroy(item);\n            } else {\n                if (v43 == NULL) {\n                    item_add_force(v41, item, 1);\n                }\n            }\n        }\n        break;\n    case GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD:\n        if (v43 == NULL) {\n            item_remove_mult(v41, item, 1);\n        }\n\n        for (;;) {\n            Object* ammo = item_w_unload(item);\n            if (ammo == NULL) {\n                break;\n            }\n\n            Rect rect;\n            obj_disconnect(ammo, &rect);\n            item_add_force(v41, ammo, 1);\n        }\n\n        if (v43 == NULL) {\n            item_add_force(v41, item, 1);\n        }\n        break;\n    default:\n        break;\n    }\n\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW);\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL && actionMenuItem != GAME_MOUSE_ACTION_MENU_ITEM_LOOK) {\n        display_stats();\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT\n        || inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, inventoryWindowType);\n    }\n\n    display_inventory(stack_offset[curr_stack], -1, inventoryWindowType);\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) {\n        display_table_inventories(barter_back_win, ptable, btable, -1);\n    }\n\n    adjust_fid();\n}\n\n// 0x473904\nint loot_container(Object* a1, Object* a2)\n{\n    // 0x46E708\n    static const int arrowFrmIds[INVENTORY_ARROW_FRM_COUNT] = {\n        122, // left arrow up\n        123, // left arrow down\n        124, // right arrow up\n        125, // right arrow down\n    };\n\n    CacheEntry* arrowFrmHandles[INVENTORY_ARROW_FRM_COUNT];\n    MessageListItem messageListItem;\n\n    if (a1 != inven_dude) {\n        return 0;\n    }\n\n    if (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER) {\n        if (critter_flag_check(a2->pid, CRITTER_NO_STEAL)) {\n            // You can't find anything to take from that.\n            messageListItem.num = 50;\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n            return 0;\n        }\n    }\n\n    if (FID_TYPE(a2->fid) == OBJ_TYPE_ITEM) {\n        if (item_get_type(a2) == ITEM_TYPE_CONTAINER) {\n            if (a2->frame == 0) {\n                CacheEntry* handle;\n                Art* frm = art_ptr_lock(a2->fid, &handle);\n                if (frm != NULL) {\n                    int frameCount = art_frame_max_frame(frm);\n                    art_ptr_unlock(handle);\n                    if (frameCount > 1) {\n                        return 0;\n                    }\n                }\n            }\n        }\n    }\n\n    int sid = -1;\n    if (!gIsSteal) {\n        if (obj_sid(a2, &sid) != -1) {\n            scr_set_objs(sid, a1, NULL);\n            exec_script_proc(sid, SCRIPT_PROC_PICKUP);\n\n            Script* script;\n            if (scr_ptr(sid, &script) != -1) {\n                if (script->scriptOverrides) {\n                    return 0;\n                }\n            }\n        }\n    }\n\n    if (inven_init() == -1) {\n        return 0;\n    }\n\n    target_pud = &(a2->data.inventory);\n    target_curr_stack = 0;\n    target_stack_offset[0] = 0;\n    target_stack[0] = a2;\n\n    Object* a1a = NULL;\n    if (obj_new(&a1a, 0, 467) == -1) {\n        return 0;\n    }\n\n    item_move_all_hidden(a2, a1a);\n\n    Object* item1 = NULL;\n    Object* item2 = NULL;\n    Object* armor = NULL;\n\n    if (gIsSteal) {\n        item1 = inven_left_hand(a2);\n        if (item1 != NULL) {\n            item_remove_mult(a2, item1, 1);\n        }\n\n        item2 = inven_right_hand(a2);\n        if (item2 != NULL) {\n            item_remove_mult(a2, item2, 1);\n        }\n\n        armor = inven_worn(a2);\n        if (armor != NULL) {\n            item_remove_mult(a2, armor, 1);\n        }\n    }\n\n    bool isoWasEnabled = setup_inventory(INVENTORY_WINDOW_TYPE_LOOT);\n\n    Object** critters = NULL;\n    int critterCount = 0;\n    int critterIndex = 0;\n    if (!gIsSteal) {\n        if (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER) {\n            critterCount = obj_create_list(a2->tile, a2->elevation, OBJ_TYPE_CRITTER, &critters);\n            int endIndex = critterCount - 1;\n            for (int index = 0; index < critterCount; index++) {\n                Object* critter = critters[index];\n                if ((critter->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) == 0) {\n                    critters[index] = critters[endIndex];\n                    critters[endIndex] = critter;\n                    critterCount--;\n                    index--;\n                    endIndex--;\n                } else {\n                    critterIndex++;\n                }\n            }\n\n            if (critterCount == 1) {\n                obj_delete_list(critters);\n                critterCount = 0;\n            }\n\n            if (critterCount > 1) {\n                int fid;\n                unsigned char* buttonUpData;\n                unsigned char* buttonDownData;\n                int btn;\n\n                for (int index = 0; index < INVENTORY_ARROW_FRM_COUNT; index++) {\n                    arrowFrmHandles[index] = INVALID_CACHE_ENTRY;\n                }\n\n                // Setup left arrow button.\n                fid = art_id(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_UP], 0, 0, 0);\n                buttonUpData = art_ptr_lock_data(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_LEFT_ARROW_UP]));\n\n                fid = art_id(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN], 0, 0, 0);\n                buttonDownData = art_ptr_lock_data(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN]));\n\n                if (buttonUpData != NULL && buttonDownData != NULL) {\n                    btn = win_register_button(i_wid, 436, 162, 20, 18, -1, -1, KEY_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0);\n                    if (btn != -1) {\n                        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n                    }\n                }\n\n                // Setup right arrow button.\n                fid = art_id(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP], 0, 0, 0);\n                buttonUpData = art_ptr_lock_data(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP]));\n\n                fid = art_id(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN], 0, 0, 0);\n                buttonDownData = art_ptr_lock_data(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN]));\n\n                if (buttonUpData != NULL && buttonDownData != NULL) {\n                    btn = win_register_button(i_wid, 456, 162, 20, 18, -1, -1, KEY_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0);\n                    if (btn != -1) {\n                        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n                    }\n                }\n\n                for (int index = 0; index < critterCount; index++) {\n                    if (a2 == critters[index]) {\n                        critterIndex = index;\n                    }\n                }\n            }\n        }\n    }\n\n    display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n    display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);\n    display_body(a2->fid, INVENTORY_WINDOW_TYPE_LOOT);\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n\n    bool isCaughtStealing = false;\n    int stealingXp = 0;\n    int stealingXpBonus = 10;\n    for (;;) {\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        if (isCaughtStealing) {\n            break;\n        }\n\n        int keyCode = get_input();\n\n        if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n            game_quit_with_confirm();\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        if (keyCode == KEY_UPPERCASE_A) {\n            if (!gIsSteal) {\n                int maxCarryWeight = critterGetStat(a1, STAT_CARRY_WEIGHT);\n                int currentWeight = item_total_weight(a1);\n                int newInventoryWeight = item_total_weight(a2);\n                if (newInventoryWeight <= maxCarryWeight - currentWeight) {\n                    item_move_all(a2, a1);\n                    display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n                    display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);\n                } else {\n                    // Sorry, you cannot carry that much.\n                    messageListItem.num = 31;\n                    if (message_search(&inventry_message_file, &messageListItem)) {\n                        dialog_out(messageListItem.text, NULL, 0, 169, 117, colorTable[32328], NULL, colorTable[32328], 0);\n                    }\n                }\n            }\n        } else if (keyCode == KEY_ARROW_UP) {\n            if (stack_offset[curr_stack] > 0) {\n                stack_offset[curr_stack] -= 1;\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);\n            }\n        } else if (keyCode == KEY_PAGE_UP) {\n            if (critterCount != 0) {\n                if (critterIndex > 0) {\n                    critterIndex -= 1;\n                } else {\n                    critterIndex = critterCount - 1;\n                }\n\n                a2 = critters[critterIndex];\n                target_pud = &(a2->data.inventory);\n                target_stack[0] = a2;\n                target_curr_stack = 0;\n                target_stack_offset[0] = 0;\n                display_target_inventory(0, -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);\n                display_body(a2->fid, INVENTORY_WINDOW_TYPE_LOOT);\n            }\n        } else if (keyCode == KEY_ARROW_DOWN) {\n            if (stack_offset[curr_stack] + inven_cur_disp < pud->length) {\n                stack_offset[curr_stack] += 1;\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);\n            }\n        } else if (keyCode == KEY_PAGE_DOWN) {\n            if (critterCount != 0) {\n                if (critterIndex < critterCount - 1) {\n                    critterIndex += 1;\n                } else {\n                    critterIndex = 0;\n                }\n\n                a2 = critters[critterIndex];\n                target_pud = &(a2->data.inventory);\n                target_stack[0] = a2;\n                target_curr_stack = 0;\n                target_stack_offset[0] = 0;\n                display_target_inventory(0, -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);\n                display_body(a2->fid, INVENTORY_WINDOW_TYPE_LOOT);\n            }\n        } else if (keyCode == KEY_CTRL_ARROW_UP) {\n            if (target_stack_offset[target_curr_stack] > 0) {\n                target_stack_offset[target_curr_stack] -= 1;\n                display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n                win_draw(i_wid);\n            }\n        } else if (keyCode == KEY_CTRL_ARROW_DOWN) {\n            if (target_stack_offset[target_curr_stack] + inven_cur_disp < target_pud->length) {\n                target_stack_offset[target_curr_stack] += 1;\n                display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n                win_draw(i_wid);\n            }\n        } else if (keyCode >= 2500 && keyCode <= 2501) {\n            container_exit(keyCode, INVENTORY_WINDOW_TYPE_LOOT);\n        } else {\n            if ((mouse_get_buttons() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) {\n                if (immode == INVENTORY_WINDOW_CURSOR_HAND) {\n                    inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW);\n                } else {\n                    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n                }\n            } else if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n                if (keyCode >= 1000 && keyCode <= 1000 + inven_cur_disp) {\n                    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                        inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_LOOT);\n                    } else {\n                        int v40 = keyCode - 1000;\n                        if (v40 + stack_offset[curr_stack] < pud->length) {\n                            gStealCount += 1;\n                            gStealSize += item_size(stack[curr_stack]);\n\n                            InventoryItem* inventoryItem = &(pud->items[pud->length - (v40 + stack_offset[curr_stack] + 1)]);\n                            int rc = move_inventory(inventoryItem->item, v40, target_stack[target_curr_stack], true);\n                            if (rc == 1) {\n                                isCaughtStealing = true;\n                            } else if (rc == 2) {\n                                stealingXp += stealingXpBonus;\n                                stealingXpBonus += 10;\n                            }\n\n                            display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n                            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);\n                        }\n\n                        keyCode = -1;\n                    }\n                } else if (keyCode >= 2000 && keyCode <= 2000 + inven_cur_disp) {\n                    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                        inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_LOOT);\n                    } else {\n                        int v46 = keyCode - 2000;\n                        if (v46 + target_stack_offset[target_curr_stack] < target_pud->length) {\n                            gStealCount += 1;\n                            gStealSize += item_size(stack[curr_stack]);\n\n                            InventoryItem* inventoryItem = &(target_pud->items[target_pud->length - (v46 + target_stack_offset[target_curr_stack] + 1)]);\n                            int rc = move_inventory(inventoryItem->item, v46, target_stack[target_curr_stack], false);\n                            if (rc == 1) {\n                                isCaughtStealing = true;\n                            } else if (rc == 2) {\n                                stealingXp += stealingXpBonus;\n                                stealingXpBonus += 10;\n                            }\n\n                            display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n                            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT);\n                        }\n                    }\n                }\n            }\n        }\n\n        if (keyCode == KEY_ESCAPE) {\n            break;\n        }\n    }\n\n    if (critterCount != 0) {\n        obj_delete_list(critters);\n\n        for (int index = 0; index < INVENTORY_ARROW_FRM_COUNT; index++) {\n            art_ptr_unlock(arrowFrmHandles[index]);\n        }\n    }\n\n    if (gIsSteal) {\n        if (item1 != NULL) {\n            item1->flags |= OBJECT_IN_LEFT_HAND;\n            item_add_force(a2, item1, 1);\n        }\n\n        if (item2 != NULL) {\n            item2->flags |= OBJECT_IN_RIGHT_HAND;\n            item_add_force(a2, item2, 1);\n        }\n\n        if (armor != NULL) {\n            armor->flags |= OBJECT_WORN;\n            item_add_force(a2, armor, 1);\n        }\n    }\n\n    item_move_all(a1a, a2);\n    obj_erase_object(a1a, NULL);\n\n    if (gIsSteal) {\n        if (!isCaughtStealing) {\n            if (stealingXp > 0) {\n                if (!isPartyMember(a2)) {\n                    stealingXp = min(300 - skill_level(a1, SKILL_STEAL), stealingXp);\n                    debug_printf(\"\\n[[[%d]]]\", 300 - skill_level(a1, SKILL_STEAL));\n\n                    // You gain %d experience points for successfully using your Steal skill.\n                    messageListItem.num = 29;\n                    if (message_search(&inventry_message_file, &messageListItem)) {\n                        char formattedText[200];\n                        sprintf(formattedText, messageListItem.text, stealingXp);\n                        display_print(formattedText);\n                    }\n\n                    stat_pc_add_experience(stealingXp);\n                }\n            }\n        }\n    }\n\n    exit_inventory(isoWasEnabled);\n\n    // NOTE: Uninline.\n    inven_exit();\n\n    if (gIsSteal) {\n        if (isCaughtStealing) {\n            if (gStealCount > 0) {\n                if (obj_sid(a2, &sid) != -1) {\n                    scr_set_objs(sid, a1, NULL);\n                    exec_script_proc(sid, SCRIPT_PROC_PICKUP);\n\n                    // TODO: Looks like inlining, script is not used.\n                    Script* script;\n                    scr_ptr(sid, &script);\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4746A0\nint inven_steal_container(Object* a1, Object* a2)\n{\n    if (a1 == a2) {\n        return -1;\n    }\n\n    gIsSteal = PID_TYPE(a1->pid) == OBJ_TYPE_CRITTER && critter_is_active(a2);\n    gStealCount = 0;\n    gStealSize = 0;\n\n    int rc = loot_container(a1, a2);\n\n    gIsSteal = 0;\n    gStealCount = 0;\n    gStealSize = 0;\n\n    return rc;\n}\n\n// 0x474708\nint move_inventory(Object* a1, int a2, Object* a3, bool a4)\n{\n    bool v38 = true;\n\n    Rect rect;\n\n    int quantity;\n    if (a4) {\n        rect.ulx = INVENTORY_LOOT_LEFT_SCROLLER_X;\n        rect.uly = INVENTORY_SLOT_HEIGHT * a2 + INVENTORY_LOOT_LEFT_SCROLLER_Y;\n\n        InventoryItem* inventoryItem = &(pud->items[pud->length - (a2 + stack_offset[curr_stack] + 1)]);\n        quantity = inventoryItem->quantity;\n        if (quantity > 1) {\n            display_inventory(stack_offset[curr_stack], a2, INVENTORY_WINDOW_TYPE_LOOT);\n            v38 = false;\n        }\n    } else {\n        rect.ulx = INVENTORY_LOOT_RIGHT_SCROLLER_X;\n        rect.uly = INVENTORY_SLOT_HEIGHT * a2 + INVENTORY_LOOT_RIGHT_SCROLLER_Y;\n\n        InventoryItem* inventoryItem = &(target_pud->items[target_pud->length - (a2 + target_stack_offset[target_curr_stack] + 1)]);\n        quantity = inventoryItem->quantity;\n        if (quantity > 1) {\n            display_target_inventory(target_stack_offset[target_curr_stack], a2, target_pud, INVENTORY_WINDOW_TYPE_LOOT);\n            win_draw(i_wid);\n            v38 = false;\n        }\n    }\n\n    if (v38) {\n        unsigned char* windowBuffer = win_get_buf(i_wid);\n\n        CacheEntry* handle;\n        int fid = art_id(OBJ_TYPE_INTERFACE, 114, 0, 0, 0);\n        unsigned char* data = art_ptr_lock_data(fid, 0, 0, &handle);\n        if (data != NULL) {\n            buf_to_buf(data + 537 * rect.uly + rect.ulx, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 537, windowBuffer + 537 * rect.uly + rect.ulx, 537);\n            art_ptr_unlock(handle);\n        }\n\n        rect.lrx = rect.ulx + INVENTORY_SLOT_WIDTH - 1;\n        rect.lry = rect.uly + INVENTORY_SLOT_HEIGHT - 1;\n        win_draw_rect(i_wid, &rect);\n    }\n\n    CacheEntry* inventoryFrmHandle;\n    int inventoryFid = item_inv_fid(a1);\n    Art* inventoryFrm = art_ptr_lock(inventoryFid, &inventoryFrmHandle);\n    if (inventoryFrm != NULL) {\n        int width = art_frame_width(inventoryFrm, 0, 0);\n        int height = art_frame_length(inventoryFrm, 0, 0);\n        unsigned char* data = art_frame_data(inventoryFrm, 0, 0);\n        mouse_set_shape(data, width, height, width, width / 2, height / 2, 0);\n        gsound_play_sfx_file(\"ipickup1\");\n    }\n\n    do {\n        get_input();\n    } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0);\n\n    if (inventoryFrm != NULL) {\n        art_ptr_unlock(inventoryFrmHandle);\n        gsound_play_sfx_file(\"iputdown\");\n    }\n\n    int rc = 0;\n    MessageListItem messageListItem;\n\n    if (a4) {\n        if (mouse_click_in(INVENTORY_LOOT_RIGHT_SCROLLER_ABS_X, INVENTORY_LOOT_RIGHT_SCROLLER_ABS_Y, INVENTORY_LOOT_RIGHT_SCROLLER_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_LOOT_RIGHT_SCROLLER_ABS_Y)) {\n            int quantityToMove;\n            if (quantity > 1) {\n                quantityToMove = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity);\n            } else {\n                quantityToMove = 1;\n            }\n\n            if (quantityToMove != -1) {\n                if (gIsSteal) {\n                    if (skill_check_stealing(inven_dude, a3, a1, true) == 0) {\n                        rc = 1;\n                    }\n                }\n\n                if (rc != 1) {\n                    if (item_move(inven_dude, a3, a1, quantityToMove) != -1) {\n                        rc = 2;\n                    } else {\n                        // There is no space left for that item.\n                        messageListItem.num = 26;\n                        if (message_search(&inventry_message_file, &messageListItem)) {\n                            display_print(messageListItem.text);\n                        }\n                    }\n                }\n            }\n        }\n    } else {\n        if (mouse_click_in(INVENTORY_LOOT_LEFT_SCROLLER_ABS_X, INVENTORY_LOOT_LEFT_SCROLLER_ABS_Y, INVENTORY_LOOT_LEFT_SCROLLER_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_LOOT_LEFT_SCROLLER_ABS_Y)) {\n            int quantityToMove;\n            if (quantity > 1) {\n                quantityToMove = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity);\n            } else {\n                quantityToMove = 1;\n            }\n\n            if (quantityToMove != -1) {\n                if (gIsSteal) {\n                    if (skill_check_stealing(inven_dude, a3, a1, false) == 0) {\n                        rc = 1;\n                    }\n                }\n\n                if (rc != 1) {\n                    if (item_move(a3, inven_dude, a1, quantityToMove) == 0) {\n                        if ((a1->flags & OBJECT_IN_RIGHT_HAND) != 0) {\n                            a3->fid = art_id(FID_TYPE(a3->fid), a3->fid & 0xFFF, FID_ANIM_TYPE(a3->fid), 0, a3->rotation + 1);\n                        }\n\n                        a3->flags &= ~OBJECT_EQUIPPED;\n\n                        rc = 2;\n                    } else {\n                        // You cannot pick that up. You are at your maximum weight capacity.\n                        messageListItem.num = 25;\n                        if (message_search(&inventry_message_file, &messageListItem)) {\n                            display_print(messageListItem.text);\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n\n    return rc;\n}\n\n// 0x474B2C\nstatic int barter_compute_value(Object* a1, Object* a2)\n{\n    if (dialog_target_is_party) {\n        return item_total_weight(btable);\n    }\n\n    int cost = item_total_cost(btable);\n    int caps = item_caps_total(btable);\n    int v14 = cost - caps;\n\n    double bonus = 0.0;\n    if (a1 == obj_dude) {\n        if (perkHasRank(obj_dude, PERK_MASTER_TRADER)) {\n            bonus = 25.0;\n        }\n    }\n\n    int partyBarter = partyMemberHighestSkillLevel(SKILL_BARTER);\n    int npcBarter = skill_level(a2, SKILL_BARTER);\n\n    // TODO: Check in debugger, complex math, probably uses floats, not doubles.\n    double v1 = (barter_mod + 100.0 - bonus) * 0.01;\n    double v2 = (160.0 + npcBarter) / (160.0 + partyBarter) * (v14 * 2.0);\n    if (v1 < 0) {\n        // TODO: Probably 0.01 as float.\n        v1 = 0.0099999998;\n    }\n\n    int rounded = (int)(v1 * v2 + caps);\n    return rounded;\n}\n\n// 0x474C50\nstatic int barter_attempt_transaction(Object* a1, Object* a2, Object* a3, Object* a4)\n{\n    MessageListItem messageListItem;\n\n    int v8 = critterGetStat(a1, STAT_CARRY_WEIGHT) - item_total_weight(a1);\n    if (item_total_weight(a4) > v8) {\n        // Sorry, you cannot carry that much.\n        messageListItem.num = 31;\n        if (message_search(&inventry_message_file, &messageListItem)) {\n            gdialogDisplayMsg(messageListItem.text);\n        }\n        return -1;\n    }\n\n    if (dialog_target_is_party) {\n        int v10 = critterGetStat(a3, STAT_CARRY_WEIGHT) - item_total_weight(a3);\n        if (item_total_weight(a2) > v10) {\n            // Sorry, that's too much to carry.\n            messageListItem.num = 32;\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                gdialogDisplayMsg(messageListItem.text);\n            }\n            return -1;\n        }\n    } else {\n        bool v11 = false;\n        if (a2->data.inventory.length == 0) {\n            v11 = true;\n        } else {\n            if (item_queued(a2)) {\n                if (a2->pid != PROTO_ID_GEIGER_COUNTER_I || item_m_turn_off(a2) == -1) {\n                    v11 = true;\n                }\n            }\n        }\n\n        if (!v11) {\n            int cost = item_total_cost(a2);\n            if (barter_compute_value(a1, a3) > cost) {\n                v11 = true;\n            }\n        }\n\n        if (v11) {\n            // No, your offer is not good enough.\n            messageListItem.num = 28;\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                gdialogDisplayMsg(messageListItem.text);\n            }\n            return -1;\n        }\n    }\n\n    item_move_all(a4, a1);\n    item_move_all(a2, a3);\n    return 0;\n}\n\n// 0x474DAC\nstatic void barter_move_inventory(Object* a1, int quantity, int a3, int a4, Object* a5, Object* a6, bool a7)\n{\n    Rect rect;\n    if (a7) {\n        rect.ulx = 23;\n        rect.uly = 48 * a3 + 34;\n    } else {\n        rect.ulx = 395;\n        rect.uly = 48 * a3 + 31;\n    }\n\n    if (quantity > 1) {\n        if (a7) {\n            display_inventory(a4, a3, INVENTORY_WINDOW_TYPE_TRADE);\n        } else {\n            display_target_inventory(a4, a3, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n        }\n    } else {\n        unsigned char* dest = win_get_buf(i_wid);\n        unsigned char* src = win_get_buf(barter_back_win);\n\n        int pitch = scr_size.lrx - scr_size.ulx + 1;\n        buf_to_buf(src + pitch * rect.uly + rect.ulx + 80, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + 480 * rect.uly + rect.ulx, 480);\n\n        rect.lrx = rect.ulx + INVENTORY_SLOT_WIDTH - 1;\n        rect.lry = rect.uly + INVENTORY_SLOT_HEIGHT - 1;\n        win_draw_rect(i_wid, &rect);\n    }\n\n    CacheEntry* inventoryFrmHandle;\n    int inventoryFid = item_inv_fid(a1);\n    Art* inventoryFrm = art_ptr_lock(inventoryFid, &inventoryFrmHandle);\n    if (inventoryFrm != NULL) {\n        int width = art_frame_width(inventoryFrm, 0, 0);\n        int height = art_frame_length(inventoryFrm, 0, 0);\n        unsigned char* data = art_frame_data(inventoryFrm, 0, 0);\n        mouse_set_shape(data, width, height, width, width / 2, height / 2, 0);\n        gsound_play_sfx_file(\"ipickup1\");\n    }\n\n    do {\n        get_input();\n    } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0);\n\n    if (inventoryFrm != NULL) {\n        art_ptr_unlock(inventoryFrmHandle);\n        gsound_play_sfx_file(\"iputdown\");\n    }\n\n    MessageListItem messageListItem;\n\n    if (a7) {\n        if (mouse_click_in(INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_X, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_Y, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_Y)) {\n            int quantityToMove = quantity > 1 ? do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1;\n            if (quantityToMove != -1) {\n                if (item_move_force(inven_dude, a6, a1, quantityToMove) == -1) {\n                    // There is no space left for that item.\n                    messageListItem.num = 26;\n                    if (message_search(&inventry_message_file, &messageListItem)) {\n                        display_print(messageListItem.text);\n                    }\n                }\n            }\n        }\n    } else {\n        if (mouse_click_in(INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_X, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_Y, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_Y)) {\n            int quantityToMove = quantity > 1 ? do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1;\n            if (quantityToMove != -1) {\n                if (item_move_force(a5, a6, a1, quantityToMove) == -1) {\n                    // You cannot pick that up. You are at your maximum weight capacity.\n                    messageListItem.num = 25;\n                    if (message_search(&inventry_message_file, &messageListItem)) {\n                        display_print(messageListItem.text);\n                    }\n                }\n            }\n        }\n    }\n\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n}\n\n// 0x475070\nstatic void barter_move_from_table_inventory(Object* a1, int quantity, int a3, Object* a4, Object* a5, bool a6)\n{\n    Rect rect;\n    if (a6) {\n        rect.ulx = 169;\n        rect.uly = 48 * a3 + 24;\n    } else {\n        rect.ulx = 254;\n        rect.uly = 48 * a3 + 24;\n    }\n\n    if (quantity > 1) {\n        if (a6) {\n            display_table_inventories(barter_back_win, a5, NULL, a3);\n        } else {\n            display_table_inventories(barter_back_win, NULL, a5, a3);\n        }\n    } else {\n        unsigned char* dest = win_get_buf(i_wid);\n        unsigned char* src = win_get_buf(barter_back_win);\n\n        int pitch = scr_size.lrx - scr_size.ulx + 1;\n        buf_to_buf(src + pitch * rect.uly + rect.ulx + 80, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + 480 * rect.uly + rect.ulx, 480);\n\n        rect.lrx = rect.ulx + INVENTORY_SLOT_WIDTH - 1;\n        rect.lry = rect.uly + INVENTORY_SLOT_HEIGHT - 1;\n        win_draw_rect(i_wid, &rect);\n    }\n\n    CacheEntry* inventoryFrmHandle;\n    int inventoryFid = item_inv_fid(a1);\n    Art* inventoryFrm = art_ptr_lock(inventoryFid, &inventoryFrmHandle);\n    if (inventoryFrm != NULL) {\n        int width = art_frame_width(inventoryFrm, 0, 0);\n        int height = art_frame_length(inventoryFrm, 0, 0);\n        unsigned char* data = art_frame_data(inventoryFrm, 0, 0);\n        mouse_set_shape(data, width, height, width, width / 2, height / 2, 0);\n        gsound_play_sfx_file(\"ipickup1\");\n    }\n\n    do {\n        get_input();\n    } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0);\n\n    if (inventoryFrm != NULL) {\n        art_ptr_unlock(inventoryFrmHandle);\n        gsound_play_sfx_file(\"iputdown\");\n    }\n\n    MessageListItem messageListItem;\n\n    if (a6) {\n        if (mouse_click_in(INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_X, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_Y, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_Y)) {\n            int quantityToMove = quantity > 1 ? do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1;\n            if (quantityToMove != -1) {\n                if (item_move_force(a5, inven_dude, a1, quantityToMove) == -1) {\n                    // There is no space left for that item.\n                    messageListItem.num = 26;\n                    if (message_search(&inventry_message_file, &messageListItem)) {\n                        display_print(messageListItem.text);\n                    }\n                }\n            }\n        }\n    } else {\n        if (mouse_click_in(INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_X, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_Y, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_Y)) {\n            int quantityToMove = quantity > 1 ? do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1;\n            if (quantityToMove != -1) {\n                if (item_move_force(a5, a4, a1, quantityToMove) == -1) {\n                    // You cannot pick that up. You are at your maximum weight capacity.\n                    messageListItem.num = 25;\n                    if (message_search(&inventry_message_file, &messageListItem)) {\n                        display_print(messageListItem.text);\n                    }\n                }\n            }\n        }\n    }\n\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n}\n\n// 0x475334\nstatic void display_table_inventories(int win, Object* a2, Object* a3, int a4)\n{\n    unsigned char* windowBuffer = win_get_buf(i_wid);\n\n    int oldFont = text_curr();\n    text_font(101);\n\n    char formattedText[80];\n    int v45 = text_height() + 48 * inven_cur_disp;\n\n    if (a2 != NULL) {\n        unsigned char* src = win_get_buf(win);\n        buf_to_buf(src + (scr_size.lrx - scr_size.ulx + 1) * 20 + 249, 64, v45 + 1, scr_size.lrx - scr_size.ulx + 1, windowBuffer + 480 * 20 + 169, 480);\n\n        unsigned char* dest = windowBuffer + 480 * 24 + 169;\n        Inventory* inventory = &(a2->data.inventory);\n        for (int index = 0; index < inven_cur_disp && index + ptable_offset < inventory->length; index++) {\n            InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + ptable_offset + 1)]);\n            int inventoryFid = item_inv_fid(inventoryItem->item);\n            scale_art(inventoryFid, dest, 56, 40, 480);\n            display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, 480, index == a4);\n\n            dest += 480 * 48;\n        }\n\n        if (dialog_target_is_party) {\n            MessageListItem messageListItem;\n            messageListItem.num = 30;\n\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                int weight = item_total_weight(a2);\n                sprintf(formattedText, \"%s %d\", messageListItem.text, weight);\n            }\n        } else {\n            int cost = item_total_cost(a2);\n            sprintf(formattedText, \"$%d\", cost);\n        }\n\n        text_to_buf(windowBuffer + 480 * (48 * inven_cur_disp + 24) + 169, formattedText, 80, 480, colorTable[32767]);\n\n        Rect rect;\n        rect.ulx = 169;\n        rect.uly = 24;\n        rect.lrx = 223;\n        rect.lry = rect.uly + v45;\n        win_draw_rect(i_wid, &rect);\n    }\n\n    if (a3 != NULL) {\n        unsigned char* src = win_get_buf(win);\n        buf_to_buf(src + (scr_size.lrx - scr_size.ulx + 1) * 20 + 334, 64, v45 + 1, scr_size.lrx - scr_size.ulx + 1, windowBuffer + 480 * 20 + 254, 480);\n\n        unsigned char* dest = windowBuffer + 480 * 24 + 254;\n        Inventory* inventory = &(a3->data.inventory);\n        for (int index = 0; index < inven_cur_disp && index + btable_offset < inventory->length; index++) {\n            InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + btable_offset + 1)]);\n            int inventoryFid = item_inv_fid(inventoryItem->item);\n            scale_art(inventoryFid, dest, 56, 40, 480);\n            display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, 480, index == a4);\n\n            dest += 480 * 48;\n        }\n\n        if (dialog_target_is_party) {\n            MessageListItem messageListItem;\n            messageListItem.num = 30;\n\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                int weight = barter_compute_value(obj_dude, target_stack[0]);\n                sprintf(formattedText, \"%s %d\", messageListItem.text, weight);\n            }\n        } else {\n            int cost = barter_compute_value(obj_dude, target_stack[0]);\n            sprintf(formattedText, \"$%d\", cost);\n        }\n\n        text_to_buf(windowBuffer + 480 * (48 * inven_cur_disp + 24) + 254, formattedText, 80, 480, colorTable[32767]);\n\n        Rect rect;\n        rect.ulx = 254;\n        rect.uly = 24;\n        rect.lrx = 318;\n        rect.lry = rect.uly + v45;\n        win_draw_rect(i_wid, &rect);\n    }\n\n    text_font(oldFont);\n}\n\n// 0x4757F0\nvoid barter_inventory(int win, Object* a2, Object* a3, Object* a4, int a5)\n{\n    barter_mod = a5;\n\n    if (inven_init() == -1) {\n        return;\n    }\n\n    Object* armor = inven_worn(a2);\n    if (armor != NULL) {\n        item_remove_mult(a2, armor, 1);\n    }\n\n    Object* item1 = NULL;\n    Object* item2 = inven_right_hand(a2);\n    if (item2 != NULL) {\n        item_remove_mult(a2, item2, 1);\n    } else {\n        if (!dialog_target_is_party) {\n            item1 = inven_find_type(a2, ITEM_TYPE_WEAPON, NULL);\n            if (item1 != NULL) {\n                item_remove_mult(a2, item1, 1);\n            }\n        }\n    }\n\n    Object* a1a = NULL;\n    if (obj_new(&a1a, 0, 467) == -1) {\n        return;\n    }\n\n    pud = &(inven_dude->data.inventory);\n    btable = a4;\n    ptable = a3;\n\n    ptable_offset = 0;\n    btable_offset = 0;\n\n    ptable_pud = &(a3->data.inventory);\n    btable_pud = &(a4->data.inventory);\n\n    barter_back_win = win;\n    target_curr_stack = 0;\n    target_pud = &(a2->data.inventory);\n\n    target_stack[0] = a2;\n    target_stack_offset[0] = 0;\n\n    bool isoWasEnabled = setup_inventory(INVENTORY_WINDOW_TYPE_TRADE);\n    display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n    display_inventory(stack_offset[0], -1, INVENTORY_WINDOW_TYPE_TRADE);\n    display_body(a2->fid, INVENTORY_WINDOW_TYPE_TRADE);\n    win_draw(barter_back_win);\n    display_table_inventories(win, a3, a4, -1);\n\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n\n    int modifier;\n    int npcReactionValue = reaction_get(a2);\n    int npcReactionType = reaction_to_level(npcReactionValue);\n    switch (npcReactionType) {\n    case NPC_REACTION_BAD:\n        modifier = 25;\n        break;\n    case NPC_REACTION_NEUTRAL:\n        modifier = 0;\n        break;\n    case NPC_REACTION_GOOD:\n        modifier = -15;\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    int keyCode = -1;\n    for (;;) {\n        if (keyCode == KEY_ESCAPE || game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        keyCode = get_input();\n        if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n            game_quit_with_confirm();\n        }\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        barter_mod = a5 + modifier;\n\n        if (keyCode == KEY_LOWERCASE_T || modifier <= -30) {\n            item_move_all(a4, a2);\n            item_move_all(a3, obj_dude);\n            barter_end_to_talk_to();\n            break;\n        } else if (keyCode == KEY_LOWERCASE_M) {\n            if (a3->data.inventory.length != 0 || btable->data.inventory.length != 0) {\n                if (barter_attempt_transaction(inven_dude, a3, a2, a4) == 0) {\n                    display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n                    display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE);\n                    display_table_inventories(win, a3, a4, -1);\n\n                    // Ok, that's a good trade.\n                    MessageListItem messageListItem;\n                    messageListItem.num = 27;\n                    if (!dialog_target_is_party) {\n                        if (message_search(&inventry_message_file, &messageListItem)) {\n                            gdialogDisplayMsg(messageListItem.text);\n                        }\n                    }\n                }\n            }\n        } else if (keyCode == KEY_ARROW_UP) {\n            if (stack_offset[curr_stack] > 0) {\n                stack_offset[curr_stack] -= 1;\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE);\n            }\n        } else if (keyCode == KEY_PAGE_UP) {\n            if (ptable_offset > 0) {\n                ptable_offset -= 1;\n                display_table_inventories(win, a3, a4, -1);\n            }\n        } else if (keyCode == KEY_ARROW_DOWN) {\n            if (stack_offset[curr_stack] + inven_cur_disp < pud->length) {\n                stack_offset[curr_stack] += 1;\n                display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE);\n            }\n        } else if (keyCode == KEY_PAGE_DOWN) {\n            if (ptable_offset + inven_cur_disp < ptable_pud->length) {\n                ptable_offset += 1;\n                display_table_inventories(win, a3, a4, -1);\n            }\n        } else if (keyCode == KEY_CTRL_PAGE_DOWN) {\n            if (btable_offset + inven_cur_disp < btable_pud->length) {\n                btable_offset++;\n                display_table_inventories(win, a3, a4, -1);\n            }\n        } else if (keyCode == KEY_CTRL_PAGE_UP) {\n            if (btable_offset > 0) {\n                btable_offset -= 1;\n                display_table_inventories(win, a3, a4, -1);\n            }\n        } else if (keyCode == KEY_CTRL_ARROW_UP) {\n            if (target_stack_offset[target_curr_stack] > 0) {\n                target_stack_offset[target_curr_stack] -= 1;\n                display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n                win_draw(i_wid);\n            }\n        } else if (keyCode == KEY_CTRL_ARROW_DOWN) {\n            if (target_stack_offset[target_curr_stack] + inven_cur_disp < target_pud->length) {\n                target_stack_offset[target_curr_stack] += 1;\n                display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n                win_draw(i_wid);\n            }\n        } else if (keyCode >= 2500 && keyCode <= 2501) {\n            container_exit(keyCode, INVENTORY_WINDOW_TYPE_TRADE);\n        } else {\n            if ((mouse_get_buttons() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) {\n                if (immode == INVENTORY_WINDOW_CURSOR_HAND) {\n                    inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW);\n                } else {\n                    inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND);\n                }\n            } else if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n                if (keyCode >= 1000 && keyCode <= 1000 + inven_cur_disp) {\n                    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                        inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_TRADE);\n                        display_table_inventories(win, a3, NULL, -1);\n                    } else {\n                        int v30 = keyCode - 1000;\n                        if (v30 + stack_offset[curr_stack] < pud->length) {\n                            int v31 = stack_offset[curr_stack];\n                            InventoryItem* inventoryItem = &(pud->items[pud->length - (v30 + v31 + 1)]);\n                            barter_move_inventory(inventoryItem->item, inventoryItem->quantity, v30, v31, a2, a3, true);\n                            display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n                            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE);\n                            display_table_inventories(win, a3, NULL, -1);\n                        }\n                    }\n\n                    keyCode = -1;\n                } else if (keyCode >= 2000 && keyCode <= 2000 + inven_cur_disp) {\n                    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                        inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_TRADE);\n                        display_table_inventories(win, NULL, a4, -1);\n                    } else {\n                        int v35 = keyCode - 2000;\n                        if (v35 + target_stack_offset[target_curr_stack] < target_pud->length) {\n                            int v36 = target_stack_offset[target_curr_stack];\n                            InventoryItem* inventoryItem = &(target_pud->items[target_pud->length - (v35 + v36 + 1)]);\n                            barter_move_inventory(inventoryItem->item, inventoryItem->quantity, v35, v36, a2, a4, false);\n                            display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n                            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE);\n                            display_table_inventories(win, NULL, a4, -1);\n                        }\n                    }\n\n                    keyCode = -1;\n                } else if (keyCode >= 2300 && keyCode <= 2300 + inven_cur_disp) {\n                    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                        inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_TRADE);\n                        display_table_inventories(win, a3, NULL, -1);\n                    } else {\n                        int v41 = keyCode - 2300;\n                        if (v41 < ptable_pud->length) {\n                            InventoryItem* inventoryItem = &(ptable_pud->items[ptable_pud->length - (v41 + ptable_offset + 1)]);\n                            barter_move_from_table_inventory(inventoryItem->item, inventoryItem->quantity, v41, a2, a3, true);\n                            display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n                            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE);\n                            display_table_inventories(win, a3, NULL, -1);\n                        }\n                    }\n\n                    keyCode = -1;\n                } else if (keyCode >= 2400 && keyCode <= 2400 + inven_cur_disp) {\n                    if (immode == INVENTORY_WINDOW_CURSOR_ARROW) {\n                        inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_TRADE);\n                        display_table_inventories(win, NULL, a4, -1);\n                    } else {\n                        int v45 = keyCode - 2400;\n                        if (v45 < btable_pud->length) {\n                            InventoryItem* inventoryItem = &(btable_pud->items[btable_pud->length - (v45 + btable_offset + 1)]);\n                            barter_move_from_table_inventory(inventoryItem->item, inventoryItem->quantity, v45, a2, a4, false);\n                            display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE);\n                            display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE);\n                            display_table_inventories(win, NULL, a4, -1);\n                        }\n                    }\n\n                    keyCode = -1;\n                }\n            }\n        }\n    }\n\n    item_move_all(a1a, a2);\n    obj_erase_object(a1a, NULL);\n\n    if (armor != NULL) {\n        armor->flags |= OBJECT_WORN;\n        item_add_force(a2, armor, 1);\n    }\n\n    if (item2 != NULL) {\n        item2->flags |= OBJECT_IN_RIGHT_HAND;\n        item_add_force(a2, item2, 1);\n    }\n\n    if (item1 != NULL) {\n        item_add_force(a2, item1, 1);\n    }\n\n    exit_inventory(isoWasEnabled);\n\n    // NOTE: Uninline.\n    inven_exit();\n}\n\n// 0x47620C\nvoid container_enter(int keyCode, int inventoryWindowType)\n{\n    if (keyCode >= 2000) {\n        int index = target_pud->length - (target_stack_offset[target_curr_stack] + keyCode - 2000 + 1);\n        if (index < target_pud->length && target_curr_stack < 9) {\n            InventoryItem* inventoryItem = &(target_pud->items[index]);\n            Object* item = inventoryItem->item;\n            if (item_get_type(item) == ITEM_TYPE_CONTAINER) {\n                target_curr_stack += 1;\n                target_stack[target_curr_stack] = item;\n                target_stack_offset[target_curr_stack] = 0;\n\n                target_pud = &(item->data.inventory);\n\n                display_body(item->fid, inventoryWindowType);\n                display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, inventoryWindowType);\n                win_draw(i_wid);\n            }\n        }\n    } else {\n        int index = pud->length - (stack_offset[curr_stack] + keyCode - 1000 + 1);\n        if (index < pud->length && curr_stack < 9) {\n            InventoryItem* inventoryItem = &(pud->items[index]);\n            Object* item = inventoryItem->item;\n            if (item_get_type(item) == ITEM_TYPE_CONTAINER) {\n                curr_stack += 1;\n\n                stack[curr_stack] = item;\n                stack_offset[curr_stack] = 0;\n\n                inven_dude = stack[curr_stack];\n                pud = &(item->data.inventory);\n\n                adjust_fid();\n                display_body(-1, inventoryWindowType);\n                display_inventory(stack_offset[curr_stack], -1, inventoryWindowType);\n            }\n        }\n    }\n}\n\n// 0x476394\nvoid container_exit(int keyCode, int inventoryWindowType)\n{\n    if (keyCode == 2500) {\n        if (curr_stack > 0) {\n            curr_stack -= 1;\n            inven_dude = stack[curr_stack];\n            pud = &inven_dude->data.inventory;\n            adjust_fid();\n            display_body(-1, inventoryWindowType);\n            display_inventory(stack_offset[curr_stack], -1, inventoryWindowType);\n        }\n    } else if (keyCode == 2501) {\n        if (target_curr_stack > 0) {\n            target_curr_stack -= 1;\n            Object* v5 = target_stack[target_curr_stack];\n            target_pud = &(v5->data.inventory);\n            display_body(v5->fid, inventoryWindowType);\n            display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, inventoryWindowType);\n            win_draw(i_wid);\n        }\n    }\n}\n\n// 0x476464\nint drop_into_container(Object* a1, Object* a2, int a3, Object** a4, int quantity)\n{\n    int quantityToMove;\n    if (quantity > 1) {\n        quantityToMove = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a2, quantity);\n    } else {\n        quantityToMove = 1;\n    }\n\n    if (quantityToMove == -1) {\n        return -1;\n    }\n\n    if (a3 != -1) {\n        if (item_remove_mult(inven_dude, a2, quantityToMove) == -1) {\n            return -1;\n        }\n    }\n\n    int rc = item_add_mult(a1, a2, quantityToMove);\n    if (rc != 0) {\n        if (a3 != -1) {\n            item_add_mult(inven_dude, a2, quantityToMove);\n        }\n    } else {\n        if (a4 != NULL) {\n            if (a4 == &i_worn) {\n                adjust_ac(stack[0], i_worn, NULL);\n            }\n            *a4 = NULL;\n        }\n    }\n\n    return rc;\n}\n\n// 0x47650C\nint drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** a3, int quantity, int keyCode)\n{\n    if (item_get_type(weapon) != ITEM_TYPE_WEAPON) {\n        return -1;\n    }\n\n    if (item_get_type(ammo) != ITEM_TYPE_AMMO) {\n        return -1;\n    }\n\n    if (!item_w_can_reload(weapon, ammo)) {\n        return -1;\n    }\n\n    int quantityToMove;\n    if (quantity > 1) {\n        quantityToMove = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, ammo, quantity);\n    } else {\n        quantityToMove = 1;\n    }\n\n    if (quantityToMove == -1) {\n        return -1;\n    }\n\n    Object* v14 = ammo;\n    bool v17 = false;\n    int rc = item_remove_mult(inven_dude, weapon, 1);\n    for (int index = 0; index < quantityToMove; index++) {\n        int v11 = item_w_reload(weapon, v14);\n        if (v11 == 0) {\n            if (a3 != NULL) {\n                *a3 = NULL;\n            }\n\n            obj_destroy(v14);\n\n            v17 = true;\n            if (inven_from_button(keyCode, &v14, NULL, NULL) == 0) {\n                break;\n            }\n        }\n        if (v11 != -1) {\n            v17 = true;\n        }\n        if (v11 != 0) {\n            break;\n        }\n    }\n\n    if (rc != -1) {\n        item_add_force(inven_dude, weapon, 1);\n    }\n\n    if (!v17) {\n        return -1;\n    }\n\n    const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, NULL);\n    gsound_play_sfx_file(sfx);\n\n    return 0;\n}\n\n// 0x47664C\nvoid draw_amount(int value, int inventoryWindowType)\n{\n    // BIGNUM.frm\n    CacheEntry* handle;\n    int fid = art_id(OBJ_TYPE_INTERFACE, 170, 0, 0, 0);\n    unsigned char* data = art_ptr_lock_data(fid, 0, 0, &handle);\n    if (data == NULL) {\n        return;\n    }\n\n    Rect rect;\n\n    int windowWidth = win_width(mt_wid);\n    unsigned char* windowBuffer = win_get_buf(mt_wid);\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) {\n        rect.ulx = 125;\n        rect.uly = 45;\n        rect.lrx = 195;\n        rect.lry = 69;\n\n        int ranks[5];\n        ranks[4] = value % 10;\n        ranks[3] = value / 10 % 10;\n        ranks[2] = value / 100 % 10;\n        ranks[1] = value / 1000 % 10;\n        ranks[0] = value / 10000 % 10;\n\n        windowBuffer += rect.uly * windowWidth + rect.ulx;\n\n        for (int index = 0; index < 5; index++) {\n            unsigned char* src = data + 14 * ranks[index];\n            buf_to_buf(src, 14, 24, 336, windowBuffer, windowWidth);\n            windowBuffer += 14;\n        }\n    } else {\n        rect.ulx = 133;\n        rect.uly = 64;\n        rect.lrx = 189;\n        rect.lry = 88;\n\n        windowBuffer += windowWidth * rect.uly + rect.ulx;\n        buf_to_buf(data + 14 * (value / 60), 14, 24, 336, windowBuffer, windowWidth);\n        buf_to_buf(data + 14 * (value % 60 / 10), 14, 24, 336, windowBuffer + 14 * 2, windowWidth);\n        buf_to_buf(data + 14 * (value % 10), 14, 24, 336, windowBuffer + 14 * 3, windowWidth);\n    }\n\n    art_ptr_unlock(handle);\n    win_draw_rect(mt_wid, &rect);\n}\n\n// 0x47688C\nstatic int do_move_timer(int inventoryWindowType, Object* item, int max)\n{\n    setup_move_timer_win(inventoryWindowType, item);\n\n    int value;\n    int min;\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) {\n        value = 1;\n        if (max > 99999) {\n            max = 99999;\n        }\n        min = 1;\n    } else {\n        value = 60;\n        min = 10;\n    }\n\n    draw_amount(value, inventoryWindowType);\n\n    bool v5 = false;\n    for (;;) {\n        int keyCode = get_input();\n        if (keyCode == KEY_ESCAPE) {\n            exit_move_timer_win(inventoryWindowType);\n            return -1;\n        }\n\n        if (keyCode == KEY_RETURN) {\n            if (value >= min && value <= max) {\n                if (inventoryWindowType != INVENTORY_WINDOW_TYPE_SET_TIMER || value % 10 == 0) {\n                    gsound_play_sfx_file(\"ib1p1xx1\");\n                    break;\n                }\n            }\n\n            gsound_play_sfx_file(\"iisxxxx1\");\n        } else if (keyCode == 5000) {\n            v5 = false;\n            value = max;\n            draw_amount(value, inventoryWindowType);\n        } else if (keyCode == 6000) {\n            v5 = false;\n            if (value < max) {\n                if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) {\n                    if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) {\n                        get_time();\n\n                        unsigned int delay = 100;\n                        while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) {\n                            if (value < max) {\n                                value++;\n                            }\n\n                            draw_amount(value, inventoryWindowType);\n                            get_input();\n\n                            if (delay > 1) {\n                                delay--;\n                                pause_for_tocks(delay);\n                            }\n                        }\n                    } else {\n                        if (value < max) {\n                            value++;\n                        }\n                    }\n                } else {\n                    value += 10;\n                }\n\n                draw_amount(value, inventoryWindowType);\n                continue;\n            }\n        } else if (keyCode == 7000) {\n            v5 = false;\n            if (value > min) {\n                if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) {\n                    if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) {\n                        get_time();\n\n                        unsigned int delay = 100;\n                        while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) {\n                            if (value > min) {\n                                value--;\n                            }\n\n                            draw_amount(value, inventoryWindowType);\n                            get_input();\n\n                            if (delay > 1) {\n                                delay--;\n                                pause_for_tocks(delay);\n                            }\n                        }\n                    } else {\n                        if (value > min) {\n                            value--;\n                        }\n                    }\n                } else {\n                    value -= 10;\n                }\n\n                draw_amount(value, inventoryWindowType);\n                continue;\n            }\n        }\n\n        if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) {\n            if (keyCode >= KEY_0 && keyCode <= KEY_9) {\n                int number = keyCode - KEY_0;\n                if (!v5) {\n                    value = 0;\n                }\n\n                value = 10 * value % 100000 + number;\n                v5 = true;\n\n                draw_amount(value, inventoryWindowType);\n                continue;\n            } else if (keyCode == KEY_BACKSPACE) {\n                if (!v5) {\n                    value = 0;\n                }\n\n                value /= 10;\n                v5 = true;\n\n                draw_amount(value, inventoryWindowType);\n                continue;\n            }\n        }\n    }\n\n    exit_move_timer_win(inventoryWindowType);\n\n    return value;\n}\n\n// Creates move items/set timer interface.\n//\n// 0x476AB8\nstatic int setup_move_timer_win(int inventoryWindowType, Object* item)\n{\n    const int oldFont = text_curr();\n    text_font(103);\n\n    for (int index = 0; index < 8; index++) {\n        mt_key[index] = NULL;\n    }\n\n    InventoryWindowDescription* windowDescription = &(iscr_data[inventoryWindowType]);\n\n    int quantityWindowX = windowDescription->x;\n    int quantityWindowY = windowDescription->y;\n    mt_wid = win_add(quantityWindowX, quantityWindowY, windowDescription->width, windowDescription->height, 257, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    unsigned char* windowBuffer = win_get_buf(mt_wid);\n\n    CacheEntry* backgroundHandle;\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, windowDescription->field_0, 0, 0, 0);\n    unsigned char* backgroundData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundHandle);\n    if (backgroundData != NULL) {\n        buf_to_buf(backgroundData, windowDescription->width, windowDescription->height, windowDescription->width, windowBuffer, windowDescription->width);\n        art_ptr_unlock(backgroundHandle);\n    }\n\n    MessageListItem messageListItem;\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) {\n        // MOVE ITEMS\n        messageListItem.num = 21;\n        if (message_search(&inventry_message_file, &messageListItem)) {\n            int length = text_width(messageListItem.text);\n            text_to_buf(windowBuffer + windowDescription->width * 9 + (windowDescription->width - length) / 2, messageListItem.text, 200, windowDescription->width, colorTable[21091]);\n        }\n    } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_SET_TIMER) {\n        // SET TIMER\n        messageListItem.num = 23;\n        if (message_search(&inventry_message_file, &messageListItem)) {\n            int length = text_width(messageListItem.text);\n            text_to_buf(windowBuffer + windowDescription->width * 9 + (windowDescription->width - length) / 2, messageListItem.text, 200, windowDescription->width, colorTable[21091]);\n        }\n\n        // Timer overlay\n        CacheEntry* overlayFrmHandle;\n        int overlayFid = art_id(OBJ_TYPE_INTERFACE, 306, 0, 0, 0);\n        unsigned char* overlayFrmData = art_ptr_lock_data(overlayFid, 0, 0, &overlayFrmHandle);\n        if (overlayFrmData != NULL) {\n            buf_to_buf(overlayFrmData, 105, 81, 105, windowBuffer + 34 * windowDescription->width + 113, windowDescription->width);\n            art_ptr_unlock(overlayFrmHandle);\n        }\n    }\n\n    int inventoryFid = item_inv_fid(item);\n    scale_art(inventoryFid, windowBuffer + windowDescription->width * 46 + 16, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, windowDescription->width);\n\n    int x;\n    int y;\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) {\n        x = 200;\n        y = 46;\n    } else {\n        x = 194;\n        y = 64;\n    }\n\n    int fid;\n    unsigned char* buttonUpData;\n    unsigned char* buttonDownData;\n    int btn;\n\n    // Plus button\n    fid = art_id(OBJ_TYPE_INTERFACE, 193, 0, 0, 0);\n    buttonUpData = art_ptr_lock_data(fid, 0, 0, &(mt_key[0]));\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 194, 0, 0, 0);\n    buttonDownData = art_ptr_lock_data(fid, 0, 0, &(mt_key[1]));\n\n    if (buttonUpData != NULL && buttonDownData != NULL) {\n        btn = win_register_button(mt_wid, x, y, 16, 12, -1, -1, 6000, -1, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT);\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n        }\n    }\n\n    // Minus button\n    fid = art_id(OBJ_TYPE_INTERFACE, 191, 0, 0, 0);\n    buttonUpData = art_ptr_lock_data(fid, 0, 0, &(mt_key[2]));\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 192, 0, 0, 0);\n    buttonDownData = art_ptr_lock_data(fid, 0, 0, &(mt_key[3]));\n\n    if (buttonUpData != NULL && buttonDownData != NULL) {\n        btn = win_register_button(mt_wid, x, y + 12, 17, 12, -1, -1, 7000, -1, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT);\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n        }\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n    buttonUpData = art_ptr_lock_data(fid, 0, 0, &(mt_key[4]));\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n    buttonDownData = art_ptr_lock_data(fid, 0, 0, &(mt_key[5]));\n\n    if (buttonUpData != NULL && buttonDownData != NULL) {\n        // Done\n        btn = win_register_button(mt_wid, 98, 128, 15, 16, -1, -1, -1, KEY_RETURN, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT);\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n        }\n\n        // Cancel\n        btn = win_register_button(mt_wid, 148, 128, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT);\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n        }\n    }\n\n    if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) {\n        fid = art_id(OBJ_TYPE_INTERFACE, 307, 0, 0, 0);\n        buttonUpData = art_ptr_lock_data(fid, 0, 0, &(mt_key[6]));\n\n        fid = art_id(OBJ_TYPE_INTERFACE, 308, 0, 0, 0);\n        buttonDownData = art_ptr_lock_data(fid, 0, 0, &(mt_key[7]));\n\n        if (buttonUpData != NULL && buttonDownData != NULL) {\n            // ALL\n            messageListItem.num = 22;\n            if (message_search(&inventry_message_file, &messageListItem)) {\n                int length = text_width(messageListItem.text);\n\n                // TODO: Where is y? Is it hardcoded in to 376?\n                text_to_buf(buttonUpData + (94 - length) / 2 + 376, messageListItem.text, 200, 94, colorTable[21091]);\n                text_to_buf(buttonDownData + (94 - length) / 2 + 376, messageListItem.text, 200, 94, colorTable[18977]);\n\n                btn = win_register_button(mt_wid, 120, 80, 94, 33, -1, -1, -1, 5000, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT);\n                if (btn != -1) {\n                    win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n                }\n            }\n        }\n    }\n\n    win_draw(mt_wid);\n    inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW);\n    text_font(oldFont);\n\n    return 0;\n}\n\n// 0x477030\nstatic int exit_move_timer_win(int inventoryWindowType)\n{\n    int count = inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS ? 8 : 6;\n\n    for (int index = 0; index < count; index++) {\n        art_ptr_unlock(mt_key[index]);\n    }\n\n    win_delete(mt_wid);\n\n    return 0;\n}\n\n// 0x477074\nint inven_set_timer(Object* a1)\n{\n    bool v1 = inven_is_initialized;\n\n    if (!v1) {\n        if (inven_init() == -1) {\n            return -1;\n        }\n    }\n\n    int seconds = do_move_timer(INVENTORY_WINDOW_TYPE_SET_TIMER, a1, 180);\n\n    if (!v1) {\n        // NOTE: Uninline.\n        inven_exit();\n    }\n\n    return seconds;\n}\n"
  },
  {
    "path": "src/game/inventry.h",
    "content": "#ifndef FALLOUT_GAME_INVENTRY_H_\n#define FALLOUT_GAME_INVENTRY_H_\n\n#include <stdbool.h>\n\n#include \"game/art.h\"\n#include \"game/object_types.h\"\n\n// TODO: Convert to enum.\n#define OFF_59E7BC_COUNT 12\n\ntypedef enum InventoryWindowCursor {\n    INVENTORY_WINDOW_CURSOR_HAND,\n    INVENTORY_WINDOW_CURSOR_ARROW,\n    INVENTORY_WINDOW_CURSOR_PICK,\n    INVENTORY_WINDOW_CURSOR_MENU,\n    INVENTORY_WINDOW_CURSOR_BLANK,\n    INVENTORY_WINDOW_CURSOR_COUNT,\n} InventoryWindowCursor;\n\ntypedef enum InventoryWindowType {\n    // Normal inventory window with quick character sheet.\n    INVENTORY_WINDOW_TYPE_NORMAL,\n\n    // Narrow inventory window with just an item scroller that's shown when\n    // a \"Use item on\" is selected from context menu.\n    INVENTORY_WINDOW_TYPE_USE_ITEM_ON,\n\n    // Looting/strealing interface.\n    INVENTORY_WINDOW_TYPE_LOOT,\n\n    // Barter interface.\n    INVENTORY_WINDOW_TYPE_TRADE,\n\n    // Supplementary \"Move items\" window. Used to set quantity of items when\n    // moving items between inventories.\n    INVENTORY_WINDOW_TYPE_MOVE_ITEMS,\n\n    // Supplementary \"Set timer\" window. Internally it's implemented as \"Move\n    // items\" window but with timer overlay and slightly different adjustment\n    // mechanics.\n    INVENTORY_WINDOW_TYPE_SET_TIMER,\n\n    INVENTORY_WINDOW_TYPE_COUNT,\n} InventoryWindowType;\n\nextern CacheEntry* ikey[OFF_59E7BC_COUNT];\n\nvoid inven_set_dude(Object* obj, int pid);\nvoid inven_reset_dude();\nvoid handle_inventory();\nbool setup_inventory(int inventoryWindowType);\nvoid exit_inventory(bool a1);\nvoid display_inventory(int a1, int a2, int inventoryWindowType);\nvoid display_target_inventory(int a1, int a2, Inventory* a3, int a4);\nvoid display_body(int fid, int inventoryWindowType);\nint inven_init();\nvoid inven_exit();\nvoid inven_set_mouse(int cursor);\nvoid inven_hover_on(int btn, int keyCode);\nvoid inven_hover_off(int btn, int keyCode);\nvoid inven_pickup(int keyCode, int a2);\nvoid switch_hand(Object* a1, Object** a2, Object** a3, int a4);\nvoid adjust_ac(Object* critter, Object* oldArmor, Object* newArmor);\nvoid adjust_fid();\nvoid use_inventory_on(Object* a1);\nObject* inven_right_hand(Object* obj);\nObject* inven_left_hand(Object* obj);\nObject* inven_worn(Object* obj);\nObject* inven_pid_is_carried(Object* obj, int pid);\nint inven_pid_quantity_carried(Object* obj, int pid);\nvoid display_stats();\nObject* inven_find_type(Object* obj, int a2, int* inout_a3);\nObject* inven_find_id(Object* obj, int a2);\nObject* inven_index_ptr(Object* obj, int a2);\nint inven_wield(Object* a1, Object* a2, int a3);\nint invenWieldFunc(Object* a1, Object* a2, int a3, bool a4);\nint inven_unwield(Object* critter_obj, int a2);\nint invenUnwieldFunc(Object* obj, int a2, int a3);\nint inven_from_button(int a1, Object** a2, Object*** a3, Object** a4);\nvoid inven_display_msg(char* string);\nvoid inven_obj_examine_func(Object* critter, Object* item);\nvoid inven_action_cursor(int eventCode, int inventoryWindowType);\nint loot_container(Object* a1, Object* a2);\nint inven_steal_container(Object* a1, Object* a2);\nint move_inventory(Object* a1, int a2, Object* a3, bool a4);\nvoid barter_inventory(int win, Object* a2, Object* a3, Object* a4, int a5);\nvoid container_enter(int a1, int a2);\nvoid container_exit(int keyCode, int inventoryWindowType);\nint drop_into_container(Object* a1, Object* a2, int a3, Object** a4, int quantity);\nint drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** a3, int quantity, int keyCode);\nvoid draw_amount(int value, int inventoryWindowType);\nint inven_set_timer(Object* a1);\n\n#endif /* FALLOUT_GAME_INVENTRY_H_ */\n"
  },
  {
    "path": "src/game/item.c",
    "content": "#include \"game/item.h\"\n\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"game/automap.h\"\n#include \"game/combat.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/intface.h\"\n#include \"game/inventry.h\"\n#include \"game/light.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n\nstatic void item_compact(int inventoryItemIndex, Inventory* inventory);\nstatic int item_move_func(Object* a1, Object* a2, Object* a3, int quantity, bool a5);\nstatic bool item_identical(Object* a1, Object* a2);\nstatic int item_m_stealth_effect_on(Object* object);\nstatic int item_m_stealth_effect_off(Object* critter, Object* item);\nstatic int insert_drug_effect(Object* critter_obj, Object* item_obj, int a3, int* stats, int* mods);\nstatic void perform_drug_effect(Object* critter_obj, int* stats, int* mods, bool is_immediate);\nstatic bool drug_effect_allowed(Object* critter, int pid);\nstatic int insert_withdrawal(Object* obj, int a2, int a3, int a4, int a5);\nstatic int item_wd_clear_all(Object* a1, void* data);\nstatic void perform_withdrawal_start(Object* obj, int perk, int a3);\nstatic void perform_withdrawal_end(Object* obj, int a2);\nstatic int pid_to_gvar(int drugPid);\n\n// TODO: Remove.\n// 0x509FFC\nchar _aItem_1[] = \"<item>\";\n\n// Maps weapon extended flags to skill.\n//\n// 0x519160\nstatic int attack_skill[9] = {\n    -1,\n    SKILL_UNARMED,\n    SKILL_UNARMED,\n    SKILL_MELEE_WEAPONS,\n    SKILL_MELEE_WEAPONS,\n    SKILL_THROWING,\n    SKILL_SMALL_GUNS,\n    SKILL_SMALL_GUNS,\n    SKILL_SMALL_GUNS,\n};\n\n// A map of item's extendedFlags to animation.\n//\n// 0x519184\nstatic int attack_anim[9] = {\n    ANIM_STAND,\n    ANIM_THROW_PUNCH,\n    ANIM_KICK_LEG,\n    ANIM_SWING_ANIM,\n    ANIM_THRUST_ANIM,\n    ANIM_THROW_ANIM,\n    ANIM_FIRE_SINGLE,\n    ANIM_FIRE_BURST,\n    ANIM_FIRE_CONTINUOUS,\n};\n\n// Maps weapon extended flags to weapon class\n//\n// 0x5191A8\nstatic int attack_subtype[9] = {\n    ATTACK_TYPE_NONE, // 0 // None\n    ATTACK_TYPE_UNARMED, // 1 // Punch // Brass Knuckles, Power First\n    ATTACK_TYPE_UNARMED, // 2 // Kick?\n    ATTACK_TYPE_MELEE, // 3 // Swing //  Sledgehammer (prim), Club, Knife (prim), Spear (prim), Crowbar\n    ATTACK_TYPE_MELEE, // 4 // Thrust // Sledgehammer (sec), Knife (sec), Spear (sec)\n    ATTACK_TYPE_THROW, // 5 // Throw // Rock,\n    ATTACK_TYPE_RANGED, // 6 // Single // 10mm SMG (prim), Rocket Launcher, Hunting Rifle, Plasma Rifle, Laser Pistol\n    ATTACK_TYPE_RANGED, // 7 // Burst // 10mm SMG (sec), Minigun\n    ATTACK_TYPE_RANGED, // 8 // Continous // Only: Flamer, Improved Flamer, Flame Breath\n};\n\n// 0x5191CC\nDrugDescription drugInfoList[ADDICTION_COUNT] = {\n    { PROTO_ID_NUKA_COLA, GVAR_NUKA_COLA_ADDICT, 0 },\n    { PROTO_ID_BUFF_OUT, GVAR_BUFF_OUT_ADDICT, 4 },\n    { PROTO_ID_MENTATS, GVAR_MENTATS_ADDICT, 4 },\n    { PROTO_ID_PSYCHO, GVAR_PSYCHO_ADDICT, 4 },\n    { PROTO_ID_RADAWAY, GVAR_RADAWAY_ADDICT, 0 },\n    { PROTO_ID_BEER, GVAR_ALCOHOL_ADDICT, 0 },\n    { PROTO_ID_BOOZE, GVAR_ALCOHOL_ADDICT, 0 },\n    { PROTO_ID_JET, GVAR_ADDICT_JET, 4 },\n    { PROTO_ID_DECK_OF_TRAGIC_CARDS, GVAR_ADDICT_TRAGIC, 0 },\n};\n\n// item.msg\n//\n// 0x59E980\nstatic MessageList item_message_file;\n\n// 0x59E988\nstatic int wd_onset;\n\n// 0x59E98C\nstatic Object* wd_obj;\n\n// 0x59E990\nstatic int wd_gvar;\n\n// 0x4770E0\nint item_init()\n{\n    if (!message_init(&item_message_file)) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"item.msg\");\n\n    if (!message_load(&item_message_file, path)) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x477144\nvoid item_reset()\n{\n    return;\n}\n\n// 0x477148\nvoid item_exit()\n{\n    message_exit(&item_message_file);\n}\n\n// NOTE: Uncollapsed 0x477154.\nint item_load(File* stream)\n{\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x477154.\nint item_save(File* stream)\n{\n    return 0;\n}\n\n// 0x477158\nint item_add_mult(Object* owner, Object* itemToAdd, int quantity)\n{\n    if (quantity < 1) {\n        return -1;\n    }\n\n    int parentType = FID_TYPE(owner->fid);\n    if (parentType == OBJ_TYPE_ITEM) {\n        int itemType = item_get_type(owner);\n        if (itemType == ITEM_TYPE_CONTAINER) {\n            // NOTE: Uninline.\n            int sizeToAdd = item_size(itemToAdd);\n            sizeToAdd *= quantity;\n\n            int currentSize = item_c_curr_size(owner);\n            int maxSize = item_c_max_size(owner);\n            if (currentSize + sizeToAdd >= maxSize) {\n                return -6;\n            }\n\n            Object* containerOwner = obj_top_environment(owner);\n            if (containerOwner != NULL) {\n                if (FID_TYPE(containerOwner->fid) == OBJ_TYPE_CRITTER) {\n                    int weightToAdd = item_weight(itemToAdd);\n                    weightToAdd *= quantity;\n\n                    int currentWeight = item_total_weight(containerOwner);\n                    int maxWeight = critterGetStat(containerOwner, STAT_CARRY_WEIGHT);\n                    if (currentWeight + weightToAdd > maxWeight) {\n                        return -6;\n                    }\n                }\n            }\n        } else if (itemType == ITEM_TYPE_MISC) {\n            // NOTE: Uninline.\n            int powerTypePid = item_m_cell_pid(owner);\n            if (powerTypePid != itemToAdd->pid) {\n                return -1;\n            }\n        } else {\n            return -1;\n        }\n    } else if (parentType == OBJ_TYPE_CRITTER) {\n        if (critter_body_type(owner) != BODY_TYPE_BIPED) {\n            return -5;\n        }\n\n        int weightToAdd = item_weight(itemToAdd);\n        weightToAdd *= quantity;\n\n        int currentWeight = item_total_weight(owner);\n        int maxWeight = critterGetStat(owner, STAT_CARRY_WEIGHT);\n        if (currentWeight + weightToAdd > maxWeight) {\n            return -6;\n        }\n    }\n\n    return item_add_force(owner, itemToAdd, quantity);\n}\n\n// item_add\n// 0x4772B8\nint item_add_force(Object* owner, Object* itemToAdd, int quantity)\n{\n    if (quantity < 1) {\n        return -1;\n    }\n\n    Inventory* inventory = &(owner->data.inventory);\n\n    int index;\n    for (index = 0; index < inventory->length; index++) {\n        if (item_identical(inventory->items[index].item, itemToAdd) != 0) {\n            break;\n        }\n    }\n\n    if (index == inventory->length) {\n        if (inventory->length == inventory->capacity || inventory->items == NULL) {\n            InventoryItem* inventoryItems = (InventoryItem*)mem_realloc(inventory->items, sizeof(InventoryItem) * (inventory->capacity + 10));\n            if (inventoryItems == NULL) {\n                return -1;\n            }\n\n            inventory->items = inventoryItems;\n            inventory->capacity += 10;\n        }\n\n        inventory->items[inventory->length].item = itemToAdd;\n        inventory->items[inventory->length].quantity = quantity;\n\n        if (itemToAdd->pid == PROTO_ID_STEALTH_BOY_II) {\n            if ((itemToAdd->flags & OBJECT_IN_ANY_HAND) != 0) {\n                // NOTE: Uninline.\n                item_m_stealth_effect_on(owner);\n            }\n        }\n\n        inventory->length++;\n        itemToAdd->owner = owner;\n\n        return 0;\n    }\n\n    if (itemToAdd == inventory->items[index].item) {\n        debug_printf(\"Warning! Attempt to add same item twice in item_add()\\n\");\n        return 0;\n    }\n\n    if (item_get_type(itemToAdd) == ITEM_TYPE_AMMO) {\n        // NOTE: Uninline.\n        int ammoQuantityToAdd = item_w_curr_ammo(itemToAdd);\n\n        int ammoQuantity = item_w_curr_ammo(inventory->items[index].item);\n\n        // NOTE: Uninline.\n        int capacity = item_w_max_ammo(itemToAdd);\n\n        ammoQuantity += ammoQuantityToAdd;\n        if (ammoQuantity > capacity) {\n            item_w_set_curr_ammo(itemToAdd, ammoQuantity - capacity);\n            inventory->items[index].quantity++;\n        } else {\n            item_w_set_curr_ammo(itemToAdd, ammoQuantity);\n        }\n\n        inventory->items[index].quantity += quantity - 1;\n    } else {\n        inventory->items[index].quantity += quantity;\n    }\n\n    obj_erase_object(inventory->items[index].item, NULL);\n    inventory->items[index].item = itemToAdd;\n    itemToAdd->owner = owner;\n\n    return 0;\n}\n\n// 0x477490\nint item_remove_mult(Object* owner, Object* itemToRemove, int quantity)\n{\n    Inventory* inventory = &(owner->data.inventory);\n    Object* item1 = inven_left_hand(owner);\n    Object* item2 = inven_right_hand(owner);\n\n    int index = 0;\n    for (; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        if (inventoryItem->item == itemToRemove) {\n            break;\n        }\n\n        if (item_get_type(inventoryItem->item) == ITEM_TYPE_CONTAINER) {\n            if (item_remove_mult(inventoryItem->item, itemToRemove, quantity) == 0) {\n                return 0;\n            }\n        }\n    }\n\n    if (index == inventory->length) {\n        return -1;\n    }\n\n    InventoryItem* inventoryItem = &(inventory->items[index]);\n    if (inventoryItem->quantity < quantity) {\n        return -1;\n    }\n\n    if (inventoryItem->quantity == quantity) {\n        // NOTE: Uninline.\n        item_compact(index, inventory);\n    } else {\n        // TODO: Not sure about this line.\n        if (obj_copy(&(inventoryItem->item), itemToRemove) == -1) {\n            return -1;\n        }\n\n        obj_disconnect(inventoryItem->item, NULL);\n\n        inventoryItem->quantity -= quantity;\n\n        if (item_get_type(itemToRemove) == ITEM_TYPE_AMMO) {\n            int capacity = item_w_max_ammo(itemToRemove);\n            item_w_set_curr_ammo(inventoryItem->item, capacity);\n        }\n    }\n\n    if (itemToRemove->pid == PROTO_ID_STEALTH_BOY_I || itemToRemove->pid == PROTO_ID_STEALTH_BOY_II) {\n        if (itemToRemove == item1 || itemToRemove == item2) {\n            Object* owner = obj_top_environment(itemToRemove);\n            if (owner != NULL) {\n                item_m_stealth_effect_off(owner, itemToRemove);\n            }\n        }\n    }\n\n    itemToRemove->owner = NULL;\n    itemToRemove->flags &= ~OBJECT_EQUIPPED;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4775D8\nstatic void item_compact(int inventoryItemIndex, Inventory* inventory)\n{\n    for (int index = inventoryItemIndex + 1; index < inventory->length; index++) {\n        InventoryItem* prev = &(inventory->items[index - 1]);\n        InventoryItem* curr = &(inventory->items[index]);\n        static_assert(sizeof(*prev) == sizeof(*curr), \"wrong size\");\n        memcpy(prev, curr, sizeof(*prev));\n    }\n    inventory->length--;\n}\n\n// 0x477608\nstatic int item_move_func(Object* a1, Object* a2, Object* a3, int quantity, bool a5)\n{\n    if (item_remove_mult(a1, a3, quantity) == -1) {\n        return -1;\n    }\n\n    int rc;\n    if (a5) {\n        rc = item_add_force(a2, a3, quantity);\n    } else {\n        rc = item_add_mult(a2, a3, quantity);\n    }\n\n    if (rc != 0) {\n        if (item_add_force(a1, a3, quantity) != 0) {\n            Object* owner = obj_top_environment(a1);\n            if (owner == NULL) {\n                owner = a1;\n            }\n\n            if (owner->tile != -1) {\n                Rect updatedRect;\n                obj_connect(a3, owner->tile, owner->elevation, &updatedRect);\n                tile_refresh_rect(&updatedRect, map_elevation);\n            }\n        }\n        return -1;\n    }\n\n    a3->owner = a2;\n\n    return 0;\n}\n\n// 0x47769C\nint item_move(Object* a1, Object* a2, Object* a3, int quantity)\n{\n    return item_move_func(a1, a2, a3, quantity, false);\n}\n\n// 0x4776A4\nint item_move_force(Object* a1, Object* a2, Object* a3, int quantity)\n{\n    return item_move_func(a1, a2, a3, quantity, true);\n}\n\n// 0x4776AC\nvoid item_move_all(Object* a1, Object* a2)\n{\n    Inventory* inventory = &(a1->data.inventory);\n    while (inventory->length > 0) {\n        InventoryItem* inventoryItem = &(inventory->items[0]);\n        item_move_func(a1, a2, inventoryItem->item, inventoryItem->quantity, true);\n    }\n}\n\n// 0x4776E0\nint item_move_all_hidden(Object* a1, Object* a2)\n{\n    Inventory* inventory = &(a1->data.inventory);\n    // TODO: Not sure about two loops.\n    for (int i = 0; i < inventory->length;) {\n        for (int j = i; j < inventory->length;) {\n            bool v5;\n            InventoryItem* inventoryItem = &(inventory->items[j]);\n            if (PID_TYPE(inventoryItem->item->pid) == OBJ_TYPE_ITEM) {\n                Proto* proto;\n                if (proto_ptr(inventoryItem->item->pid, &proto) != -1) {\n                    v5 = (proto->item.extendedFlags & ItemProtoExtendedFlags_NaturalWeapon) == 0;\n                } else {\n                    v5 = true;\n                }\n            } else {\n                v5 = true;\n            }\n\n            if (!v5) {\n                item_move_func(a1, a2, inventoryItem->item, inventoryItem->quantity, true);\n            } else {\n                i++;\n                j++;\n            }\n        }\n    }\n    return 0;\n}\n\n// 0x477770\nint item_destroy_all_hidden(Object* a1)\n{\n    Inventory* inventory = &(a1->data.inventory);\n    // TODO: Not sure about this one. Why two loops?\n    for (int i = 0; i < inventory->length;) {\n        // TODO: Probably wrong, something with two loops.\n        for (int j = i; j < inventory->length;) {\n            bool v5;\n            InventoryItem* inventoryItem = &(inventory->items[j]);\n            if (PID_TYPE(inventoryItem->item->pid) == OBJ_TYPE_ITEM) {\n                Proto* proto;\n                if (proto_ptr(inventoryItem->item->pid, &proto) != -1) {\n                    v5 = (proto->item.extendedFlags & ItemProtoExtendedFlags_NaturalWeapon) == 0;\n                } else {\n                    v5 = true;\n                }\n            } else {\n                v5 = true;\n            }\n\n            if (!v5) {\n                item_remove_mult(a1, inventoryItem->item, 1);\n                obj_destroy(inventoryItem->item);\n            } else {\n                i++;\n                j++;\n            }\n        }\n    }\n    return 0;\n}\n\n// 0x477804\nint item_drop_all(Object* critter, int tile)\n{\n    bool hasEquippedItems = false;\n\n    int frmId = critter->fid & 0xFFF;\n\n    Inventory* inventory = &(critter->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        Object* item = inventoryItem->item;\n        if (item->pid == PROTO_ID_MONEY) {\n            if (item_remove_mult(critter, item, inventoryItem->quantity) != 0) {\n                return -1;\n            }\n\n            if (obj_connect(item, tile, critter->elevation, NULL) != 0) {\n                if (item_add_force(critter, item, 1) != 0) {\n                    obj_destroy(item);\n                }\n                return -1;\n            }\n\n            item->data.item.misc.charges = inventoryItem->quantity;\n        } else {\n            if ((item->flags & OBJECT_EQUIPPED) != 0) {\n                hasEquippedItems = true;\n\n                if ((item->flags & OBJECT_WORN) != 0) {\n                    Proto* proto;\n                    if (proto_ptr(critter->pid, &proto) == -1) {\n                        return -1;\n                    }\n\n                    frmId = proto->fid & 0xFFF;\n                    adjust_ac(critter, item, NULL);\n                }\n            }\n\n            for (int index = 0; index < inventoryItem->quantity; index++) {\n                if (item_remove_mult(critter, item, 1) != 0) {\n                    return -1;\n                }\n\n                if (obj_connect(item, tile, critter->elevation, NULL) != 0) {\n                    if (item_add_force(critter, item, 1) != 0) {\n                        obj_destroy(item);\n                    }\n                    return -1;\n                }\n            }\n        }\n    }\n\n    if (hasEquippedItems) {\n        Rect updatedRect;\n        int fid = art_id(OBJ_TYPE_CRITTER, frmId, FID_ANIM_TYPE(critter->fid), 0, (critter->fid & 0x70000000) >> 28);\n        obj_change_fid(critter, fid, &updatedRect);\n        if (FID_ANIM_TYPE(critter->fid) == ANIM_STAND) {\n            tile_refresh_rect(&updatedRect, map_elevation);\n        }\n    }\n\n    return -1;\n}\n\n// 0x4779F0\nstatic bool item_identical(Object* a1, Object* a2)\n{\n    if (a1->pid != a2->pid) {\n        return false;\n    }\n\n    if (a1->sid != a2->sid) {\n        return false;\n    }\n\n    if ((a1->flags & (OBJECT_EQUIPPED | OBJECT_USED)) != 0) {\n        return false;\n    }\n\n    if ((a2->flags & (OBJECT_EQUIPPED | OBJECT_USED)) != 0) {\n        return false;\n    }\n\n    Proto* proto;\n    proto_ptr(a1->pid, &proto);\n    if (proto->item.type == ITEM_TYPE_CONTAINER) {\n        return false;\n    }\n\n    Inventory* inventory1 = &(a1->data.inventory);\n    Inventory* inventory2 = &(a2->data.inventory);\n    if (inventory1->length != 0 || inventory2->length != 0) {\n        return false;\n    }\n\n    int v1;\n    if (proto->item.type == ITEM_TYPE_AMMO || a1->pid == PROTO_ID_MONEY) {\n        v1 = a2->data.item.ammo.quantity;\n        a2->data.item.ammo.quantity = a1->data.item.ammo.quantity;\n    }\n\n    // NOTE: Probably inlined memcmp, but I'm not sure why it only checks 32\n    // bytes.\n    int i;\n    for (i = 0; i < 8; i++) {\n        if (a1->field_2C_array[i] != a2->field_2C_array[i]) {\n            break;\n        }\n    }\n\n    if (proto->item.type == ITEM_TYPE_AMMO || a1->pid == PROTO_ID_MONEY) {\n        a2->data.item.ammo.quantity = v1;\n    }\n\n    return i == 8;\n}\n\n// 0x477AE4\nchar* item_name(Object* obj)\n{\n    // 0x519238\n    static char* name = _aItem_1;\n\n    name = proto_name(obj->pid);\n    return name;\n}\n\n// 0x477AF4\nchar* item_description(Object* obj)\n{\n    return proto_description(obj->pid);\n}\n\n// 0x477AFC\nint item_get_type(Object* item)\n{\n    if (item == NULL) {\n        return ITEM_TYPE_MISC;\n    }\n\n    if (PID_TYPE(item->pid) != OBJ_TYPE_ITEM) {\n        return ITEM_TYPE_MISC;\n    }\n\n    if (item->pid == PROTO_ID_SHIV) {\n        return ITEM_TYPE_MISC;\n    }\n\n    Proto* proto;\n    proto_ptr(item->pid, &proto);\n\n    return proto->item.type;\n}\n\n// NOTE: Unused.\n//\n// 0x477B4C\nint item_material(Object* item)\n{\n    Proto* proto;\n    proto_ptr(item->pid, &proto);\n\n    return proto->item.material;\n}\n\n// 0x477B68\nint item_size(Object* item)\n{\n    if (item == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(item->pid, &proto);\n\n    return proto->item.size;\n}\n\n// 0x477B88\nint item_weight(Object* item)\n{\n    if (item == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(item->pid, &proto);\n    int weight = proto->item.weight;\n\n    // NOTE: Uninline.\n    if (item_is_hidden(item)) {\n        weight = 0;\n    }\n\n    int itemType = proto->item.type;\n    if (itemType == ITEM_TYPE_ARMOR) {\n        switch (proto->pid) {\n        case PROTO_ID_POWER_ARMOR:\n        case PROTO_ID_HARDENED_POWER_ARMOR:\n        case PROTO_ID_ADVANCED_POWER_ARMOR:\n        case PROTO_ID_ADVANCED_POWER_ARMOR_MK_II:\n            weight /= 2;\n            break;\n        }\n    } else if (itemType == ITEM_TYPE_CONTAINER) {\n        weight += item_total_weight(item);\n    } else if (itemType == ITEM_TYPE_WEAPON) {\n        // NOTE: Uninline.\n        int ammoQuantity = item_w_curr_ammo(item);\n        if (ammoQuantity > 0) {\n            // NOTE: Uninline.\n            int ammoTypePid = item_w_ammo_pid(item);\n            if (ammoTypePid != -1) {\n                Proto* ammoProto;\n                if (proto_ptr(ammoTypePid, &ammoProto) != -1) {\n                    weight += ammoProto->item.weight * ((ammoQuantity - 1) / ammoProto->item.data.ammo.quantity + 1);\n                }\n            }\n        }\n    }\n\n    return weight;\n}\n\n// Returns cost of item.\n//\n// When [item] is container the returned cost includes cost of container\n// itself plus cost of contained items.\n//\n// When [item] is a weapon the returned value includes cost of weapon\n// itself plus cost of remaining ammo (see below).\n//\n// When [item] is an ammo it's cost is calculated from ratio of fullness.\n//\n// 0x477CAC\nint item_cost(Object* obj)\n{\n    // TODO: This function needs review. A lot of functionality is inlined.\n    // Find these functions and use them.\n    if (obj == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(obj->pid, &proto);\n\n    int cost = proto->item.cost;\n\n    switch (proto->item.type) {\n    case ITEM_TYPE_CONTAINER:\n        cost += item_total_cost(obj);\n        break;\n    case ITEM_TYPE_WEAPON:\n        if (1) {\n            // NOTE: Uninline.\n            int ammoQuantity = item_w_curr_ammo(obj);\n            if (ammoQuantity > 0) {\n                // NOTE: Uninline.\n                int ammoTypePid = item_w_ammo_pid(obj);\n                if (ammoTypePid != -1) {\n                    Proto* ammoProto;\n                    proto_ptr(ammoTypePid, &ammoProto);\n\n                    cost += ammoQuantity * ammoProto->item.cost / ammoProto->item.data.ammo.quantity;\n                }\n            }\n        }\n        break;\n    case ITEM_TYPE_AMMO:\n        if (1) {\n            // NOTE: Uninline.\n            int ammoQuantity = item_w_curr_ammo(obj);\n            cost *= ammoQuantity;\n            // NOTE: Uninline.\n            int ammoCapacity = item_w_max_ammo(obj);\n            cost /= ammoCapacity;\n        }\n        break;\n    }\n\n    return cost;\n}\n\n// Returns cost of object's items.\n//\n// 0x477DAC\nint item_total_cost(Object* obj)\n{\n    if (obj == NULL) {\n        return 0;\n    }\n\n    int cost = 0;\n\n    Inventory* inventory = &(obj->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        if (item_get_type(inventoryItem->item) == ITEM_TYPE_AMMO) {\n            Proto* proto;\n            proto_ptr(inventoryItem->item->pid, &proto);\n\n            // Ammo stack in inventory is a bit special. It is counted in clips,\n            // `inventoryItem->quantity` is the number of clips. The ammo object\n            // itself tracks remaining number of ammo in only one instance of\n            // the clip implying all other clips in the stack are full.\n            //\n            // In order to correctly calculate cost of the ammo stack, add cost\n            // of all full clips...\n            cost += proto->item.cost * (inventoryItem->quantity - 1);\n\n            // ...and add cost of the current clip, which is proportional to\n            // it's capacity.\n            cost += item_cost(inventoryItem->item);\n        } else {\n            cost += item_cost(inventoryItem->item) * inventoryItem->quantity;\n        }\n    }\n\n    if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) {\n        Object* item2 = inven_right_hand(obj);\n        if (item2 != NULL && (item2->flags & OBJECT_IN_RIGHT_HAND) == 0) {\n            cost += item_cost(item2);\n        }\n\n        Object* item1 = inven_left_hand(obj);\n        if (item1 != NULL && (item1->flags & OBJECT_IN_LEFT_HAND) == 0) {\n            cost += item_cost(item1);\n        }\n\n        Object* armor = inven_worn(obj);\n        if (armor != NULL && (armor->flags & OBJECT_WORN) == 0) {\n            cost += item_cost(armor);\n        }\n    }\n\n    return cost;\n}\n\n// Calculates total weight of the items in inventory.\n//\n// 0x477E98\nint item_total_weight(Object* obj)\n{\n    if (obj == NULL) {\n        return 0;\n    }\n\n    int weight = 0;\n\n    Inventory* inventory = &(obj->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        Object* item = inventoryItem->item;\n        weight += item_weight(item) * inventoryItem->quantity;\n    }\n\n    if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) {\n        Object* item2 = inven_right_hand(obj);\n        if (item2 != NULL) {\n            if ((item2->flags & OBJECT_IN_RIGHT_HAND) == 0) {\n                weight += item_weight(item2);\n            }\n        }\n\n        Object* item1 = inven_left_hand(obj);\n        if (item1 != NULL) {\n            if ((item1->flags & OBJECT_IN_LEFT_HAND) == 0) {\n                weight += item_weight(item1);\n            }\n        }\n\n        Object* armor = inven_worn(obj);\n        if (armor != NULL) {\n            if ((armor->flags & OBJECT_WORN) == 0) {\n                weight += item_weight(armor);\n            }\n        }\n    }\n\n    return weight;\n}\n\n// 0x477F3C\nbool item_grey(Object* weapon)\n{\n    if (weapon == NULL) {\n        return false;\n    }\n\n    if (item_get_type(weapon) != ITEM_TYPE_WEAPON) {\n        return false;\n    }\n\n    int flags = obj_dude->data.critter.combat.results;\n    if ((flags & DAM_CRIP_ARM_LEFT) != 0 && (flags & DAM_CRIP_ARM_RIGHT) != 0) {\n        return true;\n    }\n\n    // NOTE: Uninline.\n    bool isTwoHanded = item_w_is_2handed(weapon);\n    if (isTwoHanded) {\n        if ((flags & DAM_CRIP_ARM_LEFT) != 0 || (flags & DAM_CRIP_ARM_RIGHT) != 0) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x477FB0\nint item_inv_fid(Object* item)\n{\n    Proto* proto;\n\n    if (item == NULL) {\n        return -1;\n    }\n\n    proto_ptr(item->pid, &proto);\n\n    return proto->item.inventoryFid;\n}\n\n// 0x477FF8\nObject* item_hit_with(Object* critter, int hitMode)\n{\n    switch (hitMode) {\n    case HIT_MODE_LEFT_WEAPON_PRIMARY:\n    case HIT_MODE_LEFT_WEAPON_SECONDARY:\n    case HIT_MODE_LEFT_WEAPON_RELOAD:\n        return inven_left_hand(critter);\n    case HIT_MODE_RIGHT_WEAPON_PRIMARY:\n    case HIT_MODE_RIGHT_WEAPON_SECONDARY:\n    case HIT_MODE_RIGHT_WEAPON_RELOAD:\n        return inven_right_hand(critter);\n    }\n\n    return NULL;\n}\n\n// 0x478040\nint item_mp_cost(Object* obj, int hitMode, bool aiming)\n{\n    if (obj == NULL) {\n        return 0;\n    }\n\n    Object* item_obj = item_hit_with(obj, hitMode);\n\n    if (item_obj != NULL && item_get_type(item_obj) != ITEM_TYPE_WEAPON) {\n        return 2;\n    }\n\n    return item_w_mp_cost(obj, hitMode, aiming);\n}\n\n// Returns quantity of [a2] in [obj]s inventory.\n//\n// 0x47808C\nint item_count(Object* obj, Object* a2)\n{\n    int quantity = 0;\n\n    Inventory* inventory = &(obj->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        Object* item = inventoryItem->item;\n        if (item == a2) {\n            quantity = inventoryItem->quantity;\n        } else {\n            if (item_get_type(item) == ITEM_TYPE_CONTAINER) {\n                quantity = item_count(item, a2);\n                if (quantity > 0) {\n                    return quantity;\n                }\n            }\n        }\n    }\n\n    return quantity;\n}\n\n// Returns true if [a1] posesses an item with 0x2000 flag.\n//\n// 0x4780E4\nint item_queued(Object* obj)\n{\n    if (obj == NULL) {\n        return false;\n    }\n\n    if ((obj->flags & OBJECT_USED) != 0) {\n        return true;\n    }\n\n    Inventory* inventory = &(obj->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        if ((inventoryItem->item->flags & OBJECT_USED) != 0) {\n            return true;\n        }\n\n        if (item_get_type(inventoryItem->item) == ITEM_TYPE_CONTAINER) {\n            if (item_queued(inventoryItem->item)) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n// 0x478154\nObject* item_replace(Object* a1, Object* a2, int a3)\n{\n    if (a1 == NULL) {\n        return NULL;\n    }\n\n    if (a2 == NULL) {\n        return NULL;\n    }\n\n    Inventory* inventory = &(a1->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        if (item_identical(inventoryItem->item, a2)) {\n            Object* item = inventoryItem->item;\n            if (item_remove_mult(a1, item, 1) == 0) {\n                item->flags |= a3;\n                if (item_add_force(a1, item, 1) == 0) {\n                    return item;\n                }\n\n                item->flags &= ~a3;\n                if (item_add_force(a1, item, 1) != 0) {\n                    obj_destroy(item);\n                }\n            }\n        }\n\n        if (item_get_type(inventoryItem->item) == ITEM_TYPE_CONTAINER) {\n            Object* obj = item_replace(inventoryItem->item, a2, a3);\n            if (obj != NULL) {\n                return obj;\n            }\n        }\n    }\n\n    return NULL;\n}\n\n// Returns true if [item] is an natural weapon of it's owner.\n//\n// See [ItemProtoExtendedFlags_NaturalWeapon] for more details on natural weapons.\n//\n// 0x478244\nint item_is_hidden(Object* obj)\n{\n    Proto* proto;\n\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_ITEM) {\n        return 0;\n    }\n\n    if (proto_ptr(obj->pid, &proto) == -1) {\n        return 0;\n    }\n\n    return proto->item.extendedFlags & ItemProtoExtendedFlags_NaturalWeapon;\n}\n\n// 0x478280\nint item_w_subtype(Object* weapon, int hitMode)\n{\n    if (weapon == NULL) {\n        return ATTACK_TYPE_UNARMED;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    int index;\n    if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) {\n        index = proto->item.extendedFlags & 0xF;\n    } else {\n        index = (proto->item.extendedFlags & 0xF0) >> 4;\n    }\n\n    return attack_subtype[index];\n}\n\n// 0x4782CC\nint item_w_skill(Object* weapon, int hitMode)\n{\n    if (weapon == NULL) {\n        return SKILL_UNARMED;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    int index;\n    if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) {\n        index = proto->item.extendedFlags & 0xF;\n    } else {\n        index = (proto->item.extendedFlags & 0xF0) >> 4;\n    }\n\n    int skill = attack_skill[index];\n\n    if (skill == SKILL_SMALL_GUNS) {\n        int damageType = item_w_damage_type(NULL, weapon);\n        if (damageType == DAMAGE_TYPE_LASER || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_ELECTRICAL) {\n            skill = SKILL_ENERGY_WEAPONS;\n        } else {\n            if ((proto->item.extendedFlags & ItemProtoExtendedFlags_BigGun) != 0) {\n                skill = SKILL_BIG_GUNS;\n            }\n        }\n    }\n\n    return skill;\n}\n\n// Returns skill value when critter is about to perform hitMode.\n//\n// 0x478370\nint item_w_skill_level(Object* critter, int hitMode)\n{\n    if (critter == NULL) {\n        return 0;\n    }\n\n    int skill;\n\n    // NOTE: Uninline.\n    Object* weapon = item_hit_with(critter, hitMode);\n    if (weapon != NULL) {\n        skill = item_w_skill(weapon, hitMode);\n    } else {\n        skill = SKILL_UNARMED;\n    }\n\n    return skill_level(critter, skill);\n}\n\n// 0x4783B8\nint item_w_damage_min_max(Object* weapon, int* minDamagePtr, int* maxDamagePtr)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    if (minDamagePtr != NULL) {\n        *minDamagePtr = proto->item.data.weapon.minDamage;\n    }\n\n    if (maxDamagePtr != NULL) {\n        *maxDamagePtr = proto->item.data.weapon.maxDamage;\n    }\n\n    return 0;\n}\n\n// 0x478448\nint item_w_damage(Object* critter, int hitMode)\n{\n    if (critter == NULL) {\n        return 0;\n    }\n\n    int minDamage = 0;\n    int maxDamage = 0;\n    int meleeDamage = 0;\n    int unarmedDamage = 0;\n\n    // NOTE: Uninline.\n    Object* weapon = item_hit_with(critter, hitMode);\n\n    if (weapon != NULL) {\n        Proto* proto;\n        proto_ptr(weapon->pid, &proto);\n\n        minDamage = proto->item.data.weapon.minDamage;\n        maxDamage = proto->item.data.weapon.maxDamage;\n\n        int attackType = item_w_subtype(weapon, hitMode);\n        if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) {\n            meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE);\n        }\n    } else {\n        minDamage = 1;\n        maxDamage = critterGetStat(critter, STAT_MELEE_DAMAGE) + 2;\n\n        switch (hitMode) {\n        case HIT_MODE_STRONG_PUNCH:\n        case HIT_MODE_JAB:\n            unarmedDamage = 3;\n            break;\n        case HIT_MODE_HAMMER_PUNCH:\n        case HIT_MODE_STRONG_KICK:\n            unarmedDamage = 4;\n            break;\n        case HIT_MODE_HAYMAKER:\n        case HIT_MODE_PALM_STRIKE:\n        case HIT_MODE_SNAP_KICK:\n        case HIT_MODE_HIP_KICK:\n            unarmedDamage = 7;\n            break;\n        case HIT_MODE_POWER_KICK:\n        case HIT_MODE_HOOK_KICK:\n            unarmedDamage = 9;\n            break;\n        case HIT_MODE_PIERCING_STRIKE:\n            unarmedDamage = 10;\n            break;\n        case HIT_MODE_PIERCING_KICK:\n            unarmedDamage = 12;\n            break;\n        }\n    }\n\n    return roll_random(unarmedDamage + minDamage, unarmedDamage + meleeDamage + maxDamage);\n}\n\n// 0x478570\nint item_w_damage_type(Object* critter, Object* weapon)\n{\n    Proto* proto;\n\n    if (weapon != NULL) {\n        proto_ptr(weapon->pid, &proto);\n\n        return proto->item.data.weapon.damageType;\n    }\n\n    if (critter != NULL) {\n        return critter_get_base_damage_type(critter);\n    }\n\n    return 0;\n}\n\n// 0x478598\nint item_w_is_2handed(Object* weapon)\n{\n    Proto* proto;\n\n    if (weapon == NULL) {\n        return 0;\n    }\n\n    proto_ptr(weapon->pid, &proto);\n\n    return (proto->item.extendedFlags & WEAPON_TWO_HAND) != 0;\n}\n\n// 0x4785DC\nint item_w_anim(Object* critter, int hitMode)\n{\n    // NOTE: Uninline.\n    Object* weapon = item_hit_with(critter, hitMode);\n    return item_w_anim_weap(weapon, hitMode);\n}\n\n// 0x47860C\nint item_w_anim_weap(Object* weapon, int hitMode)\n{\n    if (hitMode == HIT_MODE_KICK || (hitMode >= FIRST_ADVANCED_KICK_HIT_MODE && hitMode <= LAST_ADVANCED_KICK_HIT_MODE)) {\n        return ANIM_KICK_LEG;\n    }\n\n    if (weapon == NULL) {\n        return ANIM_THROW_PUNCH;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    int index;\n    if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) {\n        index = proto->item.extendedFlags & 0xF;\n    } else {\n        index = (proto->item.extendedFlags & 0xF0) >> 4;\n    }\n\n    return attack_anim[index];\n}\n\n// 0x478674\nint item_w_max_ammo(Object* ammoOrWeapon)\n{\n    if (ammoOrWeapon == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(ammoOrWeapon->pid, &proto);\n\n    if (proto->item.type == ITEM_TYPE_AMMO) {\n        return proto->item.data.ammo.quantity;\n    } else {\n        return proto->item.data.weapon.ammoCapacity;\n    }\n}\n\n// 0x4786A0\nint item_w_curr_ammo(Object* ammoOrWeapon)\n{\n    if (ammoOrWeapon == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(ammoOrWeapon->pid, &proto);\n\n    // NOTE: Looks like the condition jumps were erased during compilation only\n    // because ammo's quantity and weapon's ammo quantity coincidently stored\n    // in the same offset relative to [Object].\n    if (proto->item.type == ITEM_TYPE_AMMO) {\n        return ammoOrWeapon->data.item.ammo.quantity;\n    } else {\n        return ammoOrWeapon->data.item.weapon.ammoQuantity;\n    }\n}\n\n// 0x4786C8\nint item_w_caliber(Object* ammoOrWeapon)\n{\n    Proto* proto;\n\n    if (ammoOrWeapon == NULL) {\n        return 0;\n    }\n\n    proto_ptr(ammoOrWeapon->pid, &proto);\n\n    if (proto->item.type != ITEM_TYPE_AMMO) {\n        if (proto_ptr(ammoOrWeapon->data.item.weapon.ammoTypePid, &proto) == -1) {\n            return 0;\n        }\n    }\n\n    return proto->item.data.ammo.caliber;\n}\n\n// 0x478714\nvoid item_w_set_curr_ammo(Object* ammoOrWeapon, int quantity)\n{\n    if (ammoOrWeapon == NULL) {\n        return;\n    }\n\n    // NOTE: Uninline.\n    int capacity = item_w_max_ammo(ammoOrWeapon);\n    if (quantity > capacity) {\n        quantity = capacity;\n    }\n\n    Proto* proto;\n    proto_ptr(ammoOrWeapon->pid, &proto);\n\n    if (proto->item.type == ITEM_TYPE_AMMO) {\n        ammoOrWeapon->data.item.ammo.quantity = quantity;\n    } else {\n        ammoOrWeapon->data.item.weapon.ammoQuantity = quantity;\n    }\n}\n\n// 0x478768\nint item_w_try_reload(Object* critter, Object* weapon)\n{\n    // NOTE: Uninline.\n    int quantity = item_w_curr_ammo(weapon);\n    int capacity = item_w_max_ammo(weapon);\n    if (quantity == capacity) {\n        return -1;\n    }\n\n    if (weapon->pid != PROTO_ID_SOLAR_SCORCHER) {\n        int inventoryItemIndex = -1;\n        for (;;) {\n            Object* ammo = inven_find_type(critter, ITEM_TYPE_AMMO, &inventoryItemIndex);\n            if (ammo == NULL) {\n                break;\n            }\n\n            if (weapon->data.item.weapon.ammoTypePid == ammo->pid) {\n                if (item_w_can_reload(weapon, ammo) != 0) {\n                    int rc = item_w_reload(weapon, ammo);\n                    if (rc == 0) {\n                        obj_destroy(ammo);\n                    }\n\n                    if (rc == -1) {\n                        return -1;\n                    }\n\n                    return 0;\n                }\n            }\n        }\n\n        inventoryItemIndex = -1;\n        for (;;) {\n            Object* ammo = inven_find_type(critter, ITEM_TYPE_AMMO, &inventoryItemIndex);\n            if (ammo == NULL) {\n                break;\n            }\n\n            if (item_w_can_reload(weapon, ammo) != 0) {\n                int rc = item_w_reload(weapon, ammo);\n                if (rc == 0) {\n                    obj_destroy(ammo);\n                }\n\n                if (rc == -1) {\n                    return -1;\n                }\n\n                return 0;\n            }\n        }\n    }\n\n    if (item_w_reload(weapon, NULL) != 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// Checks if weapon can be reloaded with the specified ammo.\n//\n// 0x478874\nbool item_w_can_reload(Object* weapon, Object* ammo)\n{\n    if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) {\n        // Check light level to recharge solar scorcher.\n        if (light_get_ambient() > 62259) {\n            return true;\n        }\n\n        // There is not enough light to recharge this item.\n        MessageListItem messageListItem;\n        char* msg = getmsg(&item_message_file, &messageListItem, 500);\n        display_print(msg);\n\n        return false;\n    }\n\n    if (ammo == NULL) {\n        return false;\n    }\n\n    Proto* weaponProto;\n    proto_ptr(weapon->pid, &weaponProto);\n\n    Proto* ammoProto;\n    proto_ptr(ammo->pid, &ammoProto);\n\n    if (weaponProto->item.type != ITEM_TYPE_WEAPON) {\n        return false;\n    }\n\n    if (ammoProto->item.type != ITEM_TYPE_AMMO) {\n        return false;\n    }\n\n    // Check ammo matches weapon caliber.\n    if (weaponProto->item.data.weapon.caliber != ammoProto->item.data.ammo.caliber) {\n        return false;\n    }\n\n    // If weapon is not empty, we should only reload it with the same ammo.\n    if (item_w_curr_ammo(weapon) != 0) {\n        if (weapon->data.item.weapon.ammoTypePid != ammo->pid) {\n            return false;\n        }\n    }\n\n    return true;\n}\n\n// 0x478918\nint item_w_reload(Object* weapon, Object* ammo)\n{\n    if (!item_w_can_reload(weapon, ammo)) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    int ammoQuantity = item_w_curr_ammo(weapon);\n\n    // NOTE: Uninline.\n    int ammoCapacity = item_w_max_ammo(weapon);\n\n    if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) {\n        item_w_set_curr_ammo(weapon, ammoCapacity);\n        return 0;\n    }\n\n    // NOTE: Uninline.\n    int v10 = item_w_curr_ammo(ammo);\n\n    int v11 = v10;\n    if (ammoQuantity < ammoCapacity) {\n        int v12;\n        if (ammoQuantity + v10 > ammoCapacity) {\n            v11 = v10 - (ammoCapacity - ammoQuantity);\n            v12 = ammoCapacity;\n        } else {\n            v11 = 0;\n            v12 = ammoQuantity + v10;\n        }\n\n        weapon->data.item.weapon.ammoTypePid = ammo->pid;\n\n        item_w_set_curr_ammo(ammo, v11);\n        item_w_set_curr_ammo(weapon, v12);\n    }\n\n    return v11;\n}\n\n// 0x478A1C\nint item_w_range(Object* critter, int hitMode)\n{\n    int range;\n    int v12;\n\n    // NOTE: Uninline.\n    Object* weapon = item_hit_with(critter, hitMode);\n\n    if (weapon != NULL && hitMode != 4 && hitMode != 5 && (hitMode < 8 || hitMode > 19)) {\n        Proto* proto;\n        proto_ptr(weapon->pid, &proto);\n        if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) {\n            range = proto->item.data.weapon.maxRange1;\n        } else {\n            range = proto->item.data.weapon.maxRange2;\n        }\n\n        if (item_w_subtype(weapon, hitMode) == ATTACK_TYPE_THROW) {\n            if (critter == obj_dude) {\n                v12 = critterGetStat(critter, STAT_STRENGTH) + 2 * perk_level(critter, PERK_HEAVE_HO);\n            } else {\n                v12 = critterGetStat(critter, STAT_STRENGTH);\n            }\n\n            int maxRange = 3 * v12;\n            if (range >= maxRange) {\n                range = maxRange;\n            }\n        }\n\n        return range;\n    }\n\n    if (critter_flag_check(critter->pid, CRITTER_LONG_LIMBS)) {\n        return 2;\n    }\n\n    return 1;\n}\n\n// Returns action points required for hit mode.\n//\n// 0x478B24\nint item_w_mp_cost(Object* critter, int hitMode, bool aiming)\n{\n    int actionPoints;\n\n    // NOTE: Uninline.\n    Object* weapon = item_hit_with(critter, hitMode);\n\n    if (hitMode == HIT_MODE_LEFT_WEAPON_RELOAD || hitMode == HIT_MODE_RIGHT_WEAPON_RELOAD) {\n        if (weapon != NULL) {\n            Proto* proto;\n            proto_ptr(weapon->pid, &proto);\n            if (proto->item.data.weapon.perk == PERK_WEAPON_FAST_RELOAD) {\n                return 1;\n            }\n\n            if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) {\n                return 0;\n            }\n        }\n        return 2;\n    }\n\n    switch (hitMode) {\n    case HIT_MODE_PALM_STRIKE:\n        actionPoints = 6;\n        break;\n    case HIT_MODE_PIERCING_STRIKE:\n        actionPoints = 8;\n        break;\n    case HIT_MODE_STRONG_KICK:\n    case HIT_MODE_SNAP_KICK:\n    case HIT_MODE_POWER_KICK:\n        actionPoints = 4;\n        break;\n    case HIT_MODE_HIP_KICK:\n    case HIT_MODE_HOOK_KICK:\n        actionPoints = 7;\n        break;\n    case HIT_MODE_PIERCING_KICK:\n        actionPoints = 9;\n        break;\n    default:\n        // TODO: Inverse conditions.\n        if (weapon != NULL && hitMode != HIT_MODE_PUNCH && hitMode != HIT_MODE_KICK && hitMode != HIT_MODE_STRONG_PUNCH && hitMode != HIT_MODE_HAMMER_PUNCH && hitMode != HIT_MODE_HAYMAKER) {\n            if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) {\n                // NOTE: Uninline.\n                actionPoints = item_w_primary_mp_cost(weapon);\n            } else {\n                // NOTE: Uninline.\n                actionPoints = item_w_secondary_mp_cost(weapon);\n            }\n\n            if (critter == obj_dude) {\n                if (trait_level(TRAIT_FAST_SHOT)) {\n                    if (item_w_range(critter, hitMode) > 2) {\n                        actionPoints--;\n                    }\n                }\n            }\n        } else {\n            actionPoints = 3;\n        }\n        break;\n    }\n\n    if (critter == obj_dude) {\n        int attackType = item_w_subtype(weapon, hitMode);\n\n        if (perkHasRank(obj_dude, PERK_BONUS_HTH_ATTACKS)) {\n            if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) {\n                actionPoints -= 1;\n            }\n        }\n\n        if (perkHasRank(obj_dude, PERK_BONUS_RATE_OF_FIRE)) {\n            if (attackType == ATTACK_TYPE_RANGED) {\n                actionPoints -= 1;\n            }\n        }\n    }\n\n    if (aiming) {\n        actionPoints += 1;\n    }\n\n    if (actionPoints < 1) {\n        actionPoints = 1;\n    }\n\n    return actionPoints;\n}\n\n// 0x478D08\nint item_w_min_st(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.minStrength;\n}\n\n// 0x478D30\nint item_w_crit_fail(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.criticalFailureType;\n}\n\n// 0x478D58\nint item_w_perk(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.perk;\n}\n\n// 0x478D80\nint item_w_rounds(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.rounds;\n}\n\n// 0x478DA8\nint item_w_anim_code(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.animationCode;\n}\n\n// 0x478DD0\nint item_w_proj_pid(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.projectilePid;\n}\n\n// 0x478DF8\nint item_w_ammo_pid(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    if (item_get_type(weapon) != ITEM_TYPE_WEAPON) {\n        return -1;\n    }\n\n    return weapon->data.item.weapon.ammoTypePid;\n}\n\n// 0x478E18\nchar item_w_sound_id(Object* weapon)\n{\n    if (weapon == NULL) {\n        return '\\0';\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.soundCode & 0xFF;\n}\n\n// 0x478E5C\nint item_w_called_shot(Object* critter, int hitMode)\n{\n    if (critter == obj_dude && trait_level(TRAIT_FAST_SHOT)) {\n        return 0;\n    }\n\n    // NOTE: Uninline.\n    int anim = item_w_anim(critter, hitMode);\n    if (anim == ANIM_FIRE_BURST || anim == ANIM_FIRE_CONTINUOUS) {\n        return 0;\n    }\n\n    // NOTE: Uninline.\n    Object* weapon = item_hit_with(critter, hitMode);\n    int damageType = item_w_damage_type(critter, weapon);\n\n    return damageType != DAMAGE_TYPE_EXPLOSION\n        && damageType != DAMAGE_TYPE_FIRE\n        && damageType != DAMAGE_TYPE_EMP\n        && (damageType != DAMAGE_TYPE_PLASMA || anim != ANIM_THROW_ANIM);\n}\n\n// 0x478EF4\nint item_w_can_unload(Object* weapon)\n{\n    if (weapon == NULL) {\n        return false;\n    }\n\n    if (item_get_type(weapon) != ITEM_TYPE_WEAPON) {\n        return false;\n    }\n\n    // NOTE: Uninline.\n    int ammoCapacity = item_w_max_ammo(weapon);\n    if (ammoCapacity <= 0) {\n        return false;\n    }\n\n    // NOTE: Uninline.\n    int ammoQuantity = item_w_curr_ammo(weapon);\n    if (ammoQuantity <= 0) {\n        return false;\n    }\n\n    if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) {\n        return false;\n    }\n\n    if (item_w_ammo_pid(weapon) == -1) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x478F80\nObject* item_w_unload(Object* weapon)\n{\n    if (!item_w_can_unload(weapon)) {\n        return NULL;\n    }\n\n    // NOTE: Uninline.\n    int ammoTypePid = item_w_ammo_pid(weapon);\n    if (ammoTypePid == -1) {\n        return NULL;\n    }\n\n    Object* ammo;\n    if (obj_pid_new(&ammo, ammoTypePid) != 0) {\n        return NULL;\n    }\n\n    obj_disconnect(ammo, NULL);\n\n    // NOTE: Uninline.\n    int ammoQuantity = item_w_curr_ammo(weapon);\n\n    // NOTE: Uninline.\n    int ammoCapacity = item_w_max_ammo(ammo);\n\n    int remainingQuantity;\n    if (ammoQuantity <= ammoCapacity) {\n        item_w_set_curr_ammo(ammo, ammoQuantity);\n        remainingQuantity = 0;\n    } else {\n        item_w_set_curr_ammo(ammo, ammoCapacity);\n        remainingQuantity = ammoQuantity - ammoCapacity;\n    }\n    item_w_set_curr_ammo(weapon, remainingQuantity);\n\n    return ammo;\n}\n\n// 0x47905C\nint item_w_primary_mp_cost(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.actionPointCost1;\n}\n\n// NOTE: Inlined.\n//\n// 0x479084\nint item_w_secondary_mp_cost(Object* weapon)\n{\n    if (weapon == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(weapon->pid, &proto);\n\n    return proto->item.data.weapon.actionPointCost2;\n}\n\n// 0x4790AC\nint item_w_compute_ammo_cost(Object* obj, int* inout_a2)\n{\n    int pid;\n\n    if (inout_a2 == NULL) {\n        return -1;\n    }\n\n    if (obj == NULL) {\n        return 0;\n    }\n\n    pid = obj->pid;\n    if (pid == PROTO_ID_SUPER_CATTLE_PROD || pid == PROTO_ID_MEGA_POWER_FIST) {\n        *inout_a2 *= 2;\n    }\n\n    return 0;\n}\n\n// 0x4790E8\nbool item_w_is_grenade(Object* weapon)\n{\n    int damageType = item_w_damage_type(NULL, weapon);\n    return damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP;\n}\n\n// 0x47910C\nint item_w_area_damage_radius(Object* weapon, int hitMode)\n{\n    int attackType = item_w_subtype(weapon, hitMode);\n    int anim = item_w_anim_weap(weapon, hitMode);\n    int damageType = item_w_damage_type(NULL, weapon);\n\n    int damageRadius = 0;\n    if (attackType == ATTACK_TYPE_RANGED) {\n        if (anim == ANIM_FIRE_SINGLE && damageType == DAMAGE_TYPE_EXPLOSION) {\n            // NOTE: Uninline.\n            damageRadius = item_w_rocket_dmg_radius(weapon);\n        }\n    } else if (attackType == ATTACK_TYPE_THROW) {\n        // NOTE: Uninline.\n        if (item_w_is_grenade(weapon)) {\n            // NOTE: Uninline.\n            damageRadius = item_w_grenade_dmg_radius(weapon);\n        }\n    }\n    return damageRadius;\n}\n\n// 0x479180\nint item_w_grenade_dmg_radius(Object* weapon)\n{\n    return 2;\n}\n\n// 0x479188\nint item_w_rocket_dmg_radius(Object* weapon)\n{\n    return 3;\n}\n\n// 0x479190\nint item_w_ac_adjust(Object* weapon)\n{\n    // NOTE: Uninline.\n    int ammoTypePid = item_w_ammo_pid(weapon);\n    if (ammoTypePid == -1) {\n        return 0;\n    }\n\n    Proto* proto;\n    if (proto_ptr(ammoTypePid, &proto) == -1) {\n        return 0;\n    }\n\n    return proto->item.data.ammo.armorClassModifier;\n}\n\n// 0x4791E0\nint item_w_dr_adjust(Object* weapon)\n{\n    // NOTE: Uninline.\n    int ammoTypePid = item_w_ammo_pid(weapon);\n    if (ammoTypePid == -1) {\n        return 0;\n    }\n\n    Proto* proto;\n    if (proto_ptr(ammoTypePid, &proto) == -1) {\n        return 0;\n    }\n\n    return proto->item.data.ammo.damageResistanceModifier;\n}\n\n// 0x479230\nint item_w_dam_mult(Object* weapon)\n{\n    // NOTE: Uninline.\n    int ammoTypePid = item_w_ammo_pid(weapon);\n    if (ammoTypePid == -1) {\n        return 1;\n    }\n\n    Proto* proto;\n    if (proto_ptr(ammoTypePid, &proto) == -1) {\n        return 1;\n    }\n\n    return proto->item.data.ammo.damageMultiplier;\n}\n\n// 0x479294\nint item_w_dam_div(Object* weapon)\n{\n    // NOTE: Uninline.\n    int ammoTypePid = item_w_ammo_pid(weapon);\n    if (ammoTypePid == -1) {\n        return 1;\n    }\n\n    Proto* proto;\n    if (proto_ptr(ammoTypePid, &proto) == -1) {\n        return 1;\n    }\n\n    return proto->item.data.ammo.damageDivisor;\n}\n\n// 0x4792F8\nint item_ar_ac(Object* armor)\n{\n    if (armor == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(armor->pid, &proto);\n\n    return proto->item.data.armor.armorClass;\n}\n\n// 0x479318\nint item_ar_dr(Object* armor, int damageType)\n{\n    if (armor == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(armor->pid, &proto);\n\n    return proto->item.data.armor.damageResistance[damageType];\n}\n\n// 0x479338\nint item_ar_dt(Object* armor, int damageType)\n{\n    if (armor == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(armor->pid, &proto);\n\n    return proto->item.data.armor.damageThreshold[damageType];\n}\n\n// 0x479358\nint item_ar_perk(Object* armor)\n{\n    if (armor == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(armor->pid, &proto);\n\n    return proto->item.data.armor.perk;\n}\n\n// 0x479380\nint item_ar_male_fid(Object* armor)\n{\n    if (armor == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(armor->pid, &proto);\n\n    return proto->item.data.armor.maleFid;\n}\n\n// 0x4793A8\nint item_ar_female_fid(Object* armor)\n{\n    if (armor == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(armor->pid, &proto);\n\n    return proto->item.data.armor.femaleFid;\n}\n\n// 0x4793D0\nint item_m_max_charges(Object* miscItem)\n{\n    if (miscItem == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(miscItem->pid, &proto);\n\n    return proto->item.data.misc.charges;\n}\n\n// 0x4793F0\nint item_m_curr_charges(Object* miscItem)\n{\n    if (miscItem == NULL) {\n        return 0;\n    }\n\n    return miscItem->data.item.misc.charges;\n}\n\n// 0x4793F8\nint item_m_set_charges(Object* miscItem, int charges)\n{\n    // NOTE: Uninline.\n    int maxCharges = item_m_max_charges(miscItem);\n\n    if (charges > maxCharges) {\n        charges = maxCharges;\n    }\n\n    miscItem->data.item.misc.charges = charges;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x479434\nint item_m_cell(Object* miscItem)\n{\n    if (miscItem == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(miscItem->pid, &proto);\n\n    return proto->item.data.misc.powerType;\n}\n\n// NOTE: Inlined.\n//\n// 0x479454\nint item_m_cell_pid(Object* miscItem)\n{\n    if (miscItem == NULL) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(miscItem->pid, &proto);\n\n    return proto->item.data.misc.powerTypePid;\n}\n\n// 0x47947C\nbool item_m_uses_charges(Object* miscItem)\n{\n    if (miscItem == NULL) {\n        return false;\n    }\n\n    Proto* proto;\n    proto_ptr(miscItem->pid, &proto);\n\n    return proto->item.data.misc.charges != 0;\n}\n\n// 0x4794A4\nint item_m_use_charged_item(Object* critter, Object* miscItem)\n{\n    int pid = miscItem->pid;\n    if (pid == PROTO_ID_STEALTH_BOY_I\n        || pid == PROTO_ID_GEIGER_COUNTER_I\n        || pid == PROTO_ID_STEALTH_BOY_II\n        || pid == PROTO_ID_GEIGER_COUNTER_II) {\n        // NOTE: Uninline.\n        bool isOn = item_m_on(miscItem);\n\n        if (isOn) {\n            item_m_turn_off(miscItem);\n        } else {\n            item_m_turn_on(miscItem);\n        }\n    } else if (pid == PROTO_ID_MOTION_SENSOR) {\n        // NOTE: Uninline.\n        if (item_m_dec_charges(miscItem) == 0) {\n            automap(true, true);\n        } else {\n            MessageListItem messageListItem;\n            // %s has no charges left.\n            messageListItem.num = 5;\n            if (message_search(&item_message_file, &messageListItem)) {\n                char text[80];\n                const char* itemName = object_name(miscItem);\n                sprintf(text, messageListItem.text, itemName);\n                display_print(text);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4795A4\nint item_m_dec_charges(Object* item)\n{\n    // NOTE: Uninline.\n    int charges = item_m_curr_charges(item);\n    if (charges <= 0) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    item_m_set_charges(item, charges - 1);\n\n    return 0;\n}\n\n// 0x4795F0\nint item_m_trickle(Object* item, void* data)\n{\n    // NOTE: Uninline.\n    if (item_m_dec_charges(item) == 0) {\n        int delay;\n        if (item->pid == PROTO_ID_STEALTH_BOY_I || item->pid == PROTO_ID_STEALTH_BOY_II) {\n            delay = 600;\n        } else {\n            delay = 3000;\n        }\n\n        queue_add(delay, item, NULL, EVENT_TYPE_ITEM_TRICKLE);\n    } else {\n        Object* critter = obj_top_environment(item);\n        if (critter == obj_dude) {\n            MessageListItem messageListItem;\n            // %s has no charges left.\n            messageListItem.num = 5;\n            if (message_search(&item_message_file, &messageListItem)) {\n                char text[80];\n                const char* itemName = object_name(item);\n                sprintf(text, messageListItem.text, itemName);\n                display_print(text);\n            }\n        }\n        item_m_turn_off(item);\n    }\n\n    return 0;\n}\n\n// 0x4796A8\nbool item_m_on(Object* obj)\n{\n    if (obj == NULL) {\n        return false;\n    }\n\n    if (!item_m_uses_charges(obj)) {\n        return false;\n    }\n\n    return queue_find(obj, EVENT_TYPE_ITEM_TRICKLE);\n}\n\n// Turns on geiger counter or stealth boy.\n//\n// 0x4796D0\nint item_m_turn_on(Object* item)\n{\n    MessageListItem messageListItem;\n    char text[80];\n\n    Object* critter = obj_top_environment(item);\n    if (critter == NULL) {\n        // This item can only be used from the interface bar.\n        messageListItem.num = 9;\n        if (message_search(&item_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    if (item_m_dec_charges(item) != 0) {\n        if (critter == obj_dude) {\n            messageListItem.num = 5;\n            if (message_search(&item_message_file, &messageListItem)) {\n                char* name = object_name(item);\n                sprintf(text, messageListItem.text, name);\n                display_print(text);\n            }\n        }\n\n        return -1;\n    }\n\n    if (item->pid == PROTO_ID_STEALTH_BOY_I || item->pid == PROTO_ID_STEALTH_BOY_II) {\n        queue_add(600, item, 0, EVENT_TYPE_ITEM_TRICKLE);\n        item->pid = PROTO_ID_STEALTH_BOY_II;\n\n        if (critter != NULL) {\n            // NOTE: Uninline.\n            item_m_stealth_effect_on(critter);\n        }\n    } else {\n        queue_add(3000, item, 0, EVENT_TYPE_ITEM_TRICKLE);\n        item->pid = PROTO_ID_GEIGER_COUNTER_II;\n    }\n\n    if (critter == obj_dude) {\n        // %s is on.\n        messageListItem.num = 6;\n        if (message_search(&item_message_file, &messageListItem)) {\n            char* name = object_name(item);\n            sprintf(text, messageListItem.text, name);\n            display_print(text);\n        }\n\n        if (item->pid == PROTO_ID_GEIGER_COUNTER_II) {\n            // You pass the Geiger counter over you body. The rem counter reads: %d\n            messageListItem.num = 8;\n            if (message_search(&item_message_file, &messageListItem)) {\n                int radiation = critter_get_rads(critter);\n                sprintf(text, messageListItem.text, radiation);\n                display_print(text);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// Turns off geiger counter or stealth boy.\n//\n// 0x479898\nint item_m_turn_off(Object* item)\n{\n    Object* owner = obj_top_environment(item);\n\n    queue_remove_this(item, EVENT_TYPE_ITEM_TRICKLE);\n\n    if (owner != NULL && item->pid == PROTO_ID_STEALTH_BOY_II) {\n        item_m_stealth_effect_off(owner, item);\n    }\n\n    if (item->pid == PROTO_ID_STEALTH_BOY_I || item->pid == PROTO_ID_STEALTH_BOY_II) {\n        item->pid = PROTO_ID_STEALTH_BOY_I;\n    } else {\n        item->pid = PROTO_ID_GEIGER_COUNTER_I;\n    }\n\n    if (owner == obj_dude) {\n        intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n    }\n\n    if (owner == obj_dude) {\n        // %s is off.\n        MessageListItem messageListItem;\n        messageListItem.num = 7;\n        if (message_search(&item_message_file, &messageListItem)) {\n            const char* name = object_name(item);\n            char text[80];\n            sprintf(text, messageListItem.text, name);\n            display_print(text);\n        }\n    }\n\n    return 0;\n}\n\n// 0x479954\nint item_m_turn_off_from_queue(Object* obj, void* data)\n{\n    item_m_turn_off(obj);\n    return 1;\n}\n\n// NOTE: Inlined.\n//\n// 0x479960\nstatic int item_m_stealth_effect_on(Object* object)\n{\n    if ((object->flags & OBJECT_TRANS_GLASS) != 0) {\n        return -1;\n    }\n\n    object->flags |= OBJECT_TRANS_GLASS;\n\n    Rect rect;\n    obj_bound(object, &rect);\n    tile_refresh_rect(&rect, object->elevation);\n\n    return 0;\n}\n\n// 0x479998\nstatic int item_m_stealth_effect_off(Object* critter, Object* item)\n{\n    Object* item1 = inven_left_hand(critter);\n    if (item1 != NULL && item1 != item && item1->pid == PROTO_ID_STEALTH_BOY_II) {\n        return -1;\n    }\n\n    Object* item2 = inven_right_hand(critter);\n    if (item2 != NULL && item2 != item && item2->pid == PROTO_ID_STEALTH_BOY_II) {\n        return -1;\n    }\n\n    if ((critter->flags & OBJECT_TRANS_GLASS) == 0) {\n        return -1;\n    }\n\n    critter->flags &= ~OBJECT_TRANS_GLASS;\n\n    Rect rect;\n    obj_bound(critter, &rect);\n    tile_refresh_rect(&rect, critter->elevation);\n\n    return 0;\n}\n\n// 0x479A00\nint item_c_max_size(Object* container)\n{\n    if (container == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(container->pid, &proto);\n\n    return proto->item.data.container.maxSize;\n}\n\n// 0x479A20\nint item_c_curr_size(Object* container)\n{\n    if (container == NULL) {\n        return 0;\n    }\n\n    int totalSize = 0;\n\n    Inventory* inventory = &(container->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n\n        int size = item_size(inventoryItem->item);\n        totalSize += inventory->items[index].quantity * size;\n    }\n\n    return totalSize;\n}\n\n// 0x479A74\nint item_a_ac_adjust(Object* armor)\n{\n    if (armor == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    if (proto_ptr(armor->pid, &proto) == -1) {\n        return 0;\n    }\n\n    return proto->item.data.ammo.armorClassModifier;\n}\n\n// 0x479AA4\nint item_a_dr_adjust(Object* armor)\n{\n    if (armor == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    if (proto_ptr(armor->pid, &proto) == -1) {\n        return 0;\n    }\n\n    return proto->item.data.ammo.damageResistanceModifier;\n}\n\n// 0x479AD4\nint item_a_dam_mult(Object* armor)\n{\n    if (armor == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    if (proto_ptr(armor->pid, &proto) == -1) {\n        return 0;\n    }\n\n    return proto->item.data.ammo.damageMultiplier;\n}\n\n// 0x479B04\nint item_a_dam_div(Object* armor)\n{\n    if (armor == NULL) {\n        return 0;\n    }\n\n    Proto* proto;\n    if (proto_ptr(armor->pid, &proto) == -1) {\n        return 0;\n    }\n\n    return proto->item.data.ammo.damageDivisor;\n}\n\n// 0x479B44\nstatic int insert_drug_effect(Object* critter, Object* item, int a3, int* stats, int* mods)\n{\n    int index;\n    for (index = 0; index < 3; index++) {\n        if (mods[index] != 0) {\n            break;\n        }\n    }\n\n    if (index == 3) {\n        return -1;\n    }\n\n    DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)mem_malloc(sizeof(*drugEffectEvent));\n    if (drugEffectEvent == NULL) {\n        return -1;\n    }\n\n    drugEffectEvent->drugPid = item->pid;\n\n    for (index = 0; index < 3; index++) {\n        drugEffectEvent->stats[index] = stats[index];\n        drugEffectEvent->modifiers[index] = mods[index];\n    }\n\n    int delay = 600 * a3;\n    if (critter == obj_dude) {\n        if (trait_level(TRAIT_CHEM_RESISTANT)) {\n            delay /= 2;\n        }\n    }\n\n    if (queue_add(delay, critter, drugEffectEvent, EVENT_TYPE_DRUG) == -1) {\n        mem_free(drugEffectEvent);\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x479C20\nstatic void perform_drug_effect(Object* critter, int* stats, int* mods, bool isImmediate)\n{\n    int v10;\n    int v11;\n    int v12;\n    MessageListItem messageListItem;\n    const char* name;\n    const char* text;\n    char v24[92]; // TODO: Size is probably wrong.\n    char str[92]; // TODO: Size is probably wrong.\n\n    bool statsChanged = false;\n\n    int v5 = 0;\n    bool v32 = false;\n    if (stats[0] == -2) {\n        v5 = 1;\n        v32 = true;\n    }\n\n    for (int index = v5; index < 3; index++) {\n        int stat = stats[index];\n        if (stat == -1) {\n            continue;\n        }\n\n        if (stat == STAT_CURRENT_HIT_POINTS) {\n            critter->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n        }\n\n        v10 = stat_get_bonus(critter, stat);\n\n        int before;\n        if (critter == obj_dude) {\n            before = critterGetStat(obj_dude, stat);\n        }\n\n        if (v32) {\n            v11 = roll_random(mods[index - 1], mods[index]) + v10;\n            v32 = false;\n        } else {\n            v11 = mods[index] + v10;\n        }\n\n        if (stat == STAT_CURRENT_HIT_POINTS) {\n            v12 = stat_get_base(critter, STAT_CURRENT_HIT_POINTS);\n            if (v11 + v12 <= 0 && critter != obj_dude) {\n                name = critter_name(critter);\n                // %s succumbs to the adverse effects of chems.\n                text = getmsg(&item_message_file, &messageListItem, 600);\n                sprintf(v24, text, name);\n                combatKillCritterOutsideCombat(critter, v24);\n            }\n        }\n\n        stat_set_bonus(critter, stat, v11);\n\n        if (critter == obj_dude) {\n            if (stat == STAT_CURRENT_HIT_POINTS) {\n                intface_update_hit_points(true);\n            }\n\n            int after = critterGetStat(critter, stat);\n            if (after != before) {\n                // 1 - You gained %d %s.\n                // 2 - You lost %d %s.\n                messageListItem.num = after < before ? 2 : 1;\n                if (message_search(&item_message_file, &messageListItem)) {\n                    char* statName = stat_name(stat);\n                    sprintf(str, messageListItem.text, after < before ? before - after : after - before, statName);\n                    display_print(str);\n                    statsChanged = true;\n                }\n            }\n        }\n    }\n\n    if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) > 0) {\n        if (critter == obj_dude && !statsChanged && isImmediate) {\n            // Nothing happens.\n            messageListItem.num = 10;\n            if (message_search(&item_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n    } else {\n        if (critter == obj_dude) {\n            // You suffer a fatal heart attack from chem overdose.\n            messageListItem.num = 4;\n            if (message_search(&item_message_file, &messageListItem)) {\n                strcpy(v24, messageListItem.text);\n                // TODO: Why message is ignored?\n            }\n        } else {\n            name = critter_name(critter);\n            // %s succumbs to the adverse effects of chems.\n            text = getmsg(&item_message_file, &messageListItem, 600);\n            sprintf(v24, text, name);\n            // TODO: Why message is ignored?\n        }\n    }\n}\n\n// 0x479EE4\nstatic bool drug_effect_allowed(Object* critter, int pid)\n{\n    int index;\n    DrugDescription* drugDescription;\n    for (index = 0; index < ADDICTION_COUNT; index++) {\n        drugDescription = &(drugInfoList[index]);\n        if (drugDescription->drugPid == pid) {\n            break;\n        }\n    }\n\n    if (index == ADDICTION_COUNT) {\n        return true;\n    }\n\n    if (drugDescription->field_8 == 0) {\n        return true;\n    }\n\n    // TODO: Probably right, but let's check it once.\n    int count = 0;\n    DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)queue_find_first(critter, EVENT_TYPE_DRUG);\n    while (drugEffectEvent != NULL) {\n        if (drugEffectEvent->drugPid == pid) {\n            count++;\n            if (count >= drugDescription->field_8) {\n                return false;\n            }\n        }\n        drugEffectEvent = (DrugEffectEvent*)queue_find_next(critter, EVENT_TYPE_DRUG);\n    }\n\n    return true;\n}\n\n// 0x479F60\nint item_d_take_drug(Object* critter, Object* item)\n{\n    if (critter_is_dead(critter)) {\n        return -1;\n    }\n\n    if (critter_body_type(critter) == BODY_TYPE_ROBOTIC) {\n        return -1;\n    }\n\n    Proto* proto;\n    proto_ptr(item->pid, &proto);\n\n    if (item->pid == PROTO_ID_JET_ANTIDOTE) {\n        if (item_d_check_addict(PROTO_ID_JET)) {\n            perform_withdrawal_end(critter, PERK_JET_ADDICTION);\n\n            if (critter == obj_dude) {\n                // NOTE: Uninline.\n                item_d_unset_addict(PROTO_ID_JET);\n            }\n\n            return 0;\n        }\n    }\n\n    wd_obj = critter;\n    wd_gvar = pid_to_gvar(item->pid);\n    wd_onset = proto->item.data.drug.withdrawalOnset;\n\n    queue_clear_type(EVENT_TYPE_WITHDRAWAL, item_wd_clear_all);\n\n    if (drug_effect_allowed(critter, item->pid)) {\n        perform_drug_effect(critter, proto->item.data.drug.stat, proto->item.data.drug.amount, true);\n        insert_drug_effect(critter, item, proto->item.data.drug.duration1, proto->item.data.drug.stat, proto->item.data.drug.amount1);\n        insert_drug_effect(critter, item, proto->item.data.drug.duration2, proto->item.data.drug.stat, proto->item.data.drug.amount2);\n    } else {\n        if (critter == obj_dude) {\n            MessageListItem messageListItem;\n            // That didn't seem to do that much.\n            char* msg = getmsg(&item_message_file, &messageListItem, 50);\n            display_print(msg);\n        }\n    }\n\n    if (!item_d_check_addict(item->pid)) {\n        int addictionChance = proto->item.data.drug.addictionChance;\n        if (critter == obj_dude) {\n            if (trait_level(TRAIT_CHEM_RELIANT)) {\n                addictionChance *= 2;\n            }\n\n            if (trait_level(TRAIT_CHEM_RESISTANT)) {\n                addictionChance /= 2;\n            }\n\n            if (perk_level(obj_dude, PERK_FLOWER_CHILD)) {\n                addictionChance /= 2;\n            }\n        }\n\n        if (roll_random(1, 100) <= addictionChance) {\n            insert_withdrawal(critter, 1, proto->item.data.drug.withdrawalOnset, proto->item.data.drug.withdrawalEffect, item->pid);\n\n            if (critter == obj_dude) {\n                // NOTE: Uninline.\n                item_d_set_addict(item->pid);\n            }\n        }\n    }\n\n    return 1;\n}\n\n// 0x47A178\nint item_d_clear(Object* obj, void* data)\n{\n    if (isPartyMember(obj)) {\n        return 0;\n    }\n\n    item_d_process(obj, data);\n\n    return 1;\n}\n\n// 0x47A198\nint item_d_process(Object* obj, void* data)\n{\n    DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)data;\n\n    if (obj == NULL) {\n        return 0;\n    }\n\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n        return 0;\n    }\n\n    perform_drug_effect(obj, drugEffectEvent->stats, drugEffectEvent->modifiers, false);\n\n    if (!(obj->data.critter.combat.results & DAM_DEAD)) {\n        return 0;\n    }\n\n    return 1;\n}\n\n// 0x47A1D0\nint item_d_load(File* stream, void** dataPtr)\n{\n    DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)mem_malloc(sizeof(*drugEffectEvent));\n    if (drugEffectEvent == NULL) {\n        return -1;\n    }\n\n    if (db_freadIntCount(stream, drugEffectEvent->stats, 3) == -1) goto err;\n    if (db_freadIntCount(stream, drugEffectEvent->modifiers, 3) == -1) goto err;\n\n    *dataPtr = drugEffectEvent;\n    return 0;\n\nerr:\n\n    mem_free(drugEffectEvent);\n    return -1;\n}\n\n// 0x47A254\nint item_d_save(File* stream, void* data)\n{\n    DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)data;\n\n    if (db_fwriteIntCount(stream, drugEffectEvent->stats, 3) == -1) return -1;\n    if (db_fwriteIntCount(stream, drugEffectEvent->modifiers, 3) == -1) return -1;\n\n    return 0;\n}\n\n// 0x47A290\nstatic int insert_withdrawal(Object* obj, int a2, int duration, int perk, int pid)\n{\n    WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)mem_malloc(sizeof(*withdrawalEvent));\n    if (withdrawalEvent == NULL) {\n        return -1;\n    }\n\n    withdrawalEvent->field_0 = a2;\n    withdrawalEvent->pid = pid;\n    withdrawalEvent->perk = perk;\n\n    if (queue_add(600 * duration, obj, withdrawalEvent, EVENT_TYPE_WITHDRAWAL) == -1) {\n        mem_free(withdrawalEvent);\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x47A2FC\nint item_wd_clear(Object* obj, void* data)\n{\n    WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data;\n\n    if (isPartyMember(obj)) {\n        return 0;\n    }\n\n    if (!withdrawalEvent->field_0) {\n        perform_withdrawal_end(obj, withdrawalEvent->perk);\n    }\n\n    return 1;\n}\n\n// 0x47A324\nstatic int item_wd_clear_all(Object* a1, void* data)\n{\n    WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data;\n\n    if (a1 != wd_obj) {\n        return 0;\n    }\n\n    if (pid_to_gvar(withdrawalEvent->pid) != wd_gvar) {\n        return 0;\n    }\n\n    if (!withdrawalEvent->field_0) {\n        perform_withdrawal_end(wd_obj, withdrawalEvent->perk);\n    }\n\n    insert_withdrawal(a1, 1, wd_onset, withdrawalEvent->perk, withdrawalEvent->pid);\n\n    wd_obj = NULL;\n\n    return 1;\n}\n\n// 0x47A384\nint item_wd_process(Object* obj, void* data)\n{\n    WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data;\n\n    if (withdrawalEvent->field_0) {\n        perform_withdrawal_start(obj, withdrawalEvent->perk, withdrawalEvent->pid);\n    } else {\n        if (withdrawalEvent->perk == PERK_JET_ADDICTION) {\n            return 0;\n        }\n\n        perform_withdrawal_end(obj, withdrawalEvent->perk);\n\n        if (obj == obj_dude) {\n            // NOTE: Uninline.\n            item_d_unset_addict(withdrawalEvent->pid);\n        }\n    }\n\n    if (obj == obj_dude) {\n        return 1;\n    }\n\n    return 0;\n}\n\n// 0x47A404\nint item_wd_load(File* stream, void** dataPtr)\n{\n    WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)mem_malloc(sizeof(*withdrawalEvent));\n    if (withdrawalEvent == NULL) {\n        return -1;\n    }\n\n    if (db_freadInt(stream, &(withdrawalEvent->field_0)) == -1) goto err;\n    if (db_freadInt(stream, &(withdrawalEvent->pid)) == -1) goto err;\n    if (db_freadInt(stream, &(withdrawalEvent->perk)) == -1) goto err;\n\n    *dataPtr = withdrawalEvent;\n    return 0;\n\nerr:\n\n    mem_free(withdrawalEvent);\n    return -1;\n}\n\n// 0x47A484\nint item_wd_save(File* stream, void* data)\n{\n    WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data;\n\n    if (db_fwriteInt(stream, withdrawalEvent->field_0) == -1) return -1;\n    if (db_fwriteInt(stream, withdrawalEvent->pid) == -1) return -1;\n    if (db_fwriteInt(stream, withdrawalEvent->perk) == -1) return -1;\n\n    return 0;\n}\n\n// 0x47A4C4\nstatic void perform_withdrawal_start(Object* obj, int perk, int pid)\n{\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n        debug_printf(\"\\nERROR: perform_withdrawal_start: Was called on non-critter!\");\n        return;\n    }\n\n    perk_add_effect(obj, perk);\n\n    if (obj == obj_dude) {\n        char* description = perk_description(perk);\n        display_print(description);\n    }\n\n    int duration = 10080;\n    if (obj == obj_dude) {\n        if (trait_level(TRAIT_CHEM_RELIANT)) {\n            duration /= 2;\n        }\n\n        if (perk_level(obj, PERK_FLOWER_CHILD)) {\n            duration /= 2;\n        }\n    }\n\n    insert_withdrawal(obj, 0, duration, perk, pid);\n}\n\n// 0x47A558\nstatic void perform_withdrawal_end(Object* obj, int perk)\n{\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n        debug_printf(\"\\nERROR: perform_withdrawal_end: Was called on non-critter!\");\n        return;\n    }\n\n    perk_remove_effect(obj, perk);\n\n    if (obj == obj_dude) {\n        MessageListItem messageListItem;\n        messageListItem.num = 3;\n        if (message_search(&item_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n    }\n}\n\n// 0x47A5B4\nstatic int pid_to_gvar(int drugPid)\n{\n    for (int index = 0; index < ADDICTION_COUNT; index++) {\n        DrugDescription* drugDescription = &(drugInfoList[index]);\n        if (drugDescription->drugPid == drugPid) {\n            return drugDescription->gvar;\n        }\n    }\n\n    return -1;\n}\n\n// NOTE: Inlined.\n//\n// 0x47A5E8\nvoid item_d_set_addict(int drugPid)\n{\n    int gvar = pid_to_gvar(drugPid);\n    if (gvar != -1) {\n        game_global_vars[gvar] = 1;\n    }\n\n    pc_flag_on(DUDE_STATE_ADDICTED);\n}\n\n// NOTE: Inlined.\n//\n// 0x47A60C\nvoid item_d_unset_addict(int drugPid)\n{\n    int gvar = pid_to_gvar(drugPid);\n    if (gvar != -1) {\n        game_global_vars[gvar] = 0;\n    }\n\n    if (!item_d_check_addict(-1)) {\n        pc_flag_off(DUDE_STATE_ADDICTED);\n    }\n}\n\n// Returns `true` if dude has addiction to item with given pid or any addition\n// if [pid] is -1.\n//\n// 0x47A640\nbool item_d_check_addict(int drugPid)\n{\n    for (int index = 0; index < ADDICTION_COUNT; index++) {\n        DrugDescription* drugDescription = &(drugInfoList[index]);\n        if (drugPid == -1 || drugPid == drugDescription->drugPid) {\n            if (game_global_vars[drugDescription->gvar] != 0) {\n                return true;\n            } else {\n                return false;\n            }\n        }\n    }\n\n    return false;\n}\n\n// item_caps_total\n// 0x47A6A8\nint item_caps_total(Object* obj)\n{\n    int amount = 0;\n\n    Inventory* inventory = &(obj->data.inventory);\n    for (int i = 0; i < inventory->length; i++) {\n        InventoryItem* inventoryItem = &(inventory->items[i]);\n        Object* item = inventoryItem->item;\n\n        if (item->pid == PROTO_ID_MONEY) {\n            amount += inventoryItem->quantity;\n        } else {\n            if (item_get_type(item) == ITEM_TYPE_CONTAINER) {\n                // recursively collect amount of caps in container\n                amount += item_caps_total(item);\n            }\n        }\n    }\n\n    return amount;\n}\n\n// item_caps_adjust\n// 0x47A6F8\nint item_caps_adjust(Object* obj, int amount)\n{\n    int caps = item_caps_total(obj);\n    if (amount < 0 && caps < -amount) {\n        return -1;\n    }\n\n    if (amount <= 0 || caps != 0) {\n        Inventory* inventory = &(obj->data.inventory);\n\n        for (int index = 0; index < inventory->length && amount != 0; index++) {\n            InventoryItem* inventoryItem = &(inventory->items[index]);\n            Object* item = inventoryItem->item;\n            if (item->pid == PROTO_ID_MONEY) {\n                if (amount <= 0 && -amount >= inventoryItem->quantity) {\n                    obj_erase_object(item, NULL);\n\n                    amount += inventoryItem->quantity;\n\n                    // NOTE: Uninline.\n                    item_compact(index, inventory);\n\n                    index = -1;\n                } else {\n                    inventoryItem->quantity += amount;\n                    amount = 0;\n                }\n            }\n        }\n\n        for (int index = 0; index < inventory->length && amount != 0; index++) {\n            InventoryItem* inventoryItem = &(inventory->items[index]);\n            Object* item = inventoryItem->item;\n            if (item_get_type(item) == ITEM_TYPE_CONTAINER) {\n                int capsInContainer = item_caps_total(item);\n                if (amount <= 0 || capsInContainer <= 0) {\n                    if (amount < 0) {\n                        if (capsInContainer < -amount) {\n                            if (item_caps_adjust(item, capsInContainer) == 0) {\n                                amount += capsInContainer;\n                            }\n                        } else {\n                            if (item_caps_adjust(item, amount) == 0) {\n                                amount = 0;\n                            }\n                        }\n                    }\n                } else {\n                    if (item_caps_adjust(item, amount) == 0) {\n                        amount = 0;\n                    }\n                }\n            }\n        }\n\n        return 0;\n    }\n\n    Object* item;\n    if (obj_pid_new(&item, PROTO_ID_MONEY) == 0) {\n        obj_disconnect(item, NULL);\n        if (item_add_force(obj, item, amount) != 0) {\n            obj_erase_object(item, NULL);\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x47A8C8\nint item_caps_get_amount(Object* item)\n{\n    if (item->pid != PROTO_ID_MONEY) {\n        return -1;\n    }\n\n    return item->data.item.misc.charges;\n}\n\n// 0x47A8D8\nint item_caps_set_amount(Object* item, int amount)\n{\n    if (item->pid != PROTO_ID_MONEY) {\n        return -1;\n    }\n\n    item->data.item.misc.charges = amount;\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/item.h",
    "content": "#ifndef FALLOUT_GAME_ITEM_H_\n#define FALLOUT_GAME_ITEM_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/object_types.h\"\n\n#define ADDICTION_COUNT 9\n\ntypedef enum AttackType {\n    ATTACK_TYPE_NONE,\n    ATTACK_TYPE_UNARMED,\n    ATTACK_TYPE_MELEE,\n    ATTACK_TYPE_THROW,\n    ATTACK_TYPE_RANGED,\n    ATTACK_TYPE_COUNT,\n} AttackType;\n\ntypedef struct DrugDescription {\n    int drugPid;\n    int gvar;\n    int field_8;\n} DrugDescription;\n\nextern DrugDescription drugInfoList[ADDICTION_COUNT];\n\nint item_init();\nvoid item_reset();\nvoid item_exit();\nint item_load(File* stream);\nint item_save(File* stream);\nint item_add_mult(Object* owner, Object* itemToAdd, int quantity);\nint item_add_force(Object* owner, Object* itemToAdd, int quantity);\nint item_remove_mult(Object* a1, Object* a2, int quantity);\nint item_move(Object* a1, Object* a2, Object* a3, int quantity);\nint item_move_force(Object* a1, Object* a2, Object* a3, int quantity);\nvoid item_move_all(Object* a1, Object* a2);\nint item_move_all_hidden(Object* a1, Object* a2);\nint item_destroy_all_hidden(Object* a1);\nint item_drop_all(Object* critter, int tile);\nchar* item_name(Object* obj);\nchar* item_description(Object* obj);\nint item_get_type(Object* item);\nint item_material(Object* item);\nint item_size(Object* obj);\nint item_weight(Object* item);\nint item_cost(Object* obj);\nint item_total_cost(Object* obj);\nint item_total_weight(Object* obj);\nbool item_grey(Object* item_obj);\nint item_inv_fid(Object* obj);\nObject* item_hit_with(Object* critter, int hitMode);\nint item_mp_cost(Object* obj, int hitMode, bool aiming);\nint item_count(Object* obj, Object* a2);\nint item_queued(Object* obj);\nObject* item_replace(Object* a1, Object* a2, int a3);\nint item_is_hidden(Object* obj);\nint item_w_subtype(Object* a1, int a2);\nint item_w_skill(Object* a1, int a2);\nint item_w_skill_level(Object* a1, int a2);\nint item_w_damage_min_max(Object* weapon, int* minDamagePtr, int* maxDamagePtr);\nint item_w_damage(Object* critter, int hitMode);\nint item_w_damage_type(Object* critter, Object* weapon);\nint item_w_is_2handed(Object* weapon);\nint item_w_anim(Object* critter, int hitMode);\nint item_w_anim_weap(Object* weapon, int hitMode);\nint item_w_max_ammo(Object* ammoOrWeapon);\nint item_w_curr_ammo(Object* ammoOrWeapon);\nint item_w_caliber(Object* ammoOrWeapon);\nvoid item_w_set_curr_ammo(Object* ammoOrWeapon, int quantity);\nint item_w_try_reload(Object* critter, Object* weapon);\nbool item_w_can_reload(Object* weapon, Object* ammo);\nint item_w_reload(Object* weapon, Object* ammo);\nint item_w_range(Object* critter, int hitMode);\nint item_w_mp_cost(Object* critter, int hitMode, bool aiming);\nint item_w_min_st(Object* weapon);\nint item_w_crit_fail(Object* weapon);\nint item_w_perk(Object* weapon);\nint item_w_rounds(Object* weapon);\nint item_w_anim_code(Object* weapon);\nint item_w_proj_pid(Object* weapon);\nint item_w_ammo_pid(Object* weapon);\nchar item_w_sound_id(Object* weapon);\nint item_w_called_shot(Object* critter, int hitMode);\nint item_w_can_unload(Object* weapon);\nObject* item_w_unload(Object* weapon);\nint item_w_primary_mp_cost(Object* weapon);\nint item_w_secondary_mp_cost(Object* weapon);\nint item_w_compute_ammo_cost(Object* obj, int* inout_a2);\nbool item_w_is_grenade(Object* weapon);\nint item_w_area_damage_radius(Object* weapon, int hitMode);\nint item_w_grenade_dmg_radius(Object* weapon);\nint item_w_rocket_dmg_radius(Object* weapon);\nint item_w_ac_adjust(Object* weapon);\nint item_w_dr_adjust(Object* weapon);\nint item_w_dam_mult(Object* weapon);\nint item_w_dam_div(Object* weapon);\nint item_ar_ac(Object* armor);\nint item_ar_dr(Object* armor, int damageType);\nint item_ar_dt(Object* armor, int damageType);\nint item_ar_perk(Object* armor);\nint item_ar_male_fid(Object* armor);\nint item_ar_female_fid(Object* armor);\nint item_m_max_charges(Object* miscItem);\nint item_m_curr_charges(Object* miscItem);\nint item_m_set_charges(Object* miscItem, int charges);\nint item_m_cell(Object* miscItem);\nint item_m_cell_pid(Object* miscItem);\nbool item_m_uses_charges(Object* obj);\nint item_m_use_charged_item(Object* critter, Object* item);\nint item_m_dec_charges(Object* miscItem);\nint item_m_trickle(Object* item_obj, void* data);\nbool item_m_on(Object* obj);\nint item_m_turn_on(Object* item_obj);\nint item_m_turn_off(Object* item_obj);\nint item_m_turn_off_from_queue(Object* obj, void* data);\nint item_c_max_size(Object* container);\nint item_c_curr_size(Object* container);\nint item_a_ac_adjust(Object* armor);\nint item_a_dr_adjust(Object* armor);\nint item_a_dam_mult(Object* armor);\nint item_a_dam_div(Object* armor);\nint item_d_take_drug(Object* critter_obj, Object* item_obj);\nint item_d_clear(Object* obj, void* data);\nint item_d_process(Object* obj, void* data);\nint item_d_load(File* stream, void** dataPtr);\nint item_d_save(File* stream, void* data);\nint item_wd_clear(Object* obj, void* a2);\nint item_wd_process(Object* obj, void* data);\nint item_wd_load(File* stream, void** dataPtr);\nint item_wd_save(File* stream, void* data);\nvoid item_d_set_addict(int drugPid);\nvoid item_d_unset_addict(int drugPid);\nbool item_d_check_addict(int drugPid);\nint item_caps_total(Object* obj);\nint item_caps_adjust(Object* obj, int amount);\nint item_caps_get_amount(Object* obj);\nint item_caps_set_amount(Object* obj, int a2);\n\n#endif /* FALLOUT_GAME_ITEM_H_ */\n"
  },
  {
    "path": "src/game/light.c",
    "content": "#include \"game/light.h\"\n\n#include <math.h>\n\n#include \"game/map_defs.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/tile.h\"\n\n// 0x51923C\nstatic int ambient_light = LIGHT_LEVEL_MAX;\n\n// 0x59E994\nstatic int tile_intensity[ELEVATION_COUNT][HEX_GRID_SIZE];\n\n// 0x47A8F0\nint light_init()\n{\n    light_reset_tiles();\n    return 0;\n}\n\n// NOTE: From OS X.\nvoid light_reset()\n{\n    light_reset_tiles();\n}\n\n// NOTE: From OS X.\nvoid light_exit()\n{\n    light_reset_tiles();\n}\n\n// 0x47A8F8\nint light_get_ambient()\n{\n    return ambient_light;\n}\n\n// 0x47A908\nvoid light_set_ambient(int lightLevel, bool shouldUpdateScreen)\n{\n    int normalizedLightLevel;\n    int oldLightLevel;\n\n    normalizedLightLevel = lightLevel + perk_level(obj_dude, PERK_NIGHT_VISION) * LIGHT_LEVEL_NIGHT_VISION_BONUS;\n\n    if (normalizedLightLevel < LIGHT_LEVEL_MIN) {\n        normalizedLightLevel = LIGHT_LEVEL_MIN;\n    }\n\n    if (normalizedLightLevel > LIGHT_LEVEL_MAX) {\n        normalizedLightLevel = LIGHT_LEVEL_MAX;\n    }\n\n    oldLightLevel = ambient_light;\n    ambient_light = normalizedLightLevel;\n\n    if (shouldUpdateScreen) {\n        if (oldLightLevel != normalizedLightLevel) {\n            tile_refresh_display();\n        }\n    }\n}\n\n// NOTE: From OS X.\nvoid light_increase_ambient(int value, bool shouldUpdateScreen)\n{\n    light_set_ambient(ambient_light + value, shouldUpdateScreen);\n}\n\n// 0x47A96C\nvoid light_decrease_ambient(int value, bool shouldUpdateScreen)\n{\n    light_set_ambient(ambient_light - value, shouldUpdateScreen);\n}\n\n// 0x47A980\nint light_get_tile(int elevation, int tile)\n{\n    int result;\n\n    if (!elevationIsValid(elevation)) {\n        return 0;\n    }\n\n    if (!hexGridTileIsValid(tile)) {\n        return 0;\n    }\n\n    result = tile_intensity[elevation][tile];\n    if (result >= LIGHT_LEVEL_MAX) {\n        result = LIGHT_LEVEL_MAX;\n    }\n\n    return result;\n}\n\n// 0x47A9C4\nint light_get_tile_true(int elevation, int tile)\n{\n    if (!elevationIsValid(elevation)) {\n        return 0;\n    }\n\n    if (!hexGridTileIsValid(tile)) {\n        return 0;\n    }\n\n    return tile_intensity[elevation][tile];\n}\n\n// 0x47A9EC\nvoid light_set_tile(int elevation, int tile, int lightIntensity)\n{\n    if (!elevationIsValid(elevation)) {\n        return;\n    }\n\n    if (!hexGridTileIsValid(tile)) {\n        return;\n    }\n\n    tile_intensity[elevation][tile] = lightIntensity;\n}\n\n// 0x47AA10\nvoid light_add_to_tile(int elevation, int tile, int lightIntensity)\n{\n    if (!elevationIsValid(elevation)) {\n        return;\n    }\n\n    if (!hexGridTileIsValid(tile)) {\n        return;\n    }\n\n    tile_intensity[elevation][tile] += lightIntensity;\n}\n\n// 0x47AA48\nvoid light_subtract_from_tile(int elevation, int tile, int lightIntensity)\n{\n    if (!elevationIsValid(elevation)) {\n        return;\n    }\n\n    if (!hexGridTileIsValid(tile)) {\n        return;\n    }\n\n    tile_intensity[elevation][tile] -= lightIntensity;\n}\n\n// 0x47AA84\nvoid light_reset_tiles()\n{\n    int elevation;\n    int tile;\n\n    for (elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        for (tile = 0; tile < HEX_GRID_SIZE; tile++) {\n            tile_intensity[elevation][tile] = 655;\n        }\n    }\n}\n"
  },
  {
    "path": "src/game/light.h",
    "content": "#ifndef FALLOUT_GAME_LIGHT_H_\n#define FALLOUT_GAME_LIGHT_H_\n\n#include <stdbool.h>\n\n#define LIGHT_LEVEL_MAX 65536\n#define LIGHT_LEVEL_MIN (LIGHT_LEVEL_MAX / 4)\n\n// 20% of max light per \"Night Vision\" rank\n#define LIGHT_LEVEL_NIGHT_VISION_BONUS (LIGHT_LEVEL_MAX / 5)\n\ntypedef void(AdjustLightIntensityProc)(int elevation, int tile, int intensity);\n\nint light_init();\nvoid light_reset();\nvoid light_exit();\nint light_get_ambient();\nvoid light_set_ambient(int lightLevel, bool shouldUpdateScreen);\nvoid light_increase_ambient(int value, bool shouldUpdateScreen);\nvoid light_decrease_ambient(int value, bool shouldUpdateScreen);\nint light_get_tile(int elevation, int tile);\nint light_get_tile_true(int elevation, int tile);\nvoid light_set_tile(int elevation, int tile, int intensity);\nvoid light_add_to_tile(int elevation, int tile, int intensity);\nvoid light_subtract_from_tile(int elevation, int tile, int intensity);\nvoid light_reset_tiles();\n\n#endif /* FALLOUT_GAME_LIGHT_H_ */\n"
  },
  {
    "path": "src/game/lip_sync.c",
    "content": "#include \"game/lip_sync.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"int/audio.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/memory.h\"\n#include \"int/sound.h\"\n\nstatic char* lips_fix_string(const char* fileName, size_t length);\nstatic int lips_stop_speech();\nstatic int lips_read_phoneme_type(unsigned char* phoneme_type, File* stream);\nstatic int lips_read_marker_type(SpeechMarker* marker_type, File* stream);\nstatic int lips_read_lipsynch_info(LipsData* a1, File* stream);\nstatic int lips_make_speech();\n\n// 0x519240\nunsigned char head_phoneme_current = 0;\n\n// 0x519241\nunsigned char head_phoneme_drawn = 0;\n\n// 0x519244\nint head_marker_current = 0;\n\n// 0x519248\nbool lips_draw_head = true;\n\n// 0x51924C\nLipsData lip_info = {\n    2,\n    22528,\n    0,\n    NULL,\n    -1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    50,\n    100,\n    0,\n    0,\n    0,\n    \"TEST\",\n    \"VOC\",\n    \"TXT\",\n    \"LIP\",\n};\n\n// 0x5193B4\nint speechStartTime = 0;\n\n// 0x613CA0\nstatic char lips_subdir_name[14];\n\n// 0x47AAC0\nstatic char* lips_fix_string(const char* fileName, size_t length)\n{\n    // 0x613CAE\n    static char tmp_str[50];\n\n    strncpy(tmp_str, fileName, length);\n    return tmp_str;\n}\n\n// 0x47AAD8\nvoid lips_bkg_proc()\n{\n    int v0;\n    SpeechMarker* speech_marker;\n    int v5;\n\n    v0 = head_marker_current;\n\n    if ((lip_info.flags & LIPS_FLAG_0x02) != 0) {\n        int v1 = soundGetPosition(lip_info.sound);\n\n        speech_marker = &(lip_info.markers[v0]);\n        while (v1 > speech_marker->position) {\n            head_phoneme_current = lip_info.phonemes[v0];\n            v0++;\n\n            if (v0 >= lip_info.marker_count) {\n                v0 = 0;\n                head_phoneme_current = lip_info.phonemes[0];\n\n                if ((lip_info.flags & LIPS_FLAG_0x01) == 0) {\n                    // NOTE: Uninline.\n                    lips_stop_speech();\n                    v0 = head_marker_current;\n                }\n\n                break;\n            }\n\n            speech_marker = &(lip_info.markers[v0]);\n        }\n\n        if (v0 >= lip_info.marker_count - 1) {\n            head_marker_current = v0;\n\n            v5 = 0;\n            if (lip_info.marker_count <= 5) {\n                debug_printf(\"Error: Too few markers to stop speech!\");\n            } else {\n                v5 = 3;\n            }\n\n            speech_marker = &(lip_info.markers[v5]);\n            if (v1 < speech_marker->position) {\n                v0 = 0;\n                head_phoneme_current = lip_info.phonemes[0];\n\n                if ((lip_info.flags & LIPS_FLAG_0x01) == 0) {\n                    // NOTE: Uninline.\n                    lips_stop_speech();\n                    v0 = head_marker_current;\n                }\n            }\n        }\n    }\n\n    if (head_phoneme_drawn != head_phoneme_current) {\n        head_phoneme_drawn = head_phoneme_current;\n        lips_draw_head = true;\n    }\n\n    head_marker_current = v0;\n\n    soundContinueAll();\n}\n\n// 0x47AC2C\nint lips_play_speech()\n{\n    lip_info.flags |= LIPS_FLAG_0x02;\n    head_marker_current = 0;\n\n    if (soundSetPosition(lip_info.sound, lip_info.field_20) != 0) {\n        debug_printf(\"Failed set of start_offset!\\n\");\n    }\n\n    int v2 = head_marker_current;\n    while (1) {\n        head_marker_current = v2;\n\n        SpeechMarker* speechEntry = &(lip_info.markers[v2]);\n        if (lip_info.field_20 <= speechEntry->position) {\n            break;\n        }\n\n        v2++;\n\n        head_phoneme_current = lip_info.phonemes[v2];\n    }\n\n    int speechVolume = gsound_speech_volume_get();\n    soundVolume(lip_info.sound, (int)(speechVolume * 0.69));\n\n    speechStartTime = get_time();\n\n    if (soundPlay(lip_info.sound) != 0) {\n        debug_printf(\"Failed play!\\n\");\n\n        // NOTE: Uninline.\n        lips_stop_speech();\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x47AD2C\nstatic int lips_stop_speech()\n{\n    head_marker_current = 0;\n    soundStop(lip_info.sound);\n    lip_info.flags &= ~(LIPS_FLAG_0x01 | LIPS_FLAG_0x02);\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x47AD4C\nstatic int lips_read_phoneme_type(unsigned char* phoneme_type, File* stream)\n{\n    return db_freadByte(stream, phoneme_type);\n}\n\n// NOTE: Inlined.\n//\n// 0x47AD5C\nstatic int lips_read_marker_type(SpeechMarker* marker_type, File* stream)\n{\n    int marker;\n\n    // Marker is read into temporary variable.\n    if (db_freadInt(stream, &marker) == -1) return -1;\n\n    // Position is read directly into struct.\n    if (db_freadLong(stream, &(marker_type->position)) == -1) return -1;\n\n    marker_type->marker = marker;\n\n    return 0;\n}\n\n// 0x47AD98\nstatic int lips_read_lipsynch_info(LipsData* lipsData, File* stream)\n{\n    int sound;\n    int field_14;\n    int phonemes;\n    int markers;\n\n    if (db_freadLong(stream, &(lipsData->version)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_4)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->flags)) == -1) return -1;\n    if (db_freadInt(stream, &(sound)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_10)) == -1) return -1;\n    if (db_freadInt(stream, &(field_14)) == -1) return -1;\n    if (db_freadInt(stream, &(phonemes)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_1C)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_20)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->phoneme_count)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_28)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->marker_count)) == -1) return -1;\n    if (db_freadInt(stream, &(markers)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_34)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_38)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_3C)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_40)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_44)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_48)) == -1) return -1;\n    if (db_freadLong(stream, &(lipsData->field_4C)) == -1) return -1;\n    if (db_freadByteCount(stream, lipsData->field_50, 8) == -1) return -1;\n    if (db_freadByteCount(stream, lipsData->field_58, 4) == -1) return -1;\n    if (db_freadByteCount(stream, lipsData->field_5C, 4) == -1) return -1;\n    if (db_freadByteCount(stream, lipsData->field_60, 4) == -1) return -1;\n    if (db_freadByteCount(stream, lipsData->field_64, 260) == -1) return -1;\n\n    // TODO: What for?\n    lipsData->sound = (Sound*)sound;\n    lipsData->field_14 = (void*)field_14;\n    lipsData->phonemes = (unsigned char*)phonemes;\n    lipsData->markers = (SpeechMarker*)markers;\n\n    return 0;\n}\n\n// lips_load_file\n// 0x47AFAC\nint lips_load_file(const char* audioFileName, const char* headFileName)\n{\n    char* sep;\n    int i;\n    char v60[16];\n\n    SpeechMarker* speech_marker;\n    SpeechMarker* prev_speech_marker;\n\n    char path[260];\n    strcpy(path, \"SOUND\\\\SPEECH\\\\\");\n\n    strcpy(lips_subdir_name, headFileName);\n\n    strcat(path, lips_subdir_name);\n\n    strcat(path, \"\\\\\");\n\n    sep = strchr(path, '.');\n    if (sep != NULL) {\n        *sep = '\\0';\n    }\n\n    strcpy(v60, audioFileName);\n\n    sep = strchr(v60, '.');\n    if (sep != NULL) {\n        *sep = '\\0';\n    }\n\n    strcpy(lip_info.field_50, v60);\n\n    strcat(path, lips_fix_string(lip_info.field_50, sizeof(lip_info.field_50)));\n    strcat(path, \".\");\n    strcat(path, lip_info.field_60);\n\n    lips_free_speech();\n\n    // FIXME: stream is not closed if any error is encountered during reading.\n    File* stream = db_fopen(path, \"rb\");\n    if (stream != NULL) {\n        if (db_freadLong(stream, &(lip_info.version)) == -1) {\n            return -1;\n        }\n\n        if (lip_info.version == 1) {\n            debug_printf(\"\\nLoading old save-file version (1)\");\n\n            if (db_fseek(stream, 0, SEEK_SET) != 0) {\n                return -1;\n            }\n\n            if (lips_read_lipsynch_info(&lip_info, stream) != 0) {\n                return -1;\n            }\n        } else if (lip_info.version == 2) {\n            debug_printf(\"\\nLoading current save-file version (2)\");\n\n            if (db_freadLong(stream, &(lip_info.field_4)) == -1) return -1;\n            if (db_freadLong(stream, &(lip_info.flags)) == -1) return -1;\n            if (db_freadLong(stream, &(lip_info.field_10)) == -1) return -1;\n            if (db_freadLong(stream, &(lip_info.field_1C)) == -1) return -1;\n            if (db_freadLong(stream, &(lip_info.phoneme_count)) == -1) return -1;\n            if (db_freadLong(stream, &(lip_info.field_28)) == -1) return -1;\n            if (db_freadLong(stream, &(lip_info.marker_count)) == -1) return -1;\n            if (db_freadByteCount(stream, lip_info.field_50, 8) == -1) return -1;\n            if (db_freadByteCount(stream, lip_info.field_58, 4) == -1) return -1;\n        } else {\n            debug_printf(\"\\nError: Lips file WRONG version: %s!\", path);\n        }\n    }\n\n    lip_info.phonemes = (unsigned char*)mem_malloc(lip_info.phoneme_count);\n    if (lip_info.phonemes == NULL) {\n        debug_printf(\"Out of memory in lips_load_file.'\\n\");\n        return -1;\n    }\n\n    if (stream != NULL) {\n        for (i = 0; i < lip_info.phoneme_count; i++) {\n            if (lips_read_phoneme_type(&(lip_info.phonemes[i]), stream) != 0) {\n                debug_printf(\"lips_load_file: Error reading phoneme type.\\n\");\n                return -1;\n            }\n        }\n\n        for (i = 0; i < lip_info.phoneme_count; i++) {\n            unsigned char phoneme = lip_info.phonemes[i];\n            if (phoneme >= PHONEME_COUNT) {\n                debug_printf(\"\\nLoad error: Speech phoneme %d is invalid (%d)!\", i, phoneme);\n            }\n        }\n    }\n\n    lip_info.markers = (SpeechMarker*)mem_malloc(sizeof(*speech_marker) * lip_info.marker_count);\n    if (lip_info.markers == NULL) {\n        debug_printf(\"Out of memory in lips_load_file.'\\n\");\n        return -1;\n    }\n\n    if (stream != NULL) {\n        for (i = 0; i < lip_info.marker_count; i++) {\n            // NOTE: Uninline.\n            if (lips_read_marker_type(&(lip_info.markers[i]), stream) != 0) {\n                debug_printf(\"lips_load_file: Error reading marker type.\");\n                return -1;\n            }\n        }\n\n        speech_marker = &(lip_info.markers[0]);\n\n        if (speech_marker->marker != 1 && speech_marker->marker != 0) {\n            debug_printf(\"\\nLoad error: Speech marker 0 is invalid (%d)!\", speech_marker->marker);\n        }\n\n        if (speech_marker->position != 0) {\n            debug_printf(\"Load error: Speech marker 0 has invalid position(%d)!\", speech_marker->position);\n        }\n\n        for (i = 1; i < lip_info.marker_count; i++) {\n            speech_marker = &(lip_info.markers[i]);\n            prev_speech_marker = &(lip_info.markers[i - 1]);\n\n            if (speech_marker->marker != 1 && speech_marker->marker != 0) {\n                debug_printf(\"\\nLoad error: Speech marker %d is invalid (%d)!\", i, speech_marker->marker);\n            }\n\n            if (speech_marker->position < prev_speech_marker->position) {\n                debug_printf(\"Load error: Speech marker %d has invalid position(%d)!\", i, speech_marker->position);\n            }\n        }\n    }\n\n    if (stream != NULL) {\n        db_fclose(stream);\n    }\n\n    lip_info.field_38 = 0;\n    lip_info.field_34 = 0;\n    lip_info.field_48 = 0;\n    lip_info.field_20 = 0;\n    lip_info.field_3C = 50;\n    lip_info.field_40 = 100;\n\n    if (lip_info.version == 1) {\n        lip_info.field_4 = 22528;\n    }\n\n    strcpy(lip_info.field_58, \"ACM\");\n    strcpy(lip_info.field_5C, \"TXT\");\n    strcpy(lip_info.field_60, \"LIP\");\n\n    lips_make_speech();\n\n    head_marker_current = 0;\n    head_phoneme_current = lip_info.phonemes[0];\n\n    return 0;\n}\n\n// 0x47B5D0\nstatic int lips_make_speech()\n{\n    if (lip_info.field_14 != NULL) {\n        mem_free(lip_info.field_14);\n        lip_info.field_14 = NULL;\n    }\n\n    char path[MAX_PATH];\n    char* v1 = lips_fix_string(lip_info.field_50, sizeof(lip_info.field_50));\n    sprintf(path, \"%s%s\\\\%s.%s\", \"SOUND\\\\SPEECH\\\\\", lips_subdir_name, v1, \"ACM\");\n\n    if (lip_info.sound != NULL) {\n        soundDelete(lip_info.sound);\n        lip_info.sound = NULL;\n    }\n\n    lip_info.sound = soundAllocate(1, 8);\n    if (lip_info.sound == NULL) {\n        debug_printf(\"\\nsoundAllocate falied in lips_make_speech!\");\n        return -1;\n    }\n\n    if (soundSetFileIO(lip_info.sound, audioOpen, audioCloseFile, audioRead, NULL, audioSeek, NULL, audioFileSize)) {\n        debug_printf(\"Ack!\");\n        debug_printf(\"Error!\");\n    }\n\n    if (soundLoad(lip_info.sound, path)) {\n        soundDelete(lip_info.sound);\n        lip_info.sound = NULL;\n\n        debug_printf(\"lips_make_speech: soundLoad failed with path \");\n        debug_printf(\"%s -- file probably doesn't exist.\\n\", path);\n        return -1;\n    }\n\n    lip_info.field_34 = 8 * (lip_info.field_1C / lip_info.marker_count);\n\n    return 0;\n}\n\n// 0x47B730\nint lips_free_speech()\n{\n    if (lip_info.field_14 != NULL) {\n        mem_free(lip_info.field_14);\n        lip_info.field_14 = NULL;\n    }\n\n    if (lip_info.sound != NULL) {\n        // NOTE: Uninline.\n        lips_stop_speech();\n\n        soundDelete(lip_info.sound);\n\n        lip_info.sound = NULL;\n    }\n\n    if (lip_info.phonemes != NULL) {\n        mem_free(lip_info.phonemes);\n        lip_info.phonemes = NULL;\n    }\n\n    if (lip_info.markers != NULL) {\n        mem_free(lip_info.markers);\n        lip_info.markers = NULL;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/lip_sync.h",
    "content": "#ifndef FALLOUT_GAME_LIP_SYNC_H_\n#define FALLOUT_GAME_LIP_SYNC_H_\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"plib/db/db.h\"\n#include \"int/sound.h\"\n\n#define PHONEME_COUNT (42)\n\ntypedef enum LipsFlags {\n    LIPS_FLAG_0x01 = 0x01,\n    LIPS_FLAG_0x02 = 0x02,\n} LipsFlags;\n\ntypedef struct SpeechMarker {\n    int marker;\n    int position;\n} SpeechMarker;\n\ntypedef struct LipsData {\n    int version;\n    int field_4;\n    int flags;\n    Sound* sound;\n    int field_10;\n    void* field_14;\n    unsigned char* phonemes;\n    int field_1C;\n    int field_20;\n    int phoneme_count;\n    int field_28;\n    int marker_count;\n    SpeechMarker* markers;\n    int field_34;\n    int field_38;\n    int field_3C;\n    int field_40;\n    int field_44;\n    int field_48;\n    int field_4C;\n    char field_50[8];\n    char field_58[4];\n    char field_5C[4];\n    char field_60[4];\n    char field_64[260];\n} LipsData;\n\nextern unsigned char head_phoneme_current;\nextern unsigned char head_phoneme_drawn;\nextern int head_marker_current;\nextern bool lips_draw_head;\nextern LipsData lip_info;\nextern int speechStartTime;\n\nvoid lips_bkg_proc();\nint lips_play_speech();\nint lips_load_file(const char* audioFileName, const char* headFileName);\nint lips_free_speech();\n\n#endif /* FALLOUT_GAME_LIP_SYNC_H_ */\n"
  },
  {
    "path": "src/game/loadsave.c",
    "content": "#include \"game/loadsave.h\"\n\n#include <assert.h>\n#include <direct.h>\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n\n#include \"game/automap.h\"\n#include \"game/editor.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/cycle.h\"\n#include \"game/bmpdlog.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/gz.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gmovie.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/options.h\"\n#include \"game/perk.h\"\n#include \"game/pipboy.h\"\n#include \"game/proto.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n#include \"game/version.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"game/wordwrap.h\"\n#include \"game/worldmap.h\"\n\n#define LOAD_SAVE_SIGNATURE \"FALLOUT SAVE FILE\"\n#define LOAD_SAVE_DESCRIPTION_LENGTH 30\n#define LOAD_SAVE_HANDLER_COUNT 27\n\n#define LSGAME_MSG_NAME \"LSGAME.MSG\"\n\n#define LS_WINDOW_WIDTH 640\n#define LS_WINDOW_HEIGHT 480\n\n#define LS_PREVIEW_WIDTH 224\n#define LS_PREVIEW_HEIGHT 133\n#define LS_PREVIEW_SIZE ((LS_PREVIEW_WIDTH) * (LS_PREVIEW_HEIGHT))\n\n#define LS_COMMENT_WINDOW_X 169\n#define LS_COMMENT_WINDOW_Y 116\n\ntypedef int LoadGameHandler(File* stream);\ntypedef int SaveGameHandler(File* stream);\n\ntypedef enum LoadSaveWindowType {\n    LOAD_SAVE_WINDOW_TYPE_SAVE_GAME,\n    LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT,\n    LOAD_SAVE_WINDOW_TYPE_LOAD_GAME,\n    LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU,\n    LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_LOAD_SLOT,\n} LoadSaveWindowType;\n\ntypedef enum LoadSaveSlotState {\n    SLOT_STATE_EMPTY,\n    SLOT_STATE_OCCUPIED,\n    SLOT_STATE_ERROR,\n    SLOT_STATE_UNSUPPORTED_VERSION,\n} LoadSaveSlotState;\n\ntypedef enum LoadSaveScrollDirection {\n    LOAD_SAVE_SCROLL_DIRECTION_NONE,\n    LOAD_SAVE_SCROLL_DIRECTION_UP,\n    LOAD_SAVE_SCROLL_DIRECTION_DOWN,\n} LoadSaveScrollDirection;\n\ntypedef struct LoadSaveSlotData {\n    char signature[24];\n    short versionMinor;\n    short versionMajor;\n    // TODO: The type is probably char, but it's read with the same function as\n    // reading unsigned chars, which in turn probably result of collapsing\n    // reading functions.\n    unsigned char versionRelease;\n    char characterName[32];\n    char description[LOAD_SAVE_DESCRIPTION_LENGTH];\n    short fileMonth;\n    short fileDay;\n    short fileYear;\n    int fileTime;\n    short gameMonth;\n    short gameDay;\n    short gameYear;\n    int gameTime;\n    short elevation;\n    short map;\n    char fileName[16];\n} LoadSaveSlotData;\n\ntypedef enum LoadSaveFrm {\n    LOAD_SAVE_FRM_BACKGROUND,\n    LOAD_SAVE_FRM_BOX,\n    LOAD_SAVE_FRM_PREVIEW_COVER,\n    LOAD_SAVE_FRM_RED_BUTTON_PRESSED,\n    LOAD_SAVE_FRM_RED_BUTTON_NORMAL,\n    LOAD_SAVE_FRM_ARROW_DOWN_NORMAL,\n    LOAD_SAVE_FRM_ARROW_DOWN_PRESSED,\n    LOAD_SAVE_FRM_ARROW_UP_NORMAL,\n    LOAD_SAVE_FRM_ARROW_UP_PRESSED,\n    LOAD_SAVE_FRM_COUNT,\n} LoadSaveFrm;\n\nstatic int QuickSnapShot();\nstatic int LSGameStart(int windowType);\nstatic int LSGameEnd(int windowType);\nstatic int SaveSlot();\nstatic int LoadSlot(int slot);\nstatic int SaveHeader(int slot);\nstatic int LoadHeader(int slot);\nstatic int GetSlotList();\nstatic void ShowSlotList(int a1);\nstatic void DrawInfoBox(int a1);\nstatic int LoadTumbSlot(int a1);\nstatic int GetComment(int a1);\nstatic int get_input_str2(int win, int doneKeyCode, int cancelKeyCode, char* description, int maxLength, int x, int y, int textColor, int backgroundColor, int flags);\nstatic int DummyFunc(File* stream);\nstatic int PrepLoad(File* stream);\nstatic int EndLoad(File* stream);\nstatic int GameMap2Slot(File* stream);\nstatic int SlotMap2Game(File* stream);\nstatic int mygets(char* dest, File* stream);\nstatic int copy_file(const char* a1, const char* a2);\nstatic int SaveBackup();\nstatic int RestoreSave();\nstatic int LoadObjDudeCid(File* stream);\nstatic int SaveObjDudeCid(File* stream);\nstatic int EraseSave();\n\n// 0x47B7C0\nstatic const int lsgrphs[LOAD_SAVE_FRM_COUNT] = {\n    237, // lsgame.frm - load/save game\n    238, // lsgbox.frm - load/save game\n    239, // lscover.frm - load/save game\n    9, // lilreddn.frm - little red button down\n    8, // lilredup.frm - little red button up\n    181, // dnarwoff.frm - character editor\n    182, // dnarwon.frm - character editor\n    199, // uparwoff.frm - character editor\n    200, // uparwon.frm - character editor\n};\n\n// 0x5193B8\nstatic int slot_cursor = 0;\n\n// 0x5193BC\nstatic bool quick_done = false;\n\n// 0x5193C0\nstatic bool bk_enable = false;\n\n// 0x5193C4\nstatic int map_backup_count = -1;\n\n// 0x5193C8\nstatic int automap_db_flag = 0;\n\n// 0x5193CC\nstatic char* patches = NULL;\n\n// 0x5193D0\nstatic char emgpath[] = \"\\\\FALLOUT\\\\CD\\\\DATA\\\\SAVEGAME\";\n\n// 0x5193EC\nstatic SaveGameHandler* master_save_list[LOAD_SAVE_HANDLER_COUNT] = {\n    DummyFunc,\n    SaveObjDudeCid,\n    scr_game_save,\n    GameMap2Slot,\n    scr_game_save,\n    obj_save_dude,\n    critter_save,\n    critter_kill_count_save,\n    skill_save,\n    roll_save,\n    perk_save,\n    combat_save,\n    combat_ai_save,\n    stat_save,\n    item_save,\n    trait_save,\n    automap_save,\n    save_options,\n    editor_save,\n    wmWorldMap_save,\n    save_pipboy,\n    gmovie_save,\n    skill_use_slot_save,\n    partyMemberSave,\n    queue_save,\n    intface_save,\n    DummyFunc,\n};\n\n// 0x519458\nstatic LoadGameHandler* master_load_list[LOAD_SAVE_HANDLER_COUNT] = {\n    PrepLoad,\n    LoadObjDudeCid,\n    scr_game_load,\n    SlotMap2Game,\n    scr_game_load2,\n    obj_load_dude,\n    critter_load,\n    critter_kill_count_load,\n    skill_load,\n    roll_load,\n    perk_load,\n    combat_load,\n    combat_ai_load,\n    stat_load,\n    item_load,\n    trait_load,\n    automap_load,\n    load_options,\n    editor_load,\n    wmWorldMap_load,\n    load_pipboy,\n    gmovie_load,\n    skill_use_slot_load,\n    partyMemberLoad,\n    queue_load,\n    intface_load,\n    EndLoad,\n};\n\n// 0x5194C4\nstatic int loadingGame = 0;\n\n// 0x613CE0\nstatic Size ginfo[LOAD_SAVE_FRM_COUNT];\n\n// lsgame.msg\n//\n// 0x613D28\nstatic MessageList lsgame_msgfl;\n\n// 0x613D30\nstatic LoadSaveSlotData LSData[10];\n\n// 0x614280\nstatic int LSstatus[10];\n\n// 0x6142A8\nstatic unsigned char* thumbnail_image[2];\n\n// 0x6142B0\nstatic MessageListItem lsgmesg;\n\n// 0x6142C0\nstatic int dbleclkcntr;\n\n// 0x6142C4\nstatic int lsgwin;\n\n// 0x6142C8\nstatic unsigned char* lsbmp[LOAD_SAVE_FRM_COUNT];\n\n// 0x6142EC\nstatic unsigned char* snapshot;\n\n// 0x6142F0\nstatic char str2[MAX_PATH];\n\n// 0x6143F4\nstatic char str0[MAX_PATH];\n\n// 0x6144F8\nstatic char str1[MAX_PATH];\n\n// 0x6145FC\nstatic char str[MAX_PATH];\n\n// 0x614700\nstatic unsigned char* lsgbuf;\n\n// 0x614704\nstatic char gmpath[MAX_PATH];\n\n// 0x614808\nstatic File* flptr;\n\n// 0x61480C\nstatic int ls_error_code;\n\n// 0x614810\nstatic int fontsave;\n\n// 0x614814\nstatic CacheEntry* grphkey[LOAD_SAVE_FRM_COUNT];\n\n// 0x47B7E4\nvoid InitLoadSave()\n{\n    quick_done = false;\n    slot_cursor = 0;\n\n    if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patches)) {\n        debug_printf(\"\\nLOADSAVE: Error reading patches config variable! Using default.\\n\");\n        patches = emgpath;\n    }\n\n    MapDirErase(\"MAPS\\\\\", \"SAV\");\n    MapDirErase(\"PROTO\\\\CRITTERS\\\\\", \"PRO\");\n    MapDirErase(\"PROTO\\\\ITEMS\\\\\", \"PRO\");\n}\n\n// 0x47B85C\nvoid ResetLoadSave()\n{\n    MapDirErase(\"MAPS\\\\\", \"SAV\");\n    MapDirErase(\"PROTO\\\\CRITTERS\\\\\", \"PRO\");\n    MapDirErase(\"PROTO\\\\ITEMS\\\\\", \"PRO\");\n}\n\n// SaveGame\n// 0x47B88C\nint SaveGame(int mode)\n{\n    MessageListItem messageListItem;\n\n    ls_error_code = 0;\n\n    if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patches)) {\n        debug_printf(\"\\nLOADSAVE: Error reading patches config variable! Using default.\\n\");\n        patches = emgpath;\n    }\n\n    if (mode == LOAD_SAVE_MODE_QUICK && quick_done) {\n        sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n        strcat(gmpath, \"SAVE.DAT\");\n\n        flptr = db_fopen(gmpath, \"rb\");\n        if (flptr != NULL) {\n            LoadHeader(slot_cursor);\n            db_fclose(flptr);\n        }\n\n        thumbnail_image[1] = NULL;\n        int v6 = QuickSnapShot();\n        if (v6 == 1) {\n            int v7 = SaveSlot();\n            if (v7 != -1) {\n                v6 = v7;\n            }\n        }\n\n        if (thumbnail_image[1] != NULL) {\n            mem_free(snapshot);\n        }\n\n        gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n        if (v6 != -1) {\n            return 1;\n        }\n\n        if (!message_init(&lsgame_msgfl)) {\n            return -1;\n        }\n\n        char path[MAX_PATH];\n        sprintf(path, \"%s%s\", msg_path, \"LSGAME.MSG\");\n        if (!message_load(&lsgame_msgfl, path)) {\n            return -1;\n        }\n\n        gsound_play_sfx_file(\"iisxxxx1\");\n\n        // Error saving game!\n        strcpy(str0, getmsg(&lsgame_msgfl, &messageListItem, 132));\n        // Unable to save game.\n        strcpy(str1, getmsg(&lsgame_msgfl, &messageListItem, 133));\n\n        const char* body[] = {\n            str1,\n        };\n        dialog_out(str0, body, 1, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n\n        message_exit(&lsgame_msgfl);\n\n        return -1;\n    }\n\n    quick_done = false;\n\n    int windowType = mode == LOAD_SAVE_MODE_QUICK\n        ? LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT\n        : LOAD_SAVE_WINDOW_TYPE_SAVE_GAME;\n    if (LSGameStart(windowType) == -1) {\n        debug_printf(\"\\nLOADSAVE: ** Error loading save game screen data! **\\n\");\n        return -1;\n    }\n\n    if (GetSlotList() == -1) {\n        win_draw(lsgwin);\n\n        gsound_play_sfx_file(\"iisxxxx1\");\n\n        // Error loading save game list!\n        strcpy(str0, getmsg(&lsgame_msgfl, &messageListItem, 106));\n        // Save game directory:\n        strcpy(str1, getmsg(&lsgame_msgfl, &messageListItem, 107));\n\n        sprintf(str2, \"\\\"%s\\\\\\\"\", \"SAVEGAME\");\n\n        // TODO: Check.\n        strcpy(str2, getmsg(&lsgame_msgfl, &messageListItem, 108));\n\n        const char* body[] = {\n            str1,\n            str2,\n        };\n        dialog_out(str0, body, 2, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n\n        LSGameEnd(0);\n\n        return -1;\n    }\n\n    switch (LSstatus[slot_cursor]) {\n    case SLOT_STATE_EMPTY:\n    case SLOT_STATE_ERROR:\n    case SLOT_STATE_UNSUPPORTED_VERSION:\n        buf_to_buf(thumbnail_image[1],\n            LS_PREVIEW_WIDTH - 1,\n            LS_PREVIEW_HEIGHT - 1,\n            LS_PREVIEW_WIDTH,\n            lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n            LS_WINDOW_WIDTH);\n        break;\n    default:\n        LoadTumbSlot(slot_cursor);\n        buf_to_buf(thumbnail_image[0],\n            LS_PREVIEW_WIDTH - 1,\n            LS_PREVIEW_HEIGHT - 1,\n            LS_PREVIEW_WIDTH,\n            lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n            LS_WINDOW_WIDTH);\n        break;\n    }\n\n    ShowSlotList(0);\n    DrawInfoBox(slot_cursor);\n    win_draw(lsgwin);\n\n    dbleclkcntr = 24;\n\n    int rc = -1;\n    int doubleClickSlot = -1;\n    while (rc == -1) {\n        unsigned int tick = get_time();\n        int keyCode = get_input();\n        bool selectionChanged = false;\n        int scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE;\n\n        if (keyCode == KEY_ESCAPE || keyCode == 501 || game_user_wants_to_quit != 0) {\n            rc = 0;\n        } else {\n            switch (keyCode) {\n            case KEY_ARROW_UP:\n                slot_cursor -= 1;\n                if (slot_cursor < 0) {\n                    slot_cursor = 0;\n                }\n                selectionChanged = true;\n                doubleClickSlot = -1;\n                break;\n            case KEY_ARROW_DOWN:\n                slot_cursor += 1;\n                if (slot_cursor > 9) {\n                    slot_cursor = 9;\n                }\n                selectionChanged = true;\n                doubleClickSlot = -1;\n                break;\n            case KEY_HOME:\n                slot_cursor = 0;\n                selectionChanged = true;\n                doubleClickSlot = -1;\n                break;\n            case KEY_END:\n                slot_cursor = 9;\n                selectionChanged = true;\n                doubleClickSlot = -1;\n                break;\n            case 506:\n                scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_UP;\n                break;\n            case 504:\n                scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_DOWN;\n                break;\n            case 502:\n                if (1) {\n                    int mouseX;\n                    int mouseY;\n                    mouse_get_position(&mouseX, &mouseY);\n\n                    slot_cursor = (mouseY - 79) / (3 * text_height() + 4);\n                    if (slot_cursor < 0) {\n                        slot_cursor = 0;\n                    }\n                    if (slot_cursor > 9) {\n                        slot_cursor = 9;\n                    }\n\n                    selectionChanged = true;\n\n                    if (slot_cursor == doubleClickSlot) {\n                        keyCode = 500;\n                        gsound_play_sfx_file(\"ib1p1xx1\");\n                    }\n\n                    doubleClickSlot = slot_cursor;\n                    scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE;\n                }\n                break;\n            case KEY_CTRL_Q:\n            case KEY_CTRL_X:\n            case KEY_F10:\n                game_quit_with_confirm();\n\n                if (game_user_wants_to_quit != 0) {\n                    rc = 0;\n                }\n                break;\n            case KEY_PLUS:\n            case KEY_EQUAL:\n                IncGamma();\n                break;\n            case KEY_MINUS:\n            case KEY_UNDERSCORE:\n                DecGamma();\n                break;\n            case KEY_RETURN:\n                keyCode = 500;\n                break;\n            }\n        }\n\n        if (keyCode == 500) {\n            if (LSstatus[slot_cursor] == SLOT_STATE_OCCUPIED) {\n                rc = 1;\n                // Save game already exists, overwrite?\n                const char* title = getmsg(&lsgame_msgfl, &lsgmesg, 131);\n                if (dialog_out(title, NULL, 0, 169, 131, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_YES_NO) == 0) {\n                    rc = -1;\n                }\n            } else {\n                rc = 1;\n            }\n\n            selectionChanged = true;\n            scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE;\n        }\n\n        if (scrollDirection) {\n            unsigned int scrollVelocity = 4;\n            bool isScrolling = false;\n            int scrollCounter = 0;\n            do {\n                unsigned int start = get_time();\n                scrollCounter += 1;\n\n                if ((!isScrolling && scrollCounter == 1) || (isScrolling && scrollCounter > 14.4)) {\n                    isScrolling = true;\n\n                    if (scrollCounter > 14.4) {\n                        scrollVelocity += 1;\n                        if (scrollVelocity > 24) {\n                            scrollVelocity = 24;\n                        }\n                    }\n\n                    if (scrollDirection == LOAD_SAVE_SCROLL_DIRECTION_UP) {\n                        slot_cursor -= 1;\n                        if (slot_cursor < 0) {\n                            slot_cursor = 0;\n                        }\n                    } else {\n                        slot_cursor += 1;\n                        if (slot_cursor > 9) {\n                            slot_cursor = 9;\n                        }\n                    }\n\n                    // TODO: Does not check for unsupported version error like\n                    // other switches do.\n                    switch (LSstatus[slot_cursor]) {\n                    case SLOT_STATE_EMPTY:\n                    case SLOT_STATE_ERROR:\n                        buf_to_buf(thumbnail_image[1],\n                            LS_PREVIEW_WIDTH - 1,\n                            LS_PREVIEW_HEIGHT - 1,\n                            LS_PREVIEW_WIDTH,\n                            lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n                            LS_WINDOW_WIDTH);\n                        break;\n                    default:\n                        LoadTumbSlot(slot_cursor);\n                        buf_to_buf(thumbnail_image[0],\n                            LS_PREVIEW_WIDTH - 1,\n                            LS_PREVIEW_HEIGHT - 1,\n                            LS_PREVIEW_WIDTH,\n                            lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n                            LS_WINDOW_WIDTH);\n                        break;\n                    }\n\n                    ShowSlotList(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME);\n                    DrawInfoBox(slot_cursor);\n                    win_draw(lsgwin);\n                }\n\n                if (scrollCounter > 14.4) {\n                    while (elapsed_time(start) < 1000 / scrollVelocity) { }\n                } else {\n                    while (elapsed_time(start) < 1000 / 24) { }\n                }\n\n                keyCode = get_input();\n            } while (keyCode != 505 && keyCode != 503);\n        } else {\n            if (selectionChanged) {\n                switch (LSstatus[slot_cursor]) {\n                case SLOT_STATE_EMPTY:\n                case SLOT_STATE_ERROR:\n                case SLOT_STATE_UNSUPPORTED_VERSION:\n                    buf_to_buf(thumbnail_image[1],\n                        LS_PREVIEW_WIDTH - 1,\n                        LS_PREVIEW_HEIGHT - 1,\n                        LS_PREVIEW_WIDTH,\n                        lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n                        LS_WINDOW_WIDTH);\n                    break;\n                default:\n                    LoadTumbSlot(slot_cursor);\n                    buf_to_buf(thumbnail_image[0],\n                        LS_PREVIEW_WIDTH - 1,\n                        LS_PREVIEW_HEIGHT - 1,\n                        LS_PREVIEW_WIDTH,\n                        lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n                        LS_WINDOW_WIDTH);\n                    break;\n                }\n\n                DrawInfoBox(slot_cursor);\n                ShowSlotList(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME);\n            }\n\n            win_draw(lsgwin);\n\n            dbleclkcntr -= 1;\n            if (dbleclkcntr == 0) {\n                dbleclkcntr = 24;\n                doubleClickSlot = -1;\n            }\n\n            while (elapsed_time(tick) < 1000 / 24) {\n            }\n        }\n\n        if (rc == 1) {\n            int v50 = GetComment(slot_cursor);\n            if (v50 == -1) {\n                gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n                gsound_play_sfx_file(\"iisxxxx1\");\n                debug_printf(\"\\nLOADSAVE: ** Error getting save file comment **\\n\");\n\n                // Error saving game!\n                strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 132));\n                // Unable to save game.\n                strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 133));\n\n                const char* body[1] = {\n                    str1,\n                };\n                dialog_out(str0, body, 1, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n                rc = -1;\n            } else if (v50 == 0) {\n                gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n                rc = -1;\n            } else if (v50 == 1) {\n                if (SaveSlot() == -1) {\n                    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n                    gsound_play_sfx_file(\"iisxxxx1\");\n\n                    // Error saving game!\n                    strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 132));\n                    // Unable to save game.\n                    strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 133));\n\n                    rc = -1;\n\n                    const char* body[1] = {\n                        str1,\n                    };\n                    dialog_out(str0, body, 1, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n\n                    if (GetSlotList() == -1) {\n                        win_draw(lsgwin);\n                        gsound_play_sfx_file(\"iisxxxx1\");\n\n                        // Error loading save agme list!\n                        strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 106));\n                        // Save game directory:\n                        strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 107));\n\n                        sprintf(str2, \"\\\"%s\\\\\\\"\", \"SAVEGAME\");\n\n                        char text[260];\n                        // Doesn't exist or is corrupted.\n                        strcpy(text, getmsg(&lsgame_msgfl, &lsgmesg, 107));\n\n                        const char* body[2] = {\n                            str1,\n                            str2,\n                        };\n                        dialog_out(str0, body, 2, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE);\n\n                        LSGameEnd(0);\n\n                        return -1;\n                    }\n\n                    switch (LSstatus[slot_cursor]) {\n                    case SLOT_STATE_EMPTY:\n                    case SLOT_STATE_ERROR:\n                    case SLOT_STATE_UNSUPPORTED_VERSION:\n                        buf_to_buf(thumbnail_image[1],\n                            LS_PREVIEW_WIDTH - 1,\n                            LS_PREVIEW_HEIGHT - 1,\n                            LS_PREVIEW_WIDTH,\n                            lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n                            LS_WINDOW_WIDTH);\n                        break;\n                    default:\n                        LoadTumbSlot(slot_cursor);\n                        buf_to_buf(thumbnail_image[0],\n                            LS_PREVIEW_WIDTH - 1,\n                            LS_PREVIEW_HEIGHT - 1,\n                            LS_PREVIEW_WIDTH,\n                            lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n                            LS_WINDOW_WIDTH);\n                        break;\n                    }\n\n                    ShowSlotList(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME);\n                    DrawInfoBox(slot_cursor);\n                    win_draw(lsgwin);\n                    dbleclkcntr = 24;\n                }\n            }\n        }\n    }\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    LSGameEnd(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME);\n\n    tile_refresh_display();\n\n    if (mode == LOAD_SAVE_MODE_QUICK) {\n        if (rc == 1) {\n            quick_done = true;\n        }\n    }\n\n    return rc;\n}\n\n// 0x47C5B4\nstatic int QuickSnapShot()\n{\n    snapshot = (unsigned char*)mem_malloc(LS_PREVIEW_SIZE);\n    if (snapshot == NULL) {\n        return -1;\n    }\n\n    bool gameMouseWasVisible = gmouse_3d_is_on();\n    if (gameMouseWasVisible) {\n        gmouse_3d_off();\n    }\n\n    mouse_hide();\n    tile_refresh_display();\n    mouse_show();\n\n    if (gameMouseWasVisible) {\n        gmouse_3d_on();\n    }\n\n    unsigned char* windowBuffer = win_get_buf(display_win);\n    cscale(windowBuffer, 640, 380, 640, snapshot, LS_PREVIEW_WIDTH, LS_PREVIEW_HEIGHT, LS_PREVIEW_WIDTH);\n\n    thumbnail_image[1] = snapshot;\n\n    return 1;\n}\n\n// LoadGame\n// 0x47C640\nint LoadGame(int mode)\n{\n    MessageListItem messageListItem;\n\n    const char* body[] = {\n        str1,\n        str2,\n    };\n\n    ls_error_code = 0;\n\n    if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patches)) {\n        debug_printf(\"\\nLOADSAVE: Error reading patches config variable! Using default.\\n\");\n        patches = emgpath;\n    }\n\n    if (mode == LOAD_SAVE_MODE_QUICK && quick_done) {\n        int quickSaveWindowX = 0;\n        int quickSaveWindowY = 0;\n        int window = win_add(quickSaveWindowX,\n            quickSaveWindowY,\n            LS_WINDOW_WIDTH,\n            LS_WINDOW_HEIGHT,\n            256,\n            WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n        if (window != -1) {\n            unsigned char* windowBuffer = win_get_buf(window);\n            buf_fill(windowBuffer, LS_WINDOW_WIDTH, LS_WINDOW_HEIGHT, LS_WINDOW_WIDTH, colorTable[0]);\n            win_draw(window);\n        }\n\n        if (LoadSlot(slot_cursor) != -1) {\n            if (window != -1) {\n                win_delete(window);\n            }\n            gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n            return 1;\n        }\n\n        if (!message_init(&lsgame_msgfl)) {\n            return -1;\n        }\n\n        char path[MAX_PATH];\n        sprintf(path, \"%s\\\\%s\", msg_path, \"LSGAME.MSG\");\n        if (!message_load(&lsgame_msgfl, path)) {\n            return -1;\n        }\n\n        if (window != -1) {\n            win_delete(window);\n        }\n\n        gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n        gsound_play_sfx_file(\"iisxxxx1\");\n        strcpy(str0, getmsg(&lsgame_msgfl, &messageListItem, 134));\n        strcpy(str1, getmsg(&lsgame_msgfl, &messageListItem, 135));\n        dialog_out(str0, body, 1, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE);\n\n        message_exit(&lsgame_msgfl);\n        map_new_map();\n        game_user_wants_to_quit = 2;\n\n        return -1;\n    }\n\n    quick_done = false;\n\n    int windowType;\n    switch (mode) {\n    case LOAD_SAVE_MODE_FROM_MAIN_MENU:\n        windowType = LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU;\n        break;\n    case LOAD_SAVE_MODE_NORMAL:\n        windowType = LOAD_SAVE_WINDOW_TYPE_LOAD_GAME;\n        break;\n    case LOAD_SAVE_MODE_QUICK:\n        windowType = LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_LOAD_SLOT;\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    if (LSGameStart(windowType) == -1) {\n        debug_printf(\"\\nLOADSAVE: ** Error loading save game screen data! **\\n\");\n        return -1;\n    }\n\n    if (GetSlotList() == -1) {\n        gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n        win_draw(lsgwin);\n        gsound_play_sfx_file(\"iisxxxx1\");\n        strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 106));\n        strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 107));\n        sprintf(str2, \"\\\"%s\\\\\\\"\", \"SAVEGAME\");\n        dialog_out(str0, body, 2, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE);\n        LSGameEnd(windowType);\n        return -1;\n    }\n\n    switch (LSstatus[slot_cursor]) {\n    case SLOT_STATE_EMPTY:\n    case SLOT_STATE_ERROR:\n    case SLOT_STATE_UNSUPPORTED_VERSION:\n        buf_to_buf(lsbmp[LOAD_SAVE_FRM_PREVIEW_COVER],\n            ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width,\n            ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height,\n            ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width,\n            lsgbuf + LS_WINDOW_WIDTH * 39 + 340,\n            LS_WINDOW_WIDTH);\n        break;\n    default:\n        LoadTumbSlot(slot_cursor);\n        buf_to_buf(thumbnail_image[0],\n            LS_PREVIEW_WIDTH - 1,\n            LS_PREVIEW_HEIGHT - 1,\n            LS_PREVIEW_WIDTH,\n            lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n            LS_WINDOW_WIDTH);\n        break;\n    }\n\n    ShowSlotList(2);\n    DrawInfoBox(slot_cursor);\n    win_draw(lsgwin);\n    dbleclkcntr = 24;\n\n    int rc = -1;\n    int doubleClickSlot = -1;\n    while (rc == -1) {\n        unsigned int time = get_time();\n        int keyCode = get_input();\n        bool selectionChanged = false;\n        int scrollDirection = 0;\n\n        if (keyCode == KEY_ESCAPE || keyCode == 501 || game_user_wants_to_quit != 0) {\n            rc = 0;\n        } else {\n            switch (keyCode) {\n            case KEY_ARROW_UP:\n                if (--slot_cursor < 0) {\n                    slot_cursor = 0;\n                }\n                selectionChanged = true;\n                doubleClickSlot = -1;\n                break;\n            case KEY_ARROW_DOWN:\n                if (++slot_cursor > 9) {\n                    slot_cursor = 9;\n                }\n                selectionChanged = true;\n                doubleClickSlot = -1;\n                break;\n            case KEY_HOME:\n                slot_cursor = 0;\n                selectionChanged = true;\n                doubleClickSlot = -1;\n                break;\n            case KEY_END:\n                slot_cursor = 9;\n                selectionChanged = true;\n                doubleClickSlot = -1;\n                break;\n            case 506:\n                scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_UP;\n                break;\n            case 504:\n                scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_DOWN;\n                break;\n            case 502:\n                if (1) {\n                    int mouseX;\n                    int mouseY;\n                    mouse_get_position(&mouseX, &mouseY);\n\n                    int clickedSlot = (mouseY - 79) / (3 * text_height() + 4);\n                    if (clickedSlot < 0) {\n                        clickedSlot = 0;\n                    } else if (clickedSlot > 9) {\n                        clickedSlot = 9;\n                    }\n\n                    slot_cursor = clickedSlot;\n                    if (clickedSlot == doubleClickSlot) {\n                        keyCode = 500;\n                        gsound_play_sfx_file(\"ib1p1xx1\");\n                    }\n\n                    selectionChanged = true;\n                    scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE;\n                    doubleClickSlot = slot_cursor;\n                }\n                break;\n            case KEY_MINUS:\n            case KEY_UNDERSCORE:\n                DecGamma();\n                break;\n            case KEY_EQUAL:\n            case KEY_PLUS:\n                IncGamma();\n                break;\n            case KEY_RETURN:\n                keyCode = 500;\n                break;\n            case KEY_CTRL_Q:\n            case KEY_CTRL_X:\n            case KEY_F10:\n                game_quit_with_confirm();\n                if (game_user_wants_to_quit != 0) {\n                    rc = 0;\n                }\n                break;\n            }\n        }\n\n        if (keyCode == 500) {\n            if (LSstatus[slot_cursor] != SLOT_STATE_EMPTY) {\n                rc = 1;\n            } else {\n                rc = -1;\n            }\n\n            selectionChanged = true;\n            scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE;\n        }\n\n        if (scrollDirection != LOAD_SAVE_SCROLL_DIRECTION_NONE) {\n            unsigned int scrollVelocity = 4;\n            bool isScrolling = false;\n            int scrollCounter = 0;\n            do {\n                unsigned int start = get_time();\n                scrollCounter += 1;\n\n                if ((!isScrolling && scrollCounter == 1) || (isScrolling && scrollCounter > 14.4)) {\n                    isScrolling = true;\n\n                    if (scrollCounter > 14.4) {\n                        scrollVelocity += 1;\n                        if (scrollVelocity > 24) {\n                            scrollVelocity = 24;\n                        }\n                    }\n\n                    if (scrollDirection == LOAD_SAVE_SCROLL_DIRECTION_UP) {\n                        slot_cursor -= 1;\n                        if (slot_cursor < 0) {\n                            slot_cursor = 0;\n                        }\n                    } else {\n                        slot_cursor += 1;\n                        if (slot_cursor > 9) {\n                            slot_cursor = 9;\n                        }\n                    }\n\n                    switch (LSstatus[slot_cursor]) {\n                    case SLOT_STATE_EMPTY:\n                    case SLOT_STATE_ERROR:\n                    case SLOT_STATE_UNSUPPORTED_VERSION:\n                        buf_to_buf(lsbmp[LOAD_SAVE_FRM_PREVIEW_COVER],\n                            ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width,\n                            ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height,\n                            ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width,\n                            lsgbuf + LS_WINDOW_WIDTH * 39 + 340,\n                            LS_WINDOW_WIDTH);\n                        break;\n                    default:\n                        LoadTumbSlot(slot_cursor);\n                        buf_to_buf(lsbmp[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 39 + 340,\n                            ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width,\n                            ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height,\n                            LS_WINDOW_WIDTH,\n                            lsgbuf + LS_WINDOW_WIDTH * 39 + 340,\n                            LS_WINDOW_WIDTH);\n                        buf_to_buf(thumbnail_image[0],\n                            LS_PREVIEW_WIDTH - 1,\n                            LS_PREVIEW_HEIGHT - 1,\n                            LS_PREVIEW_WIDTH,\n                            lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n                            LS_WINDOW_WIDTH);\n                        break;\n                    }\n\n                    ShowSlotList(2);\n                    DrawInfoBox(slot_cursor);\n                    win_draw(lsgwin);\n                }\n\n                if (scrollCounter > 14.4) {\n                    while (elapsed_time(start) < 1000 / scrollVelocity) { }\n                } else {\n                    while (elapsed_time(start) < 1000 / 24) { }\n                }\n\n                keyCode = get_input();\n            } while (keyCode != 505 && keyCode != 503);\n        } else {\n            if (selectionChanged) {\n                switch (LSstatus[slot_cursor]) {\n                case SLOT_STATE_EMPTY:\n                case SLOT_STATE_ERROR:\n                case SLOT_STATE_UNSUPPORTED_VERSION:\n                    buf_to_buf(lsbmp[LOAD_SAVE_FRM_PREVIEW_COVER],\n                        ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width,\n                        ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height,\n                        ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width,\n                        lsgbuf + LS_WINDOW_WIDTH * 39 + 340,\n                        LS_WINDOW_WIDTH);\n                    break;\n                default:\n                    LoadTumbSlot(slot_cursor);\n                    buf_to_buf(lsbmp[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 39 + 340,\n                        ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width,\n                        ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height,\n                        LS_WINDOW_WIDTH,\n                        lsgbuf + LS_WINDOW_WIDTH * 39 + 340,\n                        LS_WINDOW_WIDTH);\n                    buf_to_buf(thumbnail_image[0],\n                        LS_PREVIEW_WIDTH - 1,\n                        LS_PREVIEW_HEIGHT - 1,\n                        LS_PREVIEW_WIDTH,\n                        lsgbuf + LS_WINDOW_WIDTH * 58 + 366,\n                        LS_WINDOW_WIDTH);\n                    break;\n                }\n\n                DrawInfoBox(slot_cursor);\n                ShowSlotList(2);\n            }\n\n            win_draw(lsgwin);\n\n            dbleclkcntr -= 1;\n            if (dbleclkcntr == 0) {\n                dbleclkcntr = 24;\n                doubleClickSlot = -1;\n            }\n\n            while (elapsed_time(time) < 1000 / 24) { }\n        }\n\n        if (rc == 1) {\n            switch (LSstatus[slot_cursor]) {\n            case SLOT_STATE_UNSUPPORTED_VERSION:\n                gsound_play_sfx_file(\"iisxxxx1\");\n                strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 134));\n                strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 136));\n                strcpy(str2, getmsg(&lsgame_msgfl, &lsgmesg, 135));\n                dialog_out(str0, body, 2, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE);\n                rc = -1;\n                break;\n            case SLOT_STATE_ERROR:\n                gsound_play_sfx_file(\"iisxxxx1\");\n                strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 134));\n                strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 136));\n                dialog_out(str0, body, 1, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE);\n                rc = -1;\n                break;\n            default:\n                if (LoadSlot(slot_cursor) == -1) {\n                    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n                    gsound_play_sfx_file(\"iisxxxx1\");\n                    strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 134));\n                    strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 135));\n                    dialog_out(str0, body, 1, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE);\n                    map_new_map();\n                    game_user_wants_to_quit = 2;\n                    rc = -1;\n                }\n                break;\n            }\n        }\n    }\n\n    LSGameEnd(mode == LOAD_SAVE_MODE_FROM_MAIN_MENU\n            ? LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU\n            : LOAD_SAVE_WINDOW_TYPE_LOAD_GAME);\n\n    if (mode == LOAD_SAVE_MODE_QUICK) {\n        if (rc == 1) {\n            quick_done = true;\n        }\n    }\n\n    return rc;\n}\n\n// 0x47D2E4\nstatic int LSGameStart(int windowType)\n{\n    fontsave = text_curr();\n    text_font(103);\n\n    bk_enable = false;\n    if (!message_init(&lsgame_msgfl)) {\n        return -1;\n    }\n\n    sprintf(str, \"%s%s\", msg_path, LSGAME_MSG_NAME);\n    if (!message_load(&lsgame_msgfl, str)) {\n        return -1;\n    }\n\n    snapshot = (unsigned char*)mem_malloc(61632);\n    if (snapshot == NULL) {\n        message_exit(&lsgame_msgfl);\n        text_font(fontsave);\n        return -1;\n    }\n\n    thumbnail_image[0] = snapshot;\n    thumbnail_image[1] = snapshot + LS_PREVIEW_SIZE;\n\n    if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) {\n        bk_enable = map_disable_bk_processes();\n    }\n\n    cycle_disable();\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    if (windowType == LOAD_SAVE_WINDOW_TYPE_SAVE_GAME || windowType == LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT) {\n        bool gameMouseWasVisible = gmouse_3d_is_on();\n        if (gameMouseWasVisible) {\n            gmouse_3d_off();\n        }\n\n        mouse_hide();\n        tile_refresh_display();\n        mouse_show();\n\n        if (gameMouseWasVisible) {\n            gmouse_3d_on();\n        }\n\n        unsigned char* windowBuf = win_get_buf(display_win);\n        cscale(windowBuf, 640, 380, 640, thumbnail_image[1], LS_PREVIEW_WIDTH, LS_PREVIEW_HEIGHT, LS_PREVIEW_WIDTH);\n    }\n\n    for (int index = 0; index < LOAD_SAVE_FRM_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, lsgrphs[index], 0, 0, 0);\n        lsbmp[index] = art_lock(fid,\n            &(grphkey[index]),\n            &(ginfo[index].width),\n            &(ginfo[index].height));\n\n        if (lsbmp[index] == NULL) {\n            while (--index >= 0) {\n                art_ptr_unlock(grphkey[index]);\n            }\n            mem_free(snapshot);\n            message_exit(&lsgame_msgfl);\n            text_font(fontsave);\n\n            if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) {\n                if (bk_enable) {\n                    map_enable_bk_processes();\n                }\n            }\n\n            cycle_enable();\n            gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n            return -1;\n        }\n    }\n\n    int lsWindowX = 0;\n    int lsWindowY = 0;\n    lsgwin = win_add(lsWindowX,\n        lsWindowY,\n        LS_WINDOW_WIDTH,\n        LS_WINDOW_HEIGHT,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (lsgwin == -1) {\n        // FIXME: Leaking frms.\n        mem_free(snapshot);\n        message_exit(&lsgame_msgfl);\n        text_font(fontsave);\n\n        if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) {\n            if (bk_enable) {\n                map_enable_bk_processes();\n            }\n        }\n\n        cycle_enable();\n        gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n        return -1;\n    }\n\n    lsgbuf = win_get_buf(lsgwin);\n    memcpy(lsgbuf, lsbmp[LOAD_SAVE_FRM_BACKGROUND], LS_WINDOW_WIDTH * LS_WINDOW_HEIGHT);\n\n    int messageId;\n    switch (windowType) {\n    case LOAD_SAVE_WINDOW_TYPE_SAVE_GAME:\n        // SAVE GAME\n        messageId = 102;\n        break;\n    case LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT:\n        // PICK A QUICK SAVE SLOT\n        messageId = 103;\n        break;\n    case LOAD_SAVE_WINDOW_TYPE_LOAD_GAME:\n    case LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU:\n        // LOAD GAME\n        messageId = 100;\n        break;\n    case LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_LOAD_SLOT:\n        // PICK A QUICK LOAD SLOT\n        messageId = 101;\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    char* msg;\n\n    msg = getmsg(&lsgame_msgfl, &lsgmesg, messageId);\n    text_to_buf(lsgbuf + LS_WINDOW_WIDTH * 27 + 48, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, colorTable[18979]);\n\n    // DONE\n    msg = getmsg(&lsgame_msgfl, &lsgmesg, 104);\n    text_to_buf(lsgbuf + LS_WINDOW_WIDTH * 348 + 410, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, colorTable[18979]);\n\n    // CANCEL\n    msg = getmsg(&lsgame_msgfl, &lsgmesg, 105);\n    text_to_buf(lsgbuf + LS_WINDOW_WIDTH * 348 + 515, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, colorTable[18979]);\n\n    int btn;\n\n    btn = win_register_button(lsgwin,\n        391,\n        349,\n        ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width,\n        ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height,\n        -1,\n        -1,\n        -1,\n        500,\n        lsbmp[LOAD_SAVE_FRM_RED_BUTTON_NORMAL],\n        lsbmp[LOAD_SAVE_FRM_RED_BUTTON_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    btn = win_register_button(lsgwin,\n        495,\n        349,\n        ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width,\n        ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height,\n        -1,\n        -1,\n        -1,\n        501,\n        lsbmp[LOAD_SAVE_FRM_RED_BUTTON_NORMAL],\n        lsbmp[LOAD_SAVE_FRM_RED_BUTTON_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    btn = win_register_button(lsgwin,\n        35,\n        58,\n        ginfo[LOAD_SAVE_FRM_ARROW_UP_PRESSED].width,\n        ginfo[LOAD_SAVE_FRM_ARROW_UP_PRESSED].height,\n        -1,\n        505,\n        506,\n        505,\n        lsbmp[LOAD_SAVE_FRM_ARROW_UP_NORMAL],\n        lsbmp[LOAD_SAVE_FRM_ARROW_UP_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    btn = win_register_button(lsgwin,\n        35,\n        ginfo[LOAD_SAVE_FRM_ARROW_UP_PRESSED].height + 58,\n        ginfo[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED].width,\n        ginfo[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED].height,\n        -1,\n        503,\n        504,\n        503,\n        lsbmp[LOAD_SAVE_FRM_ARROW_DOWN_NORMAL],\n        lsbmp[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    win_register_button(lsgwin, 55, 87, 230, 353, -1, -1, -1, 502, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT);\n    text_font(101);\n\n    return 0;\n}\n\n// 0x47D824\nstatic int LSGameEnd(int windowType)\n{\n    win_delete(lsgwin);\n    text_font(fontsave);\n    message_exit(&lsgame_msgfl);\n\n    for (int index = 0; index < LOAD_SAVE_FRM_COUNT; index++) {\n        art_ptr_unlock(grphkey[index]);\n    }\n\n    mem_free(snapshot);\n\n    if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) {\n        if (bk_enable) {\n            map_enable_bk_processes();\n        }\n    }\n\n    cycle_enable();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    return 0;\n}\n\n// 0x47D88C\nstatic int SaveSlot()\n{\n    ls_error_code = 0;\n    map_backup_count = -1;\n    gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET);\n\n    gsound_background_pause();\n\n    sprintf(gmpath, \"%s\\\\%s\", patches, \"SAVEGAME\");\n    mkdir(gmpath);\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    mkdir(gmpath);\n\n    strcat(gmpath, \"\\\\proto\");\n    mkdir(gmpath);\n\n    char* protoBasePath = gmpath + strlen(gmpath);\n\n    strcpy(protoBasePath, \"\\\\critters\");\n    mkdir(gmpath);\n\n    strcpy(protoBasePath, \"\\\\items\");\n    mkdir(gmpath);\n\n    if (SaveBackup() == -1) {\n        debug_printf(\"\\nLOADSAVE: Warning, can't backup save file!\\n\");\n    }\n\n    sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    strcat(gmpath, \"SAVE.DAT\");\n\n    debug_printf(\"\\nLOADSAVE: Save name: %s\\n\", gmpath);\n\n    flptr = db_fopen(gmpath, \"wb\");\n    if (flptr == NULL) {\n        debug_printf(\"\\nLOADSAVE: ** Error opening save game for writing! **\\n\");\n        RestoreSave();\n        sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n        MapDirErase(gmpath, \"BAK\");\n        partyMemberUnPrepSave();\n        gsound_background_unpause();\n        return -1;\n    }\n\n    long pos = db_ftell(flptr);\n    if (SaveHeader(slot_cursor) == -1) {\n        debug_printf(\"\\nLOADSAVE: ** Error writing save game header! **\\n\");\n        debug_printf(\"LOADSAVE: Save file header size written: %d bytes.\\n\", db_ftell(flptr) - pos);\n        db_fclose(flptr);\n        RestoreSave();\n        sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n        MapDirErase(gmpath, \"BAK\");\n        partyMemberUnPrepSave();\n        gsound_background_unpause();\n        return -1;\n    }\n\n    for (int index = 0; index < LOAD_SAVE_HANDLER_COUNT; index++) {\n        long pos = db_ftell(flptr);\n        SaveGameHandler* handler = master_save_list[index];\n        if (handler(flptr) == -1) {\n            debug_printf(\"\\nLOADSAVE: ** Error writing save function #%d data! **\\n\", index);\n            db_fclose(flptr);\n            RestoreSave();\n            sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n            MapDirErase(gmpath, \"BAK\");\n            partyMemberUnPrepSave();\n            gsound_background_unpause();\n            return -1;\n        }\n\n        debug_printf(\"LOADSAVE: Save function #%d data size written: %d bytes.\\n\", index, db_ftell(flptr) - pos);\n    }\n\n    debug_printf(\"LOADSAVE: Total save data written: %ld bytes.\\n\", db_ftell(flptr));\n\n    db_fclose(flptr);\n\n    sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    MapDirErase(gmpath, \"BAK\");\n\n    lsgmesg.num = 140;\n    if (message_search(&lsgame_msgfl, &lsgmesg)) {\n        display_print(lsgmesg.text);\n    } else {\n        debug_printf(\"\\nError: Couldn't find LoadSave Message!\");\n    }\n\n    gsound_background_unpause();\n\n    return 0;\n}\n\n// 0x47DC60\nint isLoadingGame()\n{\n    return loadingGame;\n}\n\n// 0x47DC68\nstatic int LoadSlot(int slot)\n{\n    loadingGame = 1;\n\n    if (isInCombat()) {\n        intface_end_window_close(false);\n        combat_over_from_load();\n        gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET);\n    }\n\n    sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    strcat(gmpath, \"SAVE.DAT\");\n\n    LoadSaveSlotData* ptr = &(LSData[slot]);\n    debug_printf(\"\\nLOADSAVE: Load name: %s\\n\", ptr->description);\n\n    flptr = db_fopen(gmpath, \"rb\");\n    if (flptr == NULL) {\n        debug_printf(\"\\nLOADSAVE: ** Error opening load game file for reading! **\\n\");\n        loadingGame = 0;\n        return -1;\n    }\n\n    long pos = db_ftell(flptr);\n    if (LoadHeader(slot) == -1) {\n        debug_printf(\"\\nLOADSAVE: ** Error reading save  game header! **\\n\");\n        db_fclose(flptr);\n        game_reset();\n        loadingGame = 0;\n        return -1;\n    }\n\n    debug_printf(\"LOADSAVE: Load file header size read: %d bytes.\\n\", db_ftell(flptr) - pos);\n\n    for (int index = 0; index < LOAD_SAVE_HANDLER_COUNT; index += 1) {\n        long pos = db_ftell(flptr);\n        LoadGameHandler* handler = master_load_list[index];\n        if (handler(flptr) == -1) {\n            debug_printf(\"\\nLOADSAVE: ** Error reading load function #%d data! **\\n\", index);\n            int v12 = db_ftell(flptr);\n            debug_printf(\"LOADSAVE: Load function #%d data size read: %d bytes.\\n\", index, db_ftell(flptr) - pos);\n            db_fclose(flptr);\n            game_reset();\n            loadingGame = 0;\n            return -1;\n        }\n\n        debug_printf(\"LOADSAVE: Load function #%d data size read: %d bytes.\\n\", index, db_ftell(flptr) - pos);\n    }\n\n    debug_printf(\"LOADSAVE: Total load data read: %ld bytes.\\n\", db_ftell(flptr));\n    db_fclose(flptr);\n\n    sprintf(str, \"%s\\\\\", \"MAPS\");\n    MapDirErase(str, \"BAK\");\n    proto_dude_update_gender();\n\n    // Game Loaded.\n    lsgmesg.num = 141;\n    if (message_search(&lsgame_msgfl, &lsgmesg) == 1) {\n        display_print(lsgmesg.text);\n    } else {\n        debug_printf(\"\\nError: Couldn't find LoadSave Message!\");\n    }\n\n    loadingGame = 0;\n\n    return 0;\n}\n\n// 0x47DF10\nstatic int SaveHeader(int slot)\n{\n    ls_error_code = 4;\n\n    LoadSaveSlotData* ptr = &(LSData[slot]);\n    strncpy(ptr->signature, LOAD_SAVE_SIGNATURE, 24);\n\n    if (db_fwrite(ptr->signature, 1, 24, flptr) == -1) {\n        return -1;\n    }\n\n    short temp[3];\n    temp[0] = VERSION_MAJOR;\n    temp[1] = VERSION_MINOR;\n\n    ptr->versionMinor = temp[0];\n    ptr->versionMajor = temp[1];\n\n    if (db_fwriteShortCount(flptr, temp, 2) == -1) {\n        return -1;\n    }\n\n    ptr->versionRelease = VERSION_RELEASE;\n    if (db_fwriteByte(flptr, VERSION_RELEASE) == -1) {\n        return -1;\n    }\n\n    char* characterName = critter_name(obj_dude);\n    strncpy(ptr->characterName, characterName, 32);\n\n    if (db_fwrite(ptr->characterName, 32, 1, flptr) != 1) {\n        return -1;\n    }\n\n    if (db_fwrite(ptr->description, 30, 1, flptr) != 1) {\n        return -1;\n    }\n\n    time_t now = time(NULL);\n    struct tm* local = localtime(&now);\n\n    temp[0] = local->tm_mday;\n    temp[1] = local->tm_mon + 1;\n    temp[2] = local->tm_year + 1900;\n\n    ptr->fileDay = temp[0];\n    ptr->fileMonth = temp[1];\n    ptr->fileYear = temp[2];\n    ptr->fileTime = local->tm_hour + local->tm_min;\n\n    if (db_fwriteShortCount(flptr, temp, 3) == -1) {\n        return -1;\n    }\n\n    if (db_fwriteLong(flptr, ptr->fileTime) == -1) {\n        return -1;\n    }\n\n    int month;\n    int day;\n    int year;\n    game_time_date(&month, &day, &year);\n\n    temp[0] = month;\n    temp[1] = day;\n    temp[2] = year;\n    ptr->gameTime = game_time();\n\n    if (db_fwriteShortCount(flptr, temp, 3) == -1) {\n        return -1;\n    }\n\n    if (db_fwriteLong(flptr, ptr->gameTime) == -1) {\n        return -1;\n    }\n\n    ptr->elevation = map_elevation;\n    if (db_fwriteShort(flptr, ptr->elevation) == -1) {\n        return -1;\n    }\n\n    ptr->map = map_get_index_number();\n    if (db_fwriteShort(flptr, ptr->map) == -1) {\n        return -1;\n    }\n\n    char mapName[128];\n    strcpy(mapName, map_data.name);\n\n    char* v1 = strmfe(str, mapName, \"sav\");\n    strncpy(ptr->fileName, v1, 16);\n    if (db_fwrite(ptr->fileName, 16, 1, flptr) != 1) {\n        return -1;\n    }\n\n    if (db_fwrite(thumbnail_image[1], LS_PREVIEW_SIZE, 1, flptr) != 1) {\n        return -1;\n    }\n\n    memset(mapName, 0, 128);\n    if (db_fwrite(mapName, 1, 128, flptr) != 128) {\n        return -1;\n    }\n\n    ls_error_code = 0;\n\n    return 0;\n}\n\n// 0x47E2E4\nstatic int LoadHeader(int slot)\n{\n    ls_error_code = 3;\n\n    LoadSaveSlotData* ptr = &(LSData[slot]);\n\n    if (db_fread(ptr->signature, 1, 24, flptr) != 24) {\n        return -1;\n    }\n\n    if (strncmp(ptr->signature, LOAD_SAVE_SIGNATURE, 18) != 0) {\n        debug_printf(\"\\nLOADSAVE: ** Invalid save file on load! **\\n\");\n        ls_error_code = 2;\n        return -1;\n    }\n\n    short v8[3];\n    if (db_freadShortCount(flptr, v8, 2) == -1) {\n        return -1;\n    }\n\n    ptr->versionMinor = v8[0];\n    ptr->versionMajor = v8[1];\n\n    if (db_freadByte(flptr, &(ptr->versionRelease)) == -1) {\n        return -1;\n    }\n\n    if (ptr->versionMinor != 1 || ptr->versionMajor != 2 || ptr->versionRelease != 'R') {\n        debug_printf(\"\\nLOADSAVE: Load slot #%d Version: %d.%d%c\\n\", slot, ptr->versionMinor, ptr->versionMajor, ptr->versionRelease);\n        ls_error_code = 1;\n        return -1;\n    }\n\n    if (db_fread(ptr->characterName, 32, 1, flptr) != 1) {\n        return -1;\n    }\n\n    if (db_fread(ptr->description, 30, 1, flptr) != 1) {\n        return -1;\n    }\n\n    if (db_freadShortCount(flptr, v8, 3) == -1) {\n        return -1;\n    }\n\n    ptr->fileMonth = v8[0];\n    ptr->fileDay = v8[1];\n    ptr->fileYear = v8[2];\n\n    if (db_freadLong(flptr, &(ptr->fileTime)) == -1) {\n        return -1;\n    }\n\n    if (db_freadShortCount(flptr, v8, 3) == -1) {\n        return -1;\n    }\n\n    ptr->gameMonth = v8[0];\n    ptr->gameDay = v8[1];\n    ptr->gameYear = v8[2];\n\n    if (db_freadLong(flptr, &(ptr->gameTime)) == -1) {\n        return -1;\n    }\n\n    if (db_freadShort(flptr, &(ptr->elevation)) == -1) {\n        return -1;\n    }\n\n    if (db_freadShort(flptr, &(ptr->map)) == -1) {\n        return -1;\n    }\n\n    if (db_fread(ptr->fileName, 1, 16, flptr) != 16) {\n        return -1;\n    }\n\n    if (db_fseek(flptr, LS_PREVIEW_SIZE, SEEK_CUR) != 0) {\n        return -1;\n    }\n\n    if (db_fseek(flptr, 128, 1) != 0) {\n        return -1;\n    }\n\n    ls_error_code = 0;\n\n    return 0;\n}\n\n// 0x47E5D0\nstatic int GetSlotList()\n{\n    int index = 0;\n    for (; index < 10; index += 1) {\n        sprintf(str, \"%s\\\\%s%.2d\\\\%s\", \"SAVEGAME\", \"SLOT\", index + 1, \"SAVE.DAT\");\n\n        int fileSize;\n        if (db_dir_entry(str, &fileSize) != 0) {\n            LSstatus[index] = SLOT_STATE_EMPTY;\n        } else {\n            flptr = db_fopen(str, \"rb\");\n\n            if (flptr == NULL) {\n                debug_printf(\"\\nLOADSAVE: ** Error opening save  game for reading! **\\n\");\n                return -1;\n            }\n\n            if (LoadHeader(index) == -1) {\n                if (ls_error_code == 1) {\n                    debug_printf(\"LOADSAVE: ** save file #%d is an older version! **\\n\", slot_cursor);\n                    LSstatus[index] = SLOT_STATE_UNSUPPORTED_VERSION;\n                } else {\n                    debug_printf(\"LOADSAVE: ** Save file #%d corrupt! **\", index);\n                    LSstatus[index] = SLOT_STATE_ERROR;\n                }\n            } else {\n                LSstatus[index] = SLOT_STATE_OCCUPIED;\n            }\n\n            db_fclose(flptr);\n        }\n    }\n    return index;\n}\n\n// 0x47E6D8\nstatic void ShowSlotList(int a1)\n{\n    buf_fill(lsgbuf + LS_WINDOW_WIDTH * 87 + 55, 230, 353, LS_WINDOW_WIDTH, lsgbuf[LS_WINDOW_WIDTH * 86 + 55] & 0xFF);\n\n    int y = 87;\n    for (int index = 0; index < 10; index += 1) {\n\n        int color = index == slot_cursor ? colorTable[32747] : colorTable[992];\n        const char* text = getmsg(&lsgame_msgfl, &lsgmesg, a1 != 0 ? 110 : 109);\n        sprintf(str, \"[   %s %.2d:   ]\", text, index + 1);\n        text_to_buf(lsgbuf + LS_WINDOW_WIDTH * y + 55, str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);\n\n        y += text_height();\n        switch (LSstatus[index]) {\n        case SLOT_STATE_OCCUPIED:\n            strcpy(str, LSData[index].description);\n            break;\n        case SLOT_STATE_EMPTY:\n            // - EMPTY -\n            text = getmsg(&lsgame_msgfl, &lsgmesg, 111);\n            sprintf(str, \"       %s\", text);\n            break;\n        case SLOT_STATE_ERROR:\n            // - CORRUPT SAVE FILE -\n            text = getmsg(&lsgame_msgfl, &lsgmesg, 112);\n            sprintf(str, \"%s\", text);\n            color = colorTable[32328];\n            break;\n        case SLOT_STATE_UNSUPPORTED_VERSION:\n            // - OLD VERSION -\n            text = getmsg(&lsgame_msgfl, &lsgmesg, 113);\n            sprintf(str, \" %s\", text);\n            color = colorTable[32328];\n            break;\n        }\n\n        text_to_buf(lsgbuf + LS_WINDOW_WIDTH * y + 55, str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);\n        y += 2 * text_height() + 4;\n    }\n}\n\n// 0x47E8E0\nstatic void DrawInfoBox(int a1)\n{\n    buf_to_buf(lsbmp[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 254 + 396, 164, 60, LS_WINDOW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 254 + 396, 640);\n\n    unsigned char* dest;\n    const char* text;\n    int color = colorTable[992];\n\n    switch (LSstatus[a1]) {\n    case SLOT_STATE_OCCUPIED:\n        do {\n            LoadSaveSlotData* ptr = &(LSData[a1]);\n            text_to_buf(lsgbuf + LS_WINDOW_WIDTH * 254 + 396, ptr->characterName, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);\n\n            int v4 = ptr->gameTime / 600;\n            int minutes = v4 % 60;\n            int v6 = 25 * (v4 / 60 % 24);\n            int time = 4 * v6 + minutes;\n\n            text = getmsg(&lsgame_msgfl, &lsgmesg, 116 + ptr->gameMonth);\n            sprintf(str, \"%.2d %s %.4d   %.4d\", ptr->gameDay, text, ptr->gameYear, time);\n\n            int v2 = text_height();\n            text_to_buf(lsgbuf + LS_WINDOW_WIDTH * (256 + v2) + 397, str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);\n\n            const char* v22 = map_get_elev_idx(ptr->map, ptr->elevation);\n            const char* v9 = map_get_short_name(ptr->map);\n            sprintf(str, \"%s %s\", v9, v22);\n\n            int y = v2 + 3 + v2 + 256;\n            short beginnings[WORD_WRAP_MAX_COUNT];\n            short count;\n            if (word_wrap(str, 164, beginnings, &count) == 0) {\n                for (int index = 0; index < count - 1; index += 1) {\n                    char* beginning = str + beginnings[index];\n                    char* ending = str + beginnings[index + 1];\n                    char c = *ending;\n                    *ending = '\\0';\n                    text_to_buf(lsgbuf + LS_WINDOW_WIDTH * y + 399, beginning, 164, LS_WINDOW_WIDTH, color);\n                    y += v2 + 2;\n                }\n            }\n        } while (0);\n        return;\n    case SLOT_STATE_EMPTY:\n        // Empty.\n        text = getmsg(&lsgame_msgfl, &lsgmesg, 114);\n        dest = lsgbuf + LS_WINDOW_WIDTH * 262 + 404;\n        break;\n    case SLOT_STATE_ERROR:\n        // Error!\n        text = getmsg(&lsgame_msgfl, &lsgmesg, 115);\n        dest = lsgbuf + LS_WINDOW_WIDTH * 262 + 404;\n        color = colorTable[32328];\n        break;\n    case SLOT_STATE_UNSUPPORTED_VERSION:\n        // Old version.\n        text = getmsg(&lsgame_msgfl, &lsgmesg, 116);\n        dest = lsgbuf + LS_WINDOW_WIDTH * 262 + 400;\n        color = colorTable[32328];\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    text_to_buf(dest, text, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color);\n}\n\n// 0x47EC48\nstatic int LoadTumbSlot(int a1)\n{\n    File* stream;\n    int v2;\n\n    v2 = LSstatus[slot_cursor];\n    if (v2 != 0 && v2 != 2 && v2 != 3) {\n        sprintf(str, \"%s\\\\%s%.2d\\\\%s\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1, \"SAVE.DAT\");\n        debug_printf(\" Filename %s\\n\", str);\n\n        stream = db_fopen(str, \"rb\");\n        if (stream == NULL) {\n            debug_printf(\"\\nLOADSAVE: ** (A) Error reading thumbnail #%d! **\\n\", a1);\n            return -1;\n        }\n\n        if (db_fseek(stream, 131, SEEK_SET) != 0) {\n            debug_printf(\"\\nLOADSAVE: ** (B) Error reading thumbnail #%d! **\\n\", a1);\n            db_fclose(stream);\n            return -1;\n        }\n\n        if (db_fread(thumbnail_image[0], LS_PREVIEW_SIZE, 1, stream) != 1) {\n            debug_printf(\"\\nLOADSAVE: ** (C) Error reading thumbnail #%d! **\\n\", a1);\n            db_fclose(stream);\n            return -1;\n        }\n\n        db_fclose(stream);\n    }\n\n    return 0;\n}\n\n// 0x47ED5C\nstatic int GetComment(int a1)\n{\n    int commentWindowX = LS_COMMENT_WINDOW_X;\n    int commentWindowY = LS_COMMENT_WINDOW_Y;\n    int window = win_add(commentWindowX,\n        commentWindowY,\n        ginfo[LOAD_SAVE_FRM_BOX].width,\n        ginfo[LOAD_SAVE_FRM_BOX].height,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (window == -1) {\n        return -1;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(window);\n    memcpy(windowBuffer,\n        lsbmp[LOAD_SAVE_FRM_BOX],\n        ginfo[LOAD_SAVE_FRM_BOX].height * ginfo[LOAD_SAVE_FRM_BOX].width);\n\n    text_font(103);\n\n    const char* msg;\n\n    // DONE\n    msg = getmsg(&lsgame_msgfl, &lsgmesg, 104);\n    text_to_buf(windowBuffer + ginfo[LOAD_SAVE_FRM_BOX].width * 57 + 56,\n        msg,\n        ginfo[LOAD_SAVE_FRM_BOX].width,\n        ginfo[LOAD_SAVE_FRM_BOX].width,\n        colorTable[18979]);\n\n    // CANCEL\n    msg = getmsg(&lsgame_msgfl, &lsgmesg, 105);\n    text_to_buf(windowBuffer + ginfo[LOAD_SAVE_FRM_BOX].width * 57 + 181,\n        msg,\n        ginfo[LOAD_SAVE_FRM_BOX].width,\n        ginfo[LOAD_SAVE_FRM_BOX].width,\n        colorTable[18979]);\n\n    // DESCRIPTION\n    msg = getmsg(&lsgame_msgfl, &lsgmesg, 130);\n\n    char title[260];\n    strcpy(title, msg);\n\n    int width = text_width(title);\n    text_to_buf(windowBuffer + ginfo[LOAD_SAVE_FRM_BOX].width * 7 + (ginfo[LOAD_SAVE_FRM_BOX].width - width) / 2,\n        title,\n        ginfo[LOAD_SAVE_FRM_BOX].width,\n        ginfo[LOAD_SAVE_FRM_BOX].width,\n        colorTable[18979]);\n\n    text_font(101);\n\n    int btn;\n\n    // DONE\n    btn = win_register_button(window,\n        34,\n        58,\n        ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width,\n        ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height,\n        -1,\n        -1,\n        -1,\n        507,\n        lsbmp[LOAD_SAVE_FRM_RED_BUTTON_NORMAL],\n        lsbmp[LOAD_SAVE_FRM_RED_BUTTON_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn == -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    // CANCEL\n    btn = win_register_button(window,\n        160,\n        58,\n        ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width,\n        ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height,\n        -1,\n        -1,\n        -1,\n        508,\n        lsbmp[LOAD_SAVE_FRM_RED_BUTTON_NORMAL],\n        lsbmp[LOAD_SAVE_FRM_RED_BUTTON_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn == -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    win_draw(window);\n\n    char description[LOAD_SAVE_DESCRIPTION_LENGTH];\n    if (LSstatus[slot_cursor] == SLOT_STATE_OCCUPIED) {\n        strncpy(description, LSData[a1].description, LOAD_SAVE_DESCRIPTION_LENGTH);\n    } else {\n        memset(description, '\\0', LOAD_SAVE_DESCRIPTION_LENGTH);\n    }\n\n    int rc;\n\n    if (get_input_str2(window, 507, 508, description, LOAD_SAVE_DESCRIPTION_LENGTH - 1, 24, 35, colorTable[992], lsbmp[LOAD_SAVE_FRM_BOX][ginfo[1].width * 35 + 24], 0) == 0) {\n        strncpy(LSData[a1].description, description, LOAD_SAVE_DESCRIPTION_LENGTH);\n        LSData[a1].description[LOAD_SAVE_DESCRIPTION_LENGTH - 1] = '\\0';\n        rc = 1;\n    } else {\n        rc = 0;\n    }\n\n    win_delete(window);\n\n    return rc;\n}\n\n// 0x47F084\nstatic int get_input_str2(int win, int doneKeyCode, int cancelKeyCode, char* description, int maxLength, int x, int y, int textColor, int backgroundColor, int flags)\n{\n    int cursorWidth = text_width(\"_\") - 4;\n    int windowWidth = win_width(win);\n    int lineHeight = text_height();\n    unsigned char* windowBuffer = win_get_buf(win);\n    if (maxLength > 255) {\n        maxLength = 255;\n    }\n\n    char text[256];\n    strcpy(text, description);\n\n    int textLength = strlen(text);\n    text[textLength] = ' ';\n    text[textLength + 1] = '\\0';\n\n    int nameWidth = text_width(text);\n\n    buf_fill(windowBuffer + windowWidth * y + x, nameWidth, lineHeight, windowWidth, backgroundColor);\n    text_to_buf(windowBuffer + windowWidth * y + x, text, windowWidth, windowWidth, textColor);\n\n    win_draw(win);\n\n    int blinkCounter = 3;\n    bool blink = false;\n\n    int v1 = 0;\n\n    int rc = 1;\n    while (rc == 1) {\n        int tick = get_time();\n\n        int keyCode = get_input();\n        if ((keyCode & 0x80000000) == 0) {\n            v1++;\n        }\n\n        if (keyCode == doneKeyCode || keyCode == KEY_RETURN) {\n            rc = 0;\n        } else if (keyCode == cancelKeyCode || keyCode == KEY_ESCAPE) {\n            rc = -1;\n        } else {\n            if ((keyCode == KEY_DELETE || keyCode == KEY_BACKSPACE) && textLength > 0) {\n                buf_fill(windowBuffer + windowWidth * y + x, text_width(text), lineHeight, windowWidth, backgroundColor);\n\n                // TODO: Probably incorrect, needs testing.\n                if (v1 == 1) {\n                    textLength = 1;\n                }\n\n                text[textLength - 1] = ' ';\n                text[textLength] = '\\0';\n                text_to_buf(windowBuffer + windowWidth * y + x, text, windowWidth, windowWidth, textColor);\n                textLength--;\n            } else if ((keyCode >= KEY_FIRST_INPUT_CHARACTER && keyCode <= KEY_LAST_INPUT_CHARACTER) && textLength < maxLength) {\n                if ((flags & 0x01) != 0) {\n                    if (!isdoschar(keyCode)) {\n                        break;\n                    }\n                }\n\n                buf_fill(windowBuffer + windowWidth * y + x, text_width(text), lineHeight, windowWidth, backgroundColor);\n\n                text[textLength] = keyCode & 0xFF;\n                text[textLength + 1] = ' ';\n                text[textLength + 2] = '\\0';\n                text_to_buf(windowBuffer + windowWidth * y + x, text, windowWidth, windowWidth, textColor);\n                textLength++;\n\n                win_draw(win);\n            }\n        }\n\n        blinkCounter -= 1;\n        if (blinkCounter == 0) {\n            blinkCounter = 3;\n            blink = !blink;\n\n            int color = blink ? backgroundColor : textColor;\n            buf_fill(windowBuffer + windowWidth * y + x + text_width(text) - cursorWidth, cursorWidth, lineHeight - 2, windowWidth, color);\n            win_draw(win);\n        }\n\n        while (elapsed_time(tick) < 1000 / 24) {\n        }\n    }\n\n    if (rc == 0) {\n        text[textLength] = '\\0';\n        strcpy(description, text);\n    }\n\n    return rc;\n}\n\n// 0x47F48C\nstatic int DummyFunc(File* stream)\n{\n    return 0;\n}\n\n// 0x47F490\nstatic int PrepLoad(File* stream)\n{\n    game_reset();\n    gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET);\n    map_data.name[0] = '\\0';\n    gameTimeSetTime(LSData[slot_cursor].gameTime);\n    return 0;\n}\n\n// 0x47F4C8\nstatic int EndLoad(File* stream)\n{\n    wmMapMusicStart();\n    critter_pc_set_name(LSData[slot_cursor].characterName);\n    intface_redraw();\n    refresh_box_bar_win();\n    tile_refresh_display();\n    if (isInCombat()) {\n        scripts_request_combat(NULL);\n    }\n    return 0;\n}\n\n// 0x47F510\nstatic int GameMap2Slot(File* stream)\n{\n    if (partyMemberPrepSave() == -1) {\n        return -1;\n    }\n\n    if (map_save_in_game(false) == -1) {\n        return -1;\n    }\n\n    for (int index = 1; index < partyMemberMaxCount; index += 1) {\n        int pid = partyMemberPidList[index];\n        if (pid == -2) {\n            continue;\n        }\n\n        char path[MAX_PATH];\n        if (proto_list_str(pid, path) != 0) {\n            continue;\n        }\n\n        const char* critterItemPath = PID_TYPE(pid) == OBJ_TYPE_CRITTER ? \"PROTO\\\\CRITTERS\" : \"PROTO\\\\ITEMS\";\n        sprintf(str0, \"%s\\\\%s\\\\%s\", patches, critterItemPath, path);\n        sprintf(str1, \"%s\\\\%s\\\\%s%.2d\\\\%s\\\\%s\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1, critterItemPath, path);\n        if (gzcompress_file(str0, str1) == -1) {\n            return -1;\n        }\n    }\n\n    sprintf(str0, \"%s\\\\*.%s\", \"MAPS\", \"SAV\");\n\n    char** fileNameList;\n    int fileNameListLength = db_get_file_list(str0, &fileNameList, 0, 0);\n    if (fileNameListLength == -1) {\n        return -1;\n    }\n\n    if (db_fwriteInt(stream, fileNameListLength) == -1) {\n        db_free_file_list(&fileNameList, 0);\n        return -1;\n    }\n\n    if (fileNameListLength == 0) {\n        db_free_file_list(&fileNameList, 0);\n        return -1;\n    }\n\n    sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n\n    if (MapDirErase(gmpath, \"SAV\") == -1) {\n        db_free_file_list(&fileNameList, 0);\n        return -1;\n    }\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    strmfe(str0, \"AUTOMAP.DB\", \"SAV\");\n    strcat(gmpath, str0);\n    remove(gmpath);\n\n    for (int index = 0; index < fileNameListLength; index += 1) {\n        char* string = fileNameList[index];\n        if (db_fwrite(string, strlen(string) + 1, 1, stream) == -1) {\n            db_free_file_list(&fileNameList, 0);\n            return -1;\n        }\n\n        sprintf(str0, \"%s\\\\%s\\\\%s\", patches, \"MAPS\", string);\n        sprintf(str1, \"%s\\\\%s\\\\%s%.2d\\\\%s\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1, string);\n        if (gzcompress_file(str0, str1) == -1) {\n            db_free_file_list(&fileNameList, 0);\n            return -1;\n        }\n    }\n\n    db_free_file_list(&fileNameList, 0);\n\n    strmfe(str0, \"AUTOMAP.DB\", \"SAV\");\n    sprintf(str1, \"%s\\\\%s\\\\%s%.2d\\\\%s\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1, str0);\n    sprintf(str0, \"%s\\\\%s\\\\%s\", patches, \"MAPS\", \"AUTOMAP.DB\");\n\n    if (gzcompress_file(str0, str1) == -1) {\n        return -1;\n    }\n\n    sprintf(str0, \"%s\\\\%s\", \"MAPS\", \"AUTOMAP.DB\");\n    File* inStream = db_fopen(str0, \"rb\");\n    if (inStream == NULL) {\n        return -1;\n    }\n\n    int fileSize = db_filelength(inStream);\n    if (fileSize == -1) {\n        db_fclose(inStream);\n        return -1;\n    }\n\n    db_fclose(inStream);\n\n    if (db_fwriteInt(stream, fileSize) == -1) {\n        return -1;\n    }\n\n    if (partyMemberUnPrepSave() == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// SlotMap2Game\n// 0x47F990\nstatic int SlotMap2Game(File* stream)\n{\n    debug_printf(\"LOADSAVE: in SlotMap2Game\\n\");\n\n    int fileNameListLength;\n    if (db_freadInt(stream, &fileNameListLength) == -1) {\n        debug_printf(\"LOADSAVE: returning 1\\n\");\n        return -1;\n    }\n\n    if (fileNameListLength == 0) {\n        debug_printf(\"LOADSAVE: returning 2\\n\");\n        return -1;\n    }\n\n    sprintf(str0, \"%s\\\\\", \"PROTO\\\\CRITTERS\");\n\n    if (MapDirErase(str0, \"PRO\") == -1) {\n        debug_printf(\"LOADSAVE: returning 3\\n\");\n        return -1;\n    }\n\n    sprintf(str0, \"%s\\\\\", \"PROTO\\\\ITEMS\");\n    if (MapDirErase(str0, \"PRO\") == -1) {\n        debug_printf(\"LOADSAVE: returning 4\\n\");\n        return -1;\n    }\n\n    sprintf(str0, \"%s\\\\\", \"MAPS\");\n    if (MapDirErase(str0, \"SAV\") == -1) {\n        debug_printf(\"LOADSAVE: returning 5\\n\");\n        return -1;\n    }\n\n    sprintf(str0, \"%s\\\\%s\\\\%s\", patches, \"MAPS\", \"AUTOMAP.DB\");\n    remove(str0);\n\n    for (int index = 1; index < partyMemberMaxCount; index += 1) {\n        int pid = partyMemberPidList[index];\n        if (pid != -2) {\n            char protoPath[MAX_PATH];\n            if (proto_list_str(pid, protoPath) == 0) {\n                const char* basePath = PID_TYPE(pid) == OBJ_TYPE_CRITTER\n                    ? \"PROTO\\\\CRITTERS\"\n                    : \"PROTO\\\\ITEMS\";\n                sprintf(str0, \"%s\\\\%s\\\\%s\", patches, basePath, protoPath);\n                sprintf(str1, \"%s\\\\%s\\\\%s%.2d\\\\%s\\\\%s\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1, basePath, protoPath);\n\n                if (gzdecompress_file(str1, str0) == -1) {\n                    debug_printf(\"LOADSAVE: returning 6\\n\");\n                    return -1;\n                }\n            }\n        }\n    }\n\n    for (int index = 0; index < fileNameListLength; index += 1) {\n        char fileName[MAX_PATH];\n        if (mygets(fileName, stream) == -1) {\n            break;\n        }\n\n        sprintf(str0, \"%s\\\\%s\\\\%s%.2d\\\\%s\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1, fileName);\n        sprintf(str1, \"%s\\\\%s\\\\%s\", patches, \"MAPS\", fileName);\n\n        if (gzdecompress_file(str0, str1) == -1) {\n            debug_printf(\"LOADSAVE: returning 7\\n\");\n            return -1;\n        }\n    }\n\n    const char* automapFileName = strmfe(str1, \"AUTOMAP.DB\", \"SAV\");\n    sprintf(str0, \"%s\\\\%s\\\\%s%.2d\\\\%s\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1, automapFileName);\n    sprintf(str1, \"%s\\\\%s\\\\%s\", patches, \"MAPS\", \"AUTOMAP.DB\");\n    if (gzRealUncompressCopyReal_file(str0, str1) == -1) {\n        debug_printf(\"LOADSAVE: returning 8\\n\");\n        return -1;\n    }\n\n    sprintf(str1, \"%s\\\\%s\", \"MAPS\", \"AUTOMAP.DB\");\n\n    int v12;\n    if (db_freadInt(stream, &v12) == -1) {\n        debug_printf(\"LOADSAVE: returning 9\\n\");\n        return -1;\n    }\n\n    if (map_load_in_game(LSData[slot_cursor].fileName) == -1) {\n        debug_printf(\"LOADSAVE: returning 13\\n\");\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x47FE14\nstatic int mygets(char* dest, File* stream)\n{\n    int index = 14;\n    while (true) {\n        int c = db_fgetc(stream);\n        if (c == -1) {\n            return -1;\n        }\n\n        index -= 1;\n\n        *dest = c & 0xFF;\n        dest += 1;\n\n        if (index == -1 || c == '\\0') {\n            break;\n        }\n    }\n\n    if (index == 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x47FE58\nstatic int copy_file(const char* a1, const char* a2)\n{\n    File* stream1;\n    File* stream2;\n    int length;\n    int chunk_length;\n    void* buf;\n    int result;\n\n    stream1 = NULL;\n    stream2 = NULL;\n    buf = NULL;\n    result = -1;\n\n    stream1 = db_fopen(a1, \"rb\");\n    if (stream1 == NULL) {\n        goto out;\n    }\n\n    length = db_filelength(stream1);\n    if (length == -1) {\n        goto out;\n    }\n\n    stream2 = db_fopen(a2, \"wb\");\n    if (stream2 == NULL) {\n        goto out;\n    }\n\n    buf = mem_malloc(0xFFFF);\n    if (buf == NULL) {\n        goto out;\n    }\n\n    while (length != 0) {\n        chunk_length = min(length, 0xFFFF);\n\n        if (db_fread(buf, chunk_length, 1, stream1) != 1) {\n            break;\n        }\n\n        if (db_fwrite(buf, chunk_length, 1, stream2) != 1) {\n            break;\n        }\n\n        length -= chunk_length;\n    }\n\n    if (length != 0) {\n        goto out;\n    }\n\n    result = 0;\n\nout:\n\n    if (stream1 != NULL) {\n        db_fclose(stream1);\n    }\n\n    if (stream2 != NULL) {\n        db_fclose(stream1);\n    }\n\n    if (buf != NULL) {\n        mem_free(buf);\n    }\n\n    return result;\n}\n\n// InitLoadSave\n// 0x48000C\nvoid KillOldMaps()\n{\n    char path[MAX_PATH];\n    sprintf(path, \"%s\\\\\", \"MAPS\");\n    MapDirErase(path, \"SAV\");\n}\n\n// 0x480040\nint MapDirErase(const char* relativePath, const char* extension)\n{\n    char path[MAX_PATH];\n    sprintf(path, \"%s*.%s\", relativePath, extension);\n\n    char** fileList;\n    int fileListLength = db_get_file_list(path, &fileList, 0, 0);\n    while (--fileListLength >= 0) {\n        sprintf(path, \"%s\\\\%s%s\", patches, relativePath, fileList[fileListLength]);\n        remove(path);\n    }\n    db_free_file_list(&fileList, 0);\n\n    return 0;\n}\n\n// 0x4800C8\nint MapDirEraseFile(const char* a1, const char* a2)\n{\n    char path[MAX_PATH];\n\n    sprintf(path, \"%s\\\\%s%s\", patches, a1, a2);\n    if (remove(path) != 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x480104\nstatic int SaveBackup()\n{\n    debug_printf(\"\\nLOADSAVE: Backing up save slot files..\\n\");\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    strcpy(str0, gmpath);\n\n    strcat(str0, \"SAVE.DAT\");\n\n    strmfe(str1, str0, \"BAK\");\n\n    File* stream1 = db_fopen(str0, \"rb\");\n    if (stream1 != NULL) {\n        db_fclose(stream1);\n        if (rename(str0, str1) != 0) {\n            return -1;\n        }\n    }\n\n    sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    sprintf(str0, \"%s*.%s\", gmpath, \"SAV\");\n\n    char** fileList;\n    int fileListLength = db_get_file_list(str0, &fileList, 0, 0);\n    if (fileListLength == -1) {\n        return -1;\n    }\n\n    map_backup_count = fileListLength;\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    for (int index = fileListLength - 1; index >= 0; index--) {\n        strcpy(str0, gmpath);\n        strcat(str0, fileList[index]);\n\n        strmfe(str1, str0, \"BAK\");\n        if (rename(str0, str1) != 0) {\n            db_free_file_list(&fileList, 0);\n            return -1;\n        }\n    }\n\n    db_free_file_list(&fileList, 0);\n\n    debug_printf(\"\\nLOADSAVE: %d map files backed up.\\n\", fileListLength);\n\n    sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n\n    char* v1 = strmfe(str2, \"AUTOMAP.DB\", \"SAV\");\n    sprintf(str0, \"%s\\\\%s\", gmpath, v1);\n\n    char* v2 = strmfe(str2, \"AUTOMAP.DB\", \"BAK\");\n    sprintf(str1, \"%s\\\\%s\", gmpath, v2);\n\n    automap_db_flag = 0;\n\n    File* stream2 = db_fopen(str0, \"rb\");\n    if (stream2 != NULL) {\n        db_fclose(stream2);\n\n        if (copy_file(str0, str1) == -1) {\n            return -1;\n        }\n\n        automap_db_flag = 1;\n    }\n\n    return 0;\n}\n\n// 0x4803D8\nstatic int RestoreSave()\n{\n    debug_printf(\"\\nLOADSAVE: Restoring save file backup...\\n\");\n\n    EraseSave();\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    strcpy(str0, gmpath);\n    strcat(str0, \"SAVE.DAT\");\n    strmfe(str1, str0, \"BAK\");\n    remove(str0);\n\n    if (rename(str1, str0) != 0) {\n        EraseSave();\n        return -1;\n    }\n\n    sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    sprintf(str0, \"%s*.%s\", gmpath, \"BAK\");\n\n    char** fileList;\n    int fileListLength = db_get_file_list(str0, &fileList, 0, 0);\n    if (fileListLength == -1) {\n        return -1;\n    }\n\n    if (fileListLength != map_backup_count) {\n        // FIXME: Probably leaks fileList.\n        EraseSave();\n        return -1;\n    }\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n\n    for (int index = fileListLength - 1; index >= 0; index--) {\n        strcpy(str0, gmpath);\n        strcat(str0, fileList[index]);\n        strmfe(str1, str0, \"SAV\");\n        remove(str1);\n        if (rename(str0, str1) != 0) {\n            // FIXME: Probably leaks fileList.\n            EraseSave();\n            return -1;\n        }\n    }\n\n    db_free_file_list(&fileList, 0);\n\n    if (!automap_db_flag) {\n        return 0;\n    }\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    char* v1 = strmfe(str2, \"AUTOMAP.DB\", \"BAK\");\n    strcpy(str0, gmpath);\n    strcat(str0, v1);\n\n    char* v2 = strmfe(str2, \"AUTOMAP.DB\", \"SAV\");\n    strcpy(str1, gmpath);\n    strcat(str1, v2);\n\n    if (rename(str0, str1) != 0) {\n        EraseSave();\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x480710\nstatic int LoadObjDudeCid(File* stream)\n{\n    int value;\n\n    if (db_freadInt(stream, &value) == -1) {\n        return -1;\n    }\n\n    obj_dude->cid = value;\n\n    return 0;\n}\n\n// 0x480734\nstatic int SaveObjDudeCid(File* stream)\n{\n    return db_fwriteInt(stream, obj_dude->cid);\n}\n\n// 0x480754\nstatic int EraseSave()\n{\n    debug_printf(\"\\nLOADSAVE: Erasing save(bad) slot...\\n\");\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    strcpy(str0, gmpath);\n    strcat(str0, \"SAVE.DAT\");\n    remove(str0);\n\n    sprintf(gmpath, \"%s\\\\%s%.2d\\\\\", \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    sprintf(str0, \"%s*.%s\", gmpath, \"SAV\");\n\n    char** fileList;\n    int fileListLength = db_get_file_list(str0, &fileList, 0, 0);\n    if (fileListLength == -1) {\n        return -1;\n    }\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n    for (int index = fileListLength - 1; index >= 0; index--) {\n        strcpy(str0, gmpath);\n        strcat(str0, fileList[index]);\n        remove(str0);\n    }\n\n    db_free_file_list(&fileList, 0);\n\n    sprintf(gmpath, \"%s\\\\%s\\\\%s%.2d\\\\\", patches, \"SAVEGAME\", \"SLOT\", slot_cursor + 1);\n\n    char* v1 = strmfe(str1, \"AUTOMAP.DB\", \"SAV\");\n    strcpy(str0, gmpath);\n    strcat(str0, v1);\n\n    remove(str0);\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/loadsave.h",
    "content": "#ifndef FALLOUT_GAME_LOADSAVE_H_\n#define FALLOUT_GAME_LOADSAVE_H_\n\n#include <stdbool.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"game/art.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/message.h\"\n\ntypedef enum LoadSaveMode {\n    // Special case - loading game from main menu.\n    LOAD_SAVE_MODE_FROM_MAIN_MENU,\n\n    // Normal (full-screen) save/load screen.\n    LOAD_SAVE_MODE_NORMAL,\n\n    // Quick load/save.\n    LOAD_SAVE_MODE_QUICK,\n} LoadSaveMode;\n\nvoid InitLoadSave();\nvoid ResetLoadSave();\nint SaveGame(int mode);\nint LoadGame(int mode);\nint isLoadingGame();\nvoid KillOldMaps();\nint MapDirErase(const char* path, const char* a2);\nint MapDirEraseFile(const char* a1, const char* a2);\n\n#endif /* FALLOUT_GAME_LOADSAVE_H_ */\n"
  },
  {
    "path": "src/game/main.c",
    "content": "#include \"main.h\"\n\n// NOTE: Actual file name is unknown. Functions in this module do not present\n// in debug symbols from `mapper2.exe`. In OS X binary these functions appear\n// very far from the ones found in `mainmenu.c`, implying they are in separate\n// compilation unit. In Windows binary these functions appear between\n// `loadsave.c` and `mainmenu.c`. Based on the order it's file name should be\n// between these two, so `main.c` is a perfect candidate, but again, it's just a\n// guess.\n//\n// Function names and visibility scope are from in OS X binary.\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"game/amutex.h\"\n#include \"game/art.h\"\n#include \"game/select.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/credits.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/endgame.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gmovie.h\"\n#include \"game/gsound.h\"\n#include \"game/loadsave.h\"\n#include \"game/mainmenu.h\"\n#include \"game/map.h\"\n#include \"game/object.h\"\n#include \"game/options.h\"\n#include \"game/palette.h\"\n#include \"game/proto.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/selfrun.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/intrface.h\"\n#include \"game/wordwrap.h\"\n#include \"game/worldmap.h\"\n\n#define DEATH_WINDOW_WIDTH 640\n#define DEATH_WINDOW_HEIGHT 480\n\nstatic bool main_init_system(int argc, char** argv);\nstatic int main_reset_system();\nstatic void main_exit_system();\nstatic int main_load_new(char* fname);\nstatic int main_loadgame_new();\nstatic void main_unload_new();\nstatic void main_game_loop();\nstatic bool main_selfrun_init();\nstatic void main_selfrun_exit();\nstatic void main_selfrun_record();\nstatic void main_selfrun_play();\nstatic void main_death_scene();\nstatic void main_death_voiceover_callback();\nstatic int mainDeathGrabTextFile(const char* fileName, char* dest);\nstatic int mainDeathWordWrap(char* text, int width, short* beginnings, short* count);\n\n// 0x5194C8\nstatic char mainMap[] = \"artemple.map\";\n\n// 0x5194D8\nint main_game_paused = 0;\n\n// 0x5194DC\nstatic char** main_selfrun_list = NULL;\n\n// 0x5194E0\nstatic int main_selfrun_count = 0;\n\n// 0x5194E4\nstatic int main_selfrun_index = 0;\n\n// 0x5194E8\nstatic bool main_show_death_scene = false;\n\n// 0x614838\nstatic bool main_death_voiceover_done;\n\n// 0x48099C\nint RealMain(int argc, char** argv)\n{\n    if (!autorun_mutex_create()) {\n        return 1;\n    }\n\n    if (!main_init_system(argc, argv)) {\n        return 1;\n    }\n\n    gmovie_play(MOVIE_IPLOGO, GAME_MOVIE_FADE_IN);\n    gmovie_play(MOVIE_INTRO, 0);\n    gmovie_play(MOVIE_CREDITS, 0);\n\n    if (main_menu_create() == 0) {\n        bool done = false;\n        while (!done) {\n            kb_clear();\n            gsound_background_play_level_music(\"07desert\", 11);\n            main_menu_show(1);\n\n            mouse_show();\n            int mainMenuRc = main_menu_loop();\n            mouse_hide();\n\n            switch (mainMenuRc) {\n            case MAIN_MENU_INTRO:\n                main_menu_hide(true);\n                gmovie_play(MOVIE_INTRO, GAME_MOVIE_PAUSE_MUSIC);\n                gmovie_play(MOVIE_CREDITS, 0);\n                break;\n            case MAIN_MENU_NEW_GAME:\n                main_menu_hide(true);\n                main_menu_destroy();\n                if (select_character() == 2) {\n                    gmovie_play(MOVIE_ELDER, GAME_MOVIE_STOP_MUSIC);\n                    roll_set_seed(-1);\n                    main_load_new(mainMap);\n                    main_game_loop();\n                    palette_fade_to(white_palette);\n\n                    // NOTE: Uninline.\n                    main_unload_new();\n\n                    // NOTE: Uninline.\n                    main_reset_system();\n\n                    if (main_show_death_scene != 0) {\n                        main_death_scene();\n                        main_show_death_scene = 0;\n                    }\n                }\n\n                main_menu_create();\n\n                break;\n            case MAIN_MENU_LOAD_GAME:\n                if (1) {\n                    int win = win_add(0, 0, 640, 480, colorTable[0], WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n                    main_menu_hide(true);\n                    main_menu_destroy();\n                    gsound_background_stop();\n\n                    // NOTE: Uninline.\n                    main_loadgame_new();\n\n                    loadColorTable(\"color.pal\");\n                    palette_fade_to(cmap);\n                    int loadGameRc = LoadGame(LOAD_SAVE_MODE_FROM_MAIN_MENU);\n                    if (loadGameRc == -1) {\n                        debug_printf(\"\\n ** Error running LoadGame()! **\\n\");\n                    } else if (loadGameRc != 0) {\n                        win_delete(win);\n                        win = -1;\n                        main_game_loop();\n                    }\n                    palette_fade_to(white_palette);\n                    if (win != -1) {\n                        win_delete(win);\n                    }\n\n                    // NOTE: Uninline.\n                    main_unload_new();\n\n                    // NOTE: Uninline.\n                    main_reset_system();\n\n                    if (main_show_death_scene != 0) {\n                        main_death_scene();\n                        main_show_death_scene = 0;\n                    }\n                    main_menu_create();\n                }\n                break;\n            case MAIN_MENU_TIMEOUT:\n                debug_printf(\"Main menu timed-out\\n\");\n                // FALLTHROUGH\n            case MAIN_MENU_SCREENSAVER:\n                main_selfrun_play();\n                break;\n            case MAIN_MENU_OPTIONS:\n                main_menu_hide(false);\n                mouse_show();\n                do_optionsFunc(112);\n                gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n                mouse_show();\n                main_menu_show(0);\n                break;\n            case MAIN_MENU_CREDITS:\n                main_menu_hide(true);\n                credits(\"credits.txt\", -1, false);\n                break;\n            case MAIN_MENU_QUOTES:\n                // NOTE: There is a strange cmp at 0x480C50. Both operands are\n                // zero, set before the loop and do not modify afterwards. For\n                // clarity this condition is omitted.\n                main_menu_hide(true);\n                credits(\"quotes.txt\", -1, true);\n                break;\n            case MAIN_MENU_EXIT:\n            case -1:\n                done = true;\n                main_menu_hide(true);\n                main_menu_destroy();\n                gsound_background_stop();\n                break;\n            case MAIN_MENU_SELFRUN:\n                main_selfrun_record();\n                break;\n            }\n        }\n    }\n\n    // NOTE: Uninline.\n    main_exit_system();\n\n    autorun_mutex_destroy();\n\n    return 0;\n}\n\n// 0x480CC0\nstatic bool main_init_system(int argc, char** argv)\n{\n    if (game_init(\"FALLOUT II\", false, 0, 0, argc, argv) == -1) {\n        return false;\n    }\n\n    // NOTE: Uninline.\n    main_selfrun_init();\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x480D0C\nstatic int main_reset_system()\n{\n    game_reset();\n\n    return 1;\n}\n\n// NOTE: Inlined.\n//\n// 0x480D18\nstatic void main_exit_system()\n{\n    gsound_background_stop();\n\n    // NOTE: Uninline.\n    main_selfrun_exit();\n\n    game_exit();\n}\n\n// 0x480D4C\nstatic int main_load_new(char* mapFileName)\n{\n    game_user_wants_to_quit = 0;\n    main_show_death_scene = 0;\n    obj_dude->flags &= ~OBJECT_FLAT;\n    obj_turn_on(obj_dude, NULL);\n    mouse_hide();\n\n    int win = win_add(0, 0, 640, 480, colorTable[0], WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    win_draw(win);\n\n    loadColorTable(\"color.pal\");\n    palette_fade_to(cmap);\n    map_init();\n    gmouse_set_cursor(MOUSE_CURSOR_NONE);\n    mouse_show();\n    map_load(mapFileName);\n    wmMapMusicStart();\n    palette_fade_to(white_palette);\n    win_delete(win);\n    loadColorTable(\"color.pal\");\n    palette_fade_to(cmap);\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x480DF8\nstatic int main_loadgame_new()\n{\n    game_user_wants_to_quit = 0;\n    main_show_death_scene = 0;\n\n    obj_dude->flags &= ~OBJECT_FLAT;\n\n    obj_turn_on(obj_dude, NULL);\n    mouse_hide();\n\n    map_init();\n\n    gmouse_set_cursor(MOUSE_CURSOR_NONE);\n    mouse_show();\n\n    return 0;\n}\n\n// 0x480E34\nstatic void main_unload_new()\n{\n    obj_turn_off(obj_dude, NULL);\n    map_exit();\n}\n\n// 0x480E48\nstatic void main_game_loop()\n{\n    bool cursorWasHidden = mouse_hidden();\n    if (cursorWasHidden) {\n        mouse_show();\n    }\n\n    main_game_paused = 0;\n\n    scr_enable();\n\n    while (game_user_wants_to_quit == 0) {\n        int keyCode = get_input();\n        game_handle_input(keyCode, false);\n\n        scripts_check_state();\n\n        map_check_state();\n\n        if (main_game_paused != 0) {\n            main_game_paused = 0;\n        }\n\n        if ((obj_dude->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) {\n            endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH);\n            main_show_death_scene = 1;\n            game_user_wants_to_quit = 2;\n        }\n    }\n\n    scr_disable();\n\n    if (cursorWasHidden) {\n        mouse_hide();\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x480EE4\nstatic bool main_selfrun_init()\n{\n    if (main_selfrun_list != NULL) {\n        // NOTE: Uninline.\n        main_selfrun_exit();\n    }\n\n    if (selfrun_get_list(&main_selfrun_list, &main_selfrun_count) != 0) {\n        return false;\n    }\n\n    main_selfrun_index = 0;\n\n    return true;\n}\n\n// 0x480F38\nstatic void main_selfrun_exit()\n{\n    if (main_selfrun_list != NULL) {\n        selfrun_free_list(&main_selfrun_list);\n    }\n\n    main_selfrun_count = 0;\n    main_selfrun_index = 0;\n    main_selfrun_list = NULL;\n}\n\n// 0x480F64\nstatic void main_selfrun_record()\n{\n    SelfrunData selfrunData;\n    bool ready = false;\n\n    char** fileList;\n    int fileListLength = db_get_file_list(\"maps\\\\*.map\", &fileList, 0, 0);\n    if (fileListLength != 0) {\n        int selectedFileIndex = win_list_select(\"Select Map\", fileList, fileListLength, 0, 80, 80, 0x10000 | 0x100 | 4);\n        if (selectedFileIndex != -1) {\n            // NOTE: It's size is likely 13 chars (on par with SelfrunData\n            // fields), but due to the padding it takes 16 chars on stack.\n            char recordingName[SELFRUN_RECORDING_FILE_NAME_LENGTH];\n            recordingName[0] = '\\0';\n            if (win_get_str(recordingName, sizeof(recordingName) - 2, \"Enter name for recording (8 characters max, no extension):\", 100, 100) == 0) {\n                memset(&selfrunData, 0, sizeof(selfrunData));\n                if (selfrun_prep_recording(recordingName, fileList[selectedFileIndex], &selfrunData) == 0) {\n                    ready = true;\n                }\n            }\n        }\n        db_free_file_list(&fileList, 0);\n    }\n\n    if (ready) {\n        main_menu_hide(true);\n        main_menu_destroy();\n        gsound_background_stop();\n        roll_set_seed(0xBEEFFEED);\n\n        // NOTE: Uninline.\n        main_reset_system();\n\n        proto_dude_init(\"premade\\\\combat.gcd\");\n        main_load_new(selfrunData.mapFileName);\n        selfrun_recording_loop(&selfrunData);\n        palette_fade_to(white_palette);\n\n        // NOTE: Uninline.\n        main_unload_new();\n\n        // NOTE: Uninline.\n        main_reset_system();\n\n        main_menu_create();\n\n        // NOTE: Uninline.\n        main_selfrun_init();\n    }\n}\n\n// 0x48109C\nstatic void main_selfrun_play()\n{\n    // A switch to pick selfrun vs. intro video for screensaver:\n    // - `false` - will play next selfrun recording\n    // - `true` - will play intro video\n    //\n    // This value will alternate on every attempt, even if there are no selfrun\n    // recordings.\n    //\n    // 0x5194EC\n    static bool toggle = false;\n\n    if (!toggle && main_selfrun_count > 0) {\n        SelfrunData selfrunData;\n        if (selfrun_prep_playback(main_selfrun_list[main_selfrun_index], &selfrunData) == 0) {\n            main_menu_hide(true);\n            main_menu_destroy();\n            gsound_background_stop();\n            roll_set_seed(0xBEEFFEED);\n\n            // NOTE: Uninline.\n            main_reset_system();\n\n            proto_dude_init(\"premade\\\\combat.gcd\");\n            main_load_new(selfrunData.mapFileName);\n            selfrun_playback_loop(&selfrunData);\n            palette_fade_to(white_palette);\n\n            // NOTE: Uninline.\n            main_unload_new();\n\n            // NOTE: Uninline.\n            main_reset_system();\n\n            main_menu_create();\n        }\n\n        main_selfrun_index++;\n        if (main_selfrun_index >= main_selfrun_count) {\n            main_selfrun_index = 0;\n        }\n    } else {\n        main_menu_hide(true);\n        gmovie_play(MOVIE_INTRO, GAME_MOVIE_PAUSE_MUSIC);\n    }\n\n    toggle = 1 - toggle;\n}\n\n// 0x48118C\nstatic void main_death_scene()\n{\n    art_flush();\n    cycle_disable();\n    gmouse_set_cursor(MOUSE_CURSOR_NONE);\n\n    bool oldCursorIsHidden = mouse_hidden();\n    if (oldCursorIsHidden) {\n        mouse_show();\n    }\n\n    int deathWindowX = 0;\n    int deathWindowY = 0;\n    int win = win_add(deathWindowX,\n        deathWindowY,\n        DEATH_WINDOW_WIDTH,\n        DEATH_WINDOW_HEIGHT,\n        0,\n        WINDOW_FLAG_0x04);\n    if (win != -1) {\n        do {\n            unsigned char* windowBuffer = win_get_buf(win);\n            if (windowBuffer == NULL) {\n                break;\n            }\n\n            // DEATH.FRM\n            CacheEntry* backgroundHandle;\n            int fid = art_id(OBJ_TYPE_INTERFACE, 309, 0, 0, 0);\n            unsigned char* background = art_ptr_lock_data(fid, 0, 0, &backgroundHandle);\n            if (background == NULL) {\n                break;\n            }\n\n            while (mouse_get_buttons() != 0) {\n                get_input();\n            }\n\n            kb_clear();\n            flush_input_buffer();\n\n            buf_to_buf(background, 640, 480, 640, windowBuffer, 640);\n            art_ptr_unlock(backgroundHandle);\n\n            const char* deathFileName = endgameGetDeathEndingFileName();\n\n            int subtitles = 0;\n            config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &subtitles);\n            if (subtitles != 0) {\n                char text[512];\n                if (mainDeathGrabTextFile(deathFileName, text) == 0) {\n                    debug_printf(\"\\n((ShowDeath)): %s\\n\", text);\n\n                    short beginnings[WORD_WRAP_MAX_COUNT];\n                    short count;\n                    if (mainDeathWordWrap(text, 560, beginnings, &count) == 0) {\n                        unsigned char* p = windowBuffer + 640 * (480 - text_height() * count - 8);\n                        buf_fill(p - 602, 564, text_height() * count + 2, 640, 0);\n                        p += 40;\n                        for (int index = 0; index < count; index++) {\n                            text_to_buf(p, text + beginnings[index], 560, 640, colorTable[32767]);\n                            p += 640 * text_height();\n                        }\n                    }\n                }\n            }\n\n            win_draw(win);\n\n            loadColorTable(\"art\\\\intrface\\\\death.pal\");\n            palette_fade_to(cmap);\n\n            main_death_voiceover_done = false;\n            gsound_speech_callback_set(main_death_voiceover_callback);\n\n            unsigned int delay;\n            if (gsound_speech_play(deathFileName, 10, 14, 15) == -1) {\n                delay = 3000;\n            } else {\n                delay = UINT_MAX;\n            }\n\n            gsound_speech_play_preloaded();\n\n            unsigned int time = get_time();\n            int keyCode;\n            do {\n                keyCode = get_input();\n            } while (keyCode == -1 && !main_death_voiceover_done && elapsed_time(time) < delay);\n\n            gsound_speech_callback_set(NULL);\n\n            gsound_speech_stop();\n\n            while (mouse_get_buttons() != 0) {\n                get_input();\n            }\n\n            if (keyCode == -1) {\n                pause_for_tocks(500);\n            }\n\n            palette_fade_to(black_palette);\n            loadColorTable(\"color.pal\");\n        } while (0);\n        win_delete(win);\n    }\n\n    if (oldCursorIsHidden) {\n        mouse_hide();\n    }\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    cycle_enable();\n}\n\n// 0x4814A8\nstatic void main_death_voiceover_callback()\n{\n    main_death_voiceover_done = true;\n}\n\n// Read endgame subtitle.\n//\n// 0x4814B4\nstatic int mainDeathGrabTextFile(const char* fileName, char* dest)\n{\n    const char* p = strrchr(fileName, '\\\\');\n    if (p == NULL) {\n        return -1;\n    }\n\n    char* language = NULL;\n    if (!config_get_string(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) {\n        debug_printf(\"MAIN: Error grabing language for ending. Defaulting to english.\\n\");\n        language = _aEnglish_2;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"text\\\\%s\\\\cuts\\\\%s%s\", language, p + 1, \".TXT\");\n\n    File* stream = db_fopen(path, \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    while (true) {\n        int c = db_fgetc(stream);\n        if (c == -1) {\n            break;\n        }\n\n        if (c == '\\n') {\n            c = ' ';\n        }\n\n        *dest++ = (c & 0xFF);\n    }\n\n    db_fclose(stream);\n\n    *dest = '\\0';\n\n    return 0;\n}\n\n// 0x481598\nstatic int mainDeathWordWrap(char* text, int width, short* beginnings, short* count)\n{\n    while (true) {\n        char* sep = strchr(text, ':');\n        if (sep == NULL) {\n            break;\n        }\n\n        if (sep - 1 < text) {\n            break;\n        }\n        sep[0] = ' ';\n        sep[-1] = ' ';\n    }\n\n    if (word_wrap(text, width, beginnings, count) == -1) {\n        return -1;\n    }\n\n    *count -= 1;\n\n    for (int index = 1; index < *count; index++) {\n        char* p = text + beginnings[index];\n        while (p >= text && *p != ' ') {\n            p--;\n            beginnings[index]--;\n        }\n\n        if (p != NULL) {\n            *p = '\\0';\n            beginnings[index]++;\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/main.h",
    "content": "#ifndef FALLOUT_GAME_MAIN_H_\n#define FALLOUT_GAME_MAIN_H_\n\nextern int main_game_paused;\n\nint RealMain(int argc, char** argv);\n\n#endif /* FALLOUT_GAME_MAIN_H_ */\n"
  },
  {
    "path": "src/game/mainmenu.c",
    "content": "#include \"game/mainmenu.h\"\n\n#include <ctype.h>\n#include <limits.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gsound.h\"\n#include \"game/options.h\"\n#include \"game/palette.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/version.h\"\n\n#define MAIN_MENU_WINDOW_WIDTH 640\n#define MAIN_MENU_WINDOW_HEIGHT 480\n\ntypedef enum MainMenuButton {\n    MAIN_MENU_BUTTON_INTRO,\n    MAIN_MENU_BUTTON_NEW_GAME,\n    MAIN_MENU_BUTTON_LOAD_GAME,\n    MAIN_MENU_BUTTON_OPTIONS,\n    MAIN_MENU_BUTTON_CREDITS,\n    MAIN_MENU_BUTTON_EXIT,\n    MAIN_MENU_BUTTON_COUNT,\n} MainMenuButton;\n\nstatic int main_menu_fatal_error();\nstatic void main_menu_play_sound(const char* fileName);\n\n// 0x5194F0\nstatic int main_window = -1;\n\n// 0x5194F4\nstatic unsigned char* main_window_buf = NULL;\n\n// 0x5194F8\nstatic unsigned char* background_data = NULL;\n\n// 0x5194FC\nstatic unsigned char* button_up_data = NULL;\n\n// 0x519500\nstatic unsigned char* button_down_data = NULL;\n\n// 0x519504\nbool in_main_menu = false;\n\n// 0x519508\nstatic bool main_menu_created = false;\n\n// 0x51950C\nstatic unsigned int main_menu_timeout = 120000;\n\n// 0x519510\nstatic int button_values[MAIN_MENU_BUTTON_COUNT] = {\n    KEY_LOWERCASE_I, // intro\n    KEY_LOWERCASE_N, // new game\n    KEY_LOWERCASE_L, // load game\n    KEY_LOWERCASE_O, // options\n    KEY_LOWERCASE_C, // credits\n    KEY_LOWERCASE_E, // exit\n};\n\n// 0x519528\nstatic int return_values[MAIN_MENU_BUTTON_COUNT] = {\n    MAIN_MENU_INTRO,\n    MAIN_MENU_NEW_GAME,\n    MAIN_MENU_LOAD_GAME,\n    MAIN_MENU_OPTIONS,\n    MAIN_MENU_CREDITS,\n    MAIN_MENU_EXIT,\n};\n\n// 0x614840\nstatic int buttons[MAIN_MENU_BUTTON_COUNT];\n\n// 0x614858\nstatic bool main_menu_is_hidden;\n\n// 0x61485C\nstatic CacheEntry* button_up_key;\n\n// 0x614860\nstatic CacheEntry* button_down_key;\n\n// 0x614864\nstatic CacheEntry* background_key;\n\n// 0x481650\nint main_menu_create()\n{\n    int fid;\n    MessageListItem msg;\n    int len;\n\n    if (main_menu_created) {\n        return 0;\n    }\n\n    loadColorTable(\"color.pal\");\n\n    int mainMenuWindowX = 0;\n    int mainMenuWindowY = 0;\n    main_window = win_add(mainMenuWindowX,\n        mainMenuWindowY,\n        MAIN_MENU_WINDOW_WIDTH,\n        MAIN_MENU_WINDOW_HEIGHT,\n        0,\n        WINDOW_HIDDEN | WINDOW_FLAG_0x04);\n    if (main_window == -1) {\n        // NOTE: Uninline.\n        return main_menu_fatal_error();\n    }\n\n    main_window_buf = win_get_buf(main_window);\n\n    // mainmenu.frm\n    int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 140, 0, 0, 0);\n    background_data = art_ptr_lock_data(backgroundFid, 0, 0, &background_key);\n    if (background_data == NULL) {\n        // NOTE: Uninline.\n        return main_menu_fatal_error();\n    }\n\n    buf_to_buf(background_data, 640, 480, 640, main_window_buf, 640);\n    art_ptr_unlock(background_key);\n\n    int oldFont = text_curr();\n    text_font(100);\n\n    // Copyright.\n    msg.num = 20;\n    if (message_search(&misc_message_file, &msg)) {\n        win_print(main_window, msg.text, 0, 15, 460, colorTable[21091] | 0x6000000);\n    }\n\n    // Version.\n    char version[VERSION_MAX];\n    getverstr(version);\n    len = text_width(version);\n    win_print(main_window, version, 0, 615 - len, 460, colorTable[21091] | 0x6000000);\n\n    // menuup.frm\n    fid = art_id(OBJ_TYPE_INTERFACE, 299, 0, 0, 0);\n    button_up_data = art_ptr_lock_data(fid, 0, 0, &button_up_key);\n    if (button_up_data == NULL) {\n        // NOTE: Uninline.\n        return main_menu_fatal_error();\n    }\n\n    // menudown.frm\n    fid = art_id(OBJ_TYPE_INTERFACE, 300, 0, 0, 0);\n    button_down_data = art_ptr_lock_data(fid, 0, 0, &button_down_key);\n    if (button_down_data == NULL) {\n        // NOTE: Uninline.\n        return main_menu_fatal_error();\n    }\n\n    for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) {\n        buttons[index] = -1;\n    }\n\n    for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) {\n        buttons[index] = win_register_button(main_window, 30, 19 + index * 42 - index, 26, 26, -1, -1, 1111, button_values[index], button_up_data, button_down_data, 0, 32);\n        if (buttons[index] == -1) {\n            // NOTE: Uninline.\n            return main_menu_fatal_error();\n        }\n\n        win_register_button_mask(buttons[index], button_up_data);\n    }\n\n    text_font(104);\n\n    for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) {\n        msg.num = 9 + index;\n        if (message_search(&misc_message_file, &msg)) {\n            len = text_width(msg.text);\n            text_to_buf(main_window_buf + 640 * (42 * index - index + 20) + 126 - (len / 2), msg.text, 640 - (126 - (len / 2)) - 1, 640, colorTable[21091]);\n        }\n    }\n\n    text_font(oldFont);\n\n    main_menu_created = true;\n    main_menu_is_hidden = true;\n\n    return 0;\n}\n\n// 0x481968\nvoid main_menu_destroy()\n{\n    if (!main_menu_created) {\n        return;\n    }\n\n    for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) {\n        // FIXME: Why it tries to free only invalid buttons?\n        if (buttons[index] == -1) {\n            win_delete_button(buttons[index]);\n        }\n    }\n\n    if (button_down_data) {\n        art_ptr_unlock(button_down_key);\n        button_down_key = NULL;\n        button_down_data = NULL;\n    }\n\n    if (button_up_data) {\n        art_ptr_unlock(button_up_key);\n        button_up_key = NULL;\n        button_up_data = NULL;\n    }\n\n    if (main_window != -1) {\n        win_delete(main_window);\n    }\n\n    main_menu_created = false;\n}\n\n// 0x481A00\nvoid main_menu_hide(bool animate)\n{\n    if (!main_menu_created) {\n        return;\n    }\n\n    if (main_menu_is_hidden) {\n        return;\n    }\n\n    soundContinueAll();\n\n    if (animate) {\n        palette_fade_to(black_palette);\n        soundContinueAll();\n    }\n\n    win_hide(main_window);\n\n    main_menu_is_hidden = true;\n}\n\n// 0x481A48\nvoid main_menu_show(bool animate)\n{\n    if (!main_menu_created) {\n        return;\n    }\n\n    if (!main_menu_is_hidden) {\n        return;\n    }\n\n    win_show(main_window);\n\n    if (animate) {\n        loadColorTable(\"color.pal\");\n        palette_fade_to(cmap);\n    }\n\n    main_menu_is_hidden = false;\n}\n\n// NOTE: Unused.\n//\n// 0x481A8C\nint main_menu_is_shown()\n{\n    return main_menu_created ? main_menu_is_hidden == 0 : 0;\n}\n\n// 0x481AA8\nint main_menu_is_enabled()\n{\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x481AB0\nvoid main_menu_set_timeout(unsigned int timeout)\n{\n    main_menu_timeout = 60000 * timeout;\n}\n\n// NOTE: Unused.\n//\n// 0x481AD0\nunsigned int main_menu_get_timeout()\n{\n    return main_menu_timeout / 1000 / 60;\n}\n\n// 0x481AEC\nint main_menu_loop()\n{\n    in_main_menu = true;\n\n    bool oldCursorIsHidden = mouse_hidden();\n    if (oldCursorIsHidden) {\n        mouse_show();\n    }\n\n    unsigned int tick = get_time();\n\n    int rc = -1;\n    while (rc == -1) {\n        int keyCode = get_input();\n\n        for (int buttonIndex = 0; buttonIndex < MAIN_MENU_BUTTON_COUNT; buttonIndex++) {\n            if (keyCode == button_values[buttonIndex] || keyCode == toupper(button_values[buttonIndex])) {\n                // NOTE: Uninline.\n                main_menu_play_sound(\"nmselec1\");\n\n                rc = return_values[buttonIndex];\n\n                if (buttonIndex == MAIN_MENU_BUTTON_CREDITS && (keys[DIK_RSHIFT] != KEY_STATE_UP || keys[DIK_LSHIFT] != KEY_STATE_UP)) {\n                    rc = MAIN_MENU_QUOTES;\n                }\n\n                break;\n            }\n        }\n\n        if (rc == -1) {\n            if (keyCode == KEY_CTRL_R) {\n                rc = MAIN_MENU_SELFRUN;\n                continue;\n            } else if (keyCode == KEY_PLUS || keyCode == KEY_EQUAL) {\n                IncGamma();\n            } else if (keyCode == KEY_MINUS || keyCode == KEY_UNDERSCORE) {\n                DecGamma();\n            } else if (keyCode == KEY_UPPERCASE_D || keyCode == KEY_LOWERCASE_D) {\n                rc = MAIN_MENU_SCREENSAVER;\n                continue;\n            } else if (keyCode == 1111) {\n                if (!(mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT)) {\n                    // NOTE: Uninline.\n                    main_menu_play_sound(\"nmselec0\");\n                }\n                continue;\n            }\n        }\n\n        if (keyCode == KEY_ESCAPE || game_user_wants_to_quit == 3) {\n            rc = MAIN_MENU_EXIT;\n\n            // NOTE: Uninline.\n            main_menu_play_sound(\"nmselec1\");\n            break;\n        } else if (game_user_wants_to_quit == 2) {\n            game_user_wants_to_quit = 0;\n        } else {\n            if (elapsed_time(tick) >= main_menu_timeout) {\n                rc = MAIN_MENU_TIMEOUT;\n            }\n        }\n    }\n\n    if (oldCursorIsHidden) {\n        mouse_hide();\n    }\n\n    in_main_menu = false;\n\n    return rc;\n}\n\n// NOTE: Inlined.\n//\n// 0x481C88\nstatic int main_menu_fatal_error()\n{\n    main_menu_destroy();\n\n    return -1;\n}\n\n// NOTE: Inlined.\n//\n// 0x481C94\nstatic void main_menu_play_sound(const char* fileName)\n{\n    gsound_play_sfx_file(fileName);\n}\n"
  },
  {
    "path": "src/game/mainmenu.h",
    "content": "#ifndef FALLOUT_GAME_MAINMENU_H_\n#define FALLOUT_GAME_MAINMENU_H_\n\n#include <stdbool.h>\n\ntypedef enum MainMenuOption {\n    MAIN_MENU_INTRO,\n    MAIN_MENU_NEW_GAME,\n    MAIN_MENU_LOAD_GAME,\n    MAIN_MENU_SCREENSAVER,\n    MAIN_MENU_TIMEOUT,\n    MAIN_MENU_CREDITS,\n    MAIN_MENU_QUOTES,\n    MAIN_MENU_EXIT,\n    MAIN_MENU_SELFRUN,\n    MAIN_MENU_OPTIONS,\n} MainMenuOption;\n\nextern bool in_main_menu;\n\nint main_menu_create();\nvoid main_menu_destroy();\nvoid main_menu_hide(bool animate);\nvoid main_menu_show(bool animate);\nint main_menu_is_shown();\nint main_menu_is_enabled();\nvoid main_menu_set_timeout(unsigned int timeout);\nunsigned int main_menu_get_timeout();\nint main_menu_loop();\n\n#endif /* FALLOUT_GAME_MAINMENU_H_ */\n"
  },
  {
    "path": "src/game/map.c",
    "content": "#include \"game/map.h\"\n\n#include <direct.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"game/automap.h\"\n#include \"game/editor.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gmovie.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/light.h\"\n#include \"game/loadsave.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/palette.h\"\n#include \"game/pipboy.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/intrface.h\"\n#include \"plib/gnw/svga.h\"\n#include \"game/worldmap.h\"\n\nstatic void map_display_draw(Rect* rect);\nstatic void map_scroll_refresh_game(Rect* rect);\nstatic void map_scroll_refresh_mapper(Rect* rect);\nstatic int map_allocate_global_vars(int count);\nstatic void map_free_global_vars();\nstatic int map_load_global_vars(File* stream);\nstatic int map_allocate_local_vars(int count);\nstatic void map_free_local_vars();\nstatic int map_load_local_vars(File* stream);\nstatic void map_place_dude_and_mouse();\nstatic void square_init();\nstatic void square_reset();\nstatic int square_load(File* stream, int a2);\nstatic int map_write_MapData(MapHeader* ptr, File* stream);\nstatic int map_read_MapData(MapHeader* ptr, File* stream);\n\n// 0x50B058\nchar byte_50B058[] = \"\";\n\n// 0x50B30C\nchar _aErrorF2[] = \"ERROR! F2\";\n\n// 0x519540\nstatic IsoWindowRefreshProc* map_scroll_refresh = map_scroll_refresh_game;\n\n// 0x519544\nstatic int map_data_elev_flags[ELEVATION_COUNT] = {\n    2,\n    4,\n    8,\n};\n\n// 0x519550\nstatic unsigned int map_last_scroll_time = 0;\n\n// 0x519554\nstatic bool map_bk_enabled = false;\n\n// 0x519558\nstatic int mapEntranceElevation = 0;\n\n// 0x51955C\nstatic int mapEntranceTileNum = -1;\n\n// 0x519560\nstatic int mapEntranceRotation = ROTATION_NE;\n\n// 0x519564\nint map_script_id = -1;\n\n// local_vars\n// 0x519568\nint* map_local_vars = NULL;\n\n// map_vars\n// 0x51956C\nint* map_global_vars = NULL;\n\n// local_vars_num\n// 0x519570\nint num_map_local_vars = 0;\n\n// map_vars_num\n// 0x519574\nint num_map_global_vars = 0;\n\n// Current elevation.\n//\n// 0x519578\nint map_elevation = 0;\n\n// 0x519584\nstatic int wmMapIdx = -1;\n\n// 0x614868\nTileData square_data[ELEVATION_COUNT];\n\n// 0x631D28\nstatic MapTransition map_state;\n\n// 0x631D38\nstatic Rect map_display_rect;\n\n// map.msg\n//\n// map_msg_file\n// 0x631D48\nMessageList map_msg_file;\n\n// 0x631D50\nstatic unsigned char* display_buf;\n\n// 0x631D54\nMapHeader map_data;\n\n// 0x631E40\nTileData* square[ELEVATION_COUNT];\n\n// 0x631E4C\nint display_win;\n\n// iso_init\n// 0x481CA0\nint iso_init()\n{\n    tile_disable_scroll_limiting();\n    tile_disable_scroll_blocking();\n\n    // NOTE: Uninline.\n    square_init();\n\n    display_win = win_add(0, 0, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99, 256, 10);\n    if (display_win == -1) {\n        debug_printf(\"win_add failed in iso_init\\n\");\n        return -1;\n    }\n\n    display_buf = win_get_buf(display_win);\n    if (display_buf == NULL) {\n        debug_printf(\"win_get_buf failed in iso_init\\n\");\n        return -1;\n    }\n\n    if (win_get_rect(display_win, &map_display_rect) != 0) {\n        debug_printf(\"win_get_rect failed in iso_init\\n\");\n        return -1;\n    }\n\n    if (art_init() != 0) {\n        debug_printf(\"art_init failed in iso_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">art_init\\t\\t\");\n\n    if (tile_init(square, SQUARE_GRID_WIDTH, SQUARE_GRID_HEIGHT, HEX_GRID_WIDTH, HEX_GRID_HEIGHT, display_buf, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99, scr_size.lrx - scr_size.ulx + 1, map_display_draw) != 0) {\n        debug_printf(\"tile_init failed in iso_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">tile_init\\t\\t\");\n\n    if (obj_init(display_buf, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99, scr_size.lrx - scr_size.ulx + 1) != 0) {\n        debug_printf(\"obj_init failed in iso_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">obj_init\\t\\t\");\n\n    cycle_init();\n    debug_printf(\">cycle_init\\t\\t\");\n\n    tile_enable_scroll_blocking();\n    tile_enable_scroll_limiting();\n\n    if (intface_init() != 0) {\n        debug_printf(\"intface_init failed in iso_init\\n\");\n        return -1;\n    }\n\n    debug_printf(\">intface_init\\t\\t\");\n\n    map_setup_paths();\n\n    mapEntranceElevation = -1;\n    mapEntranceTileNum = -1;\n    mapEntranceRotation = -1;\n\n    return 0;\n}\n\n// 0x481ED4\nvoid iso_reset()\n{\n    if (map_global_vars != NULL) {\n        mem_free(map_global_vars);\n        map_global_vars = NULL;\n        num_map_global_vars = 0;\n    }\n\n    if (map_local_vars != NULL) {\n        mem_free(map_local_vars);\n        map_local_vars = NULL;\n        num_map_local_vars = 0;\n    }\n\n    art_reset();\n    tile_reset();\n    obj_reset();\n    cycle_reset();\n    intface_reset();\n    mapEntranceElevation = -1;\n    mapEntranceTileNum = -1;\n    mapEntranceRotation = -1;\n}\n\n// 0x481F48\nvoid iso_exit()\n{\n    intface_exit();\n    cycle_exit();\n    obj_exit();\n    tile_exit();\n    art_exit();\n\n    if (map_global_vars != NULL) {\n        mem_free(map_global_vars);\n        map_global_vars = NULL;\n        num_map_global_vars = 0;\n    }\n\n    if (map_local_vars != NULL) {\n        mem_free(map_local_vars);\n        map_local_vars = NULL;\n        num_map_local_vars = 0;\n    }\n}\n\n// 0x481FB4\nvoid map_init()\n{\n    char* executable;\n    config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, \"executable\", &executable);\n    if (stricmp(executable, \"mapper\") == 0) {\n        map_scroll_refresh = map_scroll_refresh_mapper;\n    }\n\n    if (message_init(&map_msg_file)) {\n        char path[FILENAME_MAX];\n        sprintf(path, \"%smap.msg\", msg_path);\n\n        if (!message_load(&map_msg_file, path)) {\n            debug_printf(\"\\nError loading map_msg_file!\");\n        }\n    } else {\n        debug_printf(\"\\nError initing map_msg_file!\");\n    }\n\n    // NOTE: Uninline.\n    map_reset();\n}\n\n// NOTE: Inlined.\n//\n// 0x482064\nvoid map_reset()\n{\n    map_new_map();\n    add_bk_process(gmouse_bk_process);\n    gmouse_disable(0);\n    win_show(display_win);\n}\n\n// 0x482084\nvoid map_exit()\n{\n    win_hide(display_win);\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    remove_bk_process(gmouse_bk_process);\n    if (!message_exit(&map_msg_file)) {\n        debug_printf(\"\\nError exiting map_msg_file!\");\n    }\n}\n\n// 0x4820C0\nvoid map_enable_bk_processes()\n{\n    if (!map_bk_enabled) {\n        text_object_enable();\n        if (!game_ui_is_disabled()) {\n            gmouse_enable();\n        }\n        add_bk_process(object_animate);\n        add_bk_process(dude_fidget);\n        scr_enable_critters();\n        map_bk_enabled = true;\n    }\n}\n\n// 0x482104\nbool map_disable_bk_processes()\n{\n    if (!map_bk_enabled) {\n        return false;\n    }\n\n    scr_disable_critters();\n    remove_bk_process(dude_fidget);\n    remove_bk_process(object_animate);\n    gmouse_disable(0);\n    text_object_disable();\n\n    map_bk_enabled = false;\n\n    return true;\n}\n\n// 0x482148\nbool map_bk_processes_are_disabled()\n{\n    return map_bk_enabled == false;\n}\n\n// map_set_elevation\n// 0x482158\nint map_set_elevation(int elevation)\n{\n    if (!elevationIsValid(elevation)) {\n        return -1;\n    }\n\n    bool gameMouseWasVisible = false;\n    if (gmouse_get_cursor() != MOUSE_CURSOR_WAIT_PLANET) {\n        gameMouseWasVisible = gmouse_3d_is_on();\n        gmouse_3d_off();\n        gmouse_set_cursor(MOUSE_CURSOR_NONE);\n    }\n\n    if (elevation != map_elevation) {\n        wmMapMarkMapEntranceState(map_data.field_34, elevation, 1);\n    }\n\n    map_elevation = elevation;\n\n    register_clear(obj_dude);\n    dude_stand(obj_dude, obj_dude->rotation, obj_dude->fid);\n    partyMemberSyncPosition();\n\n    if (map_script_id != -1) {\n        scr_exec_map_update_scripts();\n    }\n\n    if (gameMouseWasVisible) {\n        gmouse_3d_on();\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4821F4\nbool map_is_elevation_empty(int elevation)\n{\n    return elevation < 0\n        || elevation >= ELEVATION_COUNT\n        || (map_data.flags & map_data_elev_flags[elevation]) != 0;\n}\n\n// 0x482220\nint map_set_global_var(int var, int value)\n{\n    if (var < 0 || var >= num_map_global_vars) {\n        debug_printf(\"ERROR: attempt to reference map var out of range: %d\", var);\n        return -1;\n    }\n\n    map_global_vars[var] = value;\n\n    return 0;\n}\n\n// 0x482250\nint map_get_global_var(int var)\n{\n    if (var < 0 || var >= num_map_global_vars) {\n        debug_printf(\"ERROR: attempt to reference map var out of range: %d\", var);\n        return 0;\n    }\n\n    return map_global_vars[var];\n}\n\n// 0x482280\nint map_set_local_var(int var, int value)\n{\n    if (var < 0 || var >= num_map_local_vars) {\n        debug_printf(\"ERROR: attempt to reference local var out of range: %d\", var);\n        return -1;\n    }\n\n    map_local_vars[var] = value;\n\n    return 0;\n}\n\n// 0x4822B0\nint map_get_local_var(int var)\n{\n    if (var < 0 || var >= num_map_local_vars) {\n        debug_printf(\"ERROR: attempt to reference local var out of range: %d\", var);\n        return 0;\n    }\n\n    return map_local_vars[var];\n}\n\n// Make a room to store more local variables.\n//\n// 0x4822E0\nint map_malloc_local_var(int a1)\n{\n    int oldMapLocalVarsLength = num_map_local_vars;\n    num_map_local_vars += a1;\n\n    int* vars = (int*)mem_realloc(map_local_vars, sizeof(*vars) * num_map_local_vars);\n    if (vars == NULL) {\n        debug_printf(\"\\nError: Ran out of memory!\");\n    }\n\n    map_local_vars = vars;\n    memset((unsigned char*)vars + sizeof(*vars) * oldMapLocalVarsLength, 0, sizeof(*vars) * a1);\n\n    return oldMapLocalVarsLength;\n}\n\n// 0x48234C\nvoid map_set_entrance_hex(int tile, int elevation, int rotation)\n{\n    map_data.enteringTile = tile;\n    map_data.enteringElevation = elevation;\n    map_data.enteringRotation = rotation;\n}\n\n// NOTE: Unused.\n//\n// 0x48247C\nvoid map_set_name(const char* name)\n{\n    strcpy(map_data.name, name);\n}\n\n// NOTE: Unused.\n//\n// 0x4824A4\nvoid map_get_name(char* name)\n{\n    strcpy(name, map_data.name);\n}\n\n// 0x4824CC\nchar* map_get_elev_idx(int map, int elevation)\n{\n    if (map < 0 || map >= wmMapMaxCount()) {\n        return NULL;\n    }\n\n    if (!elevationIsValid(elevation)) {\n        return NULL;\n    }\n\n    MessageListItem messageListItem;\n    return getmsg(&map_msg_file, &messageListItem, map * 3 + elevation + 200);\n}\n\n// TODO: Check, probably returns true if map1 and map2 represents the same city.\n//\n// 0x482528\nbool is_map_idx_same(int map1, int map2)\n{\n    if (map1 < 0 || map1 >= wmMapMaxCount()) {\n        return 0;\n    }\n\n    if (map2 < 0 || map2 >= wmMapMaxCount()) {\n        return 0;\n    }\n\n    if (!wmMapIdxIsSaveable(map1)) {\n        return 0;\n    }\n\n    if (!wmMapIdxIsSaveable(map2)) {\n        return 0;\n    }\n\n    int city1;\n    if (wmMatchAreaContainingMapIdx(map1, &city1) == -1) {\n        return 0;\n    }\n\n    int city2;\n    if (wmMatchAreaContainingMapIdx(map2, &city2) == -1) {\n        return 0;\n    }\n\n    return city1 == city2;\n}\n\n// 0x4825CC\nint get_map_idx_same(int map1, int map2)\n{\n    int city1 = -1;\n    if (wmMatchAreaContainingMapIdx(map1, &city1) == -1) {\n        return -1;\n    }\n\n    int city2 = -2;\n    if (wmMatchAreaContainingMapIdx(map2, &city2) == -1) {\n        return -1;\n    }\n\n    if (city1 != city2) {\n        return -1;\n    }\n\n    return city1;\n}\n\n// 0x48261C\nchar* map_get_short_name(int map)\n{\n    int city;\n    if (wmMatchAreaContainingMapIdx(map, &city) == -1) {\n        return _aErrorF2;\n    }\n\n    MessageListItem messageListItem;\n    char* name = getmsg(&map_msg_file, &messageListItem, 1500 + city);\n    return name;\n}\n\n// NOTE: Unused.\n//\n// 0x482684\nchar* map_get_description()\n{\n    return map_get_description_idx(map_data.field_34);\n}\n\n// 0x48268C\nchar* map_get_description_idx(int map)\n{\n    // 0x631E50\n    static char scratchStr[40];\n\n    // 0x51957C\n    static char* errMapName = byte_50B058;\n\n    int city;\n    if (wmMatchAreaContainingMapIdx(map, &city) == 0) {\n        wmGetAreaIdxName(city, scratchStr);\n    } else {\n        strcpy(scratchStr, errMapName);\n    }\n\n    return scratchStr;\n}\n\n// 0x4826B8\nint map_get_index_number()\n{\n    return map_data.field_34;\n}\n\n// 0x4826C0\nint map_scroll(int dx, int dy)\n{\n    if (elapsed_time(map_last_scroll_time) < 33) {\n        return -2;\n    }\n\n    map_last_scroll_time = get_time();\n\n    int screenDx = dx * 32;\n    int screenDy = dy * 24;\n\n    if (screenDx == 0 && screenDy == 0) {\n        return -1;\n    }\n\n    gmouse_3d_off();\n\n    int centerScreenX;\n    int centerScreenY;\n    tile_coord(tile_center_tile, &centerScreenX, &centerScreenY, map_elevation);\n    centerScreenX += screenDx + 16;\n    centerScreenY += screenDy + 8;\n\n    int newCenterTile = tile_num(centerScreenX, centerScreenY, map_elevation);\n    if (newCenterTile == -1) {\n        return -1;\n    }\n\n    if (tile_set_center(newCenterTile, 0) == -1) {\n        return -1;\n    }\n\n    Rect r1;\n    rectCopy(&r1, &map_display_rect);\n\n    Rect r2;\n    rectCopy(&r2, &r1);\n\n    int width = scr_size.lrx - scr_size.ulx + 1;\n    int pitch = width;\n    int height = scr_size.lry - scr_size.uly - 99;\n\n    if (screenDx != 0) {\n        width -= 32;\n    }\n\n    if (screenDy != 0) {\n        height -= 24;\n    }\n\n    if (screenDx < 0) {\n        r2.lrx = r2.ulx - screenDx;\n    } else {\n        r2.ulx = r2.lrx - screenDx;\n    }\n\n    unsigned char* src;\n    unsigned char* dest;\n    int step;\n    if (screenDy < 0) {\n        r1.lry = r1.uly - screenDy;\n        src = display_buf + pitch * (height - 1);\n        dest = display_buf + pitch * (scr_size.lry - scr_size.uly - 100);\n        if (screenDx < 0) {\n            dest -= screenDx;\n        } else {\n            src += screenDx;\n        }\n        step = -pitch;\n    } else {\n        r1.uly = r1.lry - screenDy;\n        dest = display_buf;\n        src = display_buf + pitch * screenDy;\n\n        if (screenDx < 0) {\n            dest -= screenDx;\n        } else {\n            src += screenDx;\n        }\n        step = pitch;\n    }\n\n    for (int y = 0; y < height; y++) {\n        memmove(dest, src, width);\n        dest += step;\n        src += step;\n    }\n\n    if (screenDx != 0) {\n        map_scroll_refresh(&r2);\n    }\n\n    if (screenDy != 0) {\n        map_scroll_refresh(&r1);\n    }\n\n    win_draw(display_win);\n\n    return 0;\n}\n\n// 0x482900\nchar* map_file_path(char* name)\n{\n    // 0x631E78\n    static char map_path[MAX_PATH];\n\n    if (*name != '\\\\') {\n        sprintf(map_path, \"maps\\\\%s\", name);\n        return map_path;\n    }\n    return name;\n}\n\n// 0x482924\nint mapSetEntranceInfo(int elevation, int tile_num, int orientation)\n{\n    mapEntranceElevation = elevation;\n    mapEntranceTileNum = tile_num;\n    mapEntranceRotation = orientation;\n    return 0;\n}\n\n// 0x482938\nvoid map_new_map()\n{\n    map_set_elevation(0);\n    tile_set_center(20100, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS);\n    memset(&map_state, 0, sizeof(map_state));\n    map_data.enteringElevation = 0;\n    map_data.enteringRotation = 0;\n    map_data.localVariablesCount = 0;\n    map_data.version = 20;\n    map_data.name[0] = '\\0';\n    map_data.enteringTile = 20100;\n    obj_remove_all();\n    anim_stop();\n\n    if (map_global_vars != NULL) {\n        mem_free(map_global_vars);\n        map_global_vars = NULL;\n        num_map_global_vars = 0;\n    }\n\n    if (map_local_vars != NULL) {\n        mem_free(map_local_vars);\n        map_local_vars = NULL;\n        num_map_local_vars = 0;\n    }\n\n    square_reset();\n    map_place_dude_and_mouse();\n    tile_refresh_display();\n}\n\n// 0x482A68\nint map_load(char* fileName)\n{\n    int rc;\n\n    strupr(fileName);\n\n    rc = -1;\n\n    char* extension = strstr(fileName, \".MAP\");\n    if (extension != NULL) {\n        strcpy(extension, \".SAV\");\n\n        const char* filePath = map_file_path(fileName);\n\n        File* stream = db_fopen(filePath, \"rb\");\n\n        strcpy(extension, \".MAP\");\n\n        if (stream != NULL) {\n            db_fclose(stream);\n            rc = map_load_in_game(fileName);\n            wmMapMusicStart();\n        }\n    }\n\n    if (rc == -1) {\n        const char* filePath = map_file_path(fileName);\n        File* stream = db_fopen(filePath, \"rb\");\n        if (stream != NULL) {\n            rc = map_load_file(stream);\n            db_fclose(stream);\n        }\n\n        if (rc == 0) {\n            strcpy(map_data.name, fileName);\n            obj_dude->data.critter.combat.whoHitMe = NULL;\n        }\n    }\n\n    return rc;\n}\n\n// 0x482B34\nint map_load_idx(int map)\n{\n    scr_set_ext_param(map_script_id, map);\n\n    char name[16];\n    if (wmMapIdxToName(map, name) == -1) {\n        return -1;\n    }\n\n    wmMapIdx = map;\n\n    int rc = map_load(name);\n\n    wmMapMusicStart();\n\n    return rc;\n}\n\n// 0x482B74\nint map_load_file(File* stream)\n{\n    map_save_in_game(true);\n    gsound_background_play(\"wind2\", 12, 13, 16);\n    map_disable_bk_processes();\n    partyMemberPrepLoad();\n    gmouse_disable_scrolling();\n\n    int savedMouseCursorId = gmouse_get_cursor();\n    gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET);\n    db_register_callback(gmouse_bk_process, 32768);\n    tile_disable_refresh();\n\n    int rc = 0;\n\n    win_fill(display_win, 0, 0, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99, colorTable[0]);\n    win_draw(display_win);\n    anim_stop();\n    scr_disable();\n\n    map_script_id = -1;\n\n    const char* error = NULL;\n\n    error = \"Invalid file handle\";\n    if (stream == NULL) {\n        goto err;\n    }\n\n    error = \"Error reading header\";\n    if (map_read_MapData(&map_data, stream) != 0) {\n        goto err;\n    }\n\n    error = \"Invalid map version\";\n    if (map_data.version != 19 && map_data.version != 20) {\n        goto err;\n    }\n\n    if (mapEntranceElevation == -1) {\n        mapEntranceElevation = map_data.enteringElevation;\n        mapEntranceTileNum = map_data.enteringTile;\n        mapEntranceRotation = map_data.enteringRotation;\n    }\n\n    obj_remove_all();\n\n    if (map_data.globalVariablesCount < 0) {\n        map_data.globalVariablesCount = 0;\n    }\n\n    if (map_data.localVariablesCount < 0) {\n        map_data.localVariablesCount = 0;\n    }\n\n    error = \"Error allocating global vars\";\n    // NOTE: Uninline.\n    if (map_allocate_global_vars(map_data.globalVariablesCount) != 0) {\n        goto err;\n    }\n\n    error = \"Error loading global vars\";\n    // NOTE: Uninline.\n    if (map_load_global_vars(stream) != 0) {\n        goto err;\n    }\n\n    error = \"Error allocating local vars\";\n    // NOTE: Uninline.\n    if (map_allocate_local_vars(map_data.localVariablesCount) != 0) {\n        goto err;\n    }\n\n    error = \"Error loading local vars\";\n    if (map_load_local_vars(stream) != 0) {\n        goto err;\n    }\n\n    if (square_load(stream, map_data.flags) != 0) {\n        goto err;\n    }\n\n    error = \"Error reading scripts\";\n    if (scr_load(stream) != 0) {\n        goto err;\n    }\n\n    error = \"Error reading objects\";\n    if (obj_load(stream) != 0) {\n        goto err;\n    }\n\n    if ((map_data.flags & 1) == 0) {\n        map_fix_critter_combat_data();\n    }\n\n    error = \"Error setting map elevation\";\n    if (map_set_elevation(mapEntranceElevation) != 0) {\n        goto err;\n    }\n\n    error = \"Error setting tile center\";\n    if (tile_set_center(mapEntranceTileNum, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) != 0) {\n        goto err;\n    }\n\n    light_set_ambient(LIGHT_LEVEL_MAX, false);\n    obj_move_to_tile(obj_dude, tile_center_tile, map_elevation, NULL);\n    obj_set_rotation(obj_dude, mapEntranceRotation, NULL);\n    map_data.field_34 = wmMapMatchNameToIdx(map_data.name);\n\n    if ((map_data.flags & 1) == 0) {\n        char path[MAX_PATH];\n        sprintf(path, \"maps\\\\%s\", map_data.name);\n\n        char* extension = strstr(path, \".MAP\");\n        if (extension == NULL) {\n            extension = strstr(path, \".map\");\n        }\n\n        if (extension != NULL) {\n            *extension = '\\0';\n        }\n\n        strcat(path, \".GAM\");\n        game_load_info_vars(path, \"MAP_GLOBAL_VARS:\", &num_map_global_vars, &map_global_vars);\n        map_data.globalVariablesCount = num_map_global_vars;\n    }\n\n    scr_enable();\n\n    if (map_data.scriptIndex > 0) {\n        error = \"Error creating new map script\";\n        if (scr_new(&map_script_id, SCRIPT_TYPE_SYSTEM) == -1) {\n            goto err;\n        }\n\n        Object* object;\n        int fid = art_id(OBJ_TYPE_MISC, 12, 0, 0, 0);\n        obj_new(&object, fid, -1);\n        object->flags |= (OBJECT_LIGHT_THRU | OBJECT_TEMPORARY | OBJECT_HIDDEN);\n        obj_move_to_tile(object, 1, 0, NULL);\n        object->sid = map_script_id;\n        scr_set_ext_param(map_script_id, (map_data.flags & 1) == 0);\n\n        Script* script;\n        scr_ptr(map_script_id, &script);\n        script->field_14 = map_data.scriptIndex - 1;\n        script->flags |= SCRIPT_FLAG_0x08;\n        object->id = new_obj_id();\n        script->field_1C = object->id;\n        script->owner = object;\n        scr_spatials_disable();\n        exec_script_proc(map_script_id, SCRIPT_PROC_MAP_ENTER);\n        scr_spatials_enable();\n\n        error = \"Error Setting up random encounter\";\n        if (wmSetupRandomEncounter() == -1) {\n            goto err;\n        }\n    }\n\n    error = NULL;\n\nerr:\n\n    if (error != NULL) {\n        char message[100]; // TODO: Size is probably wrong.\n        sprintf(message, \"%s while loading map.\", error);\n        debug_printf(message);\n        map_new_map();\n        rc = -1;\n    } else {\n        obj_preload_art_cache(map_data.flags);\n    }\n\n    partyMemberRecoverLoad();\n    intface_show();\n    proto_dude_update_gender();\n    map_place_dude_and_mouse();\n    db_register_callback(NULL, 0);\n    map_enable_bk_processes();\n    gmouse_disable_scrolling();\n    gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET);\n\n    if (scr_load_all_scripts() == -1) {\n        debug_printf(\"\\n   Error: scr_load_all_scripts failed!\");\n    }\n\n    scr_exec_map_enter_scripts();\n    scr_exec_map_update_scripts();\n    tile_enable_refresh();\n\n    if (map_state.map > 0) {\n        if (map_state.rotation >= 0) {\n            obj_set_rotation(obj_dude, map_state.rotation, NULL);\n        }\n    } else {\n        tile_refresh_display();\n    }\n\n    gtime_q_add();\n\n    if (gsound_sfx_q_start() == -1) {\n        rc = -1;\n    }\n\n    wmMapMarkVisited(map_data.field_34);\n    wmMapMarkMapEntranceState(map_data.field_34, map_elevation, 1);\n\n    if (wmCheckGameAreaEvents() != 0) {\n        rc = -1;\n    }\n\n    db_register_callback(NULL, 0);\n\n    if (game_ui_is_disabled() == 0) {\n        gmouse_enable_scrolling();\n    }\n\n    gmouse_set_cursor(savedMouseCursorId);\n\n    mapEntranceElevation = -1;\n    mapEntranceTileNum = -1;\n    mapEntranceRotation = -1;\n\n    gmPaletteFinish();\n\n    map_data.version = 20;\n\n    return rc;\n}\n\n// 0x483188\nint map_load_in_game(char* fileName)\n{\n    debug_printf(\"\\nMAP: Loading SAVED map.\");\n\n    char mapName[16]; // TODO: Size is probably wrong.\n    strmfe(mapName, fileName, \"SAV\");\n\n    int rc = map_load(mapName);\n\n    if (game_time() >= map_data.lastVisitTime) {\n        if (((game_time() - map_data.lastVisitTime) / GAME_TIME_TICKS_PER_HOUR) >= 24) {\n            obj_unjam_all_locks();\n        }\n\n        if (map_age_dead_critters() == -1) {\n            debug_printf(\"\\nError: Critter aging failed on map load!\");\n            return -1;\n        }\n    }\n\n    if (!wmMapIsSaveable()) {\n        debug_printf(\"\\nDestroying RANDOM encounter map.\");\n\n        char v15[16];\n        strcpy(v15, map_data.name);\n\n        strmfe(map_data.name, v15, \"SAV\");\n\n        MapDirEraseFile(\"MAPS\\\\\", map_data.name);\n\n        strcpy(map_data.name, v15);\n    }\n\n    return rc;\n}\n\n// 0x48328C\nint map_age_dead_critters()\n{\n    if (!wmMapDeadBodiesAge()) {\n        return 0;\n    }\n\n    int hoursSinceLastVisit = (game_time() - map_data.lastVisitTime) / GAME_TIME_TICKS_PER_HOUR;\n    if (hoursSinceLastVisit == 0) {\n        return 0;\n    }\n\n    Object* obj = obj_find_first();\n    while (obj != NULL) {\n        if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER\n            && obj != obj_dude\n            && !isPartyMember(obj)\n            && !critter_is_dead(obj)) {\n            obj->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n            if (critterGetKillType(obj) != KILL_TYPE_ROBOT && critter_flag_check(obj->pid, CRITTER_NO_HEAL) == 0) {\n                critter_heal_hours(obj, hoursSinceLastVisit);\n            }\n        }\n        obj = obj_find_next();\n    }\n\n    int agingType;\n    if (hoursSinceLastVisit > 6 * 24) {\n        agingType = 1;\n    } else if (hoursSinceLastVisit > 14 * 24) {\n        agingType = 2;\n    } else {\n        return 0;\n    }\n\n    int capacity = 100;\n    int count = 0;\n    Object** objects = (Object**)mem_malloc(sizeof(*objects) * capacity);\n\n    obj = obj_find_first();\n    while (obj != NULL) {\n        int type = PID_TYPE(obj->pid);\n        if (type == OBJ_TYPE_CRITTER) {\n            if (obj != obj_dude && critter_is_dead(obj)) {\n                if (critterGetKillType(obj) != KILL_TYPE_ROBOT && critter_flag_check(obj->pid, CRITTER_NO_HEAL) == 0) {\n                    objects[count++] = obj;\n\n                    if (count >= capacity) {\n                        capacity *= 2;\n                        objects = (Object**)mem_realloc(objects, sizeof(*objects) * capacity);\n                        if (objects == NULL) {\n                            debug_printf(\"\\nError: Out of Memory!\");\n                            return -1;\n                        }\n                    }\n                }\n            }\n        } else if (agingType == 2 && type == OBJ_TYPE_MISC && obj->pid == 0x500000B) {\n            objects[count++] = obj;\n            if (count >= capacity) {\n                capacity *= 2;\n                objects = (Object**)mem_realloc(objects, sizeof(*objects) * capacity);\n                if (objects == NULL) {\n                    debug_printf(\"\\nError: Out of Memory!\");\n                    return -1;\n                }\n            }\n        }\n        obj = obj_find_next();\n    }\n\n    int rc = 0;\n    for (int index = 0; index < count; index++) {\n        Object* obj = objects[index];\n        if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n            if (critter_flag_check(obj->pid, CRITTER_NO_DROP) == 0) {\n                item_drop_all(obj, obj->tile);\n            }\n\n            Object* blood;\n            if (obj_pid_new(&blood, 0x5000004) == -1) {\n                rc = -1;\n                break;\n            }\n\n            obj_move_to_tile(blood, obj->tile, obj->elevation, NULL);\n\n            Proto* proto;\n            proto_ptr(obj->pid, &proto);\n\n            int frame = roll_random(0, 3);\n            if ((proto->critter.flags & 0x800)) {\n                frame += 6;\n            } else {\n                if (critterGetKillType(obj) != KILL_TYPE_RAT\n                    && critterGetKillType(obj) != KILL_TYPE_MANTIS) {\n                    frame += 3;\n                }\n            }\n\n            obj_set_frame(blood, frame, NULL);\n        }\n\n        register_clear(obj);\n        obj_erase_object(obj, NULL);\n    }\n\n    mem_free(objects);\n\n    return rc;\n}\n\n// 0x48358C\nint map_target_load_area()\n{\n    int city = -1;\n    if (wmMatchAreaContainingMapIdx(map_data.field_34, &city) == -1) {\n        city = -1;\n    }\n    return city;\n}\n\n// 0x4835B4\nint map_leave_map(MapTransition* transition)\n{\n    if (transition == NULL) {\n        return -1;\n    }\n\n    memcpy(&map_state, transition, sizeof(map_state));\n\n    if (map_state.map == 0) {\n        map_state.map = -2;\n    }\n\n    if (isInCombat()) {\n        game_user_wants_to_quit = 1;\n    }\n\n    return 0;\n}\n\n// 0x4835F8\nint map_check_state()\n{\n    if (map_state.map == 0) {\n        return 0;\n    }\n\n    gmouse_3d_off();\n\n    gmouse_set_cursor(MOUSE_CURSOR_NONE);\n\n    if (map_state.map == -1) {\n        if (!isInCombat()) {\n            anim_stop();\n            wmTownMap();\n            memset(&map_state, 0, sizeof(map_state));\n        }\n    } else if (map_state.map == -2) {\n        if (!isInCombat()) {\n            anim_stop();\n            wmWorldMap();\n            memset(&map_state, 0, sizeof(map_state));\n        }\n    } else {\n        if (!isInCombat()) {\n            if (map_state.map != map_data.field_34 || map_elevation == map_state.elevation) {\n                map_load_idx(map_state.map);\n            }\n\n            if (map_state.tile != -1 && map_state.tile != 0\n                && map_data.field_34 != MAP_MODOC_BEDNBREAKFAST && map_data.field_34 != MAP_THE_SQUAT_A\n                && elevationIsValid(map_state.elevation)) {\n                obj_move_to_tile(obj_dude, map_state.tile, map_state.elevation, NULL);\n                map_set_elevation(map_state.elevation);\n                obj_set_rotation(obj_dude, map_state.rotation, NULL);\n            }\n\n            if (tile_set_center(obj_dude->tile, TILE_SET_CENTER_REFRESH_WINDOW) == -1) {\n                debug_printf(\"\\nError: map: attempt to center out-of-bounds!\");\n            }\n\n            memset(&map_state, 0, sizeof(map_state));\n\n            int city;\n            wmMatchAreaContainingMapIdx(map_data.field_34, &city);\n            if (wmTeleportToArea(city) == -1) {\n                debug_printf(\"\\nError: couldn't make jump on worldmap for map jump!\");\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x483784\nvoid map_fix_critter_combat_data()\n{\n    for (Object* object = obj_find_first(); object != NULL; object = obj_find_next()) {\n        if (object->pid == -1) {\n            continue;\n        }\n\n        if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n            continue;\n        }\n\n        if (object->data.critter.combat.whoHitMeCid == -1) {\n            object->data.critter.combat.whoHitMe = NULL;\n        }\n    }\n}\n\n// 0x483850\nint map_save()\n{\n    char temp[80];\n    temp[0] = '\\0';\n\n    char* masterPatchesPath;\n    if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) {\n        strcat(temp, masterPatchesPath);\n        mkdir(temp);\n\n        strcat(temp, \"\\\\MAPS\");\n        mkdir(temp);\n    }\n\n    int rc = -1;\n    if (map_data.name[0] != '\\0') {\n        char* mapFileName = map_file_path(map_data.name);\n        File* stream = db_fopen(mapFileName, \"wb\");\n        if (stream != NULL) {\n            rc = map_save_file(stream);\n            db_fclose(stream);\n        } else {\n            sprintf(temp, \"Unable to open %s to write!\", map_data.name);\n            debug_printf(temp);\n        }\n\n        if (rc == 0) {\n            sprintf(temp, \"%s saved.\", map_data.name);\n            debug_printf(temp);\n        }\n    } else {\n        debug_printf(\"\\nError: map_save: map header corrupt!\");\n    }\n\n    return rc;\n}\n\n// 0x483980\nint map_save_file(File* stream)\n{\n    if (stream == NULL) {\n        return -1;\n    }\n\n    scr_disable();\n\n    for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        int tile;\n        for (tile = 0; tile < SQUARE_GRID_SIZE; tile++) {\n            int fid;\n\n            fid = art_id(OBJ_TYPE_TILE, square[elevation]->field_0[tile] & 0xFFF, 0, 0, 0);\n            if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) {\n                break;\n            }\n\n            fid = art_id(OBJ_TYPE_TILE, (square[elevation]->field_0[tile] >> 16) & 0xFFF, 0, 0, 0);\n            if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) {\n                break;\n            }\n        }\n\n        if (tile == SQUARE_GRID_SIZE) {\n            Object* object = obj_find_first_at(elevation);\n            if (object != NULL) {\n                // TODO: Implementation is slightly different, check in debugger.\n                while (object != NULL && (object->flags & OBJECT_TEMPORARY)) {\n                    object = obj_find_next_at();\n                }\n\n                if (object != NULL) {\n                    map_data.flags &= ~map_data_elev_flags[elevation];\n                } else {\n                    map_data.flags |= map_data_elev_flags[elevation];\n                }\n            } else {\n                map_data.flags |= map_data_elev_flags[elevation];\n            }\n        } else {\n            map_data.flags &= ~map_data_elev_flags[elevation];\n        }\n    }\n\n    map_data.localVariablesCount = num_map_local_vars;\n    map_data.globalVariablesCount = num_map_global_vars;\n    map_data.darkness = 1;\n\n    map_write_MapData(&map_data, stream);\n\n    if (map_data.globalVariablesCount != 0) {\n        db_fwriteIntCount(stream, map_global_vars, map_data.globalVariablesCount);\n    }\n\n    if (map_data.localVariablesCount != 0) {\n        db_fwriteIntCount(stream, map_local_vars, map_data.localVariablesCount);\n    }\n\n    for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        if ((map_data.flags & map_data_elev_flags[elevation]) == 0) {\n            db_fwriteLongCount(stream, square[elevation]->field_0, SQUARE_GRID_SIZE);\n        }\n    }\n\n    char err[80];\n\n    if (scr_save(stream) == -1) {\n        sprintf(err, \"Error saving scripts in %s\", map_data.name);\n        win_msg(err, 80, 80, colorTable[31744]);\n    }\n\n    if (obj_save(stream) == -1) {\n        sprintf(err, \"Error saving objects in %s\", map_data.name);\n        win_msg(err, 80, 80, colorTable[31744]);\n    }\n\n    scr_enable();\n\n    return 0;\n}\n\n// 0x483C98\nint map_save_in_game(bool a1)\n{\n    if (map_data.name[0] == '\\0') {\n        return 0;\n    }\n\n    anim_stop();\n    partyMemberSaveProtos();\n\n    if (a1) {\n        queue_leaving_map();\n        partyMemberPrepLoad();\n        partyMemberPrepItemSaveAll();\n        scr_exec_map_exit_scripts();\n\n        if (map_script_id != -1) {\n            Script* script;\n            scr_ptr(map_script_id, &script);\n        }\n\n        gtime_q_add();\n        obj_reset_roof();\n    }\n\n    map_data.flags |= 0x01;\n    map_data.lastVisitTime = game_time();\n\n    char name[16];\n\n    if (a1 && !wmMapIsSaveable()) {\n        debug_printf(\"\\nNot saving RANDOM encounter map.\");\n\n        strcpy(name, map_data.name);\n        strmfe(map_data.name, name, \"SAV\");\n        MapDirEraseFile(\"MAPS\\\\\", map_data.name);\n        strcpy(map_data.name, name);\n    } else {\n        debug_printf(\"\\n Saving \\\".SAV\\\" map.\");\n\n        strcpy(name, map_data.name);\n        strmfe(map_data.name, name, \"SAV\");\n        if (map_save() == -1) {\n            return -1;\n        }\n\n        strcpy(map_data.name, name);\n\n        automap_pip_save();\n\n        if (a1) {\n            map_data.name[0] = '\\0';\n            obj_remove_all();\n            proto_remove_all();\n            square_reset();\n            gtime_q_add();\n        }\n    }\n\n    return 0;\n}\n\n// 0x483E28\nvoid map_setup_paths()\n{\n    char path[FILENAME_MAX];\n\n    char* masterPatchesPath;\n    if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) {\n        strcpy(path, masterPatchesPath);\n    } else {\n        strcpy(path, \"DATA\");\n    }\n\n    mkdir(path);\n\n    strcat(path, \"\\\\MAPS\");\n    mkdir(path);\n}\n\n// 0x483ED0\nstatic void map_display_draw(Rect* rect)\n{\n    win_draw_rect(display_win, rect);\n}\n\n// 0x483EE4\nstatic void map_scroll_refresh_game(Rect* rect)\n{\n    Rect clampedDirtyRect;\n    if (rect_inside_bound(rect, &map_display_rect, &clampedDirtyRect) == -1) {\n        return;\n    }\n\n    square_render_floor(&clampedDirtyRect, map_elevation);\n    grid_render(&clampedDirtyRect, map_elevation);\n    obj_render_pre_roof(&clampedDirtyRect, map_elevation);\n    square_render_roof(&clampedDirtyRect, map_elevation);\n    obj_render_post_roof(&clampedDirtyRect, map_elevation);\n}\n\n// 0x483F44\nstatic void map_scroll_refresh_mapper(Rect* rect)\n{\n    Rect clampedDirtyRect;\n    if (rect_inside_bound(rect, &map_display_rect, &clampedDirtyRect) == -1) {\n        return;\n    }\n\n    buf_fill(display_buf + clampedDirtyRect.uly * (scr_size.lrx - scr_size.ulx + 1) + clampedDirtyRect.ulx,\n        clampedDirtyRect.lrx - clampedDirtyRect.ulx + 1,\n        clampedDirtyRect.lry - clampedDirtyRect.uly + 1,\n        scr_size.lrx - scr_size.ulx + 1,\n        0);\n    square_render_floor(&clampedDirtyRect, map_elevation);\n    grid_render(&clampedDirtyRect, map_elevation);\n    obj_render_pre_roof(&clampedDirtyRect, map_elevation);\n    square_render_roof(&clampedDirtyRect, map_elevation);\n    obj_render_post_roof(&clampedDirtyRect, map_elevation);\n}\n\n// NOTE: Inlined.\n//\n// 0x483FE4\nstatic int map_allocate_global_vars(int count)\n{\n    map_free_global_vars();\n\n    if (count != 0) {\n        map_global_vars = (int*)mem_malloc(sizeof(*map_global_vars) * count);\n        if (map_global_vars == NULL) {\n            return -1;\n        }\n    }\n\n    num_map_global_vars = count;\n\n    return 0;\n}\n\n// 0x484038\nstatic void map_free_global_vars()\n{\n    if (map_global_vars != NULL) {\n        mem_free(map_global_vars);\n        map_global_vars = NULL;\n        num_map_global_vars = 0;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x48405C\nstatic int map_load_global_vars(File* stream)\n{\n    if (db_freadIntCount(stream, map_global_vars, num_map_global_vars) != 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x484080\nstatic int map_allocate_local_vars(int count)\n{\n    map_free_local_vars();\n\n    if (count != 0) {\n        map_local_vars = (int*)mem_malloc(sizeof(*map_local_vars) * count);\n        if (map_local_vars == NULL) {\n            return -1;\n        }\n    }\n\n    num_map_local_vars = count;\n\n    return 0;\n}\n\n// 0x4840D4\nstatic void map_free_local_vars()\n{\n    if (map_local_vars != NULL) {\n        mem_free(map_local_vars);\n        map_local_vars = NULL;\n        num_map_local_vars = 0;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4840F8\nstatic int map_load_local_vars(File* stream)\n{\n    if (db_freadIntCount(stream, map_local_vars, num_map_local_vars) != 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x48411C\nstatic void map_place_dude_and_mouse()\n{\n    obj_clear_seen();\n\n    if (obj_dude != NULL) {\n        if (FID_ANIM_TYPE(obj_dude->fid) != ANIM_STAND) {\n            obj_set_frame(obj_dude, 0, 0);\n            obj_dude->fid = art_id(OBJ_TYPE_CRITTER, obj_dude->fid & 0xFFF, ANIM_STAND, (obj_dude->fid & 0xF000) >> 12, obj_dude->rotation + 1);\n        }\n\n        if (obj_dude->tile == -1) {\n            obj_move_to_tile(obj_dude, tile_center_tile, map_elevation, NULL);\n            obj_set_rotation(obj_dude, map_data.enteringRotation, 0);\n        }\n\n        obj_set_light(obj_dude, 4, 0x10000, 0);\n        obj_dude->flags |= OBJECT_TEMPORARY;\n\n        dude_stand(obj_dude, obj_dude->rotation, obj_dude->fid);\n        partyMemberSyncPosition();\n    }\n\n    gmouse_3d_reset_fid();\n    gmouse_3d_on();\n}\n\n// NOTE: Inlined.\n//\n// 0x4841F0\nstatic void square_init()\n{\n    int elevation;\n\n    for (elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        square[elevation] = &(square_data[elevation]);\n    }\n}\n\n// 0x484210\nstatic void square_reset()\n{\n    for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        int* p = square[elevation]->field_0;\n        for (int y = 0; y < SQUARE_GRID_HEIGHT; y++) {\n            for (int x = 0; x < SQUARE_GRID_WIDTH; x++) {\n                // TODO: Strange math, initially right, but need to figure it out and\n                // check subsequent calls.\n                int fid = *p;\n                fid &= ~0xFFFF;\n                *p = (((art_id(OBJ_TYPE_TILE, 1, 0, 0, 0) & 0xFFF) | (((fid >> 16) & 0xF000) >> 12)) << 16) | (fid & 0xFFFF);\n\n                fid = *p;\n                int v3 = (fid & 0xF000) >> 12;\n                int v4 = (art_id(OBJ_TYPE_TILE, 1, 0, 0, 0) & 0xFFF) | v3;\n\n                fid &= ~0xFFFF;\n\n                *p = v4 | ((fid >> 16) << 16);\n\n                p++;\n            }\n        }\n    }\n}\n\n// 0x48431C\nstatic int square_load(File* stream, int flags)\n{\n    int v6;\n    int v7;\n    int v8;\n    int v9;\n\n    square_reset();\n\n    for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        if ((flags & map_data_elev_flags[elevation]) == 0) {\n            int* arr = square[elevation]->field_0;\n            if (db_freadLongCount(stream, arr, SQUARE_GRID_SIZE) != 0) {\n                return -1;\n            }\n\n            for (int tile = 0; tile < SQUARE_GRID_SIZE; tile++) {\n                v6 = arr[tile];\n                v6 &= ~(0xFFFF);\n                v6 >>= 16;\n\n                v7 = (v6 & 0xF000) >> 12;\n                v7 &= ~(0x01);\n\n                v8 = v6 & 0xFFF;\n                v9 = arr[tile] & 0xFFFF;\n                arr[tile] = ((v8 | (v7 << 12)) << 16) | v9;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4843B8\nstatic int map_write_MapData(MapHeader* ptr, File* stream)\n{\n    if (db_fwriteInt(stream, ptr->version) == -1) return -1;\n    if (db_fwriteByteCount(stream, ptr->name, 16) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->enteringTile) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->enteringElevation) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->enteringRotation) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->localVariablesCount) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->scriptIndex) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->flags) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->darkness) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->globalVariablesCount) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->field_34) == -1) return -1;\n    if (db_fwriteInt(stream, ptr->lastVisitTime) == -1) return -1;\n    if (db_fwriteIntCount(stream, ptr->field_3C, 44) == -1) return -1;\n\n    return 0;\n}\n\n// 0x4844B4\nstatic int map_read_MapData(MapHeader* ptr, File* stream)\n{\n    if (db_freadInt(stream, &(ptr->version)) == -1) return -1;\n    if (db_freadByteCount(stream, ptr->name, 16) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->enteringTile)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->enteringElevation)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->enteringRotation)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->localVariablesCount)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->scriptIndex)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->flags)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->darkness)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->globalVariablesCount)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->field_34)) == -1) return -1;\n    if (db_freadInt(stream, &(ptr->lastVisitTime)) == -1) return -1;\n    if (db_freadIntCount(stream, ptr->field_3C, 44) == -1) return -1;\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/map.h",
    "content": "#ifndef FALLOUT_GAME_MAP_H_\n#define FALLOUT_GAME_MAP_H_\n\n#include <stdbool.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"game/combat_defs.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/map_defs.h\"\n#include \"game/message.h\"\n\n// TODO: Probably not needed -> replace with array?\ntypedef struct TileData {\n    int field_0[SQUARE_GRID_SIZE];\n} TileData;\n\ntypedef struct MapHeader {\n    // map_ver\n    int version;\n\n    // map_name\n    char name[16];\n\n    // map_ent_tile\n    int enteringTile;\n\n    // map_ent_elev\n    int enteringElevation;\n\n    // map_ent_rot\n    int enteringRotation;\n\n    // map_num_loc_vars\n    int localVariablesCount;\n\n    // 0map_script_idx\n    int scriptIndex;\n\n    // map_flags\n    int flags;\n\n    // map_darkness\n    int darkness;\n\n    // map_num_glob_vars\n    int globalVariablesCount;\n\n    // map_number\n    int field_34;\n\n    // Time in game ticks when PC last visited this map.\n    int lastVisitTime;\n    int field_3C[44];\n} MapHeader;\n\ntypedef struct MapTransition {\n    int map;\n    int elevation;\n    int tile;\n    int rotation;\n} MapTransition;\n\ntypedef void IsoWindowRefreshProc(Rect* rect);\n\nextern char byte_50B058[];\nextern char _aErrorF2[];\nextern int map_script_id;\nextern int* map_local_vars;\nextern int* map_global_vars;\nextern int num_map_local_vars;\nextern int num_map_global_vars;\nextern int map_elevation;\n\nextern TileData square_data[ELEVATION_COUNT];\nextern MessageList map_msg_file;\nextern MapHeader map_data;\nextern TileData* square[ELEVATION_COUNT];\nextern int display_win;\n\nint iso_init();\nvoid iso_reset();\nvoid iso_exit();\nvoid map_init();\nvoid map_reset();\nvoid map_exit();\nvoid map_enable_bk_processes();\nbool map_disable_bk_processes();\nbool map_bk_processes_are_disabled();\nint map_set_elevation(int elevation);\nbool map_is_elevation_empty(int elevation);\nint map_set_global_var(int var, int value);\nint map_get_global_var(int var);\nint map_set_local_var(int var, int value);\nint map_get_local_var(int var);\nint map_malloc_local_var(int a1);\nvoid map_set_entrance_hex(int a1, int a2, int a3);\nvoid map_set_name(const char* name);\nvoid map_get_name(char* name);\nchar* map_get_elev_idx(int map_num, int elev);\nbool is_map_idx_same(int map_num1, int map_num2);\nint get_map_idx_same(int map_num1, int map_num2);\nchar* map_get_short_name(int map_num);\nchar* map_get_description();\nchar* map_get_description_idx(int map_index);\nint map_get_index_number();\nint map_scroll(int dx, int dy);\nchar* map_file_path(char* name);\nint mapSetEntranceInfo(int a1, int a2, int a3);\nvoid map_new_map();\nint map_load(char* fileName);\nint map_load_idx(int map_index);\nint map_load_file(File* stream);\nint map_load_in_game(char* fileName);\nstatic int map_age_dead_critters();\nint map_target_load_area();\nint map_leave_map(MapTransition* transition);\nint map_check_state();\nvoid map_fix_critter_combat_data();\nint map_save();\nint map_save_file(File* stream);\nint map_save_in_game(bool a1);\nvoid map_setup_paths();\n\n#endif /* FALLOUT_GAME_MAP_H_ */\n"
  },
  {
    "path": "src/game/map_defs.h",
    "content": "#ifndef MAPDEFS_H\n#define MAPDEFS_H\n\n#include <stdbool.h>\n\n#define ELEVATION_COUNT (3)\n\n#define SQUARE_GRID_WIDTH (100)\n#define SQUARE_GRID_HEIGHT (100)\n#define SQUARE_GRID_SIZE (SQUARE_GRID_WIDTH * SQUARE_GRID_HEIGHT)\n\n#define HEX_GRID_WIDTH (200)\n#define HEX_GRID_HEIGHT (200)\n#define HEX_GRID_SIZE (HEX_GRID_WIDTH * HEX_GRID_HEIGHT)\n\nstatic inline bool elevationIsValid(int elevation)\n{\n    return elevation >= 0 && elevation < ELEVATION_COUNT;\n}\n\nstatic inline bool squareGridTileIsValid(int tile)\n{\n    return tile >= 0 && tile < SQUARE_GRID_SIZE;\n}\n\nstatic inline bool hexGridTileIsValid(int tile)\n{\n    return tile >= 0 && tile < HEX_GRID_SIZE;\n}\n\n#endif /* MAPDEFS_H */\n"
  },
  {
    "path": "src/game/message.c",
    "content": "#include \"game/message.h\"\n\n#include <ctype.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/gconfig.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/roll.h\"\n\n#define BADWORD_LENGTH_MAX 80\n\nstatic bool message_find(MessageList* msg, int num, int* out_index);\nstatic bool message_add(MessageList* msg, MessageListItem* new_entry);\nstatic bool message_parse_number(int* out_num, const char* str);\nstatic int message_load_field(File* file, char* str);\n\n// 0x519598\nstatic char** bad_word = NULL;\n\n// 0x51959C\nstatic int bad_total = 0;\n\n// 0x5195A0\nstatic int* bad_len = NULL;\n\n// Temporary message list item text used during filtering badwords.\n//\n// 0x63207C\nstatic char bad_copy[MESSAGE_LIST_ITEM_FIELD_MAX_SIZE];\n\n// 0x484770\nint init_message()\n{\n    File* stream = db_fopen(\"data\\\\badwords.txt\", \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    char word[BADWORD_LENGTH_MAX];\n\n    bad_total = 0;\n    while (db_fgets(word, BADWORD_LENGTH_MAX - 1, stream)) {\n        bad_total++;\n    }\n\n    bad_word = (char**)mem_malloc(sizeof(*bad_word) * bad_total);\n    if (bad_word == NULL) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    bad_len = (int*)mem_malloc(sizeof(*bad_len) * bad_total);\n    if (bad_len == NULL) {\n        mem_free(bad_word);\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fseek(stream, 0, SEEK_SET);\n\n    int index = 0;\n    for (; index < bad_total; index++) {\n        if (!db_fgets(word, BADWORD_LENGTH_MAX - 1, stream)) {\n            break;\n        }\n\n        int len = strlen(word);\n        if (word[len - 1] == '\\n') {\n            len--;\n            word[len] = '\\0';\n        }\n\n        bad_word[index] = mem_strdup(word);\n        if (bad_word[index] == NULL) {\n            break;\n        }\n\n        strupr(bad_word[index]);\n\n        bad_len[index] = len;\n    }\n\n    db_fclose(stream);\n\n    if (index != bad_total) {\n        for (; index > 0; index--) {\n            mem_free(bad_word[index - 1]);\n        }\n\n        mem_free(bad_word);\n        mem_free(bad_len);\n\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4848F0\nvoid exit_message()\n{\n    for (int index = 0; index < bad_total; index++) {\n        mem_free(bad_word[index]);\n    }\n\n    if (bad_total != 0) {\n        mem_free(bad_word);\n        mem_free(bad_len);\n    }\n\n    bad_total = 0;\n}\n\n// message_init\n// 0x48494C\nbool message_init(MessageList* messageList)\n{\n    if (messageList != NULL) {\n        messageList->entries_num = 0;\n        messageList->entries = NULL;\n    }\n    return true;\n}\n\n// 0x484964\nbool message_exit(MessageList* messageList)\n{\n    int i;\n    MessageListItem* entry;\n\n    if (messageList == NULL) {\n        return false;\n    }\n\n    for (i = 0; i < messageList->entries_num; i++) {\n        entry = &(messageList->entries[i]);\n\n        if (entry->audio != NULL) {\n            mem_free(entry->audio);\n        }\n\n        if (entry->text != NULL) {\n            mem_free(entry->text);\n        }\n    }\n\n    messageList->entries_num = 0;\n\n    if (messageList->entries != NULL) {\n        mem_free(messageList->entries);\n        messageList->entries = NULL;\n    }\n\n    return true;\n}\n\n// message_load\n// 0x484AA4\nbool message_load(MessageList* messageList, const char* path)\n{\n    char* language;\n    char localized_path[FILENAME_MAX];\n    File* file_ptr;\n    char num[1024];\n    char audio[1024];\n    char text[1024];\n    int rc;\n    bool success;\n    MessageListItem entry;\n\n    success = false;\n\n    if (messageList == NULL) {\n        return false;\n    }\n\n    if (path == NULL) {\n        return false;\n    }\n\n    if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) {\n        return false;\n    }\n\n    sprintf(localized_path, \"%s\\\\%s\\\\%s\", \"text\", language, path);\n\n    file_ptr = db_fopen(localized_path, \"rt\");\n    if (file_ptr == NULL) {\n        return false;\n    }\n\n    entry.num = 0;\n    entry.audio = audio;\n    entry.text = text;\n\n    while (1) {\n        rc = message_load_field(file_ptr, num);\n        if (rc != 0) {\n            break;\n        }\n\n        if (message_load_field(file_ptr, audio) != 0) {\n            debug_printf(\"\\nError loading audio field.\\n\", localized_path);\n            goto err;\n        }\n\n        if (message_load_field(file_ptr, text) != 0) {\n            debug_printf(\"\\nError loading text field.\\n\", localized_path);\n            goto err;\n        }\n\n        if (!message_parse_number(&(entry.num), num)) {\n            debug_printf(\"\\nError parsing number.\\n\", localized_path);\n            goto err;\n        }\n\n        if (!message_add(messageList, &entry)) {\n            debug_printf(\"\\nError adding message.\\n\", localized_path);\n            goto err;\n        }\n    }\n\n    if (rc == 1) {\n        success = true;\n    }\n\nerr:\n\n    if (!success) {\n        debug_printf(\"Error loading message file %s at offset %x.\", localized_path, db_ftell(file_ptr));\n    }\n\n    db_fclose(file_ptr);\n\n    return success;\n}\n\n// 0x484C30\nbool message_search(MessageList* msg, MessageListItem* entry)\n{\n    int index;\n    MessageListItem* ptr;\n\n    if (msg == NULL) {\n        return false;\n    }\n\n    if (entry == NULL) {\n        return false;\n    }\n\n    if (msg->entries_num == 0) {\n        return false;\n    }\n\n    if (!message_find(msg, entry->num, &index)) {\n        return false;\n    }\n\n    ptr = &(msg->entries[index]);\n    entry->flags = ptr->flags;\n    entry->audio = ptr->audio;\n    entry->text = ptr->text;\n\n    return true;\n}\n\n// Builds language-aware path in \"text\" subfolder.\n//\n// 0x484CB8\nbool message_make_path(char* dest, const char* path)\n{\n    char* language;\n\n    if (dest == NULL) {\n        return false;\n    }\n\n    if (path == NULL) {\n        return false;\n    }\n\n    if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) {\n        return false;\n    }\n\n    sprintf(dest, \"%s\\\\%s\\\\%s\", \"text\", language, path);\n\n    return true;\n}\n\n// 0x484D10\nbool message_find(MessageList* msg, int num, int* out_index)\n{\n    int r, l, mid;\n    int cmp;\n\n    if (msg->entries_num == 0) {\n        *out_index = 0;\n        return false;\n    }\n\n    r = msg->entries_num - 1;\n    l = 0;\n\n    do {\n        mid = (l + r) / 2;\n        cmp = num - msg->entries[mid].num;\n        if (cmp == 0) {\n            *out_index = mid;\n            return true;\n        }\n\n        if (cmp > 0) {\n            l = l + 1;\n        } else {\n            r = r - 1;\n        }\n    } while (r >= l);\n\n    if (cmp < 0) {\n        *out_index = mid;\n    } else {\n        *out_index = mid + 1;\n    }\n\n    return false;\n}\n\n// 0x484D68\nbool message_add(MessageList* msg, MessageListItem* new_entry)\n{\n    int index;\n    MessageListItem* entries;\n    MessageListItem* existing_entry;\n\n    if (message_find(msg, new_entry->num, &index)) {\n        existing_entry = &(msg->entries[index]);\n\n        if (existing_entry->audio != NULL) {\n            mem_free(existing_entry->audio);\n        }\n\n        if (existing_entry->text != NULL) {\n            mem_free(existing_entry->text);\n        }\n    } else {\n        if (msg->entries != NULL) {\n            entries = (MessageListItem*)mem_realloc(msg->entries, sizeof(MessageListItem) * (msg->entries_num + 1));\n            if (entries == NULL) {\n                return false;\n            }\n\n            msg->entries = entries;\n\n            if (index != msg->entries_num) {\n                // Move all items below insertion point\n                memmove(&(msg->entries[index + 1]), &(msg->entries[index]), sizeof(MessageListItem) * (msg->entries_num - index));\n            }\n        } else {\n            msg->entries = (MessageListItem*)mem_malloc(sizeof(MessageListItem));\n            if (msg->entries == NULL) {\n                return false;\n            }\n            msg->entries_num = 0;\n            index = 0;\n        }\n\n        existing_entry = &(msg->entries[index]);\n        existing_entry->flags = 0;\n        existing_entry->audio = 0;\n        existing_entry->text = 0;\n        msg->entries_num++;\n    }\n\n    existing_entry->audio = mem_strdup(new_entry->audio);\n    if (existing_entry->audio == NULL) {\n        return false;\n    }\n\n    existing_entry->text = mem_strdup(new_entry->text);\n    if (existing_entry->text == NULL) {\n        return false;\n    }\n\n    existing_entry->num = new_entry->num;\n\n    return true;\n}\n\n// 0x484F60\nbool message_parse_number(int* out_num, const char* str)\n{\n    const char* ch;\n    bool success;\n\n    ch = str;\n    if (*ch == '\\0') {\n        return false;\n    }\n\n    success = true;\n    if (*ch == '+' || *ch == '-') {\n        ch++;\n    }\n\n    while (*ch != '\\0') {\n        if (!isdigit(*ch)) {\n            success = false;\n            break;\n        }\n        ch++;\n    }\n\n    *out_num = atoi(str);\n    return success;\n}\n\n// Read next message file field, the `str` should be at least 1024 bytes long.\n//\n// Returns:\n// 0 - ok\n// 1 - eof\n// 2 - mismatched delimeters\n// 3 - unterminated field\n// 4 - limit exceeded (> 1024)\n//\n// 0x484FB4\nint message_load_field(File* file, char* str)\n{\n    int ch;\n    int len;\n\n    len = 0;\n\n    while (1) {\n        ch = db_fgetc(file);\n        if (ch == -1) {\n            return 1;\n        }\n\n        if (ch == '}') {\n            debug_printf(\"\\nError reading message file - mismatched delimiters.\\n\");\n            return 2;\n        }\n\n        if (ch == '{') {\n            break;\n        }\n    }\n\n    while (1) {\n        ch = db_fgetc(file);\n\n        if (ch == -1) {\n            debug_printf(\"\\nError reading message file - EOF reached.\\n\");\n            return 3;\n        }\n\n        if (ch == '}') {\n            *(str + len) = '\\0';\n            return 0;\n        }\n\n        if (ch != '\\n') {\n            *(str + len) = ch;\n            len++;\n\n            if (len > 1024) {\n                debug_printf(\"\\nError reading message file - text exceeds limit.\\n\");\n                return 4;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x48504C\nchar* getmsg(MessageList* msg, MessageListItem* entry, int num)\n{\n    // 0x5195A4\n    static char message_error_str[] = \"Error\";\n\n    entry->num = num;\n\n    if (!message_search(msg, entry)) {\n        entry->text = message_error_str;\n        debug_printf(\"\\n ** String not found @ getmsg(), MESSAGE.C **\\n\");\n    }\n\n    return entry->text;\n}\n\n// 0x485078\nbool message_filter(MessageList* messageList)\n{\n    // TODO: Check.\n    // 0x50B960\n    static const char* replacements = \"!@#$%&*@#*!&$%#&%#*%!$&%@*$@&\";\n\n    if (messageList == NULL) {\n        return false;\n    }\n\n    if (messageList->entries_num == 0) {\n        return true;\n    }\n\n    if (bad_total == 0) {\n        return true;\n    }\n\n    int languageFilter = 0;\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter);\n    if (languageFilter != 1) {\n        return true;\n    }\n\n    int replacementsCount = strlen(replacements);\n    int replacementsIndex = roll_random(1, replacementsCount) - 1;\n\n    for (int index = 0; index < messageList->entries_num; index++) {\n        MessageListItem* item = &(messageList->entries[index]);\n        strcpy(bad_copy, item->text);\n        strupr(bad_copy);\n\n        for (int badwordIndex = 0; badwordIndex < bad_total; badwordIndex++) {\n            // I don't quite understand the loop below. It has no stop\n            // condition besides no matching substring. It also overwrites\n            // already masked words on every iteration.\n            for (char* p = bad_copy;; p++) {\n                const char* substr = strstr(p, bad_word[badwordIndex]);\n                if (substr == NULL) {\n                    break;\n                }\n\n                if (substr == bad_copy || (!isalpha(substr[-1]) && !isalpha(substr[bad_len[badwordIndex]]))) {\n                    item->flags |= MESSAGE_LIST_ITEM_TEXT_FILTERED;\n                    char* ptr = item->text + (substr - bad_copy);\n\n                    for (int j = 0; j < bad_len[badwordIndex]; j++) {\n                        *ptr++ = replacements[replacementsIndex++];\n                        if (replacementsIndex == replacementsCount) {\n                            replacementsIndex = 0;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/game/message.h",
    "content": "#ifndef FALLOUT_GAME_MESSAGE_H_\n#define FALLOUT_GAME_MESSAGE_H_\n\n#include <stdbool.h>\n\n// TODO: Convert to enum.\n#define MESSAGE_LIST_ITEM_TEXT_FILTERED 0x01\n\n// TODO: Probably should be private.\n#define MESSAGE_LIST_ITEM_FIELD_MAX_SIZE 1024\n\ntypedef struct MessageListItem {\n    int num;\n    int flags;\n    char* audio;\n    char* text;\n} MessageListItem;\n\ntypedef struct MessageList {\n    int entries_num;\n    MessageListItem* entries;\n} MessageList;\n\nint init_message();\nvoid exit_message();\nbool message_init(MessageList* msg);\nbool message_exit(MessageList* msg);\nbool message_load(MessageList* msg, const char* path);\nbool message_search(MessageList* msg, MessageListItem* entry);\nbool message_make_path(char* dest, const char* path);\nchar* getmsg(MessageList* msg, MessageListItem* entry, int num);\nbool message_filter(MessageList* messageList);\n\n#endif /* FALLOUT_GAME_MESSAGE_H_ */\n"
  },
  {
    "path": "src/game/moviefx.c",
    "content": "#include \"game/moviefx.h\"\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"game/config.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/memory.h\"\n#include \"int/movie.h\"\n#include \"game/palette.h\"\n\ntypedef enum MovieEffectType {\n    MOVIE_EFFECT_TYPE_NONE = 0,\n    MOVIE_EFFECT_TYPE_FADE_IN = 1,\n    MOVIE_EFFECT_TYPE_FADE_OUT = 2,\n} MovieEffectFadeType;\n\ntypedef struct MovieEffect {\n    int startFrame;\n    int endFrame;\n    int steps;\n    unsigned char fadeType;\n    // range 0-63\n    unsigned char r;\n    // range 0-63\n    unsigned char g;\n    // range 0-63\n    unsigned char b;\n    struct MovieEffect* next;\n} MovieEffect;\n\nstatic void moviefx_callback_func(int frame);\nstatic void moviefx_palette_func(unsigned char* palette, int start, int end);\nstatic void moviefx_add(MovieEffect* movie_effect);\nstatic void moviefx_remove_all();\n\n// 0x5195F0\nstatic bool moviefx_initialized = false;\n\n// 0x5195F4\nstatic MovieEffect* moviefx_effects_list = NULL;\n\n// 0x638EC4\nstatic unsigned char source_palette[768];\n\n// 0x6391C4\nstatic bool inside_fade;\n\n// 0x487CC0\nint moviefx_init()\n{\n    if (moviefx_initialized) {\n        return -1;\n    }\n\n    memset(source_palette, 0, sizeof(source_palette));\n\n    moviefx_initialized = true;\n\n    return 0;\n}\n\n// 0x487CF4\nvoid moviefx_reset()\n{\n    if (!moviefx_initialized) {\n        return;\n    }\n\n    movieSetCallback(NULL);\n    movieSetPaletteFunc(NULL);\n    moviefx_remove_all();\n\n    inside_fade = false;\n\n    memset(source_palette, 0, sizeof(source_palette));\n}\n\n// 0x487D30\nvoid moviefx_exit()\n{\n    if (!moviefx_initialized) {\n        return;\n    }\n\n    movieSetCallback(NULL);\n    movieSetPaletteFunc(NULL);\n    moviefx_remove_all();\n\n    inside_fade = false;\n\n    memset(source_palette, 0, sizeof(source_palette));\n}\n\n// 0x487D7C\nint moviefx_start(const char* filePath)\n{\n    if (!moviefx_initialized) {\n        return -1;\n    }\n\n    movieSetCallback(NULL);\n    movieSetPaletteFunc(NULL);\n    moviefx_remove_all();\n    inside_fade = false;\n    memset(source_palette, 0, sizeof(source_palette));\n\n    if (filePath == NULL) {\n        return -1;\n    }\n\n    Config config;\n    if (!config_init(&config)) {\n        return -1;\n    }\n\n    int rc = -1;\n\n    char path[FILENAME_MAX];\n    strcpy(path, filePath);\n\n    char* pch = strrchr(path, '.');\n    if (pch != NULL) {\n        *pch = '\\0';\n    }\n\n    strcpy(path + strlen(path), \".cfg\");\n\n    int* movieEffectFrameList;\n\n    if (!config_load(&config, path, true)) {\n        goto out;\n    }\n\n    int movieEffectsLength;\n    if (!config_get_value(&config, \"info\", \"total_effects\", &movieEffectsLength)) {\n        goto out;\n    }\n\n    movieEffectFrameList = (int*)mem_malloc(sizeof(*movieEffectFrameList) * movieEffectsLength);\n    if (movieEffectFrameList == NULL) {\n        goto out;\n    }\n\n    bool frameListRead;\n    if (movieEffectsLength >= 2) {\n        frameListRead = config_get_values(&config, \"info\", \"effect_frames\", movieEffectFrameList, movieEffectsLength);\n    } else {\n        frameListRead = config_get_value(&config, \"info\", \"effect_frames\", &(movieEffectFrameList[0]));\n    }\n\n    if (frameListRead) {\n        int movieEffectsCreated = 0;\n        for (int index = 0; index < movieEffectsLength; index++) {\n            char section[20];\n            itoa(movieEffectFrameList[index], section, 10);\n\n            char* fadeTypeString;\n            if (!config_get_string(&config, section, \"fade_type\", &fadeTypeString)) {\n                continue;\n            }\n\n            int fadeType = MOVIE_EFFECT_TYPE_NONE;\n            if (stricmp(fadeTypeString, \"in\") == 0) {\n                fadeType = MOVIE_EFFECT_TYPE_FADE_IN;\n            } else if (stricmp(fadeTypeString, \"out\") == 0) {\n                fadeType = MOVIE_EFFECT_TYPE_FADE_OUT;\n            }\n\n            if (fadeType == MOVIE_EFFECT_TYPE_NONE) {\n                continue;\n            }\n\n            int fadeColor[3];\n            if (!config_get_values(&config, section, \"fade_color\", fadeColor, 3)) {\n                continue;\n            }\n\n            int steps;\n            if (!config_get_value(&config, section, \"fade_steps\", &steps)) {\n                continue;\n            }\n\n            MovieEffect* movieEffect = (MovieEffect*)mem_malloc(sizeof(*movieEffect));\n            if (movieEffect == NULL) {\n                continue;\n            }\n\n            memset(movieEffect, 0, sizeof(*movieEffect));\n            movieEffect->startFrame = movieEffectFrameList[index];\n            movieEffect->endFrame = movieEffect->startFrame + steps - 1;\n            movieEffect->steps = steps;\n            movieEffect->fadeType = fadeType & 0xFF;\n            movieEffect->r = fadeColor[0] & 0xFF;\n            movieEffect->g = fadeColor[1] & 0xFF;\n            movieEffect->b = fadeColor[2] & 0xFF;\n\n            if (movieEffect->startFrame <= 1) {\n                inside_fade = true;\n            }\n\n            // NOTE: Uninline.\n            moviefx_add(movieEffect);\n\n            movieEffectsCreated++;\n        }\n\n        if (movieEffectsCreated != 0) {\n            movieSetCallback(moviefx_callback_func);\n            movieSetPaletteFunc(moviefx_palette_func);\n            rc = 0;\n        }\n    }\n\n    mem_free(movieEffectFrameList);\n\nout:\n\n    config_exit(&config);\n\n    return rc;\n}\n\n// 0x4880F0\nvoid moviefx_stop()\n{\n    if (!moviefx_initialized) {\n        return;\n    }\n\n    movieSetCallback(NULL);\n    movieSetPaletteFunc(NULL);\n\n    moviefx_remove_all();\n\n    inside_fade = false;\n    memset(source_palette, 0, sizeof(source_palette));\n}\n\n// 0x488144\nstatic void moviefx_callback_func(int frame)\n{\n    MovieEffect* movieEffect = moviefx_effects_list;\n    while (movieEffect != NULL) {\n        if (frame >= movieEffect->startFrame && frame <= movieEffect->endFrame) {\n            break;\n        }\n        movieEffect = movieEffect->next;\n    }\n\n    if (movieEffect != NULL) {\n        unsigned char palette[768];\n        int step = frame - movieEffect->startFrame + 1;\n\n        if (movieEffect->fadeType == MOVIE_EFFECT_TYPE_FADE_IN) {\n            for (int index = 0; index < 256; index++) {\n                palette[index * 3] = movieEffect->r - (step * (movieEffect->r - source_palette[index * 3]) / movieEffect->steps);\n                palette[index * 3 + 1] = movieEffect->g - (step * (movieEffect->g - source_palette[index * 3 + 1]) / movieEffect->steps);\n                palette[index * 3 + 2] = movieEffect->b - (step * (movieEffect->b - source_palette[index * 3 + 2]) / movieEffect->steps);\n            }\n        } else {\n            for (int index = 0; index < 256; index++) {\n                palette[index * 3] = source_palette[index * 3] - (step * (source_palette[index * 3] - movieEffect->r) / movieEffect->steps);\n                palette[index * 3 + 1] = source_palette[index * 3 + 1] - (step * (source_palette[index * 3 + 1] - movieEffect->g) / movieEffect->steps);\n                palette[index * 3 + 2] = source_palette[index * 3 + 2] - (step * (source_palette[index * 3 + 2] - movieEffect->b) / movieEffect->steps);\n            }\n        }\n\n        palette_set_to(palette);\n    }\n\n    inside_fade = movieEffect != NULL;\n}\n\n// 0x4882AC\nstatic void moviefx_palette_func(unsigned char* palette, int start, int end)\n{\n    memcpy(source_palette + 3 * start, palette, 3 * (end - start + 1));\n\n    if (!inside_fade) {\n        palette_set_entries(palette, start, end);\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4882FC\nstatic void moviefx_add(MovieEffect* movie_effect)\n{\n    movie_effect->next = moviefx_effects_list;\n    moviefx_effects_list = movie_effect;\n}\n\n// 0x488310\nstatic void moviefx_remove_all()\n{\n    MovieEffect* movieEffect = moviefx_effects_list;\n    while (movieEffect != NULL) {\n        MovieEffect* next = movieEffect->next;\n        mem_free(movieEffect);\n        movieEffect = next;\n    }\n\n    moviefx_effects_list = NULL;\n}\n"
  },
  {
    "path": "src/game/moviefx.h",
    "content": "#ifndef FALLOUT_GAME_MOVIEFX_H_\n#define FALLOUT_GAME_MOVIEFX_H_\n\nint moviefx_init();\nvoid moviefx_reset();\nvoid moviefx_exit();\nint moviefx_start(const char* fileName);\nvoid moviefx_stop();\n\n#endif /* FALLOUT_GAME_MOVIEFX_H_ */\n"
  },
  {
    "path": "src/game/object.c",
    "content": "#include \"game/object.h\"\n\n#include <assert.h>\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/item.h\"\n#include \"game/light.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/party.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/scripts.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n#include \"game/worldmap.h\"\n#include \"plib/gnw/svga.h\"\n\nstatic int obj_read_obj(Object* obj, File* stream);\nstatic int obj_load_func(File* stream);\nstatic void obj_fix_combat_cid_for_dude();\nstatic void object_fix_weapon_ammo(Object* obj);\nstatic int obj_write_obj(Object* obj, File* stream);\nstatic int obj_offset_table_init();\nstatic void obj_offset_table_exit();\nstatic int obj_order_table_init();\nstatic int obj_order_comp_func_even(const void* a1, const void* a2);\nstatic int obj_order_comp_func_odd(const void* a1, const void* a2);\nstatic void obj_order_table_exit();\nstatic int obj_render_table_init();\nstatic void obj_render_table_exit();\nstatic void obj_light_table_init();\nstatic void obj_blend_table_init();\nstatic void obj_blend_table_exit();\nstatic int obj_create_object(Object** objectPtr);\nstatic void obj_destroy_object(Object** objectPtr);\nstatic int obj_create_object_node(ObjectListNode** nodePtr);\nstatic void obj_destroy_object_node(ObjectListNode** nodePtr);\nstatic int obj_node_ptr(Object* obj, ObjectListNode** out_node, ObjectListNode** out_prev_node);\nstatic void obj_insert(ObjectListNode* ptr);\nstatic int obj_remove(ObjectListNode* a1, ObjectListNode* a2);\nstatic int obj_connect_to_tile(ObjectListNode* node, int tile_index, int elev, Rect* rect);\nstatic int obj_adjust_light(Object* obj, int a2, Rect* rect);\nstatic void obj_render_outline(Object* object, Rect* rect);\nstatic void obj_render_object(Object* object, Rect* rect, int light);\nstatic int obj_preload_sort(const void* a1, const void* a2);\n\n// 0x5195F8\nstatic bool objInitialized = false;\n\n// 0x5195FC\nstatic int updateHexWidth = 0;\n\n// 0x519600\nstatic int updateHexHeight = 0;\n\n// 0x519604\nstatic int updateHexArea = 0;\n\n// 0x519608\nstatic int* orderTable[2] = {\n    NULL,\n    NULL,\n};\n\n// 0x519610\nstatic int* offsetTable[2] = {\n    NULL,\n    NULL,\n};\n\n// 0x519618\nstatic int* offsetDivTable = NULL;\n\n// 0x51961C\nstatic int* offsetModTable = NULL;\n\n// 0x519620\nstatic ObjectListNode** renderTable = NULL;\n\n// 0x519624\nstatic int outlineCount = 0;\n\n// Contains objects that are not bounded to tiles.\n//\n// 0x519628\nstatic ObjectListNode* floatingObjects = NULL;\n\n// 0x51962C\nstatic int centerToUpperLeft = 0;\n\n// 0x519630\nstatic int find_elev = 0;\n\n// 0x519634\nstatic int find_tile = 0;\n\n// 0x519638\nstatic ObjectListNode* find_ptr = NULL;\n\n// 0x51963C\nstatic int* preload_list = NULL;\n\n// 0x519640\nstatic int preload_list_index = 0;\n\n// 0x51964C\nstatic Rect light_rect[9] = {\n    { 0, 0, 96, 42 },\n    { 0, 0, 160, 74 },\n    { 0, 0, 224, 106 },\n    { 0, 0, 288, 138 },\n    { 0, 0, 352, 170 },\n    { 0, 0, 416, 202 },\n    { 0, 0, 480, 234 },\n    { 0, 0, 544, 266 },\n    { 0, 0, 608, 298 },\n};\n\n// 0x5196DC\nstatic int light_distance[36] = {\n    1,\n    2,\n    3,\n    4,\n    5,\n    6,\n    7,\n    8,\n    2,\n    3,\n    4,\n    5,\n    6,\n    7,\n    8,\n    3,\n    4,\n    5,\n    6,\n    7,\n    8,\n    4,\n    5,\n    6,\n    7,\n    8,\n    5,\n    6,\n    7,\n    8,\n    6,\n    7,\n    8,\n    7,\n    8,\n    8,\n};\n\n// 0x51976C\nstatic int fix_violence_level = -1;\n\n// 0x519770\nstatic int obj_last_roof_x = -1;\n\n// 0x519774\nstatic int obj_last_roof_y = -1;\n\n// 0x519778\nstatic int obj_last_elev = -1;\n\n// 0x51977C\nstatic bool obj_last_is_empty = true;\n\n// 0x519780\nunsigned char* wallBlendTable = NULL;\n\n// 0x519784\nunsigned char* glassBlendTable = NULL;\n\n// 0x519788\nunsigned char* steamBlendTable = NULL;\n\n// 0x51978C\nunsigned char* energyBlendTable = NULL;\n\n// 0x519790\nunsigned char* redBlendTable = NULL;\n\n// 0x519794\nObject* moveBlockObj = NULL;\n\n// 0x519798\nstatic int objItemOutlineState = 0;\n\n// 0x6391D0\nstatic int light_blocked[6][36];\n\n// 0x639530\nstatic int light_offsets[2][6][36];\n\n// 0x639BF0\nstatic Rect buf_rect;\n\n// 0x639C00\nstatic Object* outlinedObjects[100];\n\n// 0x639D90\nstatic Rect updateAreaPixelBounds;\n\n// Contains objects that are bounded to tiles.\n//\n// 0x639DA0\nstatic ObjectListNode* objectTable[HEX_GRID_SIZE];\n\n// 0x660EA0\nunsigned char glassGrayTable[256];\n\n// 0x660FA0\nunsigned char commonGrayTable[256];\n\n// 0x6610A0\nstatic int buf_size;\n\n// 0x6610A4\nstatic unsigned char* back_buf;\n\n// 0x6610A8\nstatic int buf_length;\n\n// Translucent \"egg\" effect around player.\n//\n// 0x6610AC\nObject* obj_egg;\n\n// 0x6610B0\nstatic int buf_full;\n\n// 0x6610B4\nstatic int buf_width;\n\n// 0x6610B8\nObject* obj_dude;\n\n// 0x6610BC\nstatic char obj_seen_check[5001];\n\n// 0x662445\nstatic char obj_seen[5001];\n\n// obj_init\n// 0x488780\nint obj_init(unsigned char* buf, int width, int height, int pitch)\n{\n    int dudeFid;\n    int eggFid;\n\n    memset(obj_seen, 0, 5001);\n    updateAreaPixelBounds.lrx = width + 320;\n    updateAreaPixelBounds.ulx = -320;\n    updateAreaPixelBounds.lry = height + 240;\n    updateAreaPixelBounds.uly = -240;\n\n    updateHexWidth = (updateAreaPixelBounds.lrx + 320 + 1) / 32 + 1;\n    updateHexHeight = (updateAreaPixelBounds.lry + 240 + 1) / 12 + 1;\n    updateHexArea = updateHexWidth * updateHexHeight;\n\n    memset(objectTable, 0, sizeof(objectTable));\n\n    if (obj_offset_table_init() == -1) {\n        return -1;\n    }\n\n    if (obj_order_table_init() == -1) {\n        goto err;\n    }\n\n    if (obj_render_table_init() == -1) {\n        goto err_2;\n    }\n\n    if (light_init() == -1) {\n        goto err_2;\n    }\n\n    if (text_object_init(buf, width, height) == -1) {\n        goto err_2;\n    }\n\n    obj_light_table_init();\n    obj_blend_table_init();\n\n    centerToUpperLeft = tile_num(updateAreaPixelBounds.ulx, updateAreaPixelBounds.uly, 0) - tile_center_tile;\n    buf_width = width;\n    buf_length = height;\n    back_buf = buf;\n\n    buf_rect.ulx = 0;\n    buf_rect.uly = 0;\n    buf_rect.lrx = width - 1;\n    buf_rect.lry = height - 1;\n\n    buf_size = height * width;\n    buf_full = pitch;\n\n    dudeFid = art_id(OBJ_TYPE_CRITTER, art_vault_guy_num, 0, 0, 0);\n    obj_new(&obj_dude, dudeFid, 0x1000000);\n\n    obj_dude->flags |= OBJECT_FLAG_0x400;\n    obj_dude->flags |= OBJECT_TEMPORARY;\n    obj_dude->flags |= OBJECT_HIDDEN;\n    obj_dude->flags |= OBJECT_LIGHT_THRU;\n    obj_set_light(obj_dude, 4, 0x10000, NULL);\n\n    if (partyMemberAdd(obj_dude) == -1) {\n        debug_printf(\"\\n  Error: Can't add Player into party!\");\n        exit(1);\n    }\n\n    eggFid = art_id(OBJ_TYPE_INTERFACE, 2, 0, 0, 0);\n    obj_new(&obj_egg, eggFid, -1);\n    obj_egg->flags |= OBJECT_FLAG_0x400;\n    obj_egg->flags |= OBJECT_TEMPORARY;\n    obj_egg->flags |= OBJECT_HIDDEN;\n    obj_egg->flags |= OBJECT_LIGHT_THRU;\n\n    objInitialized = true;\n\n    return 0;\n\nerr_2:\n\n    // NOTE: Uninline.\n    obj_order_table_exit();\n\nerr:\n\n    obj_offset_table_exit();\n\n    return -1;\n}\n\n// 0x488A00\nvoid obj_reset()\n{\n    if (objInitialized) {\n        text_object_reset();\n        obj_remove_all();\n        memset(obj_seen, 0, 5001);\n        light_reset();\n    }\n}\n\n// 0x488A30\nvoid obj_exit()\n{\n    if (objInitialized) {\n        obj_dude->flags &= ~OBJECT_FLAG_0x400;\n        obj_egg->flags &= ~OBJECT_FLAG_0x400;\n\n        obj_remove_all();\n        text_object_exit();\n\n        // NOTE: Uninline.\n        obj_blend_table_exit();\n\n        light_exit();\n\n        // NOTE: Uninline.\n        obj_render_table_exit();\n\n        // NOTE: Uninline.\n        obj_order_table_exit();\n\n        obj_offset_table_exit();\n    }\n}\n\n// 0x488AF4\nstatic int obj_read_obj(Object* obj, File* stream)\n{\n    int field_74;\n\n    if (db_freadInt(stream, &(obj->id)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->tile)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->x)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->y)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->sx)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->sy)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->frame)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->rotation)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->fid)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->flags)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->elevation)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->pid)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->cid)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->lightDistance)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->lightIntensity)) == -1) return -1;\n    if (db_freadInt(stream, &field_74) == -1) return -1;\n    if (db_freadInt(stream, &(obj->sid)) == -1) return -1;\n    if (db_freadInt(stream, &(obj->field_80)) == -1) return -1;\n\n    obj->outline = 0;\n    obj->owner = NULL;\n\n    if (proto_read_protoUpdateData(obj, stream) != 0) {\n        return -1;\n    }\n\n    if (obj->pid < 0x5000010 || obj->pid > 0x5000017) {\n        if (PID_TYPE(obj->pid) == 0 && !(map_data.flags & 0x01)) {\n            object_fix_weapon_ammo(obj);\n        }\n    } else {\n        if (obj->data.misc.map <= 0) {\n            if ((obj->fid & 0xFFF) < 33) {\n                obj->fid = art_id(OBJ_TYPE_MISC, (obj->fid & 0xFFF) + 16, FID_ANIM_TYPE(obj->fid), 0, 0);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x488CE4\nint obj_load(File* stream)\n{\n    int rc = obj_load_func(stream);\n\n    fix_violence_level = -1;\n\n    return rc;\n}\n\n// 0x488CF8\nstatic int obj_load_func(File* stream)\n{\n    if (stream == NULL) {\n        return -1;\n    }\n\n    bool fixMapInventory;\n    if (!configGetBool(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_INVENTORY_KEY, &fixMapInventory)) {\n        fixMapInventory = false;\n    }\n\n    if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &fix_violence_level)) {\n        fix_violence_level = VIOLENCE_LEVEL_MAXIMUM_BLOOD;\n    }\n\n    int objectCount;\n    if (db_freadInt(stream, &objectCount) == -1) {\n        return -1;\n    }\n\n    if (preload_list != NULL) {\n        mem_free(preload_list);\n    }\n\n    if (objectCount != 0) {\n        preload_list = (int*)mem_malloc(sizeof(*preload_list) * objectCount);\n        memset(preload_list, 0, sizeof(*preload_list) * objectCount);\n        if (preload_list == NULL) {\n            return -1;\n        }\n        preload_list_index = 0;\n    }\n\n    for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        int objectCountAtElevation;\n        if (db_freadInt(stream, &objectCountAtElevation) == -1) {\n            return -1;\n        }\n\n        for (int objectIndex = 0; objectIndex < objectCountAtElevation; objectIndex++) {\n            ObjectListNode* objectListNode;\n\n            // NOTE: Uninline.\n            if (obj_create_object_node(&objectListNode) == -1) {\n                return -1;\n            }\n\n            if (obj_create_object(&(objectListNode->obj)) == -1) {\n                // NOTE: Uninline.\n                obj_destroy_object_node(&objectListNode);\n                return -1;\n            }\n\n            if (obj_read_obj(objectListNode->obj, stream) != 0) {\n                // NOTE: Uninline.\n                obj_destroy_object(&(objectListNode->obj));\n\n                // NOTE: Uninline.\n                obj_destroy_object_node(&objectListNode);\n\n                return -1;\n            }\n\n            objectListNode->obj->outline = 0;\n            preload_list[preload_list_index++] = objectListNode->obj->fid;\n\n            if (objectListNode->obj->sid != -1) {\n                Script* script;\n                if (scr_ptr(objectListNode->obj->sid, &script) == -1) {\n                    objectListNode->obj->sid = -1;\n                    debug_printf(\"\\nError connecting object to script!\");\n                } else {\n                    script->owner = objectListNode->obj;\n                    objectListNode->obj->field_80 = script->field_14;\n                }\n            }\n\n            obj_fix_violence_settings(&(objectListNode->obj->fid));\n            objectListNode->obj->elevation = elevation;\n\n            obj_insert(objectListNode);\n\n            if ((objectListNode->obj->flags & OBJECT_FLAG_0x400) && PID_TYPE(objectListNode->obj->pid) == OBJ_TYPE_CRITTER && objectListNode->obj->pid != 18000) {\n                objectListNode->obj->flags &= ~OBJECT_FLAG_0x400;\n            }\n\n            Inventory* inventory = &(objectListNode->obj->data.inventory);\n            if (inventory->length != 0) {\n                inventory->items = (InventoryItem*)mem_malloc(sizeof(InventoryItem) * inventory->capacity);\n                if (inventory->items == NULL) {\n                    return -1;\n                }\n\n                for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) {\n                    InventoryItem* inventoryItem = &(inventory->items[inventoryItemIndex]);\n                    if (db_freadInt(stream, &(inventoryItem->quantity)) != 0) {\n                        debug_printf(\"Error loading inventory\\n\");\n                        return -1;\n                    }\n\n                    if (fixMapInventory) {\n                        inventoryItem->item = (Object*)mem_malloc(sizeof(Object));\n                        if (inventoryItem->item == NULL) {\n                            debug_printf(\"Error loading inventory\\n\");\n                            return -1;\n                        }\n\n                        if (obj_read_obj(inventoryItem->item, stream) != 0) {\n                            debug_printf(\"Error loading inventory\\n\");\n                            return -1;\n                        }\n                    } else {\n                        if (obj_load_obj(stream, &(inventoryItem->item), elevation, objectListNode->obj) == -1) {\n                            return -1;\n                        }\n                    }\n                }\n            } else {\n                inventory->capacity = 0;\n                inventory->items = NULL;\n            }\n        }\n    }\n\n    obj_rebuild_all_light();\n\n    return 0;\n}\n\n// 0x48909C\nstatic void obj_fix_combat_cid_for_dude()\n{\n    Object** critterList;\n    int critterListLength = obj_create_list(-1, map_elevation, OBJ_TYPE_CRITTER, &critterList);\n\n    if (obj_dude->data.critter.combat.whoHitMeCid == -1) {\n        obj_dude->data.critter.combat.whoHitMe = NULL;\n    } else {\n        int index = find_cid(0, obj_dude->data.critter.combat.whoHitMeCid, critterList, critterListLength);\n        if (index != critterListLength) {\n            obj_dude->data.critter.combat.whoHitMe = critterList[index];\n        } else {\n            obj_dude->data.critter.combat.whoHitMe = NULL;\n        }\n    }\n\n    if (critterListLength != 0) {\n        // NOTE: Uninline.\n        obj_delete_list(critterList);\n    }\n}\n\n// Fixes ammo pid and number of charges.\n//\n// 0x48911C\nstatic void object_fix_weapon_ammo(Object* obj)\n{\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_ITEM) {\n        return;\n    }\n\n    Proto* proto;\n    if (proto_ptr(obj->pid, &proto) == -1) {\n        debug_printf(\"\\nError: obj_load: proto_ptr failed on pid\");\n        exit(1);\n    }\n\n    int charges;\n    if (item_get_type(obj) == ITEM_TYPE_WEAPON) {\n        int ammoTypePid = obj->data.item.weapon.ammoTypePid;\n        if (ammoTypePid == 0xCCCCCCCC || ammoTypePid == -1) {\n            obj->data.item.weapon.ammoTypePid = proto->item.data.weapon.ammoTypePid;\n        }\n\n        charges = obj->data.item.weapon.ammoQuantity;\n        if (charges == 0xCCCCCCCC || charges == -1 || charges != proto->item.data.weapon.ammoCapacity) {\n            obj->data.item.weapon.ammoQuantity = proto->item.data.weapon.ammoCapacity;\n        }\n    } else {\n        if (PID_TYPE(obj->pid) == OBJ_TYPE_MISC) {\n            // FIXME: looks like this code in unreachable\n            charges = obj->data.item.misc.charges;\n            if (charges == 0xCCCCCCCC) {\n                charges = proto->item.data.misc.charges;\n                obj->data.item.misc.charges = charges;\n                if (charges == 0xCCCCCCCC) {\n                    debug_printf(\"\\nError: Misc Item Prototype %s: charges incorrect!\", proto_name(obj->pid));\n                    obj->data.item.misc.charges = 0;\n                }\n            } else {\n                if (charges != proto->item.data.misc.charges) {\n                    obj->data.item.misc.charges = proto->item.data.misc.charges;\n                }\n            }\n        }\n    }\n}\n\n// 0x489200\nstatic int obj_write_obj(Object* obj, File* stream)\n{\n    if (db_fwriteInt(stream, obj->id) == -1) return -1;\n    if (db_fwriteInt(stream, obj->tile) == -1) return -1;\n    if (db_fwriteInt(stream, obj->x) == -1) return -1;\n    if (db_fwriteInt(stream, obj->y) == -1) return -1;\n    if (db_fwriteInt(stream, obj->sx) == -1) return -1;\n    if (db_fwriteInt(stream, obj->sy) == -1) return -1;\n    if (db_fwriteInt(stream, obj->frame) == -1) return -1;\n    if (db_fwriteInt(stream, obj->rotation) == -1) return -1;\n    if (db_fwriteInt(stream, obj->fid) == -1) return -1;\n    if (db_fwriteInt(stream, obj->flags) == -1) return -1;\n    if (db_fwriteInt(stream, obj->elevation) == -1) return -1;\n    if (db_fwriteInt(stream, obj->pid) == -1) return -1;\n    if (db_fwriteInt(stream, obj->cid) == -1) return -1;\n    if (db_fwriteInt(stream, obj->lightDistance) == -1) return -1;\n    if (db_fwriteInt(stream, obj->lightIntensity) == -1) return -1;\n    if (db_fwriteInt(stream, obj->outline) == -1) return -1;\n    if (db_fwriteInt(stream, obj->sid) == -1) return -1;\n    if (db_fwriteInt(stream, obj->field_80) == -1) return -1;\n    if (proto_write_protoUpdateData(obj, stream) == -1) return -1;\n\n    return 0;\n}\n\n// 0x48935C\nint obj_save(File* stream)\n{\n    if (stream == NULL) {\n        return -1;\n    }\n\n    obj_process_seen();\n\n    int objectCount = 0;\n\n    long objectCountPos = db_ftell(stream);\n    if (db_fwriteInt(stream, objectCount) == -1) {\n        return -1;\n    }\n\n    for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        int objectCountAtElevation = 0;\n\n        long objectCountAtElevationPos = db_ftell(stream);\n        if (db_fwriteInt(stream, objectCountAtElevation) == -1) {\n            return -1;\n        }\n\n        for (int tile = 0; tile < HEX_GRID_SIZE; tile++) {\n            for (ObjectListNode* objectListNode = objectTable[tile]; objectListNode != NULL; objectListNode = objectListNode->next) {\n                Object* object = objectListNode->obj;\n                if (object->elevation != elevation) {\n                    continue;\n                }\n\n                if ((object->flags & OBJECT_TEMPORARY) != 0) {\n                    continue;\n                }\n\n                CritterCombatData* combatData = NULL;\n                Object* whoHitMe = NULL;\n                if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n                    combatData = &(object->data.critter.combat);\n                    whoHitMe = combatData->whoHitMe;\n                    if (whoHitMe != 0) {\n                        if (combatData->whoHitMeCid != -1) {\n                            combatData->whoHitMeCid = whoHitMe->cid;\n                        }\n                    } else {\n                        combatData->whoHitMeCid = -1;\n                    }\n                }\n\n                if (obj_write_obj(object, stream) == -1) {\n                    return -1;\n                }\n\n                if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n                    combatData->whoHitMe = whoHitMe;\n                }\n\n                Inventory* inventory = &(object->data.inventory);\n                for (int index = 0; index < inventory->length; index++) {\n                    InventoryItem* inventoryItem = &(inventory->items[index]);\n\n                    if (db_fwriteInt(stream, inventoryItem->quantity) == -1) {\n                        return -1;\n                    }\n\n                    if (obj_save_obj(stream, inventoryItem->item) == -1) {\n                        return -1;\n                    }\n                }\n\n                objectCountAtElevation++;\n            }\n        }\n\n        long pos = db_ftell(stream);\n        db_fseek(stream, objectCountAtElevationPos, SEEK_SET);\n        db_fwriteInt(stream, objectCountAtElevation);\n        db_fseek(stream, pos, SEEK_SET);\n\n        objectCount += objectCountAtElevation;\n    }\n\n    long pos = db_ftell(stream);\n    db_fseek(stream, objectCountPos, SEEK_SET);\n    db_fwriteInt(stream, objectCount);\n    db_fseek(stream, pos, SEEK_SET);\n\n    return 0;\n}\n\n// 0x489550\nvoid obj_render_pre_roof(Rect* rect, int elevation)\n{\n    if (!objInitialized) {\n        return;\n    }\n\n    Rect updatedRect;\n    if (rect_inside_bound(rect, &buf_rect, &updatedRect) != 0) {\n        return;\n    }\n\n    int ambientLight = light_get_ambient();\n    int minX = updatedRect.ulx - 320;\n    int minY = updatedRect.uly - 240;\n    int maxX = updatedRect.lrx + 320;\n    int maxY = updatedRect.lry + 240;\n    int topLeftTile = tile_num(minX, minY, elevation);\n    int updateAreaHexWidth = (maxX - minX + 1) / 32;\n    int updateAreaHexHeight = (maxY - minY + 1) / 12;\n\n    int parity = tile_center_tile & 1;\n    int* orders = orderTable[parity];\n    int* offsets = offsetTable[parity];\n\n    outlineCount = 0;\n\n    int renderCount = 0;\n    for (int i = 0; i < updateHexArea; i++) {\n        int offsetIndex = *orders++;\n        if (updateAreaHexHeight > offsetDivTable[offsetIndex] && updateAreaHexWidth > offsetModTable[offsetIndex]) {\n            int light;\n\n            ObjectListNode* objectListNode = objectTable[topLeftTile + offsets[offsetIndex]];\n            if (objectListNode != NULL) {\n                // NOTE: calls light_get_tile two times, probably result of min/max macro\n                int tileLight = light_get_tile(elevation, objectListNode->obj->tile);\n                if (tileLight >= ambientLight) {\n                    light = tileLight;\n                } else {\n                    light = ambientLight;\n                }\n            }\n\n            while (objectListNode != NULL) {\n                if (elevation < objectListNode->obj->elevation) {\n                    break;\n                }\n\n                if (elevation == objectListNode->obj->elevation) {\n                    if ((objectListNode->obj->flags & OBJECT_FLAT) == 0) {\n                        break;\n                    }\n\n                    if ((objectListNode->obj->flags & OBJECT_HIDDEN) == 0) {\n                        obj_render_object(objectListNode->obj, &updatedRect, light);\n\n                        if ((objectListNode->obj->outline & OUTLINE_TYPE_MASK) != 0) {\n                            if ((objectListNode->obj->outline & OUTLINE_DISABLED) == 0 && outlineCount < 100) {\n                                outlinedObjects[outlineCount++] = objectListNode->obj;\n                            }\n                        }\n                    }\n                }\n\n                objectListNode = objectListNode->next;\n            }\n\n            if (objectListNode != NULL) {\n                renderTable[renderCount++] = objectListNode;\n            }\n        }\n    }\n\n    for (int i = 0; i < renderCount; i++) {\n        int light;\n\n        ObjectListNode* objectListNode = renderTable[i];\n        if (objectListNode != NULL) {\n            // NOTE: calls light_get_tile two times, probably result of min/max macro\n            int tileLight = light_get_tile(elevation, objectListNode->obj->tile);\n            if (tileLight >= ambientLight) {\n                light = tileLight;\n            } else {\n                light = ambientLight;\n            }\n        }\n\n        while (objectListNode != NULL) {\n            Object* object = objectListNode->obj;\n            if (elevation < object->elevation) {\n                break;\n            }\n\n            if (elevation == objectListNode->obj->elevation) {\n                if ((objectListNode->obj->flags & OBJECT_HIDDEN) == 0) {\n                    obj_render_object(object, &updatedRect, light);\n\n                    if ((objectListNode->obj->outline & OUTLINE_TYPE_MASK) != 0) {\n                        if ((objectListNode->obj->outline & OUTLINE_DISABLED) == 0 && outlineCount < 100) {\n                            outlinedObjects[outlineCount++] = objectListNode->obj;\n                        }\n                    }\n                }\n            }\n\n            objectListNode = objectListNode->next;\n        }\n    }\n}\n\n// 0x4897EC\nvoid obj_render_post_roof(Rect* rect, int elevation)\n{\n    if (!objInitialized) {\n        return;\n    }\n\n    Rect updatedRect;\n    if (rect_inside_bound(rect, &buf_rect, &updatedRect) != 0) {\n        return;\n    }\n\n    for (int index = 0; index < outlineCount; index++) {\n        obj_render_outline(outlinedObjects[index], &updatedRect);\n    }\n\n    text_object_render(&updatedRect);\n\n    ObjectListNode* objectListNode = floatingObjects;\n    while (objectListNode != NULL) {\n        Object* object = objectListNode->obj;\n        if ((object->flags & OBJECT_HIDDEN) == 0) {\n            obj_render_object(object, &updatedRect, 0x10000);\n        }\n        objectListNode = objectListNode->next;\n    }\n}\n\n// 0x489A84\nint obj_new(Object** objectPtr, int fid, int pid)\n{\n    ObjectListNode* objectListNode;\n\n    // NOTE: Uninline;\n    if (obj_create_object_node(&objectListNode) == -1) {\n        return -1;\n    }\n\n    if (obj_create_object(&(objectListNode->obj)) == -1) {\n        // Uninline.\n        obj_destroy_object_node(&objectListNode);\n        return -1;\n    }\n\n    objectListNode->obj->fid = fid;\n    obj_insert(objectListNode);\n\n    if (objectPtr) {\n        *objectPtr = objectListNode->obj;\n    }\n\n    objectListNode->obj->pid = pid;\n    objectListNode->obj->id = new_obj_id();\n\n    if (pid == -1 || PID_TYPE(pid) == OBJ_TYPE_TILE) {\n        Inventory* inventory = &(objectListNode->obj->data.inventory);\n        inventory->length = 0;\n        inventory->items = NULL;\n        return 0;\n    }\n\n    proto_update_init(objectListNode->obj);\n\n    Proto* proto = NULL;\n    if (proto_ptr(pid, &proto) == -1) {\n        return 0;\n    }\n\n    obj_set_light(objectListNode->obj, proto->lightDistance, proto->lightIntensity, NULL);\n\n    if ((proto->flags & 0x08) != 0) {\n        obj_toggle_flat(objectListNode->obj, NULL);\n    }\n\n    if ((proto->flags & 0x10) != 0) {\n        objectListNode->obj->flags |= OBJECT_NO_BLOCK;\n    }\n\n    if ((proto->flags & 0x800) != 0) {\n        objectListNode->obj->flags |= OBJECT_MULTIHEX;\n    }\n\n    if ((proto->flags & 0x8000) != 0) {\n        objectListNode->obj->flags |= OBJECT_TRANS_NONE;\n    } else {\n        if ((proto->flags & 0x10000) != 0) {\n            objectListNode->obj->flags |= OBJECT_TRANS_WALL;\n        } else if ((proto->flags & 0x20000) != 0) {\n            objectListNode->obj->flags |= OBJECT_TRANS_GLASS;\n        } else if ((proto->flags & 0x40000) != 0) {\n            objectListNode->obj->flags |= OBJECT_TRANS_STEAM;\n        } else if ((proto->flags & 0x80000) != 0) {\n            objectListNode->obj->flags |= OBJECT_TRANS_ENERGY;\n        } else if ((proto->flags & 0x4000) != 0) {\n            objectListNode->obj->flags |= OBJECT_TRANS_RED;\n        }\n    }\n\n    if ((proto->flags & 0x20000000) != 0) {\n        objectListNode->obj->flags |= OBJECT_LIGHT_THRU;\n    }\n\n    if ((proto->flags & 0x80000000) != 0) {\n        objectListNode->obj->flags |= OBJECT_SHOOT_THRU;\n    }\n\n    if ((proto->flags & 0x10000000) != 0) {\n        objectListNode->obj->flags |= OBJECT_WALL_TRANS_END;\n    }\n\n    if ((proto->flags & 0x1000) != 0) {\n        objectListNode->obj->flags |= OBJECT_NO_HIGHLIGHT;\n    }\n\n    obj_new_sid(objectListNode->obj, &(objectListNode->obj->sid));\n\n    return 0;\n}\n\n// 0x489C9C\nint obj_pid_new(Object** objectPtr, int pid)\n{\n    Proto* proto;\n\n    *objectPtr = NULL;\n\n    if (proto_ptr(pid, &proto) == -1) {\n        return -1;\n    }\n\n    return obj_new(objectPtr, proto->fid, pid);\n}\n\n// 0x489CCC\nint obj_copy(Object** a1, Object* a2)\n{\n    if (a2 == NULL) {\n        return -1;\n    }\n\n    ObjectListNode* objectListNode;\n\n    // NOTE: Uninline.\n    if (obj_create_object_node(&objectListNode) == -1) {\n        return -1;\n    }\n\n    if (obj_create_object(&(objectListNode->obj)) == -1) {\n        // NOTE: Uninline.\n        obj_destroy_object_node(&objectListNode);\n        return -1;\n    }\n\n    clear_pupdate_data(objectListNode->obj);\n\n    memcpy(objectListNode->obj, a2, sizeof(Object));\n\n    if (a1 != NULL) {\n        *a1 = objectListNode->obj;\n    }\n\n    obj_insert(objectListNode);\n\n    objectListNode->obj->id = new_obj_id();\n\n    if (objectListNode->obj->sid != -1) {\n        objectListNode->obj->sid = -1;\n        obj_new_sid(objectListNode->obj, &(objectListNode->obj->sid));\n    }\n\n    if (obj_set_rotation(objectListNode->obj, a2->rotation, NULL) == -1) {\n        // TODO: Probably leaking object allocated with obj_create_object.\n        // NOTE: Uninline.\n        obj_destroy_object_node(&objectListNode);\n        return -1;\n    }\n\n    objectListNode->obj->flags &= ~OBJECT_USED;\n\n    Inventory* newInventory = &(objectListNode->obj->data.inventory);\n    newInventory->length = 0;\n    newInventory->capacity = 0;\n\n    Inventory* oldInventory = &(a2->data.inventory);\n    for (int index = 0; index < oldInventory->length; index++) {\n        InventoryItem* oldInventoryItem = &(oldInventory->items[index]);\n\n        Object* newItem;\n        if (obj_copy(&newItem, oldInventoryItem->item) == -1) {\n            // TODO: Probably leaking object allocated with obj_create_object.\n            // NOTE: Uninline.\n            obj_destroy_object_node(&objectListNode);\n            return -1;\n        }\n\n        if (item_add_force(objectListNode->obj, newItem, oldInventoryItem->quantity) == 1) {\n            // TODO: Probably leaking object allocated with obj_create_object.\n            // NOTE: Uninline.\n            obj_destroy_object_node(&objectListNode);\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x489EC4\nint obj_connect(Object* object, int tile, int elevation, Rect* rect)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    if (!hexGridTileIsValid(tile)) {\n        return -1;\n    }\n\n    if (!elevationIsValid(elevation)) {\n        return -1;\n    }\n\n    ObjectListNode* objectListNode;\n\n    // NOTE: Uninline.\n    if (obj_create_object_node(&objectListNode) == -1) {\n        return -1;\n    }\n\n    objectListNode->obj = object;\n\n    return obj_connect_to_tile(objectListNode, tile, elevation, rect);\n}\n\n// 0x489F34\nint obj_disconnect(Object* obj, Rect* rect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    ObjectListNode* node;\n    ObjectListNode* prev_node;\n    if (obj_node_ptr(obj, &node, &prev_node) != 0) {\n        return -1;\n    }\n\n    if (obj_adjust_light(obj, 1, rect) == -1) {\n        if (rect != NULL) {\n            obj_bound(obj, rect);\n        }\n    }\n\n    if (prev_node != NULL) {\n        prev_node->next = node->next;\n    } else {\n        int tile = node->obj->tile;\n        if (tile == -1) {\n            floatingObjects = floatingObjects->next;\n        } else {\n            objectTable[tile] = objectTable[tile]->next;\n        }\n    }\n\n    if (node != NULL) {\n        mem_free(node);\n    }\n\n    obj->tile = -1;\n\n    return 0;\n}\n\n// 0x489FF8\nint obj_offset(Object* obj, int x, int y, Rect* rect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    ObjectListNode* node = NULL;\n    ObjectListNode* previousNode = NULL;\n    if (obj_node_ptr(obj, &node, &previousNode) == -1) {\n        return -1;\n    }\n\n    if (obj == obj_dude) {\n        if (rect != NULL) {\n            Rect eggRect;\n            obj_bound(obj_egg, &eggRect);\n            rectCopy(rect, &eggRect);\n\n            if (previousNode != NULL) {\n                previousNode->next = node->next;\n            } else {\n                int tile = node->obj->tile;\n                if (tile == -1) {\n                    floatingObjects = floatingObjects->next;\n                } else {\n                    objectTable[tile] = objectTable[tile]->next;\n                }\n            }\n\n            obj->x += x;\n            obj->sx += x;\n\n            obj->y += y;\n            obj->sy += y;\n\n            obj_insert(node);\n\n            rectOffset(&eggRect, x, y);\n\n            obj_offset(obj_egg, x, y, NULL);\n            rect_min_bound(rect, &eggRect, rect);\n        } else {\n            if (previousNode != NULL) {\n                previousNode->next = node->next;\n            } else {\n                int tile = node->obj->tile;\n                if (tile == -1) {\n                    floatingObjects = floatingObjects->next;\n                } else {\n                    objectTable[tile] = objectTable[tile]->next;\n                }\n            }\n\n            obj->x += x;\n            obj->sx += x;\n\n            obj->y += y;\n            obj->sy += y;\n\n            obj_insert(node);\n\n            obj_offset(obj_egg, x, y, NULL);\n        }\n    } else {\n        if (rect != NULL) {\n            obj_bound(obj, rect);\n\n            if (previousNode != NULL) {\n                previousNode->next = node->next;\n            } else {\n                int tile = node->obj->tile;\n                if (tile == -1) {\n                    floatingObjects = floatingObjects->next;\n                } else {\n                    objectTable[tile] = objectTable[tile]->next;\n                }\n            }\n\n            obj->x += x;\n            obj->sx += x;\n\n            obj->y += y;\n            obj->sy += y;\n\n            obj_insert(node);\n\n            Rect objectRect;\n            rectCopy(&objectRect, rect);\n\n            rectOffset(&objectRect, x, y);\n\n            rect_min_bound(rect, &objectRect, rect);\n        } else {\n            if (previousNode != NULL) {\n                previousNode->next = node->next;\n            } else {\n                int tile = node->obj->tile;\n                if (tile == -1) {\n                    floatingObjects = floatingObjects->next;\n                } else {\n                    objectTable[tile] = objectTable[tile]->next;\n                }\n            }\n\n            obj->x += x;\n            obj->sx += x;\n\n            obj->y += y;\n            obj->sy += y;\n\n            obj_insert(node);\n        }\n    }\n\n    return 0;\n}\n\n// 0x48A324\nint obj_move(Object* a1, int a2, int a3, int elevation, Rect* a5)\n{\n    if (a1 == NULL) {\n        return -1;\n    }\n\n    // TODO: Get rid of initialization.\n    ObjectListNode* node = NULL;\n    ObjectListNode* previousNode;\n    int v22 = 0;\n\n    int tile = a1->tile;\n    if (hexGridTileIsValid(tile)) {\n        if (obj_node_ptr(a1, &node, &previousNode) == -1) {\n            return -1;\n        }\n\n        if (obj_adjust_light(a1, 1, a5) == -1) {\n            if (a5 != NULL) {\n                obj_bound(a1, a5);\n            }\n        }\n\n        if (previousNode != NULL) {\n            previousNode->next = node->next;\n        } else {\n            int tile = node->obj->tile;\n            if (tile == -1) {\n                floatingObjects = floatingObjects->next;\n            } else {\n                objectTable[tile] = objectTable[tile]->next;\n            }\n        }\n\n        a1->tile = -1;\n        a1->elevation = elevation;\n        v22 = 1;\n    } else {\n        if (elevation == a1->elevation) {\n            if (a5 != NULL) {\n                obj_bound(a1, a5);\n            }\n        } else {\n            if (obj_node_ptr(a1, &node, &previousNode) == -1) {\n                return -1;\n            }\n\n            if (a5 != NULL) {\n                obj_bound(a1, a5);\n            }\n\n            if (previousNode != NULL) {\n                previousNode->next = node->next;\n            } else {\n                int tile = node->obj->tile;\n                if (tile != -1) {\n                    objectTable[tile] = objectTable[tile]->next;\n                } else {\n                    floatingObjects = floatingObjects->next;\n                }\n            }\n\n            a1->elevation = elevation;\n            v22 = 1;\n        }\n    }\n\n    CacheEntry* cacheHandle;\n    int width;\n    int height;\n    Art* art = art_ptr_lock(a1->fid, &cacheHandle);\n    if (art != NULL) {\n        art_frame_width_length(art, a1->frame, a1->rotation, &width, &height);\n        a1->sx = a2 - width / 2;\n        a1->sy = a3 - (height - 1);\n        art_ptr_unlock(cacheHandle);\n    }\n\n    if (v22) {\n        obj_insert(node);\n    }\n\n    if (a5 != NULL) {\n        Rect rect;\n        obj_bound(a1, &rect);\n        rect_min_bound(a5, &rect, a5);\n    }\n\n    if (a1 == obj_dude) {\n        if (a1 != NULL) {\n            Rect rect;\n            obj_move(obj_egg, a2, a3, elevation, &rect);\n            rect_min_bound(a5, &rect, a5);\n        } else {\n            obj_move(obj_egg, a2, a3, elevation, NULL);\n        }\n    }\n\n    return 0;\n}\n\n// 0x48A568\nint obj_move_to_tile(Object* obj, int tile, int elevation, Rect* rect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (!hexGridTileIsValid(tile)) {\n        return -1;\n    }\n\n    if (!elevationIsValid(elevation)) {\n        return -1;\n    }\n\n    ObjectListNode* node;\n    ObjectListNode* prevNode;\n    if (obj_node_ptr(obj, &node, &prevNode) == -1) {\n        return -1;\n    }\n\n    Rect v23;\n    int v5 = obj_adjust_light(obj, 1, rect);\n    if (rect != NULL) {\n        if (v5 == -1) {\n            obj_bound(obj, rect);\n        }\n\n        rectCopy(&v23, rect);\n    }\n\n    int oldElevation = obj->elevation;\n    if (prevNode != NULL) {\n        prevNode->next = node->next;\n    } else {\n        int tileIndex = node->obj->tile;\n        if (tileIndex == -1) {\n            floatingObjects = floatingObjects->next;\n        } else {\n            objectTable[tileIndex] = objectTable[tileIndex]->next;\n        }\n    }\n\n    if (obj_connect_to_tile(node, tile, elevation, rect) == -1) {\n        return -1;\n    }\n\n    if (isInCombat()) {\n        if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) {\n            bool v8 = obj->outline != 0 && (obj->outline & OUTLINE_DISABLED) == 0;\n            combat_update_critter_outline_for_los(obj, v8);\n        }\n    }\n\n    if (rect != NULL) {\n        rect_min_bound(rect, &v23, rect);\n    }\n\n    if (obj == obj_dude) {\n        ObjectListNode* objectListNode = objectTable[tile];\n        while (objectListNode != NULL) {\n            Object* obj = objectListNode->obj;\n            int elev = obj->elevation;\n            if (elevation < elev) {\n                break;\n            }\n\n            if (elevation == elev) {\n                if (FID_TYPE(obj->fid) == OBJ_TYPE_MISC) {\n                    if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) {\n                        ObjectData* data = &(obj->data);\n\n                        MapTransition transition;\n                        memset(&transition, 0, sizeof(transition));\n\n                        transition.map = data->misc.map;\n                        transition.tile = data->misc.tile;\n                        transition.elevation = data->misc.elevation;\n                        transition.rotation = data->misc.rotation;\n                        map_leave_map(&transition);\n\n                        wmMapMarkMapEntranceState(transition.map, transition.elevation, 1);\n                    }\n                }\n            }\n\n            objectListNode = objectListNode->next;\n        }\n\n        // NOTE: Uninline.\n        obj_set_seen(tile);\n\n        int roofX = tile % 200 / 2;\n        int roofY = tile / 200 / 2;\n        if (roofX != obj_last_roof_x || roofY != obj_last_roof_y || elevation != obj_last_elev) {\n            int currentSquare = square[elevation]->field_0[roofX + 100 * roofY];\n            int currentSquareFid = art_id(OBJ_TYPE_TILE, (currentSquare >> 16) & 0xFFF, 0, 0, 0);\n            int previousSquare = square[elevation]->field_0[obj_last_roof_x + 100 * obj_last_roof_y];\n            bool isEmpty = art_id(OBJ_TYPE_TILE, 1, 0, 0, 0) == currentSquareFid;\n\n            if (isEmpty != obj_last_is_empty || (((currentSquare >> 16) & 0xF000) >> 12) != (((previousSquare >> 16) & 0xF000) >> 12)) {\n                if (!obj_last_is_empty) {\n                    tile_fill_roof(obj_last_roof_x, obj_last_roof_y, elevation, 1);\n                }\n\n                if (!isEmpty) {\n                    tile_fill_roof(roofX, roofY, elevation, 0);\n                }\n\n                if (rect != NULL) {\n                    rect_min_bound(rect, &scr_size, rect);\n                }\n            }\n\n            obj_last_roof_x = roofX;\n            obj_last_roof_y = roofY;\n            obj_last_elev = elevation;\n            obj_last_is_empty = isEmpty;\n        }\n\n        if (rect != NULL) {\n            Rect r;\n            obj_move_to_tile(obj_egg, tile, elevation, &r);\n            rect_min_bound(rect, &r, rect);\n        } else {\n            obj_move_to_tile(obj_egg, tile, elevation, 0);\n        }\n\n        if (elevation != oldElevation) {\n            map_set_elevation(elevation);\n            tile_set_center(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS);\n            if (isInCombat()) {\n                game_user_wants_to_quit = 1;\n            }\n        }\n    } else {\n        if (elevation != obj_last_elev && PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n            combat_delete_critter(obj);\n        }\n    }\n\n    return 0;\n}\n\n// 0x48A9A0\nint obj_reset_roof()\n{\n    int fid = art_id(OBJ_TYPE_TILE, (square[obj_dude->elevation]->field_0[obj_last_roof_x + 100 * obj_last_roof_y] >> 16) & 0xFFF, 0, 0, 0);\n    if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) {\n        tile_fill_roof(obj_last_roof_x, obj_last_roof_y, obj_dude->elevation, 1);\n    }\n    return 0;\n}\n\n// Sets object fid.\n//\n// 0x48AA3C\nint obj_change_fid(Object* obj, int fid, Rect* dirtyRect)\n{\n    Rect new_rect;\n\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (dirtyRect != NULL) {\n        obj_bound(obj, dirtyRect);\n\n        obj->fid = fid;\n\n        obj_bound(obj, &new_rect);\n        rect_min_bound(dirtyRect, &new_rect, dirtyRect);\n    } else {\n        obj->fid = fid;\n    }\n\n    return 0;\n}\n\n// Sets object frame.\n//\n// 0x48AA84\nint obj_set_frame(Object* obj, int frame, Rect* rect)\n{\n    Rect new_rect;\n    Art* art;\n    CacheEntry* cache_entry;\n    int framesPerDirection;\n\n    if (obj == NULL) {\n        return -1;\n    }\n\n    art = art_ptr_lock(obj->fid, &cache_entry);\n    if (art == NULL) {\n        return -1;\n    }\n\n    framesPerDirection = art->frameCount;\n\n    art_ptr_unlock(cache_entry);\n\n    if (frame >= framesPerDirection) {\n        return -1;\n    }\n\n    if (rect != NULL) {\n        obj_bound(obj, rect);\n        obj->frame = frame;\n        obj_bound(obj, &new_rect);\n        rect_min_bound(rect, &new_rect, rect);\n    } else {\n        obj->frame = frame;\n    }\n\n    return 0;\n}\n\n// 0x48AAF0\nint obj_inc_frame(Object* obj, Rect* dirtyRect)\n{\n    Art* art;\n    CacheEntry* cache_entry;\n    int framesPerDirection;\n    int nextFrame;\n\n    if (obj == NULL) {\n        return -1;\n    }\n\n    art = art_ptr_lock(obj->fid, &cache_entry);\n    if (art == NULL) {\n        return -1;\n    }\n\n    framesPerDirection = art->frameCount;\n\n    art_ptr_unlock(cache_entry);\n\n    nextFrame = obj->frame + 1;\n    if (nextFrame >= framesPerDirection) {\n        nextFrame = 0;\n    }\n\n    if (dirtyRect != NULL) {\n\n        obj_bound(obj, dirtyRect);\n\n        obj->frame = nextFrame;\n\n        Rect updatedRect;\n        obj_bound(obj, &updatedRect);\n        rect_min_bound(dirtyRect, &updatedRect, dirtyRect);\n    } else {\n        obj->frame = nextFrame;\n    }\n\n    return 0;\n}\n\n// 0x48AB60\n//\nint obj_dec_frame(Object* obj, Rect* dirtyRect)\n{\n    Art* art;\n    CacheEntry* cache_entry;\n    int framesPerDirection;\n    int prevFrame;\n    Rect newRect;\n\n    if (obj == NULL) {\n        return -1;\n    }\n\n    art = art_ptr_lock(obj->fid, &cache_entry);\n    if (art == NULL) {\n        return -1;\n    }\n\n    framesPerDirection = art->frameCount;\n\n    art_ptr_unlock(cache_entry);\n\n    prevFrame = obj->frame - 1;\n    if (prevFrame < 0) {\n        prevFrame = framesPerDirection - 1;\n    }\n\n    if (dirtyRect != NULL) {\n        obj_bound(obj, dirtyRect);\n        obj->frame = prevFrame;\n        obj_bound(obj, &newRect);\n        rect_min_bound(dirtyRect, &newRect, dirtyRect);\n    } else {\n        obj->frame = prevFrame;\n    }\n\n    return 0;\n}\n\n// 0x48ABD4\nint obj_set_rotation(Object* obj, int direction, Rect* dirtyRect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (direction >= ROTATION_COUNT) {\n        return -1;\n    }\n\n    if (dirtyRect != NULL) {\n        obj_bound(obj, dirtyRect);\n        obj->rotation = direction;\n\n        Rect newRect;\n        obj_bound(obj, &newRect);\n        rect_min_bound(dirtyRect, &newRect, dirtyRect);\n    } else {\n        obj->rotation = direction;\n    }\n\n    return 0;\n}\n\n// 0x48AC20\nint obj_inc_rotation(Object* obj, Rect* dirtyRect)\n{\n    int rotation = obj->rotation + 1;\n    if (rotation >= ROTATION_COUNT) {\n        rotation = ROTATION_NE;\n    }\n\n    return obj_set_rotation(obj, rotation, dirtyRect);\n}\n\n// 0x48AC38\nint obj_dec_rotation(Object* obj, Rect* dirtyRect)\n{\n    int rotation = obj->rotation - 1;\n    if (rotation < 0) {\n        rotation = ROTATION_NW;\n    }\n\n    return obj_set_rotation(obj, rotation, dirtyRect);\n}\n\n// 0x48AC54\nvoid obj_rebuild_all_light()\n{\n    light_reset_tiles();\n\n    for (int tile = 0; tile < HEX_GRID_SIZE; tile++) {\n        ObjectListNode* objectListNode = objectTable[tile];\n        while (objectListNode != NULL) {\n            obj_adjust_light(objectListNode->obj, 0, NULL);\n            objectListNode = objectListNode->next;\n        }\n    }\n}\n\n// 0x48AC90\nint obj_set_light(Object* obj, int lightDistance, int lightIntensity, Rect* rect)\n{\n    int v7;\n    Rect new_rect;\n\n    if (obj == NULL) {\n        return -1;\n    }\n\n    v7 = obj_turn_off_light(obj, rect);\n    if (lightIntensity > 0) {\n        if (lightDistance >= 8) {\n            lightDistance = 8;\n        }\n\n        obj->lightIntensity = lightIntensity;\n        obj->lightDistance = lightDistance;\n\n        if (rect != NULL) {\n            v7 = obj_turn_on_light(obj, &new_rect);\n            rect_min_bound(rect, &new_rect, rect);\n        } else {\n            v7 = obj_turn_on_light(obj, NULL);\n        }\n    } else {\n        obj->lightIntensity = 0;\n        obj->lightDistance = 0;\n    }\n\n    return v7;\n}\n\n// 0x48AD04\nint obj_get_visible_light(Object* obj)\n{\n    int lightLevel = light_get_ambient();\n    int lightIntensity = light_get_tile_true(obj->elevation, obj->tile);\n\n    if (obj == obj_dude) {\n        lightIntensity -= obj_dude->lightIntensity;\n    }\n\n    if (lightIntensity >= lightLevel) {\n        if (lightIntensity > LIGHT_LEVEL_MAX) {\n            lightIntensity = LIGHT_LEVEL_MAX;\n        }\n    } else {\n        lightIntensity = lightLevel;\n    }\n\n    return lightIntensity;\n}\n\n// 0x48AD48\nint obj_turn_on_light(Object* obj, Rect* rect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (obj->lightIntensity <= 0) {\n        obj->flags &= ~OBJECT_LIGHTING;\n        return -1;\n    }\n\n    if ((obj->flags & OBJECT_LIGHTING) == 0) {\n        obj->flags |= OBJECT_LIGHTING;\n\n        if (obj_adjust_light(obj, 0, rect) == -1) {\n            if (rect != NULL) {\n                obj_bound(obj, rect);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x48AD9C\nint obj_turn_off_light(Object* obj, Rect* rect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (obj->lightIntensity <= 0) {\n        obj->flags &= ~OBJECT_LIGHTING;\n        return -1;\n    }\n\n    if ((obj->flags & OBJECT_LIGHTING) != 0) {\n        if (obj_adjust_light(obj, 1, rect) == -1) {\n            if (rect != NULL) {\n                obj_bound(obj, rect);\n            }\n        }\n\n        obj->flags &= ~OBJECT_LIGHTING;\n    }\n\n    return 0;\n}\n\n// 0x48ADF0\nint obj_turn_on(Object* obj, Rect* rect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if ((obj->flags & OBJECT_HIDDEN) == 0) {\n        return -1;\n    }\n\n    obj->flags &= ~OBJECT_HIDDEN;\n    obj->outline &= ~OUTLINE_DISABLED;\n\n    if (obj_adjust_light(obj, 0, rect) == -1) {\n        if (rect != NULL) {\n            obj_bound(obj, rect);\n        }\n    }\n\n    if (obj == obj_dude) {\n        if (rect != NULL) {\n            Rect eggRect;\n            obj_bound(obj_egg, &eggRect);\n            rect_min_bound(rect, &eggRect, rect);\n        }\n    }\n\n    return 0;\n}\n\n// 0x48AE68\nint obj_turn_off(Object* object, Rect* rect)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    if ((object->flags & OBJECT_HIDDEN) != 0) {\n        return -1;\n    }\n\n    if (obj_adjust_light(object, 1, rect) == -1) {\n        if (rect != NULL) {\n            obj_bound(object, rect);\n        }\n    }\n\n    object->flags |= OBJECT_HIDDEN;\n\n    if ((object->outline & OUTLINE_TYPE_MASK) != 0) {\n        object->outline |= OUTLINE_DISABLED;\n    }\n\n    if (object == obj_dude) {\n        if (rect != NULL) {\n            Rect eggRect;\n            obj_bound(obj_egg, &eggRect);\n            rect_min_bound(rect, &eggRect, rect);\n        }\n    }\n\n    return 0;\n}\n\n// 0x48AEE4\nint obj_turn_on_outline(Object* object, Rect* rect)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    object->outline &= ~OUTLINE_DISABLED;\n\n    if (rect != NULL) {\n        obj_bound(object, rect);\n    }\n\n    return 0;\n}\n\n// 0x48AF00\nint obj_turn_off_outline(Object* object, Rect* rect)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    if ((object->outline & OUTLINE_TYPE_MASK) != 0) {\n        object->outline |= OUTLINE_DISABLED;\n    }\n\n    if (rect != NULL) {\n        obj_bound(object, rect);\n    }\n\n    return 0;\n}\n\n// 0x48AF2C\nint obj_toggle_flat(Object* object, Rect* rect)\n{\n    Rect v1;\n\n    if (object == NULL) {\n        return -1;\n    }\n\n    ObjectListNode* node;\n    ObjectListNode* previousNode;\n    if (obj_node_ptr(object, &node, &previousNode) == -1) {\n        return -1;\n    }\n\n    if (rect != NULL) {\n        obj_bound(object, rect);\n\n        if (previousNode != NULL) {\n            previousNode->next = node->next;\n        } else {\n            int tile_index = node->obj->tile;\n            if (tile_index == -1) {\n                floatingObjects = floatingObjects->next;\n            } else {\n                objectTable[tile_index] = objectTable[tile_index]->next;\n            }\n        }\n\n        object->flags ^= OBJECT_FLAT;\n\n        obj_insert(node);\n        obj_bound(object, &v1);\n        rect_min_bound(rect, &v1, rect);\n    } else {\n        if (previousNode != NULL) {\n            previousNode->next = node->next;\n        } else {\n            int tile = node->obj->tile;\n            if (tile == -1) {\n                floatingObjects = floatingObjects->next;\n            } else {\n                objectTable[tile] = objectTable[tile]->next;\n            }\n        }\n\n        object->flags ^= OBJECT_FLAT;\n\n        obj_insert(node);\n    }\n\n    return 0;\n}\n\n// 0x48B0FC\nint obj_erase_object(Object* object, Rect* rect)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    gmouse_remove_item_outline(object);\n\n    ObjectListNode* node;\n    ObjectListNode* previousNode;\n    if (obj_node_ptr(object, &node, &previousNode) == 0) {\n        if (obj_adjust_light(object, 1, rect) == -1) {\n            if (rect != NULL) {\n                obj_bound(object, rect);\n            }\n        }\n\n        if (obj_remove(node, previousNode) != 0) {\n            return -1;\n        }\n\n        return 0;\n    }\n\n    // NOTE: Uninline.\n    if (obj_create_object_node(&node) == -1) {\n        return -1;\n    }\n\n    node->obj = object;\n\n    if (obj_remove(node, node) == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x48B1B0\nint obj_inven_free(Inventory* inventory)\n{\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n\n        ObjectListNode* node;\n        // NOTE: Uninline.\n        obj_create_object_node(&node);\n\n        node->obj = inventoryItem->item;\n        node->obj->flags &= ~OBJECT_FLAG_0x400;\n        obj_remove(node, node);\n\n        inventoryItem->item = NULL;\n    }\n\n    if (inventory->items != NULL) {\n        mem_free(inventory->items);\n        inventory->items = NULL;\n        inventory->capacity = 0;\n        inventory->length = 0;\n    }\n\n    return 0;\n}\n\n// 0x48B24C\nbool obj_action_can_use(Object* obj)\n{\n    int pid = obj->pid;\n    if (pid != PROTO_ID_LIT_FLARE && pid != PROTO_ID_DYNAMITE_II && pid != PROTO_ID_PLASTIC_EXPLOSIVES_II) {\n        return proto_action_can_use(pid);\n    } else {\n        return false;\n    }\n}\n\n// 0x48B278\nbool obj_action_can_talk_to(Object* obj)\n{\n    return proto_action_can_talk_to(obj->pid) && (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) && critter_is_active(obj);\n}\n\n// 0x48B2A8\nbool obj_portal_is_walk_thru(Object* obj)\n{\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_SCENERY) {\n        return false;\n    }\n\n    Proto* proto;\n    if (proto_ptr(obj->pid, &proto) == -1) {\n        return false;\n    }\n\n    return (proto->scenery.data.generic.field_0 & 0x04) != 0;\n}\n\n// 0x48B2E8\nObject* objFindObjPtrFromID(int a1)\n{\n    Object* obj = obj_find_first();\n    while (obj != NULL) {\n        if (obj->id == a1) {\n            return obj;\n        }\n        obj = obj_find_next();\n    }\n\n    return NULL;\n}\n\n// Returns root owner of given object.\n//\n// 0x48B304\nObject* obj_top_environment(Object* object)\n{\n    Object* owner = object->owner;\n    if (owner == NULL) {\n        return NULL;\n    }\n\n    while (owner->owner != NULL) {\n        owner = owner->owner;\n    }\n\n    return owner;\n}\n\n// 0x48B318\nvoid obj_remove_all()\n{\n    ObjectListNode* node;\n    ObjectListNode* prev;\n    ObjectListNode* next;\n\n    scr_remove_all();\n\n    for (int tile = 0; tile < HEX_GRID_SIZE; tile++) {\n        node = objectTable[tile];\n        prev = NULL;\n\n        while (node != NULL) {\n            next = node->next;\n            if (obj_remove(node, prev) == -1) {\n                prev = node;\n            }\n            node = next;\n        }\n    }\n\n    node = floatingObjects;\n    prev = NULL;\n\n    while (node != NULL) {\n        next = node->next;\n        if (obj_remove(node, prev) == -1) {\n            prev = node;\n        }\n        node = next;\n    }\n\n    obj_last_roof_y = -1;\n    obj_last_elev = -1;\n    obj_last_is_empty = true;\n    obj_last_roof_x = -1;\n}\n\n// 0x48B3A8\nObject* obj_find_first()\n{\n    find_elev = 0;\n\n    ObjectListNode* objectListNode;\n    for (find_tile = 0; find_tile < HEX_GRID_SIZE; find_tile++) {\n        objectListNode = objectTable[find_tile];\n        if (objectListNode) {\n            break;\n        }\n    }\n\n    if (find_tile == HEX_GRID_SIZE) {\n        find_ptr = NULL;\n        return NULL;\n    }\n\n    while (objectListNode != NULL) {\n        if (art_get_disable(FID_TYPE(objectListNode->obj->fid)) == 0) {\n            find_ptr = objectListNode;\n            return objectListNode->obj;\n        }\n        objectListNode = objectListNode->next;\n    }\n\n    find_ptr = NULL;\n    return NULL;\n}\n\n// 0x48B41C\nObject* obj_find_next()\n{\n    if (find_ptr == NULL) {\n        return NULL;\n    }\n\n    ObjectListNode* objectListNode = find_ptr->next;\n\n    while (find_tile < HEX_GRID_SIZE) {\n        if (objectListNode == NULL) {\n            objectListNode = objectTable[find_tile++];\n        }\n\n        while (objectListNode != NULL) {\n            Object* object = objectListNode->obj;\n            if (!art_get_disable(FID_TYPE(object->fid))) {\n                find_ptr = objectListNode;\n                return object;\n            }\n            objectListNode = objectListNode->next;\n        }\n    }\n\n    find_ptr = NULL;\n    return NULL;\n}\n\n// 0x48B48C\nObject* obj_find_first_at(int elevation)\n{\n    find_elev = elevation;\n    find_tile = 0;\n\n    for (find_tile = 0; find_tile < HEX_GRID_SIZE; find_tile++) {\n        ObjectListNode* objectListNode = objectTable[find_tile];\n        while (objectListNode != NULL) {\n            Object* object = objectListNode->obj;\n            if (object->elevation == elevation) {\n                if (!art_get_disable(FID_TYPE(object->fid))) {\n                    find_ptr = objectListNode;\n                    return object;\n                }\n            }\n            objectListNode = objectListNode->next;\n        }\n    }\n\n    find_ptr = NULL;\n    return NULL;\n}\n\n// 0x48B510\nObject* obj_find_next_at()\n{\n    if (find_ptr == NULL) {\n        return NULL;\n    }\n\n    ObjectListNode* objectListNode = find_ptr->next;\n\n    while (find_tile < HEX_GRID_SIZE) {\n        if (objectListNode == NULL) {\n            objectListNode = objectTable[find_tile++];\n        }\n\n        while (objectListNode != NULL) {\n            Object* object = objectListNode->obj;\n            if (object->elevation == find_elev) {\n                if (!art_get_disable(FID_TYPE(object->fid))) {\n                    find_ptr = objectListNode;\n                    return object;\n                }\n            }\n            objectListNode = objectListNode->next;\n        }\n    }\n\n    find_ptr = NULL;\n    return NULL;\n}\n\n// 0x48B5A8\nObject* obj_find_first_at_tile(int elevation, int tile)\n{\n    find_elev = elevation;\n    find_tile = tile;\n\n    ObjectListNode* objectListNode = objectTable[tile];\n    while (objectListNode != NULL) {\n        Object* object = objectListNode->obj;\n        if (object->elevation == elevation) {\n            if (!art_get_disable(FID_TYPE(object->fid))) {\n                find_ptr = objectListNode;\n                return object;\n            }\n        }\n        objectListNode = objectListNode->next;\n    }\n\n    find_ptr = NULL;\n    return NULL;\n}\n\n// 0x48B608\nObject* obj_find_next_at_tile()\n{\n    if (find_ptr == NULL) {\n        return NULL;\n    }\n\n    ObjectListNode* objectListNode = find_ptr->next;\n\n    while (objectListNode != NULL) {\n        Object* object = objectListNode->obj;\n        if (object->elevation == find_elev) {\n            if (!art_get_disable(FID_TYPE(object->fid))) {\n                find_ptr = objectListNode;\n                return object;\n            }\n        }\n        objectListNode = objectListNode->next;\n    }\n\n    find_ptr = NULL;\n    return NULL;\n}\n\n// 0x0x48B66C\nvoid obj_bound(Object* obj, Rect* rect)\n{\n    if (obj == NULL) {\n        return;\n    }\n\n    if (rect == NULL) {\n        return;\n    }\n\n    bool isOutlined = false;\n    if ((obj->outline & OUTLINE_TYPE_MASK) != 0) {\n        isOutlined = true;\n    }\n\n    CacheEntry* artHandle;\n    Art* art = art_ptr_lock(obj->fid, &artHandle);\n    if (art == NULL) {\n        rect->ulx = 0;\n        rect->uly = 0;\n        rect->lrx = 0;\n        rect->lry = 0;\n        return;\n    }\n\n    int width;\n    int height;\n    art_frame_width_length(art, obj->frame, obj->rotation, &width, &height);\n\n    if (obj->tile == -1) {\n        rect->ulx = obj->sx;\n        rect->uly = obj->sy;\n        rect->lrx = obj->sx + width - 1;\n        rect->lry = obj->sy + height - 1;\n    } else {\n        int tileScreenY;\n        int tileScreenX;\n        if (tile_coord(obj->tile, &tileScreenX, &tileScreenY, obj->elevation) == 0) {\n            tileScreenX += 16;\n            tileScreenY += 8;\n\n            tileScreenX += art->xOffsets[obj->rotation];\n            tileScreenY += art->yOffsets[obj->rotation];\n\n            tileScreenX += obj->x;\n            tileScreenY += obj->y;\n\n            rect->ulx = tileScreenX - width / 2;\n            rect->uly = tileScreenY - height + 1;\n            rect->lrx = width + rect->ulx - 1;\n            rect->lry = tileScreenY;\n        } else {\n            rect->ulx = 0;\n            rect->uly = 0;\n            rect->lrx = 0;\n            rect->lry = 0;\n            isOutlined = false;\n        }\n    }\n\n    art_ptr_unlock(artHandle);\n\n    if (isOutlined) {\n        rect->ulx--;\n        rect->uly--;\n        rect->lrx++;\n        rect->lry++;\n    }\n}\n\n// 0x48B7F8\nbool obj_occupied(int tile, int elevation)\n{\n    ObjectListNode* objectListNode = objectTable[tile];\n    while (objectListNode != NULL) {\n        if (objectListNode->obj->elevation == elevation\n            && objectListNode->obj != obj_mouse\n            && objectListNode->obj != obj_mouse_flat) {\n            return true;\n        }\n        objectListNode = objectListNode->next;\n    }\n\n    return false;\n}\n\n// 0x48B848\nObject* obj_blocking_at(Object* a1, int tile, int elev)\n{\n    ObjectListNode* objectListNode;\n    Object* v7;\n    int type;\n\n    if (!hexGridTileIsValid(tile)) {\n        return NULL;\n    }\n\n    objectListNode = objectTable[tile];\n    while (objectListNode != NULL) {\n        v7 = objectListNode->obj;\n        if (v7->elevation == elev) {\n            if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) {\n                type = FID_TYPE(v7->fid);\n                if (type == OBJ_TYPE_CRITTER\n                    || type == OBJ_TYPE_SCENERY\n                    || type == OBJ_TYPE_WALL) {\n                    return v7;\n                }\n            }\n        }\n        objectListNode = objectListNode->next;\n    }\n\n    for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n        int neighboor = tile_num_in_direction(tile, rotation, 1);\n        if (hexGridTileIsValid(neighboor)) {\n            objectListNode = objectTable[neighboor];\n            while (objectListNode != NULL) {\n                v7 = objectListNode->obj;\n                if ((v7->flags & OBJECT_MULTIHEX) != 0) {\n                    if (v7->elevation == elev) {\n                        if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) {\n                            type = FID_TYPE(v7->fid);\n                            if (type == OBJ_TYPE_CRITTER\n                                || type == OBJ_TYPE_SCENERY\n                                || type == OBJ_TYPE_WALL) {\n                                return v7;\n                            }\n                        }\n                    }\n                }\n                objectListNode = objectListNode->next;\n            }\n        }\n    }\n\n    return NULL;\n}\n\n// 0x48B930\nObject* obj_shoot_blocking_at(Object* obj, int tile, int elev)\n{\n    if (!hexGridTileIsValid(tile)) {\n        return NULL;\n    }\n\n    ObjectListNode* objectListItem = objectTable[tile];\n    while (objectListItem != NULL) {\n        Object* candidate = objectListItem->obj;\n        if (candidate->elevation == elev) {\n            unsigned int flags = candidate->flags;\n            if ((flags & OBJECT_HIDDEN) == 0 && ((flags & OBJECT_NO_BLOCK) == 0 || (flags & OBJECT_SHOOT_THRU) == 0) && candidate != obj) {\n                int type = FID_TYPE(candidate->fid);\n                if (type == OBJ_TYPE_CRITTER || type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) {\n                    return candidate;\n                }\n            }\n        }\n        objectListItem = objectListItem->next;\n    }\n\n    for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n        int adjacentTile = tile_num_in_direction(tile, rotation, 1);\n        if (!hexGridTileIsValid(adjacentTile)) {\n            continue;\n        }\n\n        ObjectListNode* objectListItem = objectTable[adjacentTile];\n        while (objectListItem != NULL) {\n            Object* candidate = objectListItem->obj;\n            unsigned int flags = candidate->flags;\n            if ((flags & OBJECT_MULTIHEX) != 0) {\n                if (candidate->elevation == elev) {\n                    if ((flags & OBJECT_HIDDEN) == 0 && (flags & OBJECT_NO_BLOCK) == 0 && candidate != obj) {\n                        int type = FID_TYPE(candidate->fid);\n                        if (type == OBJ_TYPE_CRITTER || type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) {\n                            return candidate;\n                        }\n                    }\n                }\n            }\n            objectListItem = objectListItem->next;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x48BA20\nObject* obj_ai_blocking_at(Object* a1, int tile, int elevation)\n{\n    if (!hexGridTileIsValid(tile)) {\n        return NULL;\n    }\n\n    ObjectListNode* objectListNode = objectTable[tile];\n    while (objectListNode != NULL) {\n        Object* object = objectListNode->obj;\n        if (object->elevation == elevation) {\n            if ((object->flags & OBJECT_HIDDEN) == 0\n                && (object->flags & OBJECT_NO_BLOCK) == 0\n                && object != a1) {\n                int objectType = FID_TYPE(object->fid);\n                if (objectType == OBJ_TYPE_CRITTER\n                    || objectType == OBJ_TYPE_SCENERY\n                    || objectType == OBJ_TYPE_WALL) {\n                    if (moveBlockObj != NULL || objectType != OBJ_TYPE_CRITTER) {\n                        return object;\n                    }\n\n                    moveBlockObj = object;\n                }\n            }\n        }\n        objectListNode = objectListNode->next;\n    }\n\n    for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n        int candidate = tile_num_in_direction(tile, rotation, 1);\n        if (!hexGridTileIsValid(candidate)) {\n            continue;\n        }\n\n        objectListNode = objectTable[candidate];\n        while (objectListNode != NULL) {\n            Object* object = objectListNode->obj;\n            if ((object->flags & OBJECT_MULTIHEX) != 0) {\n                if (object->elevation == elevation) {\n                    if ((object->flags & OBJECT_HIDDEN) == 0\n                        && (object->flags & OBJECT_NO_BLOCK) == 0\n                        && object != a1) {\n                        int objectType = FID_TYPE(object->fid);\n                        if (objectType == OBJ_TYPE_CRITTER\n                            || objectType == OBJ_TYPE_SCENERY\n                            || objectType == OBJ_TYPE_WALL) {\n                            if (moveBlockObj != NULL || objectType != OBJ_TYPE_CRITTER) {\n                                return object;\n                            }\n\n                            moveBlockObj = object;\n                        }\n                    }\n                }\n            }\n            objectListNode = objectListNode->next;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x48BB44\nint obj_scroll_blocking_at(int tile, int elev)\n{\n    // TODO: Might be an error - why tile 0 is excluded?\n    if (tile <= 0 || tile >= 40000) {\n        return -1;\n    }\n\n    ObjectListNode* objectListNode = objectTable[tile];\n    while (objectListNode != NULL) {\n        if (elev < objectListNode->obj->elevation) {\n            break;\n        }\n\n        if (objectListNode->obj->elevation == elev && objectListNode->obj->pid == 0x500000C) {\n            return 0;\n        }\n\n        objectListNode = objectListNode->next;\n    }\n\n    return -1;\n}\n\n// 0x48BB88\nObject* obj_sight_blocking_at(Object* a1, int tile, int elevation)\n{\n    ObjectListNode* objectListNode = objectTable[tile];\n    while (objectListNode != NULL) {\n        Object* object = objectListNode->obj;\n        if (object->elevation == elevation\n            && (object->flags & OBJECT_HIDDEN) == 0\n            && (object->flags & OBJECT_LIGHT_THRU) == 0\n            && object != a1) {\n            int objectType = FID_TYPE(object->fid);\n            if (objectType == OBJ_TYPE_SCENERY || objectType == OBJ_TYPE_WALL) {\n                return object;\n            }\n        }\n        objectListNode = objectListNode->next;\n    }\n\n    return NULL;\n}\n\n// 0x48BBD4\nint obj_dist(Object* object1, Object* object2)\n{\n    if (object1 == NULL || object2 == NULL) {\n        return 0;\n    }\n\n    int distance = tile_dist(object1->tile, object2->tile);\n\n    if ((object1->flags & OBJECT_MULTIHEX) != 0) {\n        distance -= 1;\n    }\n\n    if ((object2->flags & OBJECT_MULTIHEX) != 0) {\n        distance -= 1;\n    }\n\n    if (distance < 0) {\n        distance = 0;\n    }\n\n    return distance;\n}\n\n// 0x48BC08\nint obj_dist_with_tile(Object* object1, int tile1, Object* object2, int tile2)\n{\n    if (object1 == NULL || object2 == NULL) {\n        return 0;\n    }\n\n    int distance = tile_dist(tile1, tile2);\n\n    if ((object1->flags & OBJECT_MULTIHEX) != 0) {\n        distance -= 1;\n    }\n\n    if ((object2->flags & OBJECT_MULTIHEX) != 0) {\n        distance -= 1;\n    }\n\n    if (distance < 0) {\n        distance = 0;\n    }\n\n    return distance;\n}\n\n// 0x48BC38\nint obj_create_list(int tile, int elevation, int objectType, Object*** objectListPtr)\n{\n    if (objectListPtr == NULL) {\n        return -1;\n    }\n\n    int count = 0;\n    if (tile == -1) {\n        for (int index = 0; index < HEX_GRID_SIZE; index++) {\n            ObjectListNode* objectListNode = objectTable[index];\n            while (objectListNode != NULL) {\n                Object* obj = objectListNode->obj;\n                if ((obj->flags & OBJECT_HIDDEN) == 0\n                    && obj->elevation == elevation\n                    && FID_TYPE(obj->fid) == objectType) {\n                    count++;\n                }\n                objectListNode = objectListNode->next;\n            }\n        }\n    } else {\n        ObjectListNode* objectListNode = objectTable[tile];\n        while (objectListNode != NULL) {\n            Object* obj = objectListNode->obj;\n            if ((obj->flags & OBJECT_HIDDEN) == 0\n                && obj->elevation == elevation\n                && FID_TYPE(objectListNode->obj->fid) == objectType) {\n                count++;\n            }\n            objectListNode = objectListNode->next;\n        }\n    }\n\n    if (count == 0) {\n        return 0;\n    }\n\n    Object** objects = *objectListPtr = (Object**)mem_malloc(sizeof(*objects) * count);\n    if (objects == NULL) {\n        return -1;\n    }\n\n    if (tile == -1) {\n        for (int index = 0; index < HEX_GRID_SIZE; index++) {\n            ObjectListNode* objectListNode = objectTable[index];\n            while (objectListNode) {\n                Object* obj = objectListNode->obj;\n                if ((obj->flags & OBJECT_HIDDEN) == 0\n                    && obj->elevation == elevation\n                    && FID_TYPE(obj->fid) == objectType) {\n                    *objects++ = obj;\n                }\n                objectListNode = objectListNode->next;\n            }\n        }\n    } else {\n        ObjectListNode* objectListNode = objectTable[tile];\n        while (objectListNode != NULL) {\n            Object* obj = objectListNode->obj;\n            if ((obj->flags & OBJECT_HIDDEN) == 0\n                && obj->elevation == elevation\n                && FID_TYPE(obj->fid) == objectType) {\n                *objects++ = obj;\n            }\n            objectListNode = objectListNode->next;\n        }\n    }\n\n    return count;\n}\n\n// 0x48BDCC\nvoid obj_delete_list(Object** objectList)\n{\n    if (objectList != NULL) {\n        mem_free(objectList);\n    }\n}\n\n// 0x48BDD8\nvoid translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, unsigned char* a9, unsigned char* a10)\n{\n    dest += destPitch * destY + destX;\n    int srcStep = srcPitch - srcWidth;\n    int destStep = destPitch - srcWidth;\n\n    for (int y = 0; y < srcHeight; y++) {\n        for (int x = 0; x < srcWidth; x++) {\n            // TODO: Probably wrong.\n            unsigned char v1 = a10[*src];\n            unsigned char* v2 = a9 + (v1 << 8);\n            unsigned char v3 = *dest;\n\n            *dest = v2[v3];\n\n            src++;\n            dest++;\n        }\n\n        src += srcStep;\n        dest += destStep;\n    }\n}\n\n// 0x48BEFC\nvoid dark_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, int light)\n{\n    unsigned char* sp = src;\n    unsigned char* dp = dest + destPitch * destY + destX;\n\n    int srcStep = srcPitch - srcWidth;\n    int destStep = destPitch - srcWidth;\n    // TODO: Name might be confusing.\n    int lightModifier = light >> 9;\n\n    for (int y = 0; y < srcHeight; y++) {\n        for (int x = 0; x < srcWidth; x++) {\n            unsigned char b = *sp;\n            if (b != 0) {\n                if (b < 0xE5) {\n                    b = intensityColorTable[b][lightModifier];\n                }\n\n                *dp = b;\n            }\n\n            sp++;\n            dp++;\n        }\n\n        sp += srcStep;\n        dp += destStep;\n    }\n}\n\n// 0x48BF88\nvoid dark_translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, int light, unsigned char* a10, unsigned char* a11)\n{\n    int srcStep = srcPitch - srcWidth;\n    int destStep = destPitch - srcWidth;\n    int lightModifier = light >> 9;\n\n    dest += destPitch * destY + destX;\n\n    for (int y = 0; y < srcHeight; y++) {\n        for (int x = 0; x < srcWidth; x++) {\n            unsigned char srcByte = *src;\n            if (srcByte != 0) {\n                unsigned char destByte = *dest;\n                unsigned int index = a11[srcByte] << 8;\n                index = a10[index + destByte];\n                *dest = intensityColorTable[index][lightModifier];\n            }\n\n            src++;\n            dest++;\n        }\n\n        src += srcStep;\n        dest += destStep;\n    }\n}\n\n// 0x48C03C\nvoid intensity_mask_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destPitch, unsigned char* mask, int maskPitch, int light)\n{\n    int srcStep = srcPitch - srcWidth;\n    int destStep = destPitch - srcWidth;\n    int maskStep = maskPitch - srcWidth;\n    light >>= 9;\n\n    for (int y = 0; y < srcHeight; y++) {\n        for (int x = 0; x < srcWidth; x++) {\n            unsigned char b = *src;\n            if (b != 0) {\n                b = intensityColorTable[b][light];\n                unsigned char m = *mask;\n                if (m != 0) {\n                    unsigned char d = *dest;\n                    int q = intensityColorTable[d][128 - m];\n                    m = intensityColorTable[b][m];\n                    b = colorMixAddTable[m][q];\n                }\n                *dest = b;\n            }\n\n            src++;\n            dest++;\n            mask++;\n        }\n\n        src += srcStep;\n        dest += destStep;\n        mask += maskStep;\n    }\n}\n\n// 0x48C2B4\nint obj_outline_object(Object* obj, int outlineType, Rect* rect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if ((obj->outline & OUTLINE_TYPE_MASK) != 0) {\n        return -1;\n    }\n\n    if ((obj->flags & OBJECT_NO_HIGHLIGHT) != 0) {\n        return -1;\n    }\n\n    obj->outline = outlineType;\n\n    if ((obj->flags & OBJECT_HIDDEN) != 0) {\n        obj->outline |= OUTLINE_DISABLED;\n    }\n\n    if (rect != NULL) {\n        obj_bound(obj, rect);\n    }\n\n    return 0;\n}\n\n// 0x48C2F0\nint obj_remove_outline(Object* object, Rect* rect)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    if (rect != NULL) {\n        obj_bound(object, rect);\n    }\n\n    object->outline = 0;\n\n    return 0;\n}\n\n// 0x48C340\nint obj_intersects_with(Object* object, int x, int y)\n{\n    int flags = 0;\n\n    if (object == obj_egg || (object->flags & OBJECT_HIDDEN) == 0) {\n        CacheEntry* handle;\n        Art* art = art_ptr_lock(object->fid, &handle);\n        if (art != NULL) {\n            int width;\n            int height;\n            art_frame_width_length(art, object->frame, object->rotation, &width, &height);\n\n            int minX;\n            int minY;\n            int maxX;\n            int maxY;\n            if (object->tile == -1) {\n                minX = object->sx;\n                minY = object->sy;\n                maxX = minX + width - 1;\n                maxY = minY + height - 1;\n            } else {\n                int tileScreenX;\n                int tileScreenY;\n                tile_coord(object->tile, &tileScreenX, &tileScreenY, object->elevation);\n                tileScreenX += 16;\n                tileScreenY += 8;\n\n                tileScreenX += art->xOffsets[object->rotation];\n                tileScreenY += art->yOffsets[object->rotation];\n\n                tileScreenX += object->x;\n                tileScreenY += object->y;\n\n                minX = tileScreenX - width / 2;\n                maxX = minX + width - 1;\n\n                minY = tileScreenY - height + 1;\n                maxY = tileScreenY;\n            }\n\n            if (x >= minX && x <= maxX && y >= minY && y <= maxY) {\n                unsigned char* data = art_frame_data(art, object->frame, object->rotation);\n                if (data != NULL) {\n                    if (data[width * (y - minY) + x - minX] != 0) {\n                        flags |= 0x01;\n\n                        if ((object->flags & OBJECT_FLAG_0xFC000) != 0) {\n                            if ((object->flags & OBJECT_TRANS_NONE) == 0) {\n                                flags &= ~0x03;\n                                flags |= 0x02;\n                            }\n                        } else {\n                            int type = FID_TYPE(object->fid);\n                            if (type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) {\n                                Proto* proto;\n                                proto_ptr(object->pid, &proto);\n\n                                bool v20;\n                                int extendedFlags = proto->scenery.extendedFlags;\n                                if ((extendedFlags & 0x8000000) != 0 || (extendedFlags & 0x80000000) != 0) {\n                                    v20 = tile_in_front_of(object->tile, obj_dude->tile);\n                                } else if ((extendedFlags & 0x10000000) != 0) {\n                                    // NOTE: Original code uses bitwise or, but given the fact that these functions return\n                                    // bools, logical or is more suitable.\n                                    v20 = tile_in_front_of(object->tile, obj_dude->tile) || tile_to_right_of(obj_dude->tile, object->tile);\n                                } else if ((extendedFlags & 0x20000000) != 0) {\n                                    v20 = tile_in_front_of(object->tile, obj_dude->tile) && tile_to_right_of(obj_dude->tile, object->tile);\n                                } else {\n                                    v20 = tile_to_right_of(obj_dude->tile, object->tile);\n                                }\n\n                                if (v20) {\n                                    if (obj_intersects_with(obj_egg, x, y) != 0) {\n                                        flags |= 0x04;\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            art_ptr_unlock(handle);\n        }\n    }\n\n    return flags;\n}\n\n// 0x48C5C4\nint obj_create_intersect_list(int x, int y, int elevation, int objectType, ObjectWithFlags** entriesPtr)\n{\n    int v5 = tile_num(x - 320, y - 240, elevation);\n    *entriesPtr = NULL;\n\n    if (updateHexArea <= 0) {\n        return 0;\n    }\n\n    int count = 0;\n\n    int parity = tile_center_tile & 1;\n    for (int index = 0; index < updateHexArea; index++) {\n        int v7 = orderTable[parity][index];\n        if (offsetDivTable[v7] < 30 && offsetModTable[v7] < 20) {\n            ObjectListNode* objectListNode = objectTable[offsetTable[parity][v7] + v5];\n            while (objectListNode != NULL) {\n                Object* object = objectListNode->obj;\n                if (object->elevation > elevation) {\n                    break;\n                }\n\n                if (object->elevation == elevation\n                    && (objectType == -1 || FID_TYPE(object->fid) == objectType)\n                    && object != obj_egg) {\n                    int flags = obj_intersects_with(object, x, y);\n                    if (flags != 0) {\n                        ObjectWithFlags* entries = (ObjectWithFlags*)mem_realloc(*entriesPtr, sizeof(*entries) * (count + 1));\n                        if (entries != NULL) {\n                            *entriesPtr = entries;\n                            entries[count].object = object;\n                            entries[count].flags = flags;\n                            count++;\n                        }\n                    }\n                }\n\n                objectListNode = objectListNode->next;\n            }\n        }\n    }\n\n    return count;\n}\n\n// 0x48C74C\nvoid obj_delete_intersect_list(ObjectWithFlags** entriesPtr)\n{\n    if (entriesPtr != NULL && *entriesPtr != NULL) {\n        mem_free(*entriesPtr);\n        *entriesPtr = NULL;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x48C76C\nvoid obj_set_seen(int tile)\n{\n    obj_seen[tile >> 3] |= 1 << (tile & 7);\n}\n\n// 0x48C788\nvoid obj_clear_seen()\n{\n    memset(obj_seen, 0, sizeof(obj_seen));\n}\n\n// 0x48C7A0\nvoid obj_process_seen()\n{\n    int i;\n    int v7;\n    int v8;\n    int v5;\n    int v0;\n    int v3;\n    ObjectListNode* obj_entry;\n\n    memset(obj_seen_check, 0, 5001);\n\n    v0 = 400;\n    for (i = 0; i < 5001; i++) {\n        if (obj_seen[i] != 0) {\n            for (v3 = i - 400; v3 != v0; v3 += 25) {\n                if (v3 >= 0 && v3 < 5001) {\n                    obj_seen_check[v3] = -1;\n                    if (v3 > 0) {\n                        obj_seen_check[v3 - 1] = -1;\n                    }\n                    if (v3 < 5000) {\n                        obj_seen_check[v3 + 1] = -1;\n                    }\n                    if (v3 > 1) {\n                        obj_seen_check[v3 - 2] = -1;\n                    }\n                    if (v3 < 4999) {\n                        obj_seen_check[v3 + 2] = -1;\n                    }\n                }\n            }\n        }\n        v0++;\n    }\n\n    v7 = 0;\n    for (i = 0; i < 5001; i++) {\n        if (obj_seen_check[i] != 0) {\n            v8 = 1;\n            for (v5 = v7; v5 < v7 + 8; v5++) {\n                if (v8 & obj_seen_check[i]) {\n                    if (v5 < 40000) {\n                        for (obj_entry = objectTable[v5]; obj_entry != NULL; obj_entry = obj_entry->next) {\n                            if (obj_entry->obj->elevation == obj_dude->elevation) {\n                                obj_entry->obj->flags |= OBJECT_SEEN;\n                            }\n                        }\n                    }\n                }\n                v8 *= 2;\n            }\n        }\n        v7 += 8;\n    }\n\n    memset(obj_seen, 0, 5001);\n}\n\n// 0x48C8E4\nchar* object_name(Object* obj)\n{\n    int objectType = FID_TYPE(obj->fid);\n    switch (objectType) {\n    case OBJ_TYPE_ITEM:\n        return item_name(obj);\n    case OBJ_TYPE_CRITTER:\n        return critter_name(obj);\n    default:\n        return proto_name(obj->pid);\n    }\n}\n\n// 0x48C914\nchar* object_description(Object* obj)\n{\n    if (FID_TYPE(obj->fid) == OBJ_TYPE_ITEM) {\n        return item_description(obj);\n    }\n\n    return proto_description(obj->pid);\n}\n\n// Warm objects cache?\n//\n// 0x48C938\nvoid obj_preload_art_cache(int flags)\n{\n    if (preload_list == NULL) {\n        return;\n    }\n\n    unsigned char arr[4096];\n    memset(arr, 0, sizeof(arr));\n\n    if ((flags & 0x02) == 0) {\n        for (int i = 0; i < SQUARE_GRID_SIZE; i++) {\n            int v3 = square[0]->field_0[i];\n            arr[v3 & 0xFFF] = 1;\n            arr[(v3 >> 16) & 0xFFF] = 1;\n        }\n    }\n\n    if ((flags & 0x04) == 0) {\n        for (int i = 0; i < SQUARE_GRID_SIZE; i++) {\n            int v3 = square[1]->field_0[i];\n            arr[v3 & 0xFFF] = 1;\n            arr[(v3 >> 16) & 0xFFF] = 1;\n        }\n    }\n\n    if ((flags & 0x08) == 0) {\n        for (int i = 0; i < SQUARE_GRID_SIZE; i++) {\n            int v3 = square[2]->field_0[i];\n            arr[v3 & 0xFFF] = 1;\n            arr[(v3 >> 16) & 0xFFF] = 1;\n        }\n    }\n\n    qsort(preload_list, preload_list_index, sizeof(*preload_list), obj_preload_sort);\n\n    int v11 = preload_list_index;\n    int v12 = preload_list_index;\n\n    if (FID_TYPE(preload_list[v12 - 1]) == OBJ_TYPE_WALL) {\n        int objectType = OBJ_TYPE_ITEM;\n        do {\n            v11--;\n            objectType = FID_TYPE(preload_list[v12 - 1]);\n            v12--;\n        } while (objectType == OBJ_TYPE_WALL);\n        v11++;\n    }\n\n    CacheEntry* cache_handle;\n    if (art_ptr_lock(*preload_list, &cache_handle) != NULL) {\n        art_ptr_unlock(cache_handle);\n    }\n\n    for (int i = 1; i < v11; i++) {\n        if (preload_list[i - 1] != preload_list[i]) {\n            if (art_ptr_lock(preload_list[i], &cache_handle) != NULL) {\n                art_ptr_unlock(cache_handle);\n            }\n        }\n    }\n\n    for (int i = 0; i < 4096; i++) {\n        if (arr[i] != 0) {\n            int fid = art_id(OBJ_TYPE_TILE, i, 0, 0, 0);\n            if (art_ptr_lock(fid, &cache_handle) != NULL) {\n                art_ptr_unlock(cache_handle);\n            }\n        }\n    }\n\n    for (int i = v11; i < preload_list_index; i++) {\n        if (preload_list[i - 1] != preload_list[i]) {\n            if (art_ptr_lock(preload_list[i], &cache_handle) != NULL) {\n                art_ptr_unlock(cache_handle);\n            }\n        }\n    }\n\n    mem_free(preload_list);\n    preload_list = NULL;\n\n    preload_list_index = 0;\n}\n\n// 0x48CB88\nstatic int obj_offset_table_init()\n{\n    int i;\n\n    if (offsetTable[0] != NULL) {\n        return -1;\n    }\n\n    if (offsetTable[1] != NULL) {\n        return -1;\n    }\n\n    offsetTable[0] = (int*)mem_malloc(sizeof(int) * updateHexArea);\n    if (offsetTable[0] == NULL) {\n        goto err;\n    }\n\n    offsetTable[1] = (int*)mem_malloc(sizeof(int) * updateHexArea);\n    if (offsetTable[1] == NULL) {\n        goto err;\n    }\n\n    for (int parity = 0; parity < 2; parity++) {\n        int originTile = tile_num(updateAreaPixelBounds.ulx, updateAreaPixelBounds.uly, 0);\n        if (originTile != -1) {\n            int* offsets = offsetTable[tile_center_tile & 1];\n            int originTileX;\n            int originTileY;\n            tile_coord(originTile, &originTileX, &originTileY, 0);\n\n            int parityShift = 16;\n            originTileX += 16;\n            originTileY += 8;\n            if (originTileX > updateAreaPixelBounds.ulx) {\n                parityShift = -parityShift;\n            }\n\n            int tileX = originTileX;\n            for (int y = 0; y < updateHexHeight; y++) {\n                for (int x = 0; x < updateHexWidth; x++) {\n                    int tile = tile_num(tileX, originTileY, 0);\n                    if (tile == -1) {\n                        goto err;\n                    }\n\n                    tileX += 32;\n                    *offsets++ = tile - originTile;\n                }\n\n                tileX = parityShift + originTileX;\n                originTileY += 12;\n                parityShift = -parityShift;\n            }\n        }\n\n        if (tile_set_center(tile_center_tile + 1, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) == -1) {\n            goto err;\n        }\n    }\n\n    offsetDivTable = (int*)mem_malloc(sizeof(int) * updateHexArea);\n    if (offsetDivTable == NULL) {\n        goto err;\n    }\n\n    for (i = 0; i < updateHexArea; i++) {\n        offsetDivTable[i] = i / updateHexWidth;\n    }\n\n    offsetModTable = (int*)mem_malloc(sizeof(int) * updateHexArea);\n    if (offsetModTable == NULL) {\n        goto err;\n    }\n\n    for (i = 0; i < updateHexArea; i++) {\n        offsetModTable[i] = i % updateHexWidth;\n    }\n\n    return 0;\n\nerr:\n    obj_offset_table_exit();\n\n    return -1;\n}\n\n// 0x48CDA0\nstatic void obj_offset_table_exit()\n{\n    if (offsetModTable != NULL) {\n        mem_free(offsetModTable);\n        offsetModTable = NULL;\n    }\n\n    if (offsetDivTable != NULL) {\n        mem_free(offsetDivTable);\n        offsetDivTable = NULL;\n    }\n\n    if (offsetTable[1] != NULL) {\n        mem_free(offsetTable[1]);\n        offsetTable[1] = NULL;\n    }\n\n    if (offsetTable[0] != NULL) {\n        mem_free(offsetTable[0]);\n        offsetTable[0] = NULL;\n    }\n}\n\n// 0x48CE10\nstatic int obj_order_table_init()\n{\n    if (orderTable[0] != NULL || orderTable[1] != NULL) {\n        return -1;\n    }\n\n    orderTable[0] = (int*)mem_malloc(sizeof(int) * updateHexArea);\n    if (orderTable[0] == NULL) {\n        goto err;\n    }\n\n    orderTable[1] = (int*)mem_malloc(sizeof(int) * updateHexArea);\n    if (orderTable[1] == NULL) {\n        goto err;\n    }\n\n    for (int index = 0; index < updateHexArea; index++) {\n        orderTable[0][index] = index;\n        orderTable[1][index] = index;\n    }\n\n    qsort(orderTable[0], updateHexArea, sizeof(int), obj_order_comp_func_even);\n    qsort(orderTable[1], updateHexArea, sizeof(int), obj_order_comp_func_odd);\n\n    return 0;\n\nerr:\n\n    // NOTE: Uninline.\n    obj_order_table_exit();\n\n    return -1;\n}\n\n// 0x48CF20\nstatic int obj_order_comp_func_even(const void* a1, const void* a2)\n{\n    int v1 = *(int*)a1;\n    int v2 = *(int*)a2;\n    return offsetTable[0][v1] - offsetTable[0][v2];\n}\n\n// 0x48CF38\nstatic int obj_order_comp_func_odd(const void* a1, const void* a2)\n{\n    int v1 = *(int*)a1;\n    int v2 = *(int*)a2;\n    return offsetTable[1][v1] - offsetTable[1][v2];\n}\n\n// NOTE: Inlined.\n//\n// 0x48CF50\nstatic void obj_order_table_exit()\n{\n    if (orderTable[1] != NULL) {\n        mem_free(orderTable[1]);\n        orderTable[1] = NULL;\n    }\n\n    if (orderTable[0] != NULL) {\n        mem_free(orderTable[0]);\n        orderTable[0] = NULL;\n    }\n}\n\n// 0x48CF8C\nstatic int obj_render_table_init()\n{\n    if (renderTable != NULL) {\n        return -1;\n    }\n\n    renderTable = (ObjectListNode**)mem_malloc(sizeof(*renderTable) * updateHexArea);\n    if (renderTable == NULL) {\n        return -1;\n    }\n\n    for (int index = 0; index < updateHexArea; index++) {\n        renderTable[index] = NULL;\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x48D000\nstatic void obj_render_table_exit()\n{\n    if (renderTable != NULL) {\n        mem_free(renderTable);\n        renderTable = NULL;\n    }\n}\n\n// 0x48D020\nstatic void obj_light_table_init()\n{\n    for (int s = 0; s < 2; s++) {\n        int v4 = tile_center_tile + s;\n        for (int i = 0; i < ROTATION_COUNT; i++) {\n            int v15 = 8;\n            int* p = light_offsets[v4 & 1][i];\n            for (int j = 0; j < 8; j++) {\n                int tile = tile_num_in_direction(v4, (i + 1) % ROTATION_COUNT, j);\n\n                for (int m = 0; m < v15; m++) {\n                    *p++ = tile_num_in_direction(tile, i, m + 1) - v4;\n                }\n\n                v15--;\n            }\n        }\n    }\n}\n\n// 0x48D1E4\nstatic void obj_blend_table_init()\n{\n    for (int index = 0; index < 256; index++) {\n        int r = (Color2RGB(index) & 0x7C00) >> 10;\n        int g = (Color2RGB(index) & 0x3E0) >> 5;\n        int b = Color2RGB(index) & 0x1F;\n        glassGrayTable[index] = ((r + 5 * g + 4 * b) / 10) >> 2;\n        commonGrayTable[index] = ((b + 3 * r + 6 * g) / 10) >> 2;\n    }\n\n    glassGrayTable[0] = 0;\n    commonGrayTable[0] = 0;\n\n    wallBlendTable = getColorBlendTable(colorTable[25439]);\n    glassBlendTable = getColorBlendTable(colorTable[10239]);\n    steamBlendTable = getColorBlendTable(colorTable[32767]);\n    energyBlendTable = getColorBlendTable(colorTable[30689]);\n    redBlendTable = getColorBlendTable(colorTable[31744]);\n}\n\n// NOTE: Inlined.\n//\n// 0x48D2E8\nstatic void obj_blend_table_exit()\n{\n    freeColorBlendTable(colorTable[25439]);\n    freeColorBlendTable(colorTable[10239]);\n    freeColorBlendTable(colorTable[32767]);\n    freeColorBlendTable(colorTable[30689]);\n    freeColorBlendTable(colorTable[31744]);\n}\n\n// 0x48D348\nint obj_save_obj(File* stream, Object* object)\n{\n    if ((object->flags & OBJECT_TEMPORARY) != 0) {\n        return 0;\n    }\n\n    CritterCombatData* combatData = NULL;\n    Object* whoHitMe = NULL;\n    if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n        combatData = &(object->data.critter.combat);\n        whoHitMe = combatData->whoHitMe;\n        if (whoHitMe != 0) {\n            if (combatData->whoHitMeCid != -1) {\n                combatData->whoHitMeCid = whoHitMe->cid;\n            }\n        } else {\n            combatData->whoHitMeCid = -1;\n        }\n    }\n\n    if (obj_write_obj(object, stream) == -1) {\n        return -1;\n    }\n\n    if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n        combatData->whoHitMe = whoHitMe;\n    }\n\n    Inventory* inventory = &(object->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n\n        if (db_fwriteInt(stream, inventoryItem->quantity) == -1) {\n            return -1;\n        }\n\n        if (obj_save_obj(stream, inventoryItem->item) == -1) {\n            return -1;\n        }\n\n        if ((inventoryItem->item->flags & OBJECT_TEMPORARY) != 0) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x48D414\nint obj_load_obj(File* stream, Object** objectPtr, int elevation, Object* owner)\n{\n    Object* obj;\n\n    if (obj_create_object(&obj) == -1) {\n        *objectPtr = NULL;\n        return -1;\n    }\n\n    if (obj_read_obj(obj, stream) != 0) {\n        *objectPtr = NULL;\n        return -1;\n    }\n\n    if (obj->sid != -1) {\n        Script* script;\n        if (scr_ptr(obj->sid, &script) == -1) {\n            obj->sid = -1;\n        } else {\n            script->owner = obj;\n        }\n    }\n\n    obj_fix_violence_settings(&(obj->fid));\n\n    if (!art_fid_valid(obj->fid)) {\n        debug_printf(\"\\nError: invalid object art fid: %u\\n\", obj->fid);\n        // NOTE: Uninline.\n        obj_destroy_object(&obj);\n        return -2;\n    }\n\n    if (elevation == -1) {\n        elevation = obj->elevation;\n    } else {\n        obj->elevation = elevation;\n    }\n\n    obj->owner = owner;\n\n    Inventory* inventory = &(obj->data.inventory);\n    if (inventory->length <= 0) {\n        inventory->capacity = 0;\n        inventory->items = NULL;\n        *objectPtr = obj;\n        return 0;\n    }\n\n    InventoryItem* inventoryItems = inventory->items = (InventoryItem*)mem_malloc(sizeof(*inventoryItems) * inventory->capacity);\n    if (inventoryItems == NULL) {\n        return -1;\n    }\n\n    for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) {\n        InventoryItem* inventoryItem = &(inventoryItems[inventoryItemIndex]);\n        if (db_freadInt(stream, &(inventoryItem->quantity)) != 0) {\n            return -1;\n        }\n\n        if (obj_load_obj(stream, &(inventoryItem->item), elevation, obj) != 0) {\n            return -1;\n        }\n    }\n\n    *objectPtr = obj;\n\n    return 0;\n}\n\n// obj_save_dude\n// 0x48D59C\nint obj_save_dude(File* stream)\n{\n    int field_78 = obj_dude->sid;\n\n    obj_dude->flags &= ~OBJECT_TEMPORARY;\n    obj_dude->sid = -1;\n\n    int rc = obj_save_obj(stream, obj_dude);\n\n    obj_dude->sid = field_78;\n    obj_dude->flags |= OBJECT_TEMPORARY;\n\n    if (db_fwriteInt(stream, tile_center_tile) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    return rc;\n}\n\n// obj_load_dude\n// 0x48D600\nint obj_load_dude(File* stream)\n{\n    int savedTile = obj_dude->tile;\n    int savedElevation = obj_dude->elevation;\n    int savedRotation = obj_dude->rotation;\n    int savedOid = obj_dude->id;\n\n    scr_clear_dude_script();\n\n    Object* temp;\n    int rc = obj_load_obj(stream, &temp, -1, NULL);\n\n    memcpy(obj_dude, temp, sizeof(*obj_dude));\n\n    obj_dude->flags |= OBJECT_TEMPORARY;\n\n    scr_clear_dude_script();\n\n    obj_dude->id = savedOid;\n\n    scr_set_dude_script();\n\n    int newTile = obj_dude->tile;\n    obj_dude->tile = savedTile;\n\n    int newElevation = obj_dude->elevation;\n    obj_dude->elevation = savedElevation;\n\n    int newRotation = obj_dude->rotation;\n    obj_dude->rotation = newRotation;\n\n    scr_set_dude_script();\n\n    if (rc != -1) {\n        obj_move_to_tile(obj_dude, newTile, newElevation, NULL);\n        obj_set_rotation(obj_dude, newRotation, NULL);\n    }\n\n    // Set ownership of inventory items from temporary instance to dude.\n    Inventory* inventory = &(obj_dude->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        inventoryItem->item->owner = obj_dude;\n    }\n\n    obj_fix_combat_cid_for_dude();\n\n    // Dude has claimed ownership of items in temporary instance's inventory.\n    // We don't need object's dealloc routine to remove these items from the\n    // game, so simply nullify temporary inventory as if nothing was there.\n    Inventory* tempInventory = &(temp->data.inventory);\n    tempInventory->length = 0;\n    tempInventory->capacity = 0;\n    tempInventory->items = NULL;\n\n    temp->flags &= ~OBJECT_FLAG_0x400;\n\n    if (obj_erase_object(temp, NULL) == -1) {\n        debug_printf(\"\\nError: obj_load_dude: Can't destroy temp object!\\n\");\n    }\n\n    inven_reset_dude();\n\n    int tile;\n    if (db_freadInt(stream, &tile) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    tile_set_center(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS);\n\n    return rc;\n}\n\n// 0x48D778\nstatic int obj_create_object(Object** objectPtr)\n{\n    if (objectPtr == NULL) {\n        return -1;\n    }\n\n    Object* object = *objectPtr = (Object*)mem_malloc(sizeof(Object));\n    if (object == NULL) {\n        return -1;\n    }\n\n    memset(object, 0, sizeof(Object));\n\n    object->id = -1;\n    object->tile = -1;\n    object->cid = -1;\n    object->outline = 0;\n    object->pid = -1;\n    object->sid = -1;\n    object->owner = NULL;\n    object->field_80 = -1;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x48D7F8\nstatic void obj_destroy_object(Object** objectPtr)\n{\n    if (objectPtr == NULL) {\n        return;\n    }\n\n    if (*objectPtr == NULL) {\n        return;\n    }\n\n    mem_free(*objectPtr);\n\n    *objectPtr = NULL;\n}\n\n// NOTE: Inlined.\n//\n// 0x48D818\nstatic int obj_create_object_node(ObjectListNode** nodePtr)\n{\n    if (nodePtr == NULL) {\n        return -1;\n    }\n\n    ObjectListNode* node = *nodePtr = (ObjectListNode*)mem_malloc(sizeof(*node));\n    if (node == NULL) {\n        return -1;\n    }\n\n    node->obj = NULL;\n    node->next = NULL;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x48D84C\nstatic void obj_destroy_object_node(ObjectListNode** nodePtr)\n{\n    if (nodePtr == NULL) {\n        return;\n    }\n\n    if (*nodePtr == NULL) {\n        return;\n    }\n\n    mem_free(*nodePtr);\n\n    *nodePtr = NULL;\n}\n\n// 0x48D86C\nstatic int obj_node_ptr(Object* object, ObjectListNode** nodePtr, ObjectListNode** previousNodePtr)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    if (nodePtr == NULL) {\n        return -1;\n    }\n\n    int tile = object->tile;\n    if (tile != -1) {\n        *nodePtr = objectTable[tile];\n    } else {\n        *nodePtr = floatingObjects;\n    }\n\n    if (previousNodePtr != NULL) {\n        *previousNodePtr = NULL;\n        while (*nodePtr != NULL) {\n            if (object == (*nodePtr)->obj) {\n                break;\n            }\n\n            *previousNodePtr = *nodePtr;\n\n            *nodePtr = (*nodePtr)->next;\n        }\n    } else {\n        while (*nodePtr != NULL) {\n            if (object == (*nodePtr)->obj) {\n                break;\n            }\n\n            *nodePtr = (*nodePtr)->next;\n        }\n    }\n\n    if (*nodePtr != NULL) {\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x48D8E8\nstatic void obj_insert(ObjectListNode* objectListNode)\n{\n    ObjectListNode** objectListNodePtr;\n\n    if (objectListNode == NULL) {\n        return;\n    }\n\n    if (objectListNode->obj->tile == -1) {\n        objectListNodePtr = &floatingObjects;\n    } else {\n        Art* art = NULL;\n        CacheEntry* cacheHandle = NULL;\n\n        objectListNodePtr = &(objectTable[objectListNode->obj->tile]);\n\n        while (*objectListNodePtr != NULL) {\n            Object* obj = (*objectListNodePtr)->obj;\n            if (obj->elevation > objectListNode->obj->elevation) {\n                break;\n            }\n\n            if (obj->elevation == objectListNode->obj->elevation) {\n                if ((obj->flags & OBJECT_FLAT) == 0 && (objectListNode->obj->flags & OBJECT_FLAT) != 0) {\n                    break;\n                }\n\n                if ((obj->flags & OBJECT_FLAT) == (objectListNode->obj->flags & OBJECT_FLAT)) {\n                    bool v11 = false;\n                    CacheEntry* a2;\n                    Art* v12 = art_ptr_lock(obj->fid, &a2);\n                    if (v12 != NULL) {\n\n                        if (art == NULL) {\n                            art = art_ptr_lock(objectListNode->obj->fid, &cacheHandle);\n                        }\n\n                        // TODO: Incomplete.\n\n                        art_ptr_unlock(a2);\n\n                        if (v11) {\n                            break;\n                        }\n                    }\n                }\n            }\n\n            objectListNodePtr = &((*objectListNodePtr)->next);\n        }\n\n        if (art != NULL) {\n            art_ptr_unlock(cacheHandle);\n        }\n    }\n\n    objectListNode->next = *objectListNodePtr;\n    *objectListNodePtr = objectListNode;\n}\n\n// 0x48DA58\nstatic int obj_remove(ObjectListNode* a1, ObjectListNode* a2)\n{\n    if (a1->obj == NULL) {\n        return -1;\n    }\n\n    if ((a1->obj->flags & OBJECT_FLAG_0x400) != 0) {\n        return -1;\n    }\n\n    obj_inven_free(&(a1->obj->data.inventory));\n\n    if (a1->obj->sid != -1) {\n        exec_script_proc(a1->obj->sid, SCRIPT_PROC_DESTROY);\n        scr_remove(a1->obj->sid);\n    }\n\n    if (a1 != a2) {\n        if (a2 != NULL) {\n            a2->next = a1->next;\n        } else {\n            int tile = a1->obj->tile;\n            if (tile == -1) {\n                floatingObjects = floatingObjects->next;\n            } else {\n                objectTable[tile] = objectTable[tile]->next;\n            }\n        }\n    }\n\n    // NOTE: Uninline.\n    obj_destroy_object(&(a1->obj));\n\n    // NOTE: Uninline.\n    obj_destroy_object_node(&a1);\n\n    return 0;\n}\n\n// 0x48DB28\nstatic int obj_connect_to_tile(ObjectListNode* node, int tile, int elevation, Rect* rect)\n{\n    if (node == NULL) {\n        return -1;\n    }\n\n    if (!hexGridTileIsValid(tile)) {\n        return -1;\n    }\n\n    if (!elevationIsValid(elevation)) {\n        return -1;\n    }\n\n    node->obj->tile = tile;\n    node->obj->elevation = elevation;\n    node->obj->x = 0;\n    node->obj->y = 0;\n    node->obj->owner = 0;\n\n    obj_insert(node);\n\n    if (obj_adjust_light(node->obj, 0, rect) == -1) {\n        if (rect != NULL) {\n            obj_bound(node->obj, rect);\n        }\n    }\n\n    return 0;\n}\n\n// 0x48DC28\nstatic int obj_adjust_light(Object* obj, int a2, Rect* rect)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (obj->lightIntensity <= 0) {\n        return -1;\n    }\n\n    if ((obj->flags & OBJECT_HIDDEN) != 0) {\n        return -1;\n    }\n\n    if ((obj->flags & OBJECT_LIGHTING) == 0) {\n        return -1;\n    }\n\n    if (!hexGridTileIsValid(obj->tile)) {\n        return -1;\n    }\n\n    AdjustLightIntensityProc* adjustLightIntensity = a2 ? light_subtract_from_tile : light_add_to_tile;\n    adjustLightIntensity(obj->elevation, obj->tile, obj->lightIntensity);\n\n    Rect objectRect;\n    obj_bound(obj, &objectRect);\n\n    if (obj->lightDistance > 8) {\n        obj->lightDistance = 8;\n    }\n\n    if (obj->lightIntensity > 65536) {\n        obj->lightIntensity = 65536;\n    }\n\n    int(*v70)[36] = light_offsets[obj->tile & 1];\n    int v7 = (obj->lightIntensity - 655) / (obj->lightDistance + 1);\n    int v28[36];\n    v28[0] = obj->lightIntensity - v7;\n    v28[1] = v28[0] - v7;\n    v28[8] = v28[0] - v7;\n    v28[2] = v28[0] - v7 - v7;\n    v28[9] = v28[2];\n    v28[15] = v28[0] - v7 - v7;\n    v28[3] = v28[2] - v7;\n    v28[10] = v28[2] - v7;\n    v28[16] = v28[2] - v7;\n    v28[21] = v28[2] - v7;\n    v28[4] = v28[2] - v7 - v7;\n    v28[11] = v28[4];\n    v28[17] = v28[2] - v7 - v7;\n    v28[22] = v28[2] - v7 - v7;\n    v28[26] = v28[2] - v7 - v7;\n    v28[5] = v28[4] - v7;\n    v28[12] = v28[4] - v7;\n    v28[18] = v28[4] - v7;\n    v28[23] = v28[4] - v7;\n    v28[27] = v28[4] - v7;\n    v28[30] = v28[4] - v7;\n    v28[6] = v28[4] - v7 - v7;\n    v28[13] = v28[6];\n    v28[19] = v28[4] - v7 - v7;\n    v28[24] = v28[4] - v7 - v7;\n    v28[28] = v28[4] - v7 - v7;\n    v28[31] = v28[4] - v7 - v7;\n    v28[33] = v28[4] - v7 - v7;\n    v28[7] = v28[6] - v7;\n    v28[14] = v28[6] - v7;\n    v28[20] = v28[6] - v7;\n    v28[25] = v28[6] - v7;\n    v28[29] = v28[6] - v7;\n    v28[32] = v28[6] - v7;\n    v28[34] = v28[6] - v7;\n    v28[35] = v28[6] - v7;\n\n    for (int index = 0; index < 36; index++) {\n        if (obj->lightDistance >= light_distance[index]) {\n            for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n                int v14;\n                int nextRotation = (rotation + 1) % ROTATION_COUNT;\n                int eax;\n                int edx;\n                int ebx;\n                int esi;\n                int edi;\n                switch (index) {\n                case 0:\n                    v14 = 0;\n                    break;\n                case 1:\n                    v14 = light_blocked[rotation][0];\n                    break;\n                case 2:\n                    v14 = light_blocked[rotation][1];\n                    break;\n                case 3:\n                    v14 = light_blocked[rotation][2];\n                    break;\n                case 4:\n                    v14 = light_blocked[rotation][3];\n                    break;\n                case 5:\n                    v14 = light_blocked[rotation][4];\n                    break;\n                case 6:\n                    v14 = light_blocked[rotation][5];\n                    break;\n                case 7:\n                    v14 = light_blocked[rotation][6];\n                    break;\n                case 8:\n                    v14 = light_blocked[rotation][0] & light_blocked[nextRotation][0];\n                    break;\n                case 9:\n                    v14 = light_blocked[rotation][1] & light_blocked[rotation][8];\n                    break;\n                case 10:\n                    v14 = light_blocked[rotation][2] & light_blocked[rotation][9];\n                    break;\n                case 11:\n                    v14 = light_blocked[rotation][3] & light_blocked[rotation][10];\n                    break;\n                case 12:\n                    v14 = light_blocked[rotation][4] & light_blocked[rotation][11];\n                    break;\n                case 13:\n                    v14 = light_blocked[rotation][5] & light_blocked[rotation][12];\n                    break;\n                case 14:\n                    v14 = light_blocked[rotation][6] & light_blocked[rotation][13];\n                    break;\n                case 15:\n                    v14 = light_blocked[rotation][8] & light_blocked[nextRotation][1];\n                    break;\n                case 16:\n                    v14 = light_blocked[rotation][8] | (light_blocked[rotation][9] & light_blocked[rotation][15]);\n                    break;\n                case 17:\n                    edx = light_blocked[rotation][9];\n                    edx |= light_blocked[rotation][10];\n                    ebx = light_blocked[rotation][8];\n                    esi = light_blocked[rotation][16];\n                    ebx &= edx;\n                    edx &= esi;\n                    edi = light_blocked[rotation][15];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][10];\n                    eax = light_blocked[rotation][9];\n                    edx |= edi;\n                    eax &= edx;\n                    v14 = ebx | eax;\n                    break;\n                case 18:\n                    edx = light_blocked[rotation][0];\n                    ebx = light_blocked[rotation][9];\n                    esi = light_blocked[rotation][10];\n                    edx |= ebx;\n                    edi = light_blocked[rotation][11];\n                    edx |= esi;\n                    ebx = light_blocked[rotation][17];\n                    edx |= edi;\n                    ebx &= edx;\n                    edx = esi;\n                    esi = light_blocked[rotation][16];\n                    edi = light_blocked[rotation][9];\n                    edx &= esi;\n                    edx |= edi;\n                    edx |= ebx;\n                    v14 = edx;\n                    break;\n                case 19:\n                    edx = light_blocked[rotation][17];\n                    edi = light_blocked[rotation][18];\n                    ebx = light_blocked[rotation][11];\n                    edx |= edi;\n                    esi = light_blocked[rotation][10];\n                    ebx &= edx;\n                    edx = light_blocked[rotation][9];\n                    edx |= esi;\n                    ebx |= edx;\n                    edx = light_blocked[rotation][12];\n                    edx &= edi;\n                    ebx |= edx;\n                    v14 = ebx;\n                    break;\n                case 20:\n                    edx = light_blocked[rotation][2];\n                    esi = light_blocked[rotation][11];\n                    edi = light_blocked[rotation][12];\n                    ebx = light_blocked[rotation][8];\n                    edx |= esi;\n                    esi = light_blocked[rotation][9];\n                    edx |= edi;\n                    edi = light_blocked[rotation][10];\n                    ebx &= edx;\n                    edx &= esi;\n                    esi = light_blocked[rotation][17];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][16];\n                    ebx |= edi;\n                    edi = light_blocked[rotation][18];\n                    edx |= esi;\n                    esi = light_blocked[rotation][19];\n                    edx |= edi;\n                    eax = light_blocked[rotation][11];\n                    edx |= esi;\n                    eax &= edx;\n                    ebx |= eax;\n                    v14 = ebx;\n                    break;\n                case 21:\n                    v14 = (light_blocked[rotation][8] & light_blocked[nextRotation][1])\n                        | (light_blocked[rotation][15] & light_blocked[nextRotation][2]);\n                    break;\n                case 22:\n                    edx = light_blocked[nextRotation][1];\n                    ebx = light_blocked[rotation][15];\n                    esi = light_blocked[rotation][21];\n                    edx |= ebx;\n                    ebx = light_blocked[rotation][8];\n                    edx |= esi;\n                    ebx &= edx;\n                    edx = light_blocked[rotation][9];\n                    edi = esi;\n                    edx |= esi;\n                    esi = light_blocked[rotation][15];\n                    edx &= esi;\n                    ebx |= edx;\n                    edx = esi;\n                    esi = light_blocked[rotation][16];\n                    edx |= edi;\n                    edx &= esi;\n                    ebx |= edx;\n                    v14 = ebx;\n                    break;\n                case 23:\n                    edx = light_blocked[rotation][3];\n                    ebx = light_blocked[rotation][16];\n                    esi = light_blocked[rotation][15];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][9];\n                    edx &= esi;\n                    edi = light_blocked[rotation][22];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][17];\n                    edx &= edi;\n                    ebx |= edx;\n                    v14 = ebx;\n                    break;\n                case 24:\n                    edx = light_blocked[rotation][0];\n                    edi = light_blocked[rotation][9];\n                    ebx = light_blocked[rotation][10];\n                    edx |= edi;\n                    esi = light_blocked[rotation][17];\n                    edx |= ebx;\n                    edi = light_blocked[rotation][18];\n                    edx |= esi;\n                    ebx = light_blocked[rotation][16];\n                    edx |= edi;\n                    esi = light_blocked[rotation][16];\n                    ebx &= edx;\n                    edx = light_blocked[rotation][15];\n                    edi = light_blocked[rotation][23];\n                    edx |= esi;\n                    esi = light_blocked[rotation][9];\n                    edx |= edi;\n                    edi = light_blocked[rotation][8];\n                    edx &= esi;\n                    edx |= edi;\n                    esi = light_blocked[rotation][22];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][15];\n                    edi = light_blocked[rotation][23];\n                    edx |= esi;\n                    esi = light_blocked[rotation][17];\n                    edx |= edi;\n                    edx &= esi;\n                    ebx |= edx;\n                    edx = light_blocked[rotation][18];\n                    edx &= edi;\n                    ebx |= edx;\n                    v14 = ebx;\n                    break;\n                case 25:\n                    edx = light_blocked[rotation][8];\n                    edi = light_blocked[rotation][15];\n                    ebx = light_blocked[rotation][16];\n                    edx |= edi;\n                    esi = light_blocked[rotation][23];\n                    edx |= ebx;\n                    edi = light_blocked[rotation][24];\n                    edx |= esi;\n                    ebx = light_blocked[rotation][9];\n                    edx |= edi;\n                    esi = light_blocked[rotation][1];\n                    ebx &= edx;\n                    edx = light_blocked[rotation][8];\n                    edx &= esi;\n                    edi = light_blocked[rotation][16];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][8];\n                    esi = light_blocked[rotation][17];\n                    edx |= edi;\n                    edi = light_blocked[rotation][24];\n                    esi |= edx;\n                    esi |= edi;\n                    esi &= light_blocked[rotation][10];\n                    edi = light_blocked[rotation][23];\n                    ebx |= esi;\n                    esi = light_blocked[rotation][17];\n                    edx |= edi;\n                    ebx |= esi;\n                    esi = light_blocked[rotation][24];\n                    edi = light_blocked[rotation][18];\n                    edx |= esi;\n                    edx &= edi;\n                    esi = light_blocked[rotation][19];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][0];\n                    eax = light_blocked[rotation][24];\n                    edx |= esi;\n                    eax &= edx;\n                    ebx |= eax;\n                    v14 = ebx;\n                    break;\n                case 26:\n                    ebx = light_blocked[rotation][8];\n                    esi = light_blocked[nextRotation][1];\n                    edi = light_blocked[nextRotation][2];\n                    esi &= ebx;\n                    ebx = light_blocked[rotation][15];\n                    ebx &= edi;\n                    eax = light_blocked[rotation][21];\n                    ebx |= esi;\n                    eax &= light_blocked[nextRotation][3];\n                    ebx |= eax;\n                    v14 = ebx;\n                    break;\n                case 27:\n                    edx = light_blocked[nextRotation][0];\n                    edi = light_blocked[rotation][15];\n                    esi = light_blocked[rotation][21];\n                    edx |= edi;\n                    edi = light_blocked[rotation][26];\n                    edx |= esi;\n                    esi = light_blocked[rotation][22];\n                    edx |= edi;\n                    edi = light_blocked[nextRotation][1];\n                    esi &= edx;\n                    edx = light_blocked[rotation][8];\n                    ebx = light_blocked[rotation][15];\n                    edx &= edi;\n                    edx |= ebx;\n                    edi = light_blocked[rotation][16];\n                    esi |= edx;\n                    edx = light_blocked[rotation][8];\n                    eax = light_blocked[rotation][21];\n                    edx |= edi;\n                    eax &= edx;\n                    esi |= eax;\n                    v14 = esi;\n                    break;\n                case 28:\n                    ebx = light_blocked[rotation][9];\n                    edi = light_blocked[rotation][16];\n                    esi = light_blocked[rotation][23];\n                    edx = light_blocked[nextRotation][0];\n                    ebx |= edi;\n                    edi = light_blocked[rotation][15];\n                    ebx |= esi;\n                    esi = light_blocked[rotation][8];\n                    ebx &= edi;\n                    edi = light_blocked[rotation][21];\n                    ebx |= esi;\n                    esi = light_blocked[rotation][22];\n                    edx |= edi;\n                    edi = light_blocked[rotation][27];\n                    edx |= esi;\n                    esi = light_blocked[rotation][16];\n                    edx |= edi;\n                    edx &= esi;\n                    edi = light_blocked[rotation][17];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][9];\n                    esi = light_blocked[rotation][23];\n                    edx |= edi;\n                    edi = light_blocked[rotation][22];\n                    edx |= esi;\n                    edx &= edi;\n                    ebx |= edx;\n                    edx = esi;\n                    edx &= light_blocked[rotation][27];\n                    ebx |= edx;\n                    v14 = ebx;\n                    break;\n                case 29:\n                    edx = light_blocked[rotation][8];\n                    edi = light_blocked[rotation][16];\n                    ebx = light_blocked[rotation][23];\n                    edx |= edi;\n                    esi = light_blocked[rotation][15];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][9];\n                    edx &= esi;\n                    edi = light_blocked[rotation][22];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][17];\n                    edx &= edi;\n                    esi = light_blocked[rotation][28];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][24];\n                    edx &= esi;\n                    ebx |= edx;\n                    v14 = ebx;\n                    break;\n                case 30:\n                    ebx = light_blocked[rotation][8];\n                    esi = light_blocked[nextRotation][1];\n                    edi = light_blocked[nextRotation][2];\n                    esi &= ebx;\n                    ebx = light_blocked[rotation][15];\n                    ebx &= edi;\n                    edi = light_blocked[nextRotation][3];\n                    esi |= ebx;\n                    ebx = light_blocked[rotation][21];\n                    ebx &= edi;\n                    eax = light_blocked[rotation][26];\n                    ebx |= esi;\n                    eax &= light_blocked[nextRotation][4];\n                    ebx |= eax;\n                    v14 = ebx;\n                    break;\n                case 31:\n                    edx = light_blocked[rotation][8];\n                    esi = light_blocked[nextRotation][1];\n                    edi = light_blocked[rotation][15];\n                    edx &= esi;\n                    ebx = light_blocked[rotation][21];\n                    edx |= edi;\n                    esi = light_blocked[rotation][22];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][8];\n                    edi = light_blocked[rotation][27];\n                    edx |= esi;\n                    esi = light_blocked[rotation][26];\n                    edx |= edi;\n                    edx &= esi;\n                    ebx |= edx;\n                    edx = edi;\n                    edx &= light_blocked[rotation][30];\n                    ebx |= edx;\n                    v14 = ebx;\n                    break;\n                case 32:\n                    ebx = light_blocked[rotation][8];\n                    edi = light_blocked[rotation][9];\n                    esi = light_blocked[rotation][16];\n                    ebx |= edi;\n                    edi = light_blocked[rotation][23];\n                    ebx |= esi;\n                    esi = light_blocked[rotation][28];\n                    ebx |= edi;\n                    ebx |= esi;\n                    esi = light_blocked[rotation][15];\n                    esi &= ebx;\n                    edx = light_blocked[rotation][8];\n                    edx &= light_blocked[nextRotation][1];\n                    ebx = light_blocked[rotation][16];\n                    esi |= edx;\n                    edx = light_blocked[rotation][8];\n                    edx |= ebx;\n                    ebx = light_blocked[rotation][28];\n                    edi = light_blocked[rotation][21];\n                    ebx |= edx;\n                    ebx &= edi;\n                    edi = light_blocked[rotation][23];\n                    ebx |= esi;\n                    esi = light_blocked[rotation][22];\n                    edx |= edi;\n                    ebx |= esi;\n                    esi = light_blocked[rotation][28];\n                    edi = light_blocked[rotation][27];\n                    edx |= esi;\n                    edx &= edi;\n                    esi = light_blocked[rotation][31];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][0];\n                    edi = light_blocked[rotation][28];\n                    edx |= esi;\n                    edx &= edi;\n                    ebx |= edx;\n                    v14 = ebx;\n                    break;\n                case 33:\n                    esi = light_blocked[rotation][8];\n                    edi = light_blocked[nextRotation][1];\n                    ebx = light_blocked[rotation][15];\n                    esi &= edi;\n                    ebx &= light_blocked[nextRotation][2];\n                    edi = light_blocked[nextRotation][3];\n                    esi |= ebx;\n                    ebx = light_blocked[rotation][21];\n                    ebx &= edi;\n                    edi = light_blocked[nextRotation][4];\n                    esi |= ebx;\n                    ebx = light_blocked[rotation][26];\n                    ebx &= edi;\n                    eax = light_blocked[rotation][30];\n                    ebx |= esi;\n                    eax &= light_blocked[nextRotation][5];\n                    ebx |= eax;\n                    v14 = ebx;\n                    break;\n                case 34:\n                    edx = light_blocked[nextRotation][2];\n                    edi = light_blocked[rotation][26];\n                    ebx = light_blocked[rotation][30];\n                    edx |= edi;\n                    esi = light_blocked[rotation][15];\n                    edx |= ebx;\n                    ebx = light_blocked[rotation][8];\n                    edi = light_blocked[rotation][21];\n                    ebx &= edx;\n                    edx &= esi;\n                    esi = light_blocked[rotation][22];\n                    ebx |= edx;\n                    edx = light_blocked[rotation][16];\n                    ebx |= edi;\n                    edi = light_blocked[rotation][27];\n                    edx |= esi;\n                    esi = light_blocked[rotation][31];\n                    edx |= edi;\n                    eax = light_blocked[rotation][26];\n                    edx |= esi;\n                    eax &= edx;\n                    ebx |= eax;\n                    v14 = ebx;\n                    break;\n                case 35:\n                    ebx = light_blocked[rotation][8];\n                    esi = light_blocked[nextRotation][1];\n                    edi = light_blocked[nextRotation][2];\n                    esi &= ebx;\n                    ebx = light_blocked[rotation][15];\n                    ebx &= edi;\n                    edi = light_blocked[nextRotation][3];\n                    esi |= ebx;\n                    ebx = light_blocked[rotation][21];\n                    ebx &= edi;\n                    edi = light_blocked[nextRotation][4];\n                    esi |= ebx;\n                    ebx = light_blocked[rotation][26];\n                    ebx &= edi;\n                    edi = light_blocked[nextRotation][5];\n                    esi |= ebx;\n                    ebx = light_blocked[rotation][30];\n                    ebx &= edi;\n                    eax = light_blocked[rotation][33];\n                    ebx |= esi;\n                    eax &= light_blocked[nextRotation][6];\n                    ebx |= eax;\n                    v14 = ebx;\n                    break;\n                default:\n                    assert(false && \"Should be unreachable\");\n                }\n\n                if (v14 == 0) {\n                    // TODO: Check.\n                    int tile = obj->tile + v70[rotation][index];\n                    if (hexGridTileIsValid(tile)) {\n                        bool v12 = true;\n\n                        ObjectListNode* objectListNode = objectTable[tile];\n                        while (objectListNode != NULL) {\n                            if ((objectListNode->obj->flags & OBJECT_HIDDEN) == 0) {\n                                if (objectListNode->obj->elevation > obj->elevation) {\n                                    break;\n                                }\n\n                                if (objectListNode->obj->elevation == obj->elevation) {\n                                    Rect v29;\n                                    obj_bound(objectListNode->obj, &v29);\n                                    rect_min_bound(&objectRect, &v29, &objectRect);\n\n                                    v14 = (objectListNode->obj->flags & OBJECT_LIGHT_THRU) == 0;\n\n                                    if (FID_TYPE(objectListNode->obj->fid) == OBJ_TYPE_WALL) {\n                                        if ((objectListNode->obj->flags & OBJECT_FLAT) == 0) {\n                                            Proto* proto;\n                                            proto_ptr(objectListNode->obj->pid, &proto);\n                                            if ((proto->wall.extendedFlags & 0x8000000) != 0 || (proto->wall.extendedFlags & 0x40000000) != 0) {\n                                                if (rotation != ROTATION_W\n                                                    && rotation != ROTATION_NW\n                                                    && (rotation != ROTATION_NE || index >= 8)\n                                                    && (rotation != ROTATION_SW || index <= 15)) {\n                                                    v12 = false;\n                                                }\n                                            } else if ((proto->wall.extendedFlags & 0x10000000) != 0) {\n                                                if (rotation != ROTATION_NE && rotation != ROTATION_NW) {\n                                                    v12 = false;\n                                                }\n                                            } else if ((proto->wall.extendedFlags & 0x20000000) != 0) {\n                                                if (rotation != ROTATION_NE\n                                                    && rotation != ROTATION_E\n                                                    && rotation != ROTATION_W\n                                                    && rotation != ROTATION_NW\n                                                    && (rotation != ROTATION_SW || index <= 15)) {\n                                                    v12 = false;\n                                                }\n                                            } else {\n                                                if (rotation != ROTATION_NE\n                                                    && rotation != ROTATION_E\n                                                    && (rotation != ROTATION_NW || index <= 7)) {\n                                                    v12 = false;\n                                                }\n                                            }\n                                        }\n                                    } else {\n                                        if (v14 && rotation >= ROTATION_E && rotation <= ROTATION_SW) {\n                                            v12 = false;\n                                        }\n                                    }\n\n                                    if (v14) {\n                                        break;\n                                    }\n                                }\n                            }\n                            objectListNode = objectListNode->next;\n                        }\n\n                        if (v12) {\n                            adjustLightIntensity(obj->elevation, tile, v28[index]);\n                        }\n                    }\n                }\n\n                light_blocked[rotation][index] = v14;\n            }\n        }\n    }\n\n    if (rect != NULL) {\n        Rect* lightDistanceRect = &(light_rect[obj->lightDistance]);\n        memcpy(rect, lightDistanceRect, sizeof(*lightDistanceRect));\n\n        int x;\n        int y;\n        tile_coord(obj->tile, &x, &y, obj->elevation);\n        x += 16;\n        y += 8;\n\n        x -= rect->lrx / 2;\n        y -= rect->lry / 2;\n\n        rectOffset(rect, x, y);\n        rect_min_bound(rect, &objectRect, rect);\n    }\n\n    return 0;\n}\n\n// 0x48EABC\nstatic void obj_render_outline(Object* object, Rect* rect)\n{\n    CacheEntry* cacheEntry;\n    Art* art = art_ptr_lock(object->fid, &cacheEntry);\n    if (art == NULL) {\n        return;\n    }\n\n    int frameWidth = 0;\n    int frameHeight = 0;\n    art_frame_width_length(art, object->frame, object->rotation, &frameWidth, &frameHeight);\n\n    Rect v49;\n    v49.ulx = 0;\n    v49.uly = 0;\n    v49.lrx = frameWidth - 1;\n\n    // FIXME: I'm not sure why it ignores frameHeight and makes separate call\n    // to obtain height.\n    int v8 = art_frame_length(art, object->frame, object->rotation);\n    v49.lry = v8 - 1;\n\n    Rect objectRect;\n    if (object->tile == -1) {\n        objectRect.ulx = object->sx;\n        objectRect.uly = object->sy;\n        objectRect.lrx = object->sx + frameWidth - 1;\n        objectRect.lry = object->sy + frameHeight - 1;\n    } else {\n        int x;\n        int y;\n        tile_coord(object->tile, &x, &y, object->elevation);\n        x += 16;\n        y += 8;\n\n        x += art->xOffsets[object->rotation];\n        y += art->yOffsets[object->rotation];\n\n        x += object->x;\n        y += object->y;\n\n        objectRect.ulx = x - frameWidth / 2;\n        objectRect.uly = y - (frameHeight - 1);\n        objectRect.lrx = objectRect.ulx + frameWidth - 1;\n        objectRect.lry = y;\n\n        object->sx = objectRect.ulx;\n        object->sy = objectRect.uly;\n    }\n\n    Rect v32;\n    rectCopy(&v32, rect);\n\n    v32.ulx--;\n    v32.uly--;\n    v32.lrx++;\n    v32.lry++;\n\n    rect_inside_bound(&v32, &buf_rect, &v32);\n\n    if (rect_inside_bound(&objectRect, &v32, &objectRect) == 0) {\n        v49.ulx += objectRect.ulx - object->sx;\n        v49.uly += objectRect.uly - object->sy;\n        v49.lrx = v49.ulx + (objectRect.lrx - objectRect.ulx);\n        v49.lry = v49.uly + (objectRect.lry - objectRect.uly);\n\n        unsigned char* src = art_frame_data(art, object->frame, object->rotation);\n\n        unsigned char* dest = back_buf + buf_full * object->sy + object->sx;\n        int destStep = buf_full - frameWidth;\n\n        unsigned char color;\n        unsigned char* v47 = NULL;\n        unsigned char* v48 = NULL;\n        int v53 = object->outline & OUTLINE_PALETTED;\n        int outlineType = object->outline & OUTLINE_TYPE_MASK;\n        int v43;\n        int v44;\n\n        switch (outlineType) {\n        case OUTLINE_TYPE_HOSTILE:\n            color = 243;\n            v53 = 0;\n            v43 = 5;\n            v44 = frameHeight / 5;\n            break;\n        case OUTLINE_TYPE_2:\n            color = colorTable[31744];\n            v44 = 0;\n            if (v53 != 0) {\n                v47 = commonGrayTable;\n                v48 = redBlendTable;\n            }\n            break;\n        case OUTLINE_TYPE_4:\n            color = colorTable[15855];\n            v44 = 0;\n            if (v53 != 0) {\n                v47 = commonGrayTable;\n                v48 = wallBlendTable;\n            }\n            break;\n        case OUTLINE_TYPE_FRIENDLY:\n            v43 = 4;\n            v44 = frameHeight / 4;\n            color = 229;\n            v53 = 0;\n            break;\n        case OUTLINE_TYPE_ITEM:\n            v44 = 0;\n            color = colorTable[30632];\n            if (v53 != 0) {\n                v47 = commonGrayTable;\n                v48 = redBlendTable;\n            }\n            break;\n        case OUTLINE_TYPE_32:\n            color = 61;\n            v53 = 0;\n            v43 = 1;\n            v44 = frameHeight;\n            break;\n        default:\n            color = colorTable[31775];\n            v53 = 0;\n            v44 = 0;\n            break;\n        }\n\n        unsigned char v54 = color;\n        unsigned char* dest14 = dest;\n        unsigned char* src15 = src;\n        for (int y = 0; y < frameHeight; y++) {\n            bool cycle = true;\n            if (v44 != 0) {\n                if (y % v44 == 0) {\n                    v54++;\n                }\n\n                if (v54 > v43 + color - 1) {\n                    v54 = color;\n                }\n            }\n\n            int v22 = dest14 - back_buf;\n            for (int x = 0; x < frameWidth; x++) {\n                v22 = dest14 - back_buf;\n                if (*src15 != 0 && cycle) {\n                    if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry && v22 > 0 && v22 % buf_full != 0) {\n                        unsigned char v20;\n                        if (v53 != 0) {\n                            v20 = v48[(v47[v54] << 8) + *(dest14 - 1)];\n                        } else {\n                            v20 = v54;\n                        }\n                        *(dest14 - 1) = v20;\n                    }\n                    cycle = false;\n                } else if (*src15 == 0 && !cycle) {\n                    if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry) {\n                        int v21;\n                        if (v53 != 0) {\n                            v21 = v48[(v47[v54] << 8) + *dest14];\n                        } else {\n                            v21 = v54;\n                        }\n                        *dest14 = v21 & 0xFF;\n                    }\n                    cycle = true;\n                }\n                dest14++;\n                src15++;\n            }\n\n            if (*(src15 - 1) != 0) {\n                if (v22 < buf_size) {\n                    int v23 = frameWidth - 1;\n                    if (v23 >= v49.ulx && v23 <= v49.lrx && y >= v49.uly && y <= v49.lry) {\n                        if (v53 != 0) {\n                            *dest14 = v48[(v47[v54] << 8) + *dest14];\n                        } else {\n                            *dest14 = v54;\n                        }\n                    }\n                }\n            }\n\n            dest14 += destStep;\n        }\n\n        for (int x = 0; x < frameWidth; x++) {\n            bool cycle = true;\n            unsigned char v28 = color;\n            unsigned char* dest27 = dest + x;\n            unsigned char* src27 = src + x;\n            for (int y = 0; y < frameHeight; y++) {\n                if (v44 != 0) {\n                    if (y % v44 == 0) {\n                        v28++;\n                    }\n\n                    if (v28 > color + v43 - 1) {\n                        v28 = color;\n                    }\n                }\n\n                if (*src27 != 0 && cycle) {\n                    if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry) {\n                        unsigned char* v29 = dest27 - buf_full;\n                        if (v29 >= back_buf) {\n                            if (v53) {\n                                *v29 = v48[(v47[v28] << 8) + *v29];\n                            } else {\n                                *v29 = v28;\n                            }\n                        }\n                    }\n                    cycle = false;\n                } else if (*src27 == 0 && !cycle) {\n                    if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry) {\n                        if (v53) {\n                            *dest27 = v48[(v47[v28] << 8) + *dest27];\n                        } else {\n                            *dest27 = v28;\n                        }\n                    }\n                    cycle = true;\n                }\n\n                dest27 += buf_full;\n                src27 += frameWidth;\n            }\n\n            if (src27[-frameWidth] != 0) {\n                if (dest27 - back_buf < buf_size) {\n                    int y = frameHeight - 1;\n                    if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry) {\n                        if (v53) {\n                            *dest27 = v48[(v47[v28] << 8) + *dest27];\n                        } else {\n                            *dest27 = v28;\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    art_ptr_unlock(cacheEntry);\n}\n\n// 0x48F1B0\nstatic void obj_render_object(Object* object, Rect* rect, int light)\n{\n    int type = FID_TYPE(object->fid);\n    if (art_get_disable(type)) {\n        return;\n    }\n\n    CacheEntry* cacheEntry;\n    Art* art = art_ptr_lock(object->fid, &cacheEntry);\n    if (art == NULL) {\n        return;\n    }\n\n    int frameWidth = art_frame_width(art, object->frame, object->rotation);\n    int frameHeight = art_frame_length(art, object->frame, object->rotation);\n\n    Rect objectRect;\n    if (object->tile == -1) {\n        objectRect.ulx = object->sx;\n        objectRect.uly = object->sy;\n        objectRect.lrx = object->sx + frameWidth - 1;\n        objectRect.lry = object->sy + frameHeight - 1;\n    } else {\n        int objectScreenX;\n        int objectScreenY;\n        tile_coord(object->tile, &objectScreenX, &objectScreenY, object->elevation);\n        objectScreenX += 16;\n        objectScreenY += 8;\n\n        objectScreenX += art->xOffsets[object->rotation];\n        objectScreenY += art->yOffsets[object->rotation];\n\n        objectScreenX += object->x;\n        objectScreenY += object->y;\n\n        objectRect.ulx = objectScreenX - frameWidth / 2;\n        objectRect.uly = objectScreenY - (frameHeight - 1);\n        objectRect.lrx = objectRect.ulx + frameWidth - 1;\n        objectRect.lry = objectScreenY;\n\n        object->sx = objectRect.ulx;\n        object->sy = objectRect.uly;\n    }\n\n    if (rect_inside_bound(&objectRect, rect, &objectRect) != 0) {\n        art_ptr_unlock(cacheEntry);\n        return;\n    }\n\n    unsigned char* src = art_frame_data(art, object->frame, object->rotation);\n    unsigned char* src2 = src;\n    int v50 = objectRect.ulx - object->sx;\n    int v49 = objectRect.uly - object->sy;\n    src += frameWidth * v49 + v50;\n    int objectWidth = objectRect.lrx - objectRect.ulx + 1;\n    int objectHeight = objectRect.lry - objectRect.uly + 1;\n\n    if (type == 6) {\n        trans_buf_to_buf(src,\n            objectWidth,\n            objectHeight,\n            frameWidth,\n            back_buf + buf_full * objectRect.uly + objectRect.ulx,\n            buf_full);\n        art_ptr_unlock(cacheEntry);\n        return;\n    }\n\n    if (type == 2 || type == 3) {\n        if ((obj_dude->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_FLAG_0xFC000) == 0) {\n            Proto* proto;\n            proto_ptr(object->pid, &proto);\n\n            bool v17;\n            int extendedFlags = proto->critter.extendedFlags;\n            if ((extendedFlags & 0x8000000) != 0 || (extendedFlags & 0x80000000) != 0) {\n                // TODO: Probably wrong.\n                v17 = tile_in_front_of(object->tile, obj_dude->tile);\n                if (!v17\n                    || !tile_to_right_of(object->tile, obj_dude->tile)\n                    || (object->flags & OBJECT_WALL_TRANS_END) == 0) {\n                    // nothing\n                } else {\n                    v17 = false;\n                }\n            } else if ((extendedFlags & 0x10000000) != 0) {\n                // NOTE: Uses bitwise OR, so both functions are evaluated.\n                v17 = tile_in_front_of(object->tile, obj_dude->tile)\n                    || tile_to_right_of(obj_dude->tile, object->tile);\n            } else if ((extendedFlags & 0x20000000) != 0) {\n                v17 = tile_in_front_of(object->tile, obj_dude->tile)\n                    && tile_to_right_of(obj_dude->tile, object->tile);\n            } else {\n                v17 = tile_to_right_of(obj_dude->tile, object->tile);\n                if (v17\n                    && tile_in_front_of(obj_dude->tile, object->tile)\n                    && (object->flags & OBJECT_WALL_TRANS_END) != 0) {\n                    v17 = 0;\n                }\n            }\n\n            if (v17) {\n                CacheEntry* eggHandle;\n                Art* egg = art_ptr_lock(obj_egg->fid, &eggHandle);\n                if (egg == NULL) {\n                    return;\n                }\n\n                int eggWidth;\n                int eggHeight;\n                art_frame_width_length(egg, 0, 0, &eggWidth, &eggHeight);\n\n                int eggScreenX;\n                int eggScreenY;\n                tile_coord(obj_egg->tile, &eggScreenX, &eggScreenY, obj_egg->elevation);\n                eggScreenX += 16;\n                eggScreenY += 8;\n\n                eggScreenX += egg->xOffsets[0];\n                eggScreenY += egg->yOffsets[0];\n\n                eggScreenX += obj_egg->x;\n                eggScreenY += obj_egg->y;\n\n                Rect eggRect;\n                eggRect.ulx = eggScreenX - eggWidth / 2;\n                eggRect.uly = eggScreenY - (eggHeight - 1);\n                eggRect.lrx = eggRect.ulx + eggWidth - 1;\n                eggRect.lry = eggScreenY;\n\n                obj_egg->sx = eggRect.ulx;\n                obj_egg->sy = eggRect.uly;\n\n                Rect updatedEggRect;\n                if (rect_inside_bound(&eggRect, &objectRect, &updatedEggRect) == 0) {\n                    Rect rects[4];\n\n                    rects[0].ulx = objectRect.ulx;\n                    rects[0].uly = objectRect.uly;\n                    rects[0].lrx = objectRect.lrx;\n                    rects[0].lry = updatedEggRect.uly - 1;\n\n                    rects[1].ulx = objectRect.ulx;\n                    rects[1].uly = updatedEggRect.uly;\n                    rects[1].lrx = updatedEggRect.ulx - 1;\n                    rects[1].lry = updatedEggRect.lry;\n\n                    rects[2].ulx = updatedEggRect.lrx + 1;\n                    rects[2].uly = updatedEggRect.uly;\n                    rects[2].lrx = objectRect.lrx;\n                    rects[2].lry = updatedEggRect.lry;\n\n                    rects[3].ulx = objectRect.ulx;\n                    rects[3].uly = updatedEggRect.lry + 1;\n                    rects[3].lrx = objectRect.lrx;\n                    rects[3].lry = objectRect.lry;\n\n                    for (int i = 0; i < 4; i++) {\n                        Rect* v21 = &(rects[i]);\n                        if (v21->ulx <= v21->lrx && v21->uly <= v21->lry) {\n                            unsigned char* sp = src + frameWidth * (v21->uly - objectRect.uly) + (v21->ulx - objectRect.ulx);\n                            dark_trans_buf_to_buf(sp, v21->lrx - v21->ulx + 1, v21->lry - v21->uly + 1, frameWidth, back_buf, v21->ulx, v21->uly, buf_full, light);\n                        }\n                    }\n\n                    unsigned char* mask = art_frame_data(egg, 0, 0);\n                    intensity_mask_buf_to_buf(\n                        src + frameWidth * (updatedEggRect.uly - objectRect.uly) + (updatedEggRect.ulx - objectRect.ulx),\n                        updatedEggRect.lrx - updatedEggRect.ulx + 1,\n                        updatedEggRect.lry - updatedEggRect.uly + 1,\n                        frameWidth,\n                        back_buf + buf_full * updatedEggRect.uly + updatedEggRect.ulx,\n                        buf_full,\n                        mask + eggWidth * (updatedEggRect.uly - eggRect.uly) + (updatedEggRect.ulx - eggRect.ulx),\n                        eggWidth,\n                        light);\n                    art_ptr_unlock(eggHandle);\n                    art_ptr_unlock(cacheEntry);\n                    return;\n                }\n\n                art_ptr_unlock(eggHandle);\n            }\n        }\n    }\n\n    switch (object->flags & OBJECT_FLAG_0xFC000) {\n    case OBJECT_TRANS_RED:\n        dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light, redBlendTable, commonGrayTable);\n        break;\n    case OBJECT_TRANS_WALL:\n        dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, 0x10000, wallBlendTable, commonGrayTable);\n        break;\n    case OBJECT_TRANS_GLASS:\n        dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light, glassBlendTable, glassGrayTable);\n        break;\n    case OBJECT_TRANS_STEAM:\n        dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light, steamBlendTable, commonGrayTable);\n        break;\n    case OBJECT_TRANS_ENERGY:\n        dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light, energyBlendTable, commonGrayTable);\n        break;\n    default:\n        dark_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light);\n        break;\n    }\n\n    art_ptr_unlock(cacheEntry);\n}\n\n// Updates fid according to current violence level.\n//\n// 0x48FA14\nvoid obj_fix_violence_settings(int* fid)\n{\n    if (FID_TYPE(*fid) != OBJ_TYPE_CRITTER) {\n        return;\n    }\n\n    bool shouldResetViolenceLevel = false;\n    if (fix_violence_level == -1) {\n        if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &fix_violence_level)) {\n            fix_violence_level = VIOLENCE_LEVEL_MAXIMUM_BLOOD;\n        }\n        shouldResetViolenceLevel = true;\n    }\n\n    int start;\n    int end;\n\n    switch (fix_violence_level) {\n    case VIOLENCE_LEVEL_NONE:\n        start = ANIM_BIG_HOLE_SF;\n        end = ANIM_FALL_FRONT_BLOOD_SF;\n        break;\n    case VIOLENCE_LEVEL_MINIMAL:\n        start = ANIM_BIG_HOLE_SF;\n        end = ANIM_FIRE_DANCE_SF;\n        break;\n    case VIOLENCE_LEVEL_NORMAL:\n        start = ANIM_BIG_HOLE_SF;\n        end = ANIM_SLICED_IN_HALF_SF;\n        break;\n    default:\n        // Do not replace anything.\n        start = ANIM_COUNT + 1;\n        end = ANIM_COUNT + 1;\n        break;\n    }\n\n    int anim = FID_ANIM_TYPE(*fid);\n    if (anim >= start && anim <= end) {\n        anim = (anim == ANIM_FALL_BACK_BLOOD_SF)\n            ? ANIM_FALL_BACK_SF\n            : ANIM_FALL_FRONT_SF;\n        *fid = art_id(OBJ_TYPE_CRITTER, *fid & 0xFFF, anim, (*fid & 0xF000) >> 12, (*fid & 0x70000000) >> 28);\n    }\n\n    if (shouldResetViolenceLevel) {\n        fix_violence_level = -1;\n    }\n}\n\n// 0x48FB08\nstatic int obj_preload_sort(const void* a1, const void* a2)\n{\n    // 0x51979C\n    static int cd_order[9] = {\n        1,\n        0,\n        3,\n        5,\n        4,\n        2,\n        0,\n        0,\n        0,\n    };\n\n    int v1 = *(int*)a1;\n    int v2 = *(int*)a2;\n\n    int v3 = cd_order[FID_TYPE(v1)];\n    int v4 = cd_order[FID_TYPE(v2)];\n\n    int cmp = v3 - v4;\n    if (cmp != 0) {\n        return cmp;\n    }\n\n    cmp = (v1 & 0xFFF) - (v2 & 0xFFF);\n    if (cmp != 0) {\n        return cmp;\n    }\n\n    cmp = ((v1 & 0xF000) >> 12) - (((v2 & 0xF000) >> 12));\n    if (cmp != 0) {\n        return cmp;\n    }\n\n    cmp = ((v1 & 0xFF0000) >> 16) - (((v2 & 0xFF0000) >> 16));\n    return cmp;\n}\n"
  },
  {
    "path": "src/game/object.h",
    "content": "#ifndef FALLOUT_GAME_OBJECT_H_\n#define FALLOUT_GAME_OBJECT_H_\n\n#include \"plib/db/db.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/inventry.h\"\n#include \"game/map_defs.h\"\n#include \"game/object_types.h\"\n\ntypedef struct ObjectWithFlags {\n    int flags;\n    Object* object;\n} ObjectWithFlags;\n\nextern unsigned char* wallBlendTable;\nextern unsigned char* glassBlendTable;\nextern unsigned char* steamBlendTable;\nextern unsigned char* energyBlendTable;\nextern unsigned char* redBlendTable;\nextern Object* moveBlockObj;\n\nextern unsigned char glassGrayTable[256];\nextern unsigned char commonGrayTable[256];\nextern Object* obj_egg;\nextern Object* obj_dude;\n\nint obj_init(unsigned char* buf, int width, int height, int pitch);\nvoid obj_reset();\nvoid obj_exit();\nint obj_load(File* stream);\nint obj_save(File* stream);\nvoid obj_render_pre_roof(Rect* rect, int elevation);\nvoid obj_render_post_roof(Rect* rect, int elevation);\nint obj_new(Object** objectPtr, int fid, int pid);\nint obj_pid_new(Object** objectPtr, int pid);\nint obj_copy(Object** a1, Object* a2);\nint obj_connect(Object* obj, int tile_index, int elev, Rect* rect);\nint obj_disconnect(Object* obj, Rect* rect);\nint obj_offset(Object* obj, int x, int y, Rect* rect);\nint obj_move(Object* a1, int a2, int a3, int elevation, Rect* a5);\nint obj_move_to_tile(Object* obj, int tile, int elevation, Rect* rect);\nint obj_reset_roof();\nint obj_change_fid(Object* obj, int fid, Rect* rect);\nint obj_set_frame(Object* obj, int frame, Rect* rect);\nint obj_inc_frame(Object* obj, Rect* rect);\nint obj_dec_frame(Object* obj, Rect* rect);\nint obj_set_rotation(Object* obj, int direction, Rect* rect);\nint obj_inc_rotation(Object* obj, Rect* rect);\nint obj_dec_rotation(Object* obj, Rect* rect);\nvoid obj_rebuild_all_light();\nint obj_set_light(Object* obj, int lightDistance, int lightIntensity, Rect* rect);\nint obj_get_visible_light(Object* obj);\nint obj_turn_on_light(Object* obj, Rect* rect);\nint obj_turn_off_light(Object* obj, Rect* rect);\nint obj_turn_on(Object* obj, Rect* rect);\nint obj_turn_off(Object* obj, Rect* rect);\nint obj_turn_on_outline(Object* obj, Rect* rect);\nint obj_turn_off_outline(Object* obj, Rect* rect);\nint obj_toggle_flat(Object* obj, Rect* rect);\nint obj_erase_object(Object* a1, Rect* a2);\nint obj_inven_free(Inventory* inventory);\nbool obj_action_can_use(Object* obj);\nbool obj_action_can_talk_to(Object* obj);\nbool obj_portal_is_walk_thru(Object* obj);\nObject* objFindObjPtrFromID(int a1);\nObject* obj_top_environment(Object* obj);\nvoid obj_remove_all();\nObject* obj_find_first();\nObject* obj_find_next();\nObject* obj_find_first_at(int elevation);\nObject* obj_find_next_at();\nObject* obj_find_first_at_tile(int elevation, int tile);\nObject* obj_find_next_at_tile();\nvoid obj_bound(Object* obj, Rect* rect);\nbool obj_occupied(int tile_num, int elev);\nObject* obj_blocking_at(Object* a1, int tile_num, int elev);\nObject* obj_shoot_blocking_at(Object* obj, int tile, int elev);\nObject* obj_ai_blocking_at(Object* a1, int tile, int elevation);\nint obj_scroll_blocking_at(int tile_num, int elev);\nObject* obj_sight_blocking_at(Object* a1, int tile_num, int elev);\nint obj_dist(Object* object1, Object* object2);\nint obj_dist_with_tile(Object* object1, int tile1, Object* object2, int tile2);\nint obj_create_list(int tile, int elevation, int objectType, Object*** objectsPtr);\nvoid obj_delete_list(Object** objects);\nvoid translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, unsigned char* a9, unsigned char* a10);\nvoid dark_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, int light);\nvoid dark_translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, int light, unsigned char* a10, unsigned char* a11);\nvoid intensity_mask_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destPitch, unsigned char* mask, int maskPitch, int light);\nint obj_outline_object(Object* obj, int a2, Rect* rect);\nint obj_remove_outline(Object* obj, Rect* rect);\nint obj_intersects_with(Object* object, int x, int y);\nint obj_create_intersect_list(int x, int y, int elevation, int objectType, ObjectWithFlags** entriesPtr);\nvoid obj_delete_intersect_list(ObjectWithFlags** a1);\nvoid obj_set_seen(int tile);\nvoid obj_clear_seen();\nvoid obj_process_seen();\nchar* object_name(Object* obj);\nchar* object_description(Object* obj);\nvoid obj_preload_art_cache(int flags);\nint obj_save_obj(File* stream, Object* object);\nint obj_load_obj(File* stream, Object** objectPtr, int elevation, Object* owner);\nint obj_save_dude(File* stream);\nint obj_load_dude(File* stream);\nvoid obj_fix_violence_settings(int* fid);\n\n#endif /* FALLOUT_GAME_OBJECT_H_ */\n"
  },
  {
    "path": "src/game/object_types.h",
    "content": "#ifndef FALLOUT_GAME_OBJECT_TYPES_H_\n#define FALLOUT_GAME_OBJECT_TYPES_H_\n\n// Rotation\ntypedef enum Rotation {\n    ROTATION_NE, // 0\n    ROTATION_E, // 1\n    ROTATION_SE, // 2\n    ROTATION_SW, // 3\n    ROTATION_W, // 4\n    ROTATION_NW, // 5\n    ROTATION_COUNT,\n} Rotation;\n\nenum {\n    OBJ_TYPE_ITEM,\n    OBJ_TYPE_CRITTER,\n    OBJ_TYPE_SCENERY,\n    OBJ_TYPE_WALL,\n    OBJ_TYPE_TILE,\n    OBJ_TYPE_MISC,\n    OBJ_TYPE_INTERFACE,\n    OBJ_TYPE_INVENTORY,\n    OBJ_TYPE_HEAD,\n    OBJ_TYPE_BACKGROUND,\n    OBJ_TYPE_SKILLDEX,\n    OBJ_TYPE_COUNT,\n};\n\n#define FID_TYPE(value) ((value) & 0xF000000) >> 24\n#define PID_TYPE(value) (value) >> 24\n#define SID_TYPE(value) (value) >> 24\n\ntypedef enum OutlineType {\n    OUTLINE_TYPE_HOSTILE = 1,\n    OUTLINE_TYPE_2 = 2,\n    OUTLINE_TYPE_4 = 4,\n    OUTLINE_TYPE_FRIENDLY = 8,\n    OUTLINE_TYPE_ITEM = 16,\n    OUTLINE_TYPE_32 = 32,\n} OutlineType;\n\ntypedef enum ObjectFlags {\n    OBJECT_HIDDEN = 0x01,\n    OBJECT_TEMPORARY = 0x04,\n    OBJECT_FLAT = 0x08,\n    OBJECT_NO_BLOCK = 0x10,\n    OBJECT_LIGHTING = 0x20,\n    OBJECT_FLAG_0x400 = 0x400,\n    OBJECT_MULTIHEX = 0x800,\n    OBJECT_NO_HIGHLIGHT = 0x1000,\n    OBJECT_USED = 0x2000,\n    OBJECT_TRANS_RED = 0x4000,\n    OBJECT_TRANS_NONE = 0x8000,\n    OBJECT_TRANS_WALL = 0x10000,\n    OBJECT_TRANS_GLASS = 0x20000,\n    OBJECT_TRANS_STEAM = 0x40000,\n    OBJECT_TRANS_ENERGY = 0x80000,\n    OBJECT_IN_LEFT_HAND = 0x1000000,\n    OBJECT_IN_RIGHT_HAND = 0x2000000,\n    OBJECT_WORN = 0x4000000,\n    OBJECT_WALL_TRANS_END = 0x10000000,\n    OBJECT_LIGHT_THRU = 0x20000000,\n    OBJECT_SEEN = 0x40000000,\n    OBJECT_SHOOT_THRU = 0x80000000,\n\n    OBJECT_IN_ANY_HAND = OBJECT_IN_LEFT_HAND | OBJECT_IN_RIGHT_HAND,\n    OBJECT_EQUIPPED = OBJECT_IN_ANY_HAND | OBJECT_WORN,\n    OBJECT_FLAG_0xFC000 = OBJECT_TRANS_ENERGY | OBJECT_TRANS_STEAM | OBJECT_TRANS_GLASS | OBJECT_TRANS_WALL | OBJECT_TRANS_NONE | OBJECT_TRANS_RED,\n    OBJECT_OPEN_DOOR = OBJECT_SHOOT_THRU | OBJECT_LIGHT_THRU | OBJECT_NO_BLOCK,\n} ObjectFlags;\n\ntypedef enum CritterFlags {\n    CRITTER_BARTER = 0x02,\n    CRITTER_NO_STEAL = 0x20,\n    CRITTER_NO_DROP = 0x40,\n    CRITTER_NO_LIMBS = 0x80,\n    CRITTER_NO_AGE = 0x100,\n    CRITTER_NO_HEAL = 0x200,\n    CRITTER_INVULNERABLE = 0x400,\n    CRITTER_FLAT = 0x800,\n    CRITTER_SPECIAL_DEATH = 0x1000,\n    CRITTER_LONG_LIMBS = 0x2000,\n    CRITTER_NO_KNOCKBACK = 0x4000,\n} CritterFlags;\n\n#define OUTLINE_TYPE_MASK 0xFFFFFF\n#define OUTLINE_PALETTED 0x40000000\n#define OUTLINE_DISABLED 0x80000000\n\n// These two values are the same but stored in different fields.\n#define CONTAINER_FLAG_JAMMED 0x04000000\n#define DOOR_FLAG_JAMMGED 0x04000000\n\n#define CONTAINER_FLAG_LOCKED 0x02000000\n#define DOOR_FLAG_LOCKED 0x02000000\n\ntypedef enum CritterManeuver {\n    CRITTER_MANEUVER_NONE = 0,\n    CRITTER_MANEUVER_0x01 = 0x01,\n    CRITTER_MANEUVER_STOP_ATTACKING = 0x02,\n    CRITTER_MANUEVER_FLEEING = 0x04,\n} CritterManeuver;\n\ntypedef enum Dam {\n    DAM_KNOCKED_OUT = 0x01,\n    DAM_KNOCKED_DOWN = 0x02,\n    DAM_CRIP_LEG_LEFT = 0x04,\n    DAM_CRIP_LEG_RIGHT = 0x08,\n    DAM_CRIP_ARM_LEFT = 0x10,\n    DAM_CRIP_ARM_RIGHT = 0x20,\n    DAM_BLIND = 0x40,\n    DAM_DEAD = 0x80,\n    DAM_HIT = 0x100,\n    DAM_CRITICAL = 0x200,\n    DAM_ON_FIRE = 0x400,\n    DAM_BYPASS = 0x800,\n    DAM_EXPLODE = 0x1000,\n    DAM_DESTROY = 0x2000,\n    DAM_DROP = 0x4000,\n    DAM_LOSE_TURN = 0x8000,\n    DAM_HIT_SELF = 0x10000,\n    DAM_LOSE_AMMO = 0x20000,\n    DAM_DUD = 0x40000,\n    DAM_HURT_SELF = 0x80000,\n    DAM_RANDOM_HIT = 0x100000,\n    DAM_CRIP_RANDOM = 0x200000,\n    DAM_BACKWASH = 0x400000,\n    DAM_PERFORM_REVERSE = 0x800000,\n    DAM_CRIP_LEG_ANY = DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT,\n    DAM_CRIP_ARM_ANY = DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT,\n    DAM_CRIP = DAM_CRIP_LEG_ANY | DAM_CRIP_ARM_ANY | DAM_BLIND,\n} Dam;\n\n#define OBJ_LOCKED 0x02000000\n#define OBJ_JAMMED 0x04000000\n\ntypedef struct Object Object;\n\ntypedef struct InventoryItem {\n    Object* item;\n    int quantity;\n} InventoryItem;\n\n// Represents inventory of the object.\ntypedef struct Inventory {\n    int length;\n    int capacity;\n    InventoryItem* items;\n} Inventory;\n\ntypedef struct WeaponObjectData {\n    int ammoQuantity; // obj_pudg.pudweapon.cur_ammo_quantity\n    int ammoTypePid; // obj_pudg.pudweapon.cur_ammo_type_pid\n} WeaponObjectData;\n\ntypedef struct AmmoItemData {\n    int quantity; // obj_pudg.pudammo.cur_ammo_quantity\n} AmmoItemData;\n\ntypedef struct MiscItemData {\n    int charges; // obj_pudg.pudmisc_item.curr_charges\n} MiscItemData;\n\ntypedef struct KeyItemData {\n    int keyCode; // obj_pudg.pudkey_item.cur_key_code\n} KeyItemData;\n\ntypedef union ItemObjectData {\n    WeaponObjectData weapon;\n    AmmoItemData ammo;\n    MiscItemData misc;\n    KeyItemData key;\n} ItemObjectData;\n\ntypedef struct CritterCombatData {\n    int maneuver; // obj_pud.combat_data.maneuver\n    int ap; // obj_pud.combat_data.curr_mp\n    int results; // obj_pud.combat_data.results\n    int damageLastTurn; // obj_pud.combat_data.damage_last_turn\n    int aiPacket; // obj_pud.combat_data.ai_packet\n    int team; // obj_pud.combat_data.team_num\n    union {\n        Object* whoHitMe; // obj_pud.combat_data.who_hit_me\n        int whoHitMeCid;\n    };\n} CritterCombatData;\n\ntypedef struct CritterObjectData {\n    int field_0; // obj_pud.reaction_to_pc\n    CritterCombatData combat; // obj_pud.combat_data\n    int hp; // obj_pud.curr_hp\n    int radiation; // obj_pud.curr_rad\n    int poison; // obj_pud.curr_poison\n} CritterObjectData;\n\ntypedef struct DoorSceneryData {\n    int openFlags; // obj_pudg.pudportal.cur_open_flags\n} DoorSceneryData;\n\ntypedef struct StairsSceneryData {\n    int destinationMap; // obj_pudg.pudstairs.destMap\n    int destinationBuiltTile; // obj_pudg.pudstairs.destBuiltTile\n} StairsSceneryData;\n\ntypedef struct ElevatorSceneryData {\n    int type;\n    int level;\n} ElevatorSceneryData;\n\ntypedef struct LadderSceneryData {\n    int destinationMap;\n    int destinationBuiltTile;\n} LadderSceneryData;\n\ntypedef union SceneryObjectData {\n    DoorSceneryData door;\n    StairsSceneryData stairs;\n    ElevatorSceneryData elevator;\n    LadderSceneryData ladder;\n} SceneryObjectData;\n\ntypedef struct MiscObjectData {\n    int map;\n    int tile;\n    int elevation;\n    int rotation;\n} MiscObjectData;\n\ntypedef struct ObjectData {\n    Inventory inventory;\n    union {\n        CritterObjectData critter;\n        struct {\n            int flags;\n            union {\n                ItemObjectData item;\n                SceneryObjectData scenery;\n                MiscObjectData misc;\n            };\n        };\n    };\n} ObjectData;\n\ntypedef struct Object {\n    int id; // obj_id\n    int tile; // obj_tile_num\n    int x; // obj_x\n    int y; // obj_y\n    int sx; // obj_sx\n    int sy; // obj_sy\n    int frame; // obj_cur_frm\n    int rotation; // obj_cur_rot\n    int fid; // obj_fid\n    int flags; // obj_flags\n    int elevation; // obj_elev\n    union {\n        int field_2C_array[14];\n        ObjectData data;\n    };\n    int pid; // obj_pid\n    int cid; // obj_cid\n    int lightDistance; // obj_light_distance\n    int lightIntensity; // obj_light_intensity\n    int outline; // obj_outline\n    int sid; // obj_sid\n    Object* owner;\n    int field_80;\n} Object;\n\n#ifdef _WIN32\nstatic_assert(sizeof(Object) == 132, \"wrong size\");\n#endif\n\ntypedef struct ObjectListNode {\n    Object* obj;\n    struct ObjectListNode* next;\n} ObjectListNode;\n\n#define BUILT_TILE_TILE_MASK 0x3FFFFFF\n#define BUILT_TILE_ELEVATION_MASK 0xE0000000\n#define BUILT_TILE_ELEVATION_SHIFT 29\n#define BUILT_TILE_ROTATION_MASK 0x1C000000\n#define BUILT_TILE_ROTATION_SHIFT 26\n\nstatic inline int builtTileGetTile(int builtTile)\n{\n    return builtTile & BUILT_TILE_TILE_MASK;\n}\n\nstatic inline int builtTileGetElevation(int builtTile)\n{\n    return (builtTile & BUILT_TILE_ELEVATION_MASK) >> BUILT_TILE_ELEVATION_SHIFT;\n}\n\nstatic inline int builtTileGetRotation(int builtTile)\n{\n    return (builtTile & BUILT_TILE_ROTATION_MASK) >> BUILT_TILE_ROTATION_SHIFT;\n}\n\nstatic inline int builtTileCreate(int tile, int elevation)\n{\n    return tile | ((elevation << BUILT_TILE_ELEVATION_SHIFT) & BUILT_TILE_ELEVATION_MASK);\n}\n\n#endif /* FALLOUT_GAME_OBJECT_TYPES_H_ */\n"
  },
  {
    "path": "src/game/options.c",
    "content": "#include \"game/options.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"plib/color/color.h\"\n#include \"game/art.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/graphlib.h\"\n#include \"game/gsound.h\"\n#include \"game/loadsave.h\"\n#include \"game/message.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/scripts.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define PREFERENCES_WINDOW_WIDTH 640\n#define PREFERENCES_WINDOW_HEIGHT 480\n\n#define OPTIONS_WINDOW_BUTTONS_COUNT 10\n#define PRIMARY_OPTION_VALUE_COUNT 4\n#define SECONDARY_OPTION_VALUE_COUNT 2\n\n#define GAMMA_MIN 1.0\n#define GAMMA_MAX 1.17999267578125\n#define GAMMA_STEP 0.01124954223632812\n\ntypedef enum Preference {\n    PREF_GAME_DIFFICULTY,\n    PREF_COMBAT_DIFFICULTY,\n    PREF_VIOLENCE_LEVEL,\n    PREF_TARGET_HIGHLIGHT,\n    PREF_COMBAT_LOOKS,\n    PREF_COMBAT_MESSAGES,\n    PREF_COMBAT_TAUNTS,\n    PREF_LANGUAGE_FILTER,\n    PREF_RUNNING,\n    PREF_SUBTITLES,\n    PREF_ITEM_HIGHLIGHT,\n    PREF_COMBAT_SPEED,\n    PREF_TEXT_BASE_DELAY,\n    PREF_MASTER_VOLUME,\n    PREF_MUSIC_VOLUME,\n    PREF_SFX_VOLUME,\n    PREF_SPEECH_VOLUME,\n    PREF_BRIGHTNESS,\n    PREF_MOUSE_SENSITIVIY,\n    PREF_COUNT,\n    FIRST_PRIMARY_PREF = PREF_GAME_DIFFICULTY,\n    LAST_PRIMARY_PREF = PREF_COMBAT_LOOKS,\n    PRIMARY_PREF_COUNT = LAST_PRIMARY_PREF - FIRST_PRIMARY_PREF + 1,\n    FIRST_SECONDARY_PREF = PREF_COMBAT_MESSAGES,\n    LAST_SECONDARY_PREF = PREF_ITEM_HIGHLIGHT,\n    SECONDARY_PREF_COUNT = LAST_SECONDARY_PREF - FIRST_SECONDARY_PREF + 1,\n    FIRST_RANGE_PREF = PREF_COMBAT_SPEED,\n    LAST_RANGE_PREF = PREF_MOUSE_SENSITIVIY,\n    RANGE_PREF_COUNT = LAST_RANGE_PREF - FIRST_RANGE_PREF + 1,\n} Preference;\n\ntypedef enum PauseWindowFrm {\n    PAUSE_WINDOW_FRM_BACKGROUND,\n    PAUSE_WINDOW_FRM_DONE_BOX,\n    PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_UP,\n    PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN,\n    PAUSE_WINDOW_FRM_COUNT,\n} PauseWindowFrm;\n\ntypedef enum OptionsWindowFrm {\n    OPTIONS_WINDOW_FRM_BACKGROUND,\n    OPTIONS_WINDOW_FRM_BUTTON_ON,\n    OPTIONS_WINDOW_FRM_BUTTON_OFF,\n    OPTIONS_WINDOW_FRM_COUNT,\n} OptionsWindowFrm;\n\ntypedef enum PreferencesWindowFrm {\n    PREFERENCES_WINDOW_FRM_BACKGROUND,\n    // Knob (for range preferences)\n    PREFERENCES_WINDOW_FRM_KNOB_OFF,\n    // 4-way switch (for primary preferences)\n    PREFERENCES_WINDOW_FRM_PRIMARY_SWITCH,\n    // 2-way switch (for secondary preferences)\n    PREFERENCES_WINDOW_FRM_SECONDARY_SWITCH,\n    PREFERENCES_WINDOW_FRM_CHECKBOX_ON,\n    PREFERENCES_WINDOW_FRM_CHECKBOX_OFF,\n    PREFERENCES_WINDOW_FRM_6,\n    // Active knob (for range preferences)\n    PREFERENCES_WINDOW_FRM_KNOB_ON,\n    PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP,\n    PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN,\n    PREFERENCES_WINDOW_FRM_COUNT,\n} PreferencesWindowFrm;\n\n#pragma pack(2)\ntypedef struct PreferenceDescription {\n    // The number of options.\n    short valuesCount;\n\n    // Direction of rotation:\n    // 0 - clockwise (incrementing value),\n    // 1 - counter-clockwise (decrementing value)\n    short direction;\n    short knobX;\n    short knobY;\n    // Min x coordinate of the preference control bounding box.\n    short minX;\n    // Max x coordinate of the preference control bounding box.\n    short maxX;\n    short labelIds[PRIMARY_OPTION_VALUE_COUNT];\n    int btn;\n    char name[32];\n    double minValue;\n    double maxValue;\n    int* valuePtr;\n} PreferenceDescription;\n#pragma pack()\n\nstatic_assert(sizeof(PreferenceDescription) == 76, \"wrong size\");\n\nstatic int OptnStart();\nstatic int OptnEnd();\nstatic void ShadeScreen(bool a1);\nstatic int do_prefscreen();\nstatic int PrefStart();\nstatic void DoThing(int eventCode);\nstatic void UpdateThing(int index);\nstatic int PrefEnd();\nstatic void SetSystemPrefs();\nstatic int SavePrefs(bool save);\nstatic void SetDefaults(bool a1);\nstatic void SaveSettings();\nstatic void RestoreSettings();\nstatic void JustUpdate();\n\n// 0x48FBD0\nstatic const short row1Ytab[PRIMARY_PREF_COUNT] = {\n    48,\n    125,\n    203,\n    286,\n    363,\n};\n\n// 0x48FBDA\nstatic const short row2Ytab[SECONDARY_PREF_COUNT] = {\n    49,\n    116,\n    181,\n    247,\n    313,\n    380,\n};\n\n// 0x48FBE6\nstatic const short row3Ytab[RANGE_PREF_COUNT] = {\n    19,\n    94,\n    165,\n    216,\n    268,\n    319,\n    369,\n    420,\n};\n\n// x offsets for primary preferences from the knob position\n// 0x48FBF6\nstatic const short bglbx[PRIMARY_OPTION_VALUE_COUNT] = {\n    2,\n    25,\n    46,\n    46,\n};\n\n// y offsets for primary preference option values from the knob position\n// 0x48FBFE\nstatic const short bglby[PRIMARY_OPTION_VALUE_COUNT] = {\n    10,\n    -4,\n    10,\n    31,\n};\n\n// x offsets for secondary prefrence option values from the knob position\n// 0x48FC06\nstatic const short smlbx[SECONDARY_OPTION_VALUE_COUNT] = {\n    4,\n    21,\n};\n\n// 0x5197C0\nstatic int opgrphs[OPTIONS_WINDOW_FRM_COUNT] = {\n    220, // opbase.frm - character editor\n    222, // opbtnon.frm - character editor\n    221, // opbtnoff.frm - character editor\n};\n\n// 0x5197CC\nstatic int prfgrphs[PREFERENCES_WINDOW_FRM_COUNT] = {\n    240, // prefscrn.frm - options screen\n    241, // prfsldof.frm - options screen\n    242, // prfbknbs.frm - options screen\n    243, // prflknbs.frm - options screen\n    244, // prfxin.frm - options screen\n    245, // prfxout.frm - options screen\n    246, // prefcvr.frm - options screen\n    247, // prfsldon.frm - options screen\n    8, // lilredup.frm - little red button up\n    9, // lilreddn.frm - little red button down\n};\n\n// 0x6637D0\nstatic Size ginfo[OPTIONS_WINDOW_FRM_COUNT];\n\n// 0x6637E8\nstatic MessageList optn_msgfl;\n\n// 0x6637F0\nstatic Size ginfo2[PREFERENCES_WINDOW_FRM_COUNT];\n\n// 0x663840\nstatic MessageListItem optnmesg;\n\n// 0x663850\nstatic unsigned char* prfbmp[PREFERENCES_WINDOW_FRM_COUNT];\n\n// 0x663878\nstatic unsigned char* opbtns[OPTIONS_WINDOW_BUTTONS_COUNT];\n\n// 0x6638A0\nstatic CacheEntry* grphkey2[PREFERENCES_WINDOW_FRM_COUNT];\n\n// 0x6638C8\nstatic double text_delay_back;\n\n// 0x6638D0\nstatic double gamma_value;\n\n// 0x6638D8\nstatic double gamma_value_back;\n\n// 0x6638E0\nstatic double text_delay;\n\n// 0x6638E8\nstatic double mouse_sens;\n\n// 0x6638F0\nstatic double mouse_sens_back;\n\n// 0x6638F8\nstatic unsigned char* prefbuf;\n\n// 0x6638FC\nstatic bool mouse_3d_was_on;\n\n// 0x663900\nstatic int optnwin;\n\n// 0x663904\nstatic int prfwin;\n\n// 0x663908\nstatic unsigned char* winbuf;\n\n// 0x66390C\nstatic CacheEntry* grphkey[OPTIONS_WINDOW_FRM_COUNT];\n\n// 0x663918\nstatic unsigned char* opbmp[OPTIONS_WINDOW_FRM_COUNT];\n\n// This array stores backup for only integer-representable prefs. Because\n// `player_speedup` is not a part of prefs enum, it's value is stored in\n// `PREF_TEXT_BASE_DELAY`.\n//\n// 0x663924\nstatic int settings_backup[PREF_COUNT];\n\n// 0x663970\nstatic int sndfx_volume;\n\n// 0x663974\nstatic int subtitles;\n\n// 0x663978\nstatic int language_filter;\n\n// 0x66397C\nstatic int speech_volume;\n\n// 0x663980\nstatic int master_volume;\n\n// 0x663984\nstatic int player_speedup;\n\n// 0x663988\nstatic int combat_taunts;\n\n// 0x66398C\nstatic int fontsave;\n\n// 0x663990\nstatic int music_volume;\n\n// 0x663994\nstatic bool bk_enable;\n\n// 0x663998\nstatic int prf_running;\n\n// 0x66399C\nstatic int combat_speed;\n\n// 0x6639A0\nstatic int plyrspdbid;\n\n// 0x6639A4\nstatic int item_highlight;\n\n// 0x6639A8\nstatic bool changed;\n\n// 0x6639AC\nstatic int combat_messages;\n\n// 0x6639B0\nstatic int target_highlight;\n\n// 0x6639B4\nstatic int combat_difficulty;\n\n// 0x6639B8\nstatic int violence_level;\n\n// 0x6639BC\nstatic int game_difficulty;\n\n// 0x6639C0\nstatic int combatLookValue;\n\n// 0x5197F8\nstatic PreferenceDescription btndat[PREF_COUNT] = {\n    { 3, 0, 76, 71, 0, 0, { 203, 204, 205, 0 }, 0, GAME_CONFIG_GAME_DIFFICULTY_KEY, 0, 0, &game_difficulty },\n    { 3, 0, 76, 149, 0, 0, { 206, 204, 208, 0 }, 0, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, 0, 0, &combat_difficulty },\n    { 4, 0, 76, 226, 0, 0, { 214, 215, 204, 216 }, 0, GAME_CONFIG_VIOLENCE_LEVEL_KEY, 0, 0, &violence_level },\n    { 3, 0, 76, 309, 0, 0, { 202, 201, 213, 0 }, 0, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, 0, 0, &target_highlight },\n    { 2, 0, 76, 387, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_COMBAT_LOOKS_KEY, 0, 0, &combatLookValue },\n    { 2, 0, 299, 74, 0, 0, { 211, 212, 0, 0 }, 0, GAME_CONFIG_COMBAT_MESSAGES_KEY, 0, 0, &combat_messages },\n    { 2, 0, 299, 141, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_COMBAT_TAUNTS_KEY, 0, 0, &combat_taunts },\n    { 2, 0, 299, 207, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_LANGUAGE_FILTER_KEY, 0, 0, &language_filter },\n    { 2, 0, 299, 271, 0, 0, { 209, 219, 0, 0 }, 0, GAME_CONFIG_RUNNING_KEY, 0, 0, &prf_running },\n    { 2, 0, 299, 338, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_SUBTITLES_KEY, 0, 0, &subtitles },\n    { 2, 0, 299, 404, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, 0, 0, &item_highlight },\n    { 2, 0, 374, 50, 0, 0, { 207, 210, 0, 0 }, 0, GAME_CONFIG_COMBAT_SPEED_KEY, 0.0, 50.0, &combat_speed },\n    { 3, 0, 374, 125, 0, 0, { 217, 209, 218, 0 }, 0, GAME_CONFIG_TEXT_BASE_DELAY_KEY, 1.0, 6.0, NULL },\n    { 4, 0, 374, 196, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_MASTER_VOLUME_KEY, 0, 32767.0, &master_volume },\n    { 4, 0, 374, 247, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_MUSIC_VOLUME_KEY, 0, 32767.0, &music_volume },\n    { 4, 0, 374, 298, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_SNDFX_VOLUME_KEY, 0, 32767.0, &sndfx_volume },\n    { 4, 0, 374, 349, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_SPEECH_VOLUME_KEY, 0, 32767.0, &speech_volume },\n    { 2, 0, 374, 400, 0, 0, { 207, 223, 0, 0 }, 0, GAME_CONFIG_BRIGHTNESS_KEY, 1.0, 1.17999267578125, NULL },\n    { 2, 0, 374, 451, 0, 0, { 207, 218, 0, 0 }, 0, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, 1.0, 2.5, NULL },\n};\n\n// 0x48FC48\nint do_options()\n{\n    return do_optionsFunc(-1);\n}\n\n// 0x48FC50\nint do_optionsFunc(int initialKeyCode)\n{\n    if (OptnStart() == -1) {\n        debug_printf(\"\\nOPTION MENU: Error loading option dialog data!\\n\");\n        return -1;\n    }\n\n    int rc = -1;\n    while (rc == -1) {\n        int keyCode = get_input();\n        bool showPreferences = false;\n\n        if (initialKeyCode != -1) {\n            keyCode = initialKeyCode;\n            rc = 1;\n        }\n\n        if (keyCode == KEY_ESCAPE || keyCode == 504 || game_user_wants_to_quit != 0) {\n            rc = 0;\n        } else {\n            switch (keyCode) {\n            case KEY_RETURN:\n            case KEY_UPPERCASE_O:\n            case KEY_LOWERCASE_O:\n            case KEY_UPPERCASE_D:\n            case KEY_LOWERCASE_D:\n                gsound_play_sfx_file(\"ib1p1xx1\");\n                rc = 0;\n                break;\n            case KEY_UPPERCASE_S:\n            case KEY_LOWERCASE_S:\n            case 500:\n                if (SaveGame(LOAD_SAVE_MODE_NORMAL) == 1) {\n                    rc = 1;\n                }\n                break;\n            case KEY_UPPERCASE_L:\n            case KEY_LOWERCASE_L:\n            case 501:\n                if (LoadGame(LOAD_SAVE_MODE_NORMAL) == 1) {\n                    rc = 1;\n                }\n                break;\n            case KEY_UPPERCASE_P:\n            case KEY_LOWERCASE_P:\n                gsound_play_sfx_file(\"ib1p1xx1\");\n                // FALLTHROUGH\n            case 502:\n                // PREFERENCES\n                showPreferences = true;\n                break;\n            case KEY_PLUS:\n            case KEY_EQUAL:\n                IncGamma();\n                break;\n            case KEY_UNDERSCORE:\n            case KEY_MINUS:\n                DecGamma();\n                break;\n            }\n        }\n\n        if (showPreferences) {\n            do_prefscreen();\n        } else {\n            switch (keyCode) {\n            case KEY_F12:\n                dump_screen();\n                break;\n            case KEY_UPPERCASE_E:\n            case KEY_LOWERCASE_E:\n            case KEY_CTRL_Q:\n            case KEY_CTRL_X:\n            case KEY_F10:\n            case 503:\n                game_quit_with_confirm();\n                break;\n            }\n        }\n    }\n\n    OptnEnd();\n\n    return rc;\n}\n\n// 0x48FE14\nstatic int OptnStart()\n{\n    fontsave = text_curr();\n\n    if (!message_init(&optn_msgfl)) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"options.msg\");\n    if (!message_load(&optn_msgfl, path)) {\n        return -1;\n    }\n\n    for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, opgrphs[index], 0, 0, 0);\n        opbmp[index] = art_lock(fid, &(grphkey[index]), &(ginfo[index].width), &(ginfo[index].height));\n\n        if (opbmp[index] == NULL) {\n            while (--index >= 0) {\n                art_ptr_unlock(grphkey[index]);\n            }\n\n            message_exit(&optn_msgfl);\n\n            return -1;\n        }\n    }\n\n    int cycle = 0;\n    for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) {\n        opbtns[index] = (unsigned char*)mem_malloc(ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width * ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height + 1024);\n        if (opbtns[index] == NULL) {\n            while (--index >= 0) {\n                mem_free(opbtns[index]);\n            }\n\n            for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) {\n                art_ptr_unlock(grphkey[index]);\n            }\n\n            message_exit(&optn_msgfl);\n\n            return -1;\n        }\n\n        cycle = cycle ^ 1;\n\n        memcpy(opbtns[index], opbmp[cycle + 1], ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width * ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height);\n    }\n\n    int optionsWindowX = (640 - ginfo[OPTIONS_WINDOW_FRM_BACKGROUND].width) / 2;\n    int optionsWindowY = (480 - ginfo[OPTIONS_WINDOW_FRM_BACKGROUND].height) / 2 - 60;\n    optnwin = win_add(optionsWindowX,\n        optionsWindowY,\n        ginfo[0].width,\n        ginfo[0].height,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n\n    if (optnwin == -1) {\n        for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) {\n            mem_free(opbtns[index]);\n        }\n\n        for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) {\n            art_ptr_unlock(grphkey[index]);\n        }\n\n        message_exit(&optn_msgfl);\n\n        return -1;\n    }\n\n    bk_enable = map_disable_bk_processes();\n\n    mouse_3d_was_on = gmouse_3d_is_on();\n    if (mouse_3d_was_on) {\n        gmouse_3d_off();\n    }\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    winbuf = win_get_buf(optnwin);\n    memcpy(winbuf, opbmp[OPTIONS_WINDOW_FRM_BACKGROUND], ginfo[OPTIONS_WINDOW_FRM_BACKGROUND].width * ginfo[OPTIONS_WINDOW_FRM_BACKGROUND].height);\n\n    text_font(103);\n\n    int textY = (ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height - text_height()) / 2 + 1;\n    int buttonY = 17;\n\n    for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index += 2) {\n        char text[128];\n\n        const char* msg = getmsg(&optn_msgfl, &optnmesg, index / 2);\n        strcpy(text, msg);\n\n        int textX = (ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width - text_width(text)) / 2;\n        if (textX < 0) {\n            textX = 0;\n        }\n\n        text_to_buf(opbtns[index] + ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width * textY + textX, text, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, colorTable[18979]);\n        text_to_buf(opbtns[index + 1] + ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width * textY + textX, text, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, colorTable[14723]);\n\n        int btn = win_register_button(optnwin, 13, buttonY, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height, -1, -1, -1, index / 2 + 500, opbtns[index], opbtns[index + 1], NULL, 32);\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_lrg_butt_press, gsound_lrg_butt_release);\n        }\n\n        buttonY += ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height + 3;\n    }\n\n    text_font(101);\n\n    win_draw(optnwin);\n\n    return 0;\n}\n\n// 0x490244\nstatic int OptnEnd()\n{\n    win_delete(optnwin);\n    text_font(fontsave);\n    message_exit(&optn_msgfl);\n\n    for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) {\n        mem_free(opbtns[index]);\n    }\n\n    for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) {\n        art_ptr_unlock(grphkey[index]);\n    }\n\n    if (mouse_3d_was_on) {\n        gmouse_3d_on();\n    }\n\n    if (bk_enable) {\n        map_enable_bk_processes();\n    }\n\n    return 0;\n}\n\n// 0x4902B0\nint PauseWindow(bool a1)\n{\n    // 0x48FC0C\n    static const int graphicIds[PAUSE_WINDOW_FRM_COUNT] = {\n        208, // charwin.frm - character editor\n        209, // donebox.frm - character editor\n        8, // lilredup.frm - little red button up\n        9, // lilreddn.frm - little red button down\n    };\n\n    unsigned char* frmData[PAUSE_WINDOW_FRM_COUNT];\n    CacheEntry* frmHandles[PAUSE_WINDOW_FRM_COUNT];\n    Size frmSizes[PAUSE_WINDOW_FRM_COUNT];\n\n    bool gameMouseWasVisible;\n    if (!a1) {\n        bk_enable = map_disable_bk_processes();\n        cycle_disable();\n\n        gameMouseWasVisible = gmouse_3d_is_on();\n        if (gameMouseWasVisible) {\n            gmouse_3d_off();\n        }\n    }\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    ShadeScreen(a1);\n\n    for (int index = 0; index < PAUSE_WINDOW_FRM_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, graphicIds[index], 0, 0, 0);\n        frmData[index] = art_lock(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height));\n        if (frmData[index] == NULL) {\n            while (--index >= 0) {\n                art_ptr_unlock(frmHandles[index]);\n            }\n\n            debug_printf(\"\\n** Error loading pause window graphics! **\\n\");\n            return -1;\n        }\n    }\n\n    if (!message_init(&optn_msgfl)) {\n        // FIXME: Leaking graphics.\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"options.msg\");\n    if (!message_load(&optn_msgfl, path)) {\n        // FIXME: Leaking graphics.\n        return -1;\n    }\n\n    int pauseWindowX = (640 - frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width) / 2;\n    int pauseWindowY = (480 - frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].height) / 2;\n\n    if (a1) {\n        pauseWindowX -= 65;\n        pauseWindowY -= 24;\n    } else {\n        pauseWindowY -= 54;\n    }\n\n    int window = win_add(pauseWindowX,\n        pauseWindowY,\n        frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width,\n        frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].height,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n    if (window == -1) {\n        for (int index = 0; index < PAUSE_WINDOW_FRM_COUNT; index++) {\n            art_ptr_unlock(frmHandles[index]);\n        }\n\n        message_exit(&optn_msgfl);\n\n        debug_printf(\"\\n** Error opening pause window! **\\n\");\n        return -1;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(window);\n    memcpy(windowBuffer,\n        frmData[PAUSE_WINDOW_FRM_BACKGROUND],\n        frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].height);\n\n    trans_buf_to_buf(frmData[PAUSE_WINDOW_FRM_DONE_BOX],\n        frmSizes[PAUSE_WINDOW_FRM_DONE_BOX].width,\n        frmSizes[PAUSE_WINDOW_FRM_DONE_BOX].height,\n        frmSizes[PAUSE_WINDOW_FRM_DONE_BOX].width,\n        windowBuffer + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * 42 + 13,\n        frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width);\n\n    fontsave = text_curr();\n    text_font(103);\n\n    char* messageItemText;\n\n    messageItemText = getmsg(&optn_msgfl, &optnmesg, 300);\n    text_to_buf(windowBuffer + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * 45 + 52,\n        messageItemText,\n        frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width,\n        frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width,\n        colorTable[18979]);\n\n    text_font(104);\n\n    messageItemText = getmsg(&optn_msgfl, &optnmesg, 301);\n    strcpy(path, messageItemText);\n\n    int length = text_width(path);\n    text_to_buf(windowBuffer + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * 10 + 2 + (frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width - length) / 2,\n        path,\n        frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width,\n        frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width,\n        colorTable[18979]);\n\n    int doneBtn = win_register_button(window,\n        26,\n        46,\n        frmSizes[PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width,\n        frmSizes[PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        504,\n        frmData[PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_UP],\n        frmData[PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (doneBtn != -1) {\n        win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    win_draw(window);\n\n    bool done = false;\n    while (!done) {\n        int keyCode = get_input();\n        switch (keyCode) {\n        case KEY_PLUS:\n        case KEY_EQUAL:\n            IncGamma();\n            break;\n        case KEY_MINUS:\n        case KEY_UNDERSCORE:\n            DecGamma();\n            break;\n        default:\n            if (keyCode != -1 && keyCode != -2) {\n                done = true;\n            }\n\n            if (game_user_wants_to_quit != 0) {\n                done = true;\n            }\n        }\n    }\n\n    if (!a1) {\n        tile_refresh_display();\n    }\n\n    win_delete(window);\n\n    for (int index = 0; index < PAUSE_WINDOW_FRM_COUNT; index++) {\n        art_ptr_unlock(frmHandles[index]);\n    }\n\n    message_exit(&optn_msgfl);\n\n    if (!a1) {\n        if (gameMouseWasVisible) {\n            gmouse_3d_on();\n        }\n\n        if (bk_enable) {\n            map_enable_bk_processes();\n        }\n\n        cycle_enable();\n\n        gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    }\n\n    text_font(fontsave);\n\n    return 0;\n}\n\n// 0x490748\nstatic void ShadeScreen(bool a1)\n{\n    if (a1) {\n        mouse_hide();\n    } else {\n        mouse_hide();\n        tile_refresh_display();\n\n        int windowWidth = 640;\n        int windowHeight = win_height(display_win);\n        unsigned char* windowBuffer = win_get_buf(display_win);\n        grey_buf(windowBuffer, windowWidth, windowHeight, windowWidth);\n\n        win_draw(display_win);\n    }\n\n    mouse_show();\n}\n\n// 0x490798\nstatic int do_prefscreen()\n{\n    if (PrefStart() == -1) {\n        debug_printf(\"\\nPREFERENCE MENU: Error loading preference dialog data!\\n\");\n        return -1;\n    }\n\n    int rc = -1;\n    while (rc == -1) {\n        int eventCode = get_input();\n\n        switch (eventCode) {\n        case KEY_RETURN:\n        case KEY_UPPERCASE_P:\n        case KEY_LOWERCASE_P:\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            // FALLTHROUGH\n        case 504:\n            rc = 1;\n            break;\n        case KEY_CTRL_Q:\n        case KEY_CTRL_X:\n        case KEY_F10:\n            game_quit_with_confirm();\n            break;\n        case KEY_EQUAL:\n        case KEY_PLUS:\n            IncGamma();\n            break;\n        case KEY_MINUS:\n        case KEY_UNDERSCORE:\n            DecGamma();\n            break;\n        case KEY_F12:\n            dump_screen();\n            break;\n        case 527:\n            SetDefaults(true);\n            break;\n        default:\n            if (eventCode == KEY_ESCAPE || eventCode == 528 || game_user_wants_to_quit != 0) {\n                RestoreSettings();\n                rc = 0;\n            } else if (eventCode >= 505 && eventCode <= 524) {\n                DoThing(eventCode);\n            }\n            break;\n        }\n    }\n\n    PrefEnd();\n\n    return rc;\n}\n\n// 0x4908A0\nstatic int PrefStart()\n{\n    int i;\n    int fid;\n    char* messageItemText;\n    int x;\n    int y;\n    int width;\n    int height;\n    int messageItemId;\n    int btn;\n\n    SaveSettings();\n\n    for (i = 0; i < PREFERENCES_WINDOW_FRM_COUNT; i++) {\n        fid = art_id(OBJ_TYPE_INTERFACE, prfgrphs[i], 0, 0, 0);\n        prfbmp[i] = art_lock(fid, &(grphkey2[i]), &(ginfo2[i].width), &(ginfo2[i].height));\n        if (prfbmp[i] == NULL) {\n            for (; i != 0; i--) {\n                art_ptr_unlock(grphkey2[i - 1]);\n            }\n            return -1;\n        }\n    }\n\n    changed = false;\n\n    int preferencesWindowX = 0;\n    int preferencesWindowY = 0;\n    prfwin = win_add(preferencesWindowX,\n        preferencesWindowY,\n        PREFERENCES_WINDOW_WIDTH,\n        PREFERENCES_WINDOW_HEIGHT,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n    if (prfwin == -1) {\n        for (i = 0; i < PREFERENCES_WINDOW_FRM_COUNT; i++) {\n            art_ptr_unlock(grphkey2[i]);\n        }\n        return -1;\n    }\n\n    prefbuf = win_get_buf(prfwin);\n    memcpy(prefbuf,\n        prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND],\n        ginfo2[PREFERENCES_WINDOW_FRM_BACKGROUND].width * ginfo2[PREFERENCES_WINDOW_FRM_BACKGROUND].height);\n\n    text_font(104);\n\n    messageItemText = getmsg(&optn_msgfl, &optnmesg, 100);\n    text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 10 + 74, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n\n    text_font(103);\n\n    messageItemId = 101;\n    for (i = 0; i < PRIMARY_PREF_COUNT; i++) {\n        messageItemText = getmsg(&optn_msgfl, &optnmesg, messageItemId++);\n        x = 99 - text_width(messageItemText) / 2;\n        text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * row1Ytab[i] + x, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n    }\n\n    for (i = 0; i < SECONDARY_PREF_COUNT; i++) {\n        messageItemText = getmsg(&optn_msgfl, &optnmesg, messageItemId++);\n        text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * row2Ytab[i] + 206, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n    }\n\n    for (i = 0; i < RANGE_PREF_COUNT; i++) {\n        messageItemText = getmsg(&optn_msgfl, &optnmesg, messageItemId++);\n        text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * row3Ytab[i] + 384, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n    }\n\n    // DEFAULT\n    messageItemText = getmsg(&optn_msgfl, &optnmesg, 120);\n    text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 449 + 43, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n\n    // DONE\n    messageItemText = getmsg(&optn_msgfl, &optnmesg, 4);\n    text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 449 + 169, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n\n    // CANCEL\n    messageItemText = getmsg(&optn_msgfl, &optnmesg, 121);\n    text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 449 + 283, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n\n    // Affect player speed\n    messageItemText = getmsg(&optn_msgfl, &optnmesg, 122);\n    text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 72 + 405, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n\n    for (i = 0; i < PREF_COUNT; i++) {\n        UpdateThing(i);\n    }\n\n    for (i = 0; i < PREF_COUNT; i++) {\n        int mouseEnterEventCode;\n        int mouseExitEventCode;\n        int mouseDownEventCode;\n        int mouseUpEventCode;\n\n        if (i >= FIRST_RANGE_PREF) {\n            x = 384;\n            y = btndat[i].knobY - 12;\n            width = 240;\n            height = 23;\n            mouseEnterEventCode = 526;\n            mouseExitEventCode = 526;\n            mouseDownEventCode = 505 + i;\n            mouseUpEventCode = 526;\n\n        } else if (i >= FIRST_SECONDARY_PREF) {\n            x = btndat[i].minX;\n            y = btndat[i].knobY - 5;\n            width = btndat[i].maxX - x;\n            height = 28;\n            mouseEnterEventCode = -1;\n            mouseExitEventCode = -1;\n            mouseDownEventCode = -1;\n            mouseUpEventCode = 505 + i;\n        } else {\n            x = btndat[i].minX;\n            y = btndat[i].knobY - 4;\n            width = btndat[i].maxX - x;\n            height = 48;\n            mouseEnterEventCode = -1;\n            mouseExitEventCode = -1;\n            mouseDownEventCode = -1;\n            mouseUpEventCode = 505 + i;\n        }\n\n        btndat[i].btn = win_register_button(prfwin, x, y, width, height, mouseEnterEventCode, mouseExitEventCode, mouseDownEventCode, mouseUpEventCode, NULL, NULL, NULL, 32);\n    }\n\n    plyrspdbid = win_register_button(prfwin,\n        383,\n        68,\n        ginfo2[PREFERENCES_WINDOW_FRM_CHECKBOX_OFF].width,\n        ginfo2[PREFERENCES_WINDOW_FRM_CHECKBOX_ON].height,\n        -1,\n        -1,\n        524,\n        524,\n        prfbmp[PREFERENCES_WINDOW_FRM_CHECKBOX_OFF],\n        prfbmp[PREFERENCES_WINDOW_FRM_CHECKBOX_ON],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x01 | BUTTON_FLAG_0x02);\n    if (plyrspdbid != -1) {\n        win_set_button_rest_state(plyrspdbid, player_speedup, 0);\n    }\n\n    win_register_button_sound_func(plyrspdbid, gsound_med_butt_press, gsound_med_butt_press);\n\n    // DEFAULT\n    btn = win_register_button(prfwin,\n        23,\n        450,\n        ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width,\n        ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height,\n        -1,\n        -1,\n        -1,\n        527,\n        prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP],\n        prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    // DONE\n    btn = win_register_button(prfwin,\n        148,\n        450,\n        ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width,\n        ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height,\n        -1,\n        -1,\n        -1,\n        504,\n        prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP],\n        prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    // CANCEL\n    btn = win_register_button(prfwin,\n        263,\n        450,\n        ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width,\n        ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height,\n        -1,\n        -1,\n        -1,\n        528,\n        prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP],\n        prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (btn != -1) {\n        win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    text_font(101);\n\n    win_draw(prfwin);\n\n    return 0;\n}\n\n// 0x490E8C\nstatic void DoThing(int eventCode)\n{\n    int x;\n    int y;\n    mouse_get_position(&x, &y);\n\n    // This preference index also contains out-of-bounds value 19,\n    // which is the only preference expressed as checkbox.\n    int preferenceIndex = eventCode - 505;\n\n    if (preferenceIndex >= FIRST_PRIMARY_PREF && preferenceIndex <= LAST_PRIMARY_PREF) {\n        PreferenceDescription* meta = &(btndat[preferenceIndex]);\n        int* valuePtr = meta->valuePtr;\n        int value = *valuePtr;\n        bool valueChanged = false;\n\n        int v1 = meta->knobX + 23;\n        int v2 = meta->knobY + 21;\n\n        if (sqrt(pow((double)x - (double)v1, 2) + pow((double)y - (double)v2, 2)) > 16.0) {\n            if (y > meta->knobY) {\n                int v14 = meta->knobY + bglby[0];\n                if (y >= v14 && y <= v14 + text_height()) {\n                    if (x >= meta->minX && x <= meta->knobX) {\n                        *valuePtr = 0;\n                        meta->direction = 0;\n                        valueChanged = true;\n                    } else {\n                        if (meta->valuesCount >= 3 && x >= meta->knobX + bglbx[2] && x <= meta->maxX) {\n                            *valuePtr = 2;\n                            meta->direction = 0;\n                            valueChanged = true;\n                        }\n                    }\n                }\n            } else {\n                if (x >= meta->knobX + 9 && x <= meta->knobX + 37) {\n                    *valuePtr = 1;\n                    if (value != 0) {\n                        meta->direction = 1;\n                    } else {\n                        meta->direction = 0;\n                    }\n                    valueChanged = true;\n                }\n            }\n\n            if (meta->valuesCount == 4) {\n                int v19 = meta->knobY + bglby[3];\n                if (y >= v19 && y <= v19 + 2 * text_height() && x >= meta->knobX + bglbx[3] && x <= meta->maxX) {\n                    *valuePtr = 3;\n                    meta->direction = 1;\n                    valueChanged = true;\n                }\n            }\n        } else {\n            if (meta->direction != 0) {\n                if (value == 0) {\n                    meta->direction = 0;\n                }\n            } else {\n                if (value == meta->valuesCount - 1) {\n                    meta->direction = 1;\n                }\n            }\n\n            if (meta->direction != 0) {\n                *valuePtr = value - 1;\n            } else {\n                *valuePtr = value + 1;\n            }\n\n            valueChanged = true;\n        }\n\n        if (valueChanged) {\n            gsound_play_sfx_file(\"ib3p1xx1\");\n            block_for_tocks(70);\n            gsound_play_sfx_file(\"ib3lu1x1\");\n            UpdateThing(preferenceIndex);\n            win_draw(prfwin);\n            changed = true;\n            return;\n        }\n    } else if (preferenceIndex >= FIRST_SECONDARY_PREF && preferenceIndex <= LAST_SECONDARY_PREF) {\n        PreferenceDescription* meta = &(btndat[preferenceIndex]);\n        int* valuePtr = meta->valuePtr;\n        int value = *valuePtr;\n        bool valueChanged = false;\n\n        int v1 = meta->knobX + 11;\n        int v2 = meta->knobY + 12;\n\n        if (sqrt(pow((double)x - (double)v1, 2) + pow((double)y - (double)v2, 2)) > 10.0) {\n            int v23 = meta->knobY - 5;\n            if (y >= v23 && y <= v23 + text_height() + 2) {\n                if (x >= meta->minX && x <= meta->knobX) {\n                    *valuePtr = preferenceIndex == PREF_COMBAT_MESSAGES ? 1 : 0;\n                    valueChanged = true;\n                } else if (x >= meta->knobX + 22.0 && x <= meta->maxX) {\n                    *valuePtr = preferenceIndex == PREF_COMBAT_MESSAGES ? 0 : 1;\n                    valueChanged = true;\n                }\n            }\n        } else {\n            *valuePtr ^= 1;\n            valueChanged = true;\n        }\n\n        if (valueChanged) {\n            gsound_play_sfx_file(\"ib2p1xx1\");\n            block_for_tocks(70);\n            gsound_play_sfx_file(\"ib2lu1x1\");\n            UpdateThing(preferenceIndex);\n            win_draw(prfwin);\n            changed = true;\n            return;\n        }\n    } else if (preferenceIndex >= FIRST_RANGE_PREF && preferenceIndex <= LAST_RANGE_PREF) {\n        PreferenceDescription* meta = &(btndat[preferenceIndex]);\n        int* valuePtr = meta->valuePtr;\n\n        gsound_play_sfx_file(\"ib1p1xx1\");\n\n        double value;\n        switch (preferenceIndex) {\n        case PREF_TEXT_BASE_DELAY:\n            value = 6.0 - text_delay + 1.0;\n            break;\n        case PREF_BRIGHTNESS:\n            value = gamma_value;\n            break;\n        case PREF_MOUSE_SENSITIVIY:\n            value = mouse_sens;\n            break;\n        default:\n            value = *valuePtr;\n            break;\n        }\n\n        int knobX = (int)(219.0 / (meta->maxValue - meta->minValue));\n        int v31 = (int)((value - meta->minValue) * (219.0 / (meta->maxValue - meta->minValue)) + 384.0);\n        buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + PREFERENCES_WINDOW_WIDTH * meta->knobY + 384, 240, 12, PREFERENCES_WINDOW_WIDTH, prefbuf + PREFERENCES_WINDOW_WIDTH * meta->knobY + 384, PREFERENCES_WINDOW_WIDTH);\n        trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_ON], 21, 12, 21, prefbuf + PREFERENCES_WINDOW_WIDTH * meta->knobY + v31, PREFERENCES_WINDOW_WIDTH);\n\n        win_draw(prfwin);\n\n        int sfxVolumeExample = 0;\n        int speechVolumeExample = 0;\n        while (true) {\n            get_input();\n\n            int tick = get_time();\n\n            mouse_get_position(&x, &y);\n\n            if (mouse_get_buttons() & 0x10) {\n                gsound_play_sfx_file(\"ib1lu1x1\");\n                UpdateThing(preferenceIndex);\n                win_draw(prfwin);\n                changed = true;\n                return;\n            }\n\n            if (v31 + 14 > x) {\n                if (v31 + 6 > x) {\n                    v31 = x - 6;\n                    if (v31 < 384) {\n                        v31 = 384;\n                    }\n                }\n            } else {\n                v31 = x - 6;\n                if (v31 > 603) {\n                    v31 = 603;\n                }\n            }\n\n            double newValue = ((double)v31 - 384.0) / (219.0 / (meta->maxValue - meta->minValue)) + meta->minValue;\n\n            int v52 = 0;\n\n            switch (preferenceIndex) {\n            case PREF_COMBAT_SPEED:\n                *meta->valuePtr = (int)newValue;\n                break;\n            case PREF_TEXT_BASE_DELAY:\n                text_delay = 6.0 - newValue + 1.0;\n                break;\n            case PREF_MASTER_VOLUME:\n                *meta->valuePtr = (int)newValue;\n                gsound_set_master_volume(master_volume);\n                v52 = 1;\n                break;\n            case PREF_MUSIC_VOLUME:\n                *meta->valuePtr = (int)newValue;\n                gsound_background_volume_set(music_volume);\n                v52 = 1;\n                break;\n            case PREF_SFX_VOLUME:\n                *meta->valuePtr = (int)newValue;\n                gsound_set_sfx_volume(sndfx_volume);\n                v52 = 1;\n                if (sfxVolumeExample == 0) {\n                    gsound_play_sfx_file(\"butin1\");\n                    sfxVolumeExample = 7;\n                } else {\n                    sfxVolumeExample--;\n                }\n                break;\n            case PREF_SPEECH_VOLUME:\n                *meta->valuePtr = (int)newValue;\n                gsound_speech_volume_set(speech_volume);\n                v52 = 1;\n                if (speechVolumeExample == 0) {\n                    gsound_speech_play(\"narrator\\\\options\", 12, 13, 15);\n                    speechVolumeExample = 40;\n                } else {\n                    speechVolumeExample--;\n                }\n                break;\n            case PREF_BRIGHTNESS:\n                gamma_value = newValue;\n                colorGamma(newValue);\n                break;\n            case PREF_MOUSE_SENSITIVIY:\n                mouse_sens = newValue;\n                break;\n            }\n\n            if (v52) {\n                int off = PREFERENCES_WINDOW_WIDTH * (meta->knobY - 12) + 384;\n                buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + off, 240, 24, PREFERENCES_WINDOW_WIDTH, prefbuf + off, PREFERENCES_WINDOW_WIDTH);\n\n                for (int optionIndex = 0; optionIndex < meta->valuesCount; optionIndex++) {\n                    const char* str = getmsg(&optn_msgfl, &optnmesg, meta->labelIds[optionIndex]);\n\n                    int x;\n                    switch (optionIndex) {\n                    case 0:\n                        // 0x4926AA\n                        x = 384;\n                        // TODO: Incomplete.\n                        break;\n                    case 1:\n                        // 0x4926F3\n                        switch (meta->valuesCount) {\n                        case 2:\n                            x = 624 - text_width(str);\n                            break;\n                        case 3:\n                            // This code path does not use floating-point arithmetic\n                            x = 504 - text_width(str) / 2 - 2;\n                            break;\n                        case 4:\n                            // Uses floating-point arithmetic\n                            x = 444 + text_width(str) / 2 - 8;\n                            break;\n                        }\n                        break;\n                    case 2:\n                        // 0x492766\n                        switch (meta->valuesCount) {\n                        case 3:\n                            x = 624 - text_width(str);\n                            break;\n                        case 4:\n                            // Uses floating-point arithmetic\n                            x = 564 - text_width(str) - 4;\n                            break;\n                        }\n                        break;\n                    case 3:\n                        // 0x49279E\n                        x = 624 - text_width(str);\n                        break;\n                    }\n                    text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * (meta->knobY - 12) + x, str, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]);\n                }\n            } else {\n                int off = PREFERENCES_WINDOW_WIDTH * meta->knobY + 384;\n                buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + off, 240, 12, PREFERENCES_WINDOW_WIDTH, prefbuf + off, PREFERENCES_WINDOW_WIDTH);\n            }\n\n            trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_ON], 21, 12, 21, prefbuf + PREFERENCES_WINDOW_WIDTH * meta->knobY + v31, PREFERENCES_WINDOW_WIDTH);\n            win_draw(prfwin);\n\n            while (elapsed_time(tick) < 35)\n                ;\n        }\n    } else if (preferenceIndex == 19) {\n        player_speedup ^= 1;\n    }\n\n    changed = true;\n}\n\n// 0x491A68\nstatic void UpdateThing(int index)\n{\n    text_font(101);\n\n    PreferenceDescription* meta = &(btndat[index]);\n\n    if (index >= FIRST_PRIMARY_PREF && index <= LAST_PRIMARY_PREF) {\n        // 0x48FC1C\n        static const int offsets[PRIMARY_PREF_COUNT] = {\n            66, // game difficulty\n            143, // combat difficulty\n            222, // violence level\n            304, // target highlight\n            382, // combat looks\n        };\n\n        int primaryOptionIndex = index - FIRST_PRIMARY_PREF;\n\n        buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * offsets[primaryOptionIndex] + 23, 160, 54, 640, prefbuf + 640 * offsets[primaryOptionIndex] + 23, 640);\n\n        for (int valueIndex = 0; valueIndex < meta->valuesCount; valueIndex++) {\n            const char* text = getmsg(&optn_msgfl, &optnmesg, meta->labelIds[valueIndex]);\n\n            char copy[100]; // TODO: Size is probably wrong.\n            strcpy(copy, text);\n\n            int x = meta->knobX + bglbx[valueIndex];\n            int len = text_width(copy);\n            switch (valueIndex) {\n            case 0:\n                x -= text_width(copy);\n                meta->minX = x;\n                break;\n            case 1:\n                x -= len / 2;\n                meta->maxX = x + len;\n                break;\n            case 2:\n            case 3:\n                meta->maxX = x + len;\n                break;\n            }\n\n            char* p = copy;\n            while (*p != '\\0' && *p != ' ') {\n                p++;\n            }\n\n            int y = meta->knobY + bglby[valueIndex];\n            const char* s;\n            if (*p != '\\0') {\n                *p = '\\0';\n                text_to_buf(prefbuf + 640 * y + x, copy, 640, 640, colorTable[18979]);\n                s = p + 1;\n                y += text_height();\n            } else {\n                s = copy;\n            }\n\n            text_to_buf(prefbuf + 640 * y + x, s, 640, 640, colorTable[18979]);\n        }\n\n        int value = *(meta->valuePtr);\n        trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_PRIMARY_SWITCH] + (46 * 47) * value, 46, 47, 46, prefbuf + 640 * meta->knobY + meta->knobX, 640);\n    } else if (index >= FIRST_SECONDARY_PREF && index <= LAST_SECONDARY_PREF) {\n        // 0x48FC30\n        static const int offsets[SECONDARY_PREF_COUNT] = {\n            66, // combat messages\n            133, // combat taunts\n            200, // language filter\n            264, // running\n            331, // subtitles\n            397, // item highlight\n        };\n\n        int secondaryOptionIndex = index - FIRST_SECONDARY_PREF;\n\n        buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * offsets[secondaryOptionIndex] + 251, 113, 34, 640, prefbuf + 640 * offsets[secondaryOptionIndex] + 251, 640);\n\n        // Secondary options are booleans, so it's index is also it's value.\n        for (int value = 0; value < 2; value++) {\n            const char* text = getmsg(&optn_msgfl, &optnmesg, meta->labelIds[value]);\n\n            int x;\n            if (value) {\n                x = meta->knobX + smlbx[value];\n                meta->maxX = x + text_width(text);\n            } else {\n                x = meta->knobX + smlbx[value] - text_width(text);\n                meta->minX = x;\n            }\n            text_to_buf(prefbuf + 640 * (meta->knobY - 5) + x, text, 640, 640, colorTable[18979]);\n        }\n\n        int value = *(meta->valuePtr);\n        if (index == PREF_COMBAT_MESSAGES) {\n            value ^= 1;\n        }\n        trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_SECONDARY_SWITCH] + (22 * 25) * value, 22, 25, 22, prefbuf + 640 * meta->knobY + meta->knobX, 640);\n    } else if (index >= FIRST_RANGE_PREF && index <= LAST_RANGE_PREF) {\n        buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * (meta->knobY - 12) + 384, 240, 24, 640, prefbuf + 640 * (meta->knobY - 12) + 384, 640);\n        switch (index) {\n        case PREF_COMBAT_SPEED:\n            if (1) {\n                double value = *meta->valuePtr;\n                value = min(max(value, 0.0), 50.0);\n\n                int x = (int)((value - meta->minValue) * 219.0 / (meta->maxValue - meta->minValue) + 384.0);\n                trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640);\n            }\n            break;\n        case PREF_TEXT_BASE_DELAY:\n            if (1) {\n                text_delay = min(max(text_delay, 1.0), 6.0);\n\n                int x = (int)((6.0 - text_delay) * 43.8 + 384.0);\n                trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640);\n\n                double value = (text_delay - 1.0) * 0.2 * 2.0;\n                value = min(max(value, 0.0), 2.0);\n\n                text_object_set_base_delay(text_delay);\n                text_object_set_line_delay(value);\n            }\n            break;\n        case PREF_MASTER_VOLUME:\n        case PREF_MUSIC_VOLUME:\n        case PREF_SFX_VOLUME:\n        case PREF_SPEECH_VOLUME:\n            if (1) {\n                double value = *meta->valuePtr;\n                value = min(max(value, meta->minValue), meta->maxValue);\n\n                int x = (int)((value - meta->minValue) * 219.0 / (meta->maxValue - meta->minValue) + 384.0);\n                trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640);\n\n                switch (index) {\n                case PREF_MASTER_VOLUME:\n                    gsound_set_master_volume(master_volume);\n                    break;\n                case PREF_MUSIC_VOLUME:\n                    gsound_background_volume_set(music_volume);\n                    break;\n                case PREF_SFX_VOLUME:\n                    gsound_set_sfx_volume(sndfx_volume);\n                    break;\n                case PREF_SPEECH_VOLUME:\n                    gsound_speech_volume_set(speech_volume);\n                    break;\n                }\n            }\n            break;\n        case PREF_BRIGHTNESS:\n            if (1) {\n                gamma_value = min(max(gamma_value, 1.0), 1.17999267578125);\n\n                int x = (int)((gamma_value - meta->minValue) * (219.0 / (meta->maxValue - meta->minValue)) + 384.0);\n                trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640);\n\n                colorGamma(gamma_value);\n            }\n            break;\n        case PREF_MOUSE_SENSITIVIY:\n            if (1) {\n                mouse_sens = min(max(mouse_sens, 1.0), 2.5);\n\n                int x = (int)((mouse_sens - meta->minValue) * (219.0 / (meta->maxValue - meta->minValue)) + 384.0);\n                trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640);\n\n                mouse_set_sensitivity(mouse_sens);\n            }\n            break;\n        }\n\n        for (int optionIndex = 0; optionIndex < meta->valuesCount; optionIndex++) {\n            const char* str = getmsg(&optn_msgfl, &optnmesg, meta->labelIds[optionIndex]);\n\n            int x;\n            switch (optionIndex) {\n            case 0:\n                // 0x4926AA\n                x = 384;\n                // TODO: Incomplete.\n                break;\n            case 1:\n                // 0x4926F3\n                switch (meta->valuesCount) {\n                case 2:\n                    x = 624 - text_width(str);\n                    break;\n                case 3:\n                    // This code path does not use floating-point arithmetic\n                    x = 504 - text_width(str) / 2 - 2;\n                    break;\n                case 4:\n                    // Uses floating-point arithmetic\n                    x = 444 + text_width(str) / 2 - 8;\n                    break;\n                }\n                break;\n            case 2:\n                // 0x492766\n                switch (meta->valuesCount) {\n                case 3:\n                    x = 624 - text_width(str);\n                    break;\n                case 4:\n                    // Uses floating-point arithmetic\n                    x = 564 - text_width(str) - 4;\n                    break;\n                }\n                break;\n            case 3:\n                // 0x49279E\n                x = 624 - text_width(str);\n                break;\n            }\n            text_to_buf(prefbuf + 640 * (meta->knobY - 12) + x, str, 640, 640, colorTable[18979]);\n        }\n    } else {\n        // return false;\n    }\n\n    // TODO: Incomplete.\n\n    // return true;\n}\n\n// 0x492870\nstatic int PrefEnd()\n{\n    if (changed) {\n        SavePrefs(1);\n        JustUpdate();\n        combat_highlight_change();\n    }\n\n    win_delete(prfwin);\n\n    for (int index = 0; index < PREFERENCES_WINDOW_FRM_COUNT; index++) {\n        art_ptr_unlock(grphkey2[index]);\n    }\n\n    return 0;\n}\n\n// 0x4928B8\nint init_options_menu()\n{\n    for (int index = 0; index < 11; index++) {\n        btndat[index].direction = 0;\n    }\n\n    SetSystemPrefs();\n\n    InitGreyTable(0, 255);\n\n    return 0;\n}\n\n// 0x4928E4\nvoid IncGamma()\n{\n    gamma_value = GAMMA_MIN;\n    config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gamma_value);\n\n    if (gamma_value < GAMMA_MAX) {\n        gamma_value += GAMMA_STEP;\n\n        if (gamma_value >= GAMMA_MIN) {\n            if (gamma_value > GAMMA_MAX) {\n                gamma_value = GAMMA_MAX;\n            }\n        } else {\n            gamma_value = GAMMA_MIN;\n        }\n\n        colorGamma(gamma_value);\n\n        config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gamma_value);\n\n        gconfig_save();\n    }\n}\n\n// 0x4929C8\nvoid DecGamma()\n{\n    gamma_value = GAMMA_MIN;\n    config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gamma_value);\n\n    if (gamma_value > GAMMA_MIN) {\n        gamma_value -= GAMMA_STEP;\n\n        if (gamma_value >= GAMMA_MIN) {\n            if (gamma_value > GAMMA_MAX) {\n                gamma_value = GAMMA_MAX;\n            }\n        } else {\n            gamma_value = GAMMA_MIN;\n        }\n\n        colorGamma(gamma_value);\n\n        config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gamma_value);\n\n        gconfig_save();\n    }\n}\n\n// 0x492AA8\nstatic void SetSystemPrefs()\n{\n    SetDefaults(false);\n\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &game_difficulty);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combat_difficulty);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violence_level);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &target_highlight);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, &combat_messages);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, &combatLookValue);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, &combat_taunts);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &language_filter);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, &prf_running);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &subtitles);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, &item_highlight);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, &combat_speed);\n    config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, &text_delay);\n    config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, &player_speedup);\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, &master_volume);\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, &music_volume);\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, &sndfx_volume);\n    config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, &speech_volume);\n    config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gamma_value);\n    config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, &mouse_sens);\n\n    JustUpdate();\n}\n\n// 0x492CB0\nstatic int SavePrefs(bool save)\n{\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, game_difficulty);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, combat_difficulty);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, violence_level);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, target_highlight);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, combat_messages);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, combatLookValue);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, combat_taunts);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, language_filter);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, prf_running);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, subtitles);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, item_highlight);\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, combat_speed);\n    config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, text_delay);\n\n    double textLineDelay = (text_delay - 1.0) / 5.0 * 2.0;\n    if (textLineDelay >= 0.0) {\n        if (textLineDelay > 2.0) {\n            textLineDelay = 2.0;\n        }\n\n        config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, textLineDelay);\n    } else {\n        config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, 0.0);\n    }\n\n    config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, player_speedup);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, master_volume);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, music_volume);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, sndfx_volume);\n    config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, speech_volume);\n\n    config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gamma_value);\n    config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, mouse_sens);\n\n    if (save) {\n        gconfig_save();\n    }\n\n    return 0;\n}\n\n// 0x492F60\nstatic void SetDefaults(bool a1)\n{\n    combat_difficulty = COMBAT_DIFFICULTY_NORMAL;\n    violence_level = VIOLENCE_LEVEL_MAXIMUM_BLOOD;\n    target_highlight = TARGET_HIGHLIGHT_TARGETING_ONLY;\n    combat_messages = 1;\n    combatLookValue = 0;\n    combat_taunts = 1;\n    prf_running = 0;\n    subtitles = 0;\n    item_highlight = 1;\n    combat_speed = 0;\n    player_speedup = 0;\n    text_delay = 3.5;\n    gamma_value = 1.0;\n    mouse_sens = 1.0;\n    game_difficulty = 1;\n    language_filter = 0;\n    master_volume = 22281;\n    music_volume = 22281;\n    sndfx_volume = 22281;\n    speech_volume = 22281;\n\n    if (a1) {\n        for (int index = 0; index < PREF_COUNT; index++) {\n            UpdateThing(index);\n        }\n        win_set_button_rest_state(plyrspdbid, player_speedup, 0);\n        win_draw(prfwin);\n        changed = true;\n    }\n}\n\n// 0x493054\nstatic void SaveSettings()\n{\n    settings_backup[PREF_GAME_DIFFICULTY] = game_difficulty;\n    settings_backup[PREF_COMBAT_DIFFICULTY] = combat_difficulty;\n    settings_backup[PREF_VIOLENCE_LEVEL] = violence_level;\n    settings_backup[PREF_TARGET_HIGHLIGHT] = target_highlight;\n    settings_backup[PREF_COMBAT_LOOKS] = combatLookValue;\n    settings_backup[PREF_COMBAT_MESSAGES] = combat_messages;\n    settings_backup[PREF_COMBAT_TAUNTS] = combat_taunts;\n    settings_backup[PREF_LANGUAGE_FILTER] = language_filter;\n    settings_backup[PREF_RUNNING] = prf_running;\n    settings_backup[PREF_SUBTITLES] = subtitles;\n    settings_backup[PREF_ITEM_HIGHLIGHT] = item_highlight;\n    settings_backup[PREF_COMBAT_SPEED] = combat_speed;\n    settings_backup[PREF_TEXT_BASE_DELAY] = player_speedup;\n    settings_backup[PREF_MASTER_VOLUME] = master_volume;\n    text_delay_back = text_delay;\n    settings_backup[PREF_MUSIC_VOLUME] = music_volume;\n    gamma_value_back = gamma_value;\n    settings_backup[PREF_SFX_VOLUME] = sndfx_volume;\n    mouse_sens_back = mouse_sens;\n    settings_backup[PREF_SPEECH_VOLUME] = speech_volume;\n}\n\n// 0x493128\nstatic void RestoreSettings()\n{\n    game_difficulty = settings_backup[PREF_GAME_DIFFICULTY];\n    combat_difficulty = settings_backup[PREF_COMBAT_DIFFICULTY];\n    violence_level = settings_backup[PREF_VIOLENCE_LEVEL];\n    target_highlight = settings_backup[PREF_TARGET_HIGHLIGHT];\n    combatLookValue = settings_backup[PREF_COMBAT_LOOKS];\n    combat_messages = settings_backup[PREF_COMBAT_MESSAGES];\n    combat_taunts = settings_backup[PREF_COMBAT_TAUNTS];\n    language_filter = settings_backup[PREF_LANGUAGE_FILTER];\n    prf_running = settings_backup[PREF_RUNNING];\n    subtitles = settings_backup[PREF_SUBTITLES];\n    item_highlight = settings_backup[PREF_ITEM_HIGHLIGHT];\n    combat_speed = settings_backup[PREF_COMBAT_SPEED];\n    player_speedup = settings_backup[PREF_TEXT_BASE_DELAY];\n    master_volume = settings_backup[PREF_MASTER_VOLUME];\n    text_delay = text_delay_back;\n    music_volume = settings_backup[PREF_MUSIC_VOLUME];\n    gamma_value = gamma_value_back;\n    sndfx_volume = settings_backup[PREF_SFX_VOLUME];\n    mouse_sens = mouse_sens_back;\n    speech_volume = settings_backup[PREF_SPEECH_VOLUME];\n\n    JustUpdate();\n}\n\n// 0x4931F8\nstatic void JustUpdate()\n{\n    game_difficulty = min(max(game_difficulty, 0), 2);\n    combat_difficulty = min(max(combat_difficulty, 0), 2);\n    violence_level = min(max(violence_level, 0), 3);\n    target_highlight = min(max(target_highlight, 0), 2);\n    combat_messages = min(max(combat_messages, 0), 1);\n    combatLookValue = min(max(combatLookValue, 0), 1);\n    combat_taunts = min(max(combat_taunts, 0), 1);\n    language_filter = min(max(language_filter, 0), 1);\n    prf_running = min(max(prf_running, 0), 1);\n    subtitles = min(max(subtitles, 0), 1);\n    item_highlight = min(max(item_highlight, 0), 1);\n    combat_speed = min(max(combat_speed, 0), 50);\n    player_speedup = min(max(player_speedup, 0), 1);\n    text_delay = min(max(text_delay, 1.0), 6.0);\n    master_volume = min(max(master_volume, 0), VOLUME_MAX);\n    music_volume = min(max(music_volume, 0), VOLUME_MAX);\n    sndfx_volume = min(max(sndfx_volume, 0), VOLUME_MAX);\n    speech_volume = min(max(speech_volume, 0), VOLUME_MAX);\n    gamma_value = min(max(gamma_value, 1.0), 1.17999267578125);\n    mouse_sens = min(max(mouse_sens, 1.0), 2.5);\n\n    text_object_set_base_delay(text_delay);\n    gmouse_3d_synch_item_highlight();\n\n    double textLineDelay = (text_delay + (-1.0)) * 0.2 * 2.0;\n    textLineDelay = min(max(textLineDelay, 0.0), 2.0);\n\n    text_object_set_line_delay(textLineDelay);\n    combatai_refresh_messages();\n    scr_message_free();\n    gsound_set_master_volume(master_volume);\n    gsound_background_volume_set(music_volume);\n    gsound_set_sfx_volume(sndfx_volume);\n    gsound_speech_volume_set(speech_volume);\n    mouse_set_sensitivity(mouse_sens);\n    colorGamma(gamma_value);\n}\n\n// 0x493224\nint save_options(File* stream)\n{\n    float textBaseDelay = (float)text_delay;\n    float brightness = (float)gamma_value;\n    float mouseSensitivity = (float)mouse_sens;\n\n    if (db_fwriteInt(stream, game_difficulty) == -1) goto err;\n    if (db_fwriteInt(stream, combat_difficulty) == -1) goto err;\n    if (db_fwriteInt(stream, violence_level) == -1) goto err;\n    if (db_fwriteInt(stream, target_highlight) == -1) goto err;\n    if (db_fwriteInt(stream, combatLookValue) == -1) goto err;\n    if (db_fwriteInt(stream, combat_messages) == -1) goto err;\n    if (db_fwriteInt(stream, combat_taunts) == -1) goto err;\n    if (db_fwriteInt(stream, language_filter) == -1) goto err;\n    if (db_fwriteInt(stream, prf_running) == -1) goto err;\n    if (db_fwriteInt(stream, subtitles) == -1) goto err;\n    if (db_fwriteInt(stream, item_highlight) == -1) goto err;\n    if (db_fwriteInt(stream, combat_speed) == -1) goto err;\n    if (db_fwriteInt(stream, player_speedup) == -1) goto err;\n    if (db_fwriteFloat(stream, textBaseDelay) == -1) goto err;\n    if (db_fwriteInt(stream, master_volume) == -1) goto err;\n    if (db_fwriteInt(stream, music_volume) == -1) goto err;\n    if (db_fwriteInt(stream, sndfx_volume) == -1) goto err;\n    if (db_fwriteInt(stream, speech_volume) == -1) goto err;\n    if (db_fwriteFloat(stream, brightness) == -1) goto err;\n    if (db_fwriteFloat(stream, mouseSensitivity) == -1) goto err;\n\n    return 0;\n\nerr:\n\n    debug_printf(\"\\nOPTION MENU: Error save option data!\\n\");\n\n    return -1;\n}\n\n// 0x49340C\nint load_options(File* stream)\n{\n    float textBaseDelay;\n    float brightness;\n    float mouseSensitivity;\n\n    SetDefaults(false);\n\n    if (db_freadInt(stream, &game_difficulty) == -1) goto err;\n    if (db_freadInt(stream, &combat_difficulty) == -1) goto err;\n    if (db_freadInt(stream, &violence_level) == -1) goto err;\n    if (db_freadInt(stream, &target_highlight) == -1) goto err;\n    if (db_freadInt(stream, &combatLookValue) == -1) goto err;\n    if (db_freadInt(stream, &combat_messages) == -1) goto err;\n    if (db_freadInt(stream, &combat_taunts) == -1) goto err;\n    if (db_freadInt(stream, &language_filter) == -1) goto err;\n    if (db_freadInt(stream, &prf_running) == -1) goto err;\n    if (db_freadInt(stream, &subtitles) == -1) goto err;\n    if (db_freadInt(stream, &item_highlight) == -1) goto err;\n    if (db_freadInt(stream, &combat_speed) == -1) goto err;\n    if (db_freadInt(stream, &player_speedup) == -1) goto err;\n    if (db_freadFloat(stream, &textBaseDelay) == -1) goto err;\n    if (db_freadInt(stream, &master_volume) == -1) goto err;\n    if (db_freadInt(stream, &music_volume) == -1) goto err;\n    if (db_freadInt(stream, &sndfx_volume) == -1) goto err;\n    if (db_freadInt(stream, &speech_volume) == -1) goto err;\n    if (db_freadFloat(stream, &brightness) == -1) goto err;\n    if (db_freadFloat(stream, &mouseSensitivity) == -1) goto err;\n\n    gamma_value = brightness;\n    mouse_sens = mouseSensitivity;\n    text_delay = textBaseDelay;\n\n    JustUpdate();\n    SavePrefs(0);\n\n    return 0;\n\nerr:\n\n    debug_printf(\"\\nOPTION MENU: Error loading option data!, using defaults.\\n\");\n\n    SetDefaults(false);\n    JustUpdate();\n    SavePrefs(0);\n\n    return -1;\n}\n"
  },
  {
    "path": "src/game/options.h",
    "content": "#ifndef FALLOUT_GAME_OPTIONS_H_\n#define FALLOUT_GAME_OPTIONS_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n\nint do_options();\nint do_optionsFunc(int initialKeyCode);\nint PauseWindow(bool a1);\nint init_options_menu();\nint save_options(File* stream);\nint load_options(File* stream);\nvoid IncGamma();\nvoid DecGamma();\n\n#endif /* FALLOUT_GAME_OPTIONS_H_ */\n"
  },
  {
    "path": "src/game/palette.c",
    "content": "#include \"game/palette.h\"\n\n#include <string.h>\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/gsound.h\"\n\n// 0x6639D0\nstatic unsigned char current_palette[256 * 3];\n\n// 0x663CD0\nunsigned char white_palette[256 * 3];\n\n// 0x663FD0\nunsigned char black_palette[256 * 3];\n\n// 0x6642D0\nstatic int fade_steps;\n\n// 0x493A00\nvoid palette_init()\n{\n    memset(black_palette, 0, 256 * 3);\n    memset(white_palette, 63, 256 * 3);\n    memcpy(current_palette, cmap, 256 * 3);\n\n    unsigned int tick = get_time();\n    if (gsound_background_is_enabled() || gsound_speech_is_enabled()) {\n        colorSetFadeBkFunc(soundContinueAll);\n    }\n\n    fadeSystemPalette(current_palette, current_palette, 60);\n\n    colorSetFadeBkFunc(NULL);\n\n    unsigned int diff = elapsed_time(tick);\n\n    // NOTE: Modern CPUs are super fast, so it's possible that less than 10ms\n    // (the resolution of underlying GetTicks) is needed to fade between two\n    // palettes, which leads to zero diff, which in turn leads to unpredictable\n    // number of fade steps. To fix that the fallback value is used (46). This\n    // value is commonly seen when running the game in 1 core VM.\n    if (diff == 0) {\n        diff = 46;\n    }\n\n    fade_steps = (int)(60.0 / (diff * (1.0 / 700.0)));\n\n    debug_printf(\"\\nFade time is %u\\nFade steps are %d\\n\", diff, fade_steps);\n}\n\n// NOTE: Uncollapsed 0x493AD0.\nvoid palette_reset()\n{\n}\n\n// NOTE: Uncollapsed 0x493AD0.\nvoid palette_exit()\n{\n}\n\n// 0x493AD4\nvoid palette_fade_to(unsigned char* palette)\n{\n    bool colorCycleWasEnabled = cycle_is_enabled();\n    cycle_disable();\n\n    if (gsound_background_is_enabled() || gsound_speech_is_enabled()) {\n        colorSetFadeBkFunc(soundContinueAll);\n    }\n\n    fadeSystemPalette(current_palette, palette, fade_steps);\n    colorSetFadeBkFunc(NULL);\n\n    memcpy(current_palette, palette, 768);\n\n    if (colorCycleWasEnabled) {\n        cycle_enable();\n    }\n}\n\n// 0x493B48\nvoid palette_set_to(unsigned char* palette)\n{\n    memcpy(current_palette, palette, sizeof(current_palette));\n    setSystemPalette(palette);\n}\n\n// 0x493B78\nvoid palette_set_entries(unsigned char* palette, int start, int end)\n{\n    memcpy(current_palette + 3 * start, palette, 3 * (end - start + 1));\n    setSystemPaletteEntries(palette, start, end);\n}\n"
  },
  {
    "path": "src/game/palette.h",
    "content": "#ifndef FALLOUT_GAME_PALETTE_H_\n#define FALLOUT_GAME_PALETTE_H_\n\nextern unsigned char white_palette[256 * 3];\nextern unsigned char black_palette[256 * 3];\n\nvoid palette_init();\nvoid _palette_reset_();\nvoid palette_reset();\nvoid palette_exit();\nvoid palette_fade_to(unsigned char* palette);\nvoid palette_set_to(unsigned char* palette);\nvoid palette_set_entries(unsigned char* palette, int start, int end);\n\n#endif /* FALLOUT_GAME_PALETTE_H_ */\n"
  },
  {
    "path": "src/game/party.c",
    "content": "#include \"game/party.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"plib/color/color.h\"\n#include \"game/combatai.h\"\n#include \"game/config.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/gdialog.h\"\n#include \"game/item.h\"\n#include \"game/loadsave.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/strparse.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/gnw.h\"\n\ntypedef struct PartyMemberAI {\n    bool area_attack_mode[AREA_ATTACK_MODE_COUNT];\n    bool run_away_mode[RUN_AWAY_MODE_COUNT];\n    bool best_weapon[BEST_WEAPON_COUNT];\n    bool distance_mode[DISTANCE_COUNT];\n    bool attack_who[ATTACK_WHO_COUNT];\n    bool chem_use[CHEM_USE_COUNT];\n    bool disposition[DISPOSITION_COUNT];\n    int level_minimum;\n    int level_up_every;\n    int level_pids_num;\n    int level_pids[5];\n} PartyMemberAI;\n\n// TODO: Rename fields.\ntypedef struct PartyMemberLevelUpInfo {\n    int field_0;\n    int field_4; // party member level\n    int field_8; // early what?\n} PartyMemberLevelUpInfo;\n\n// TODO: Not sure if the same struct is used in `itemSaveListHead` and\n// `partyMemberList`.\ntypedef struct PartyMember {\n    Object* object;\n    Script* script;\n    int* vars;\n    struct PartyMember* next;\n} PartyMember;\n\nstatic int partyMemberGetAIOptions(Object* object, PartyMemberAI** aiOptionsPtr);\nstatic void partyMemberAISlotInit(PartyMemberAI* aiOptions);\nstatic int partyMemberSlotInit(int index);\nstatic int partyMemberPrepLoadInstance(PartyMember* partyMember);\nstatic int partyMemberRecoverLoadInstance(PartyMember* partyMember);\nstatic int partyMemberNewObjID();\nstatic int partyMemberNewObjIDRecurseFind(Object* object, int objectId);\nstatic int partyMemberPrepItemSave(Object* object);\nstatic int partyMemberItemSave(Object* object);\nstatic int partyMemberItemRecover(PartyMember* partyMember);\nstatic int partyMemberClearItemList();\nstatic int partyFixMultipleMembers();\nstatic int partyMemberCopyLevelInfo(Object* object, int a2);\n\n// 0x519D9C\nint partyMemberMaxCount = 0;\n\n// 0x519DA0\nint* partyMemberPidList = NULL;\n\n// 0x519DA4\nPartyMember* itemSaveListHead = NULL;\n\n// List of party members, it's length is [partyMemberMaxCount] + 20.\n//\n// 0x519DA8\nstatic PartyMember* partyMemberList = NULL;\n\n// Number of critters added to party.\n//\n// 0x519DAC\nstatic int partyMemberCount = 0;\n\n// 0x519DB0\nstatic int partyMemberItemCount = 20000;\n\n// 0x519DB4\nstatic int partyStatePrepped = 0;\n\n// 0x519DB8\nstatic PartyMemberAI* partyMemberAIOptions = NULL;\n\n// 0x519DBC\nstatic PartyMemberLevelUpInfo* partyMemberLevelUpInfoList = NULL;\n\n// 0x493BC0\nint partyMember_init()\n{\n    Config config;\n\n    partyMemberMaxCount = 0;\n\n    if (!config_init(&config)) {\n        return -1;\n    }\n\n    if (!config_load(&config, \"data\\\\party.txt\", true)) {\n        goto err;\n    }\n\n    char section[50];\n    sprintf(section, \"Party Member %d\", partyMemberMaxCount);\n\n    int partyMemberPid;\n    while (config_get_value(&config, section, \"party_member_pid\", &partyMemberPid)) {\n        partyMemberMaxCount++;\n        sprintf(section, \"Party Member %d\", partyMemberMaxCount);\n    }\n\n    partyMemberPidList = (int*)mem_malloc(sizeof(*partyMemberPidList) * partyMemberMaxCount);\n    if (partyMemberPidList == NULL) {\n        goto err;\n    }\n\n    memset(partyMemberPidList, 0, sizeof(*partyMemberPidList) * partyMemberMaxCount);\n\n    partyMemberList = (PartyMember*)mem_malloc(sizeof(*partyMemberList) * (partyMemberMaxCount + 20));\n    if (partyMemberList == NULL) {\n        goto err;\n    }\n\n    memset(partyMemberList, 0, sizeof(*partyMemberList) * (partyMemberMaxCount + 20));\n\n    partyMemberAIOptions = (PartyMemberAI*)mem_malloc(sizeof(*partyMemberAIOptions) * partyMemberMaxCount);\n    if (partyMemberAIOptions == NULL) {\n        goto err;\n    }\n\n    memset(partyMemberAIOptions, 0, sizeof(*partyMemberAIOptions) * partyMemberMaxCount);\n\n    partyMemberLevelUpInfoList = (PartyMemberLevelUpInfo*)mem_malloc(sizeof(*partyMemberLevelUpInfoList) * partyMemberMaxCount);\n    if (partyMemberLevelUpInfoList == NULL) goto err;\n\n    memset(partyMemberLevelUpInfoList, 0, sizeof(*partyMemberLevelUpInfoList) * partyMemberMaxCount);\n\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        sprintf(section, \"Party Member %d\", index);\n\n        if (!config_get_value(&config, section, \"party_member_pid\", &partyMemberPid)) {\n            break;\n        }\n\n        PartyMemberAI* aiOptions = &(partyMemberAIOptions[index]);\n\n        partyMemberPidList[index] = partyMemberPid;\n\n        partyMemberAISlotInit(aiOptions);\n\n        char* string;\n\n        if (config_get_string(&config, section, \"area_attack_mode\", &string)) {\n            while (*string != '\\0') {\n                int areaAttackMode;\n                strParseStrFromList(&string, &areaAttackMode, area_attack_mode_strs, AREA_ATTACK_MODE_COUNT);\n                aiOptions->area_attack_mode[areaAttackMode] = true;\n            }\n        }\n\n        if (config_get_string(&config, section, \"attack_who\", &string)) {\n            while (*string != '\\0') {\n                int attachWho;\n                strParseStrFromList(&string, &attachWho, attack_who_mode_strs, ATTACK_WHO_COUNT);\n                aiOptions->attack_who[attachWho] = true;\n            }\n        }\n\n        if (config_get_string(&config, section, \"best_weapon\", &string)) {\n            while (*string != '\\0') {\n                int bestWeapon;\n                strParseStrFromList(&string, &bestWeapon, weapon_pref_strs, BEST_WEAPON_COUNT);\n                aiOptions->best_weapon[bestWeapon] = true;\n            }\n        }\n\n        if (config_get_string(&config, section, \"chem_use\", &string)) {\n            while (*string != '\\0') {\n                int chemUse;\n                strParseStrFromList(&string, &chemUse, chem_use_mode_strs, CHEM_USE_COUNT);\n                aiOptions->chem_use[chemUse] = true;\n            }\n        }\n\n        if (config_get_string(&config, section, \"distance\", &string)) {\n            while (*string != '\\0') {\n                int distanceMode;\n                strParseStrFromList(&string, &distanceMode, distance_pref_strs, DISTANCE_COUNT);\n                aiOptions->distance_mode[distanceMode] = true;\n            }\n        }\n\n        if (config_get_string(&config, section, \"run_away_mode\", &string)) {\n            while (*string != '\\0') {\n                int runAwayMode;\n                strParseStrFromList(&string, &runAwayMode, run_away_mode_strs, RUN_AWAY_MODE_COUNT);\n                aiOptions->run_away_mode[runAwayMode] = true;\n            }\n        }\n\n        if (config_get_string(&config, section, \"disposition\", &string)) {\n            while (*string != '\\0') {\n                int disposition;\n                strParseStrFromList(&string, &disposition, disposition_strs, DISPOSITION_COUNT);\n                aiOptions->disposition[disposition] = true;\n            }\n        }\n\n        int levelUpEvery;\n        if (config_get_value(&config, section, \"level_up_every\", &levelUpEvery)) {\n            aiOptions->level_up_every = levelUpEvery;\n\n            int levelMinimum;\n            if (config_get_value(&config, section, \"level_minimum\", &levelMinimum)) {\n                aiOptions->level_minimum = levelMinimum;\n            }\n\n            if (config_get_string(&config, section, \"level_pids\", &string)) {\n                while (*string != '\\0' && aiOptions->level_pids_num < 5) {\n                    int levelPid;\n                    strParseValue(&string, &levelPid);\n                    aiOptions->level_pids[aiOptions->level_pids_num] = levelPid;\n                    aiOptions->level_pids_num++;\n                }\n            }\n        }\n    }\n\n    config_exit(&config);\n\n    return 0;\n\nerr:\n\n    config_exit(&config);\n\n    return -1;\n}\n\n// 0x4940E4\nvoid partyMember_reset()\n{\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        // NOTE: Uninline.\n        partyMemberSlotInit(index);\n    }\n}\n\n// 0x494134\nvoid partyMember_exit()\n{\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        // NOTE: Uninline.\n        partyMemberSlotInit(index);\n    }\n\n    partyMemberMaxCount = 0;\n\n    if (partyMemberPidList != NULL) {\n        mem_free(partyMemberPidList);\n        partyMemberPidList = NULL;\n    }\n\n    if (partyMemberList != NULL) {\n        mem_free(partyMemberList);\n        partyMemberList = NULL;\n    }\n\n    if (partyMemberAIOptions != NULL) {\n        mem_free(partyMemberAIOptions);\n        partyMemberAIOptions = NULL;\n    }\n\n    if (partyMemberLevelUpInfoList != NULL) {\n        mem_free(partyMemberLevelUpInfoList);\n        partyMemberLevelUpInfoList = NULL;\n    }\n}\n\n// 0x4941F0\nstatic int partyMemberGetAIOptions(Object* object, PartyMemberAI** aiOptionsPtr)\n{\n    for (int index = 1; index < partyMemberMaxCount; index++) {\n        if (partyMemberPidList[index] == object->pid) {\n            *aiOptionsPtr = &(partyMemberAIOptions[index]);\n            return 0;\n        }\n    }\n\n    return -1;\n}\n\n// 0x49425C\nstatic void partyMemberAISlotInit(PartyMemberAI* aiOptions)\n{\n    for (int index = 0; index < AREA_ATTACK_MODE_COUNT; index++) {\n        aiOptions->area_attack_mode[index] = 0;\n    }\n\n    for (int index = 0; index < RUN_AWAY_MODE_COUNT; index++) {\n        aiOptions->run_away_mode[index] = 0;\n    }\n\n    for (int index = 0; index < BEST_WEAPON_COUNT; index++) {\n        aiOptions->best_weapon[index] = 0;\n    }\n\n    for (int index = 0; index < DISTANCE_COUNT; index++) {\n        aiOptions->distance_mode[index] = 0;\n    }\n\n    for (int index = 0; index < ATTACK_WHO_COUNT; index++) {\n        aiOptions->attack_who[index] = 0;\n    }\n\n    for (int index = 0; index < CHEM_USE_COUNT; index++) {\n        aiOptions->chem_use[index] = 0;\n    }\n\n    for (int index = 0; index < DISPOSITION_COUNT; index++) {\n        aiOptions->disposition[index] = 0;\n    }\n\n    aiOptions->level_minimum = 0;\n    aiOptions->level_up_every = 0;\n    aiOptions->level_pids_num = 0;\n\n    aiOptions->level_pids[0] = -1;\n\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        // NOTE: Uninline.\n        partyMemberSlotInit(index);\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x494340\nstatic int partyMemberSlotInit(int index)\n{\n    if (index >= partyMemberMaxCount) {\n        return -1;\n    }\n\n    partyMemberLevelUpInfoList[index].field_0 = 0;\n    partyMemberLevelUpInfoList[index].field_4 = 0;\n    partyMemberLevelUpInfoList[index].field_8 = 0;\n\n    return 0;\n}\n\n// 0x494378\nint partyMemberAdd(Object* object)\n{\n    if (partyMemberCount >= partyMemberMaxCount + 20) {\n        return -1;\n    }\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n        if (partyMember->object == object || partyMember->object->pid == object->pid) {\n            return 0;\n        }\n    }\n\n    if (partyStatePrepped) {\n        debug_printf(\"\\npartyMemberAdd DENIED: %s\\n\", critter_name(object));\n        return -1;\n    }\n\n    PartyMember* partyMember = &(partyMemberList[partyMemberCount]);\n    partyMember->object = object;\n    partyMember->script = NULL;\n    partyMember->vars = NULL;\n\n    object->id = (object->pid & 0xFFFFFF) + 18000;\n    object->flags |= (OBJECT_FLAG_0x400 | OBJECT_TEMPORARY);\n\n    partyMemberCount++;\n\n    Script* script;\n    if (scr_ptr(object->sid, &script) != -1) {\n        script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n        script->field_1C = object->id;\n\n        object->sid = ((object->pid & 0xFFFFFF) + 18000) | (object->sid & 0xFF000000);\n        script->sid = object->sid;\n    }\n\n    combatai_switch_team(object, 0);\n    queue_remove_this(object, EVENT_TYPE_SCRIPT);\n\n    if (gdialogActive()) {\n        if (object == dialog_target) {\n            gdialogUpdatePartyStatus();\n        }\n    }\n\n    return 0;\n}\n\n// 0x4944DC\nint partyMemberRemove(Object* object)\n{\n    if (partyMemberCount == 0) {\n        return -1;\n    }\n\n    if (object == NULL) {\n        return -1;\n    }\n\n    int index;\n    for (index = 1; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n        if (partyMember->object == object) {\n            break;\n        }\n    }\n\n    if (index == partyMemberCount) {\n        return -1;\n    }\n\n    if (partyStatePrepped) {\n        debug_printf(\"\\npartyMemberRemove DENIED: %s\\n\", critter_name(object));\n        return -1;\n    }\n\n    if (index < partyMemberCount - 1) {\n        partyMemberList[index].object = partyMemberList[partyMemberCount - 1].object;\n    }\n\n    object->flags &= ~(OBJECT_FLAG_0x400 | OBJECT_TEMPORARY);\n\n    partyMemberCount--;\n\n    Script* script;\n    if (scr_ptr(object->sid, &script) != -1) {\n        script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n    }\n\n    queue_remove_this(object, EVENT_TYPE_SCRIPT);\n\n    if (gdialogActive()) {\n        if (object == dialog_target) {\n            gdialogUpdatePartyStatus();\n        }\n    }\n\n    return 0;\n}\n\n// 0x49460C\nint partyMemberPrepSave()\n{\n    partyStatePrepped = 1;\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        PartyMember* ptr = &(partyMemberList[index]);\n\n        if (index > 0) {\n            ptr->object->flags &= ~(OBJECT_FLAG_0x400 | OBJECT_TEMPORARY);\n        }\n\n        Script* script;\n        if (scr_ptr(ptr->object->sid, &script) != -1) {\n            script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n        }\n    }\n\n    return 0;\n}\n\n// 0x49466C\nint partyMemberUnPrepSave()\n{\n    for (int index = 0; index < partyMemberCount; index++) {\n        PartyMember* ptr = &(partyMemberList[index]);\n\n        if (index > 0) {\n            ptr->object->flags |= (OBJECT_FLAG_0x400 | OBJECT_TEMPORARY);\n        }\n\n        Script* script;\n        if (scr_ptr(ptr->object->sid, &script) != -1) {\n            script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n        }\n    }\n\n    partyStatePrepped = 0;\n\n    return 0;\n}\n\n// 0x4946CC\nint partyMemberSave(File* stream)\n{\n    if (db_fwriteInt(stream, partyMemberCount) == -1) return -1;\n    if (db_fwriteInt(stream, partyMemberItemCount) == -1) return -1;\n\n    for (int index = 1; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n        if (db_fwriteInt(stream, partyMember->object->id) == -1) return -1;\n    }\n\n    for (int index = 1; index < partyMemberMaxCount; index++) {\n        PartyMemberLevelUpInfo* levelUpInfo = &(partyMemberLevelUpInfoList[index]);\n        if (db_fwriteInt(stream, levelUpInfo->field_0) == -1) return -1;\n        if (db_fwriteInt(stream, levelUpInfo->field_4) == -1) return -1;\n        if (db_fwriteInt(stream, levelUpInfo->field_8) == -1) return -1;\n    }\n\n    return 0;\n}\n\n// 0x4947AC\nint partyMemberPrepLoad()\n{\n    if (partyStatePrepped) {\n        return -1;\n    }\n\n    partyStatePrepped = 1;\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        PartyMember* ptr_519DA8 = &(partyMemberList[index]);\n        if (partyMemberPrepLoadInstance(ptr_519DA8) != 0) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x49480C\nstatic int partyMemberPrepLoadInstance(PartyMember* partyMember)\n{\n    Object* obj = partyMember->object;\n\n    if (obj == NULL) {\n        debug_printf(\"\\n  Error!: partyMemberPrepLoadInstance: No Critter Object!\");\n        partyMember->script = NULL;\n        partyMember->vars = NULL;\n        partyMember->next = NULL;\n        return 0;\n    }\n\n    if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n        obj->data.critter.combat.whoHitMe = NULL;\n    }\n\n    Script* script;\n    if (scr_ptr(obj->sid, &script) == -1) {\n        debug_printf(\"\\n  Error!: partyMemberPrepLoadInstance: Can't find script!\");\n        debug_printf(\"\\n          partyMemberPrepLoadInstance: script was: (%s)\", critter_name(obj));\n        partyMember->script = NULL;\n        partyMember->vars = NULL;\n        partyMember->next = NULL;\n        return 0;\n    }\n\n    partyMember->script = (Script*)mem_malloc(sizeof(*script));\n    if (partyMember->script == NULL) {\n        GNWSystemError(\"\\n  Error!: partyMemberPrepLoad: Out of memory!\");\n        exit(1);\n    }\n\n    memcpy(partyMember->script, script, sizeof(*script));\n\n    if (script->localVarsCount != 0 && script->localVarsOffset != -1) {\n        partyMember->vars = (int*)mem_malloc(sizeof(*partyMember->vars) * script->localVarsCount);\n        if (partyMember->vars == NULL) {\n            GNWSystemError(\"\\n  Error!: partyMemberPrepLoad: Out of memory!\");\n            exit(1);\n        }\n\n        if (map_local_vars != NULL) {\n            memcpy(partyMember->vars, map_local_vars + script->localVarsOffset, sizeof(int) * script->localVarsCount);\n        } else {\n            debug_printf(\"\\nWarning: partyMemberPrepLoadInstance: No map_local_vars found, but script references them!\");\n            memset(partyMember->vars, 0, sizeof(int) * script->localVarsCount);\n        }\n    }\n\n    Inventory* inventory = &(obj->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        partyMemberItemSave(inventoryItem->item);\n    }\n\n    script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n\n    scr_remove(script->sid);\n\n    if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n        dude_stand(obj, obj->rotation, -1);\n    }\n\n    return 0;\n}\n\n// 0x4949C4\nint partyMemberRecoverLoad()\n{\n    if (partyStatePrepped != 1) {\n        debug_printf(\"\\npartyMemberRecoverLoad DENIED\");\n        return -1;\n    }\n\n    debug_printf(\"\\n\");\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        if (partyMemberRecoverLoadInstance(&(partyMemberList[index])) != 0) {\n            return -1;\n        }\n\n        debug_printf(\"[Party Member %d]: %s\\n\", index, critter_name(partyMemberList[index].object));\n    }\n\n    PartyMember* node = itemSaveListHead;\n    while (node != NULL) {\n        itemSaveListHead = node->next;\n\n        partyMemberItemRecover(node);\n        mem_free(node);\n\n        node = itemSaveListHead;\n    }\n\n    partyStatePrepped = 0;\n\n    if (!isLoadingGame()) {\n        partyFixMultipleMembers();\n    }\n\n    return 0;\n}\n\n// 0x494A88\nstatic int partyMemberRecoverLoadInstance(PartyMember* partyMember)\n{\n    if (partyMember->script == NULL) {\n        GNWSystemError(\"\\n  Error!: partyMemberRecoverLoadInstance: No script!\");\n        return 0;\n    }\n\n    int scriptType = SCRIPT_TYPE_CRITTER;\n    if (PID_TYPE(partyMember->object->pid) != OBJ_TYPE_CRITTER) {\n        scriptType = SCRIPT_TYPE_ITEM;\n    }\n\n    int v1 = -1;\n    if (scr_new(&v1, scriptType) == -1) {\n        GNWSystemError(\"\\n  Error!: partyMemberRecoverLoad: Can't create script!\");\n        exit(1);\n    }\n\n    Script* script;\n    if (scr_ptr(v1, &script) == -1) {\n        GNWSystemError(\"\\n  Error!: partyMemberRecoverLoad: Can't find script!\");\n        exit(1);\n    }\n\n    memcpy(script, partyMember->script, sizeof(*script));\n\n    int sid = (scriptType << 24) | ((partyMember->object->pid & 0xFFFFFF) + 18000);\n    partyMember->object->sid = sid;\n    script->sid = sid;\n\n    script->flags &= ~(SCRIPT_FLAG_0x01 | SCRIPT_FLAG_0x04);\n\n    mem_free(partyMember->script);\n    partyMember->script = NULL;\n\n    script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n\n    if (partyMember->vars != NULL) {\n        script->localVarsOffset = map_malloc_local_var(script->localVarsCount);\n        memcpy(map_local_vars + script->localVarsOffset, partyMember->vars, sizeof(int) * script->localVarsCount);\n    }\n\n    return 0;\n}\n\n// 0x494BBC\nint partyMemberLoad(File* stream)\n{\n    int* partyMemberObjectIds = (int*)mem_malloc(sizeof(*partyMemberObjectIds) * (partyMemberMaxCount + 20));\n    if (partyMemberObjectIds == NULL) {\n        return -1;\n    }\n\n    // FIXME: partyMemberObjectIds is never free'd in this function, obviously memory leak.\n\n    if (db_freadInt(stream, &partyMemberCount) == -1) return -1;\n    if (db_freadInt(stream, &partyMemberItemCount) == -1) return -1;\n\n    partyMemberList->object = obj_dude;\n\n    if (partyMemberCount != 0) {\n        for (int index = 1; index < partyMemberCount; index++) {\n            if (db_freadInt(stream, &(partyMemberObjectIds[index])) == -1) return -1;\n        }\n\n        for (int index = 1; index < partyMemberCount; index++) {\n            int objectId = partyMemberObjectIds[index];\n\n            Object* object = obj_find_first();\n            while (object != NULL) {\n                if (object->id == objectId) {\n                    break;\n                }\n                object = obj_find_next();\n            }\n\n            if (object != NULL) {\n                partyMemberList[index].object = object;\n            } else {\n                debug_printf(\"Couldn't find party member on map...trying to load anyway.\\n\");\n                if (index + 1 >= partyMemberCount) {\n                    partyMemberObjectIds[index] = 0;\n                } else {\n                    memcpy(&(partyMemberObjectIds[index]), &(partyMemberObjectIds[index + 1]), sizeof(*partyMemberObjectIds) * (partyMemberCount - (index + 1)));\n                }\n\n                index--;\n                partyMemberCount--;\n            }\n        }\n\n        if (partyMemberUnPrepSave() == -1) {\n            return -1;\n        }\n    }\n\n    partyFixMultipleMembers();\n\n    for (int index = 1; index < partyMemberMaxCount; index++) {\n        PartyMemberLevelUpInfo* levelUpInfo = &(partyMemberLevelUpInfoList[index]);\n\n        if (db_freadInt(stream, &(levelUpInfo->field_0)) == -1) return -1;\n        if (db_freadInt(stream, &(levelUpInfo->field_4)) == -1) return -1;\n        if (db_freadInt(stream, &(levelUpInfo->field_8)) == -1) return -1;\n    }\n\n    return 0;\n}\n\n// 0x494D7C\nvoid partyMemberClear()\n{\n    if (partyStatePrepped) {\n        partyMemberUnPrepSave();\n    }\n\n    for (int index = partyMemberCount; index > 1; index--) {\n        partyMemberRemove(partyMemberList[1].object);\n    }\n\n    partyMemberCount = 1;\n\n    scr_remove_all();\n    partyMemberClearItemList();\n\n    partyStatePrepped = 0;\n}\n\n// 0x494DD0\nint partyMemberSyncPosition()\n{\n    int clockwiseRotation = (obj_dude->rotation + 2) % ROTATION_COUNT;\n    int counterClockwiseRotation = (obj_dude->rotation + 4) % ROTATION_COUNT;\n\n    int n = 0;\n    int distance = 2;\n    for (int index = 1; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n        Object* partyMemberObj = partyMember->object;\n        if ((partyMemberObj->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(partyMemberObj->pid) == OBJ_TYPE_CRITTER) {\n            int rotation;\n            if ((n % 2) != 0) {\n                rotation = clockwiseRotation;\n            } else {\n                rotation = counterClockwiseRotation;\n            }\n\n            int tile = tile_num_in_direction(obj_dude->tile, rotation, distance / 2);\n            objPMAttemptPlacement(partyMemberObj, tile, obj_dude->elevation);\n\n            distance++;\n            n++;\n        }\n    }\n\n    return 0;\n}\n\n// Heals party members according to their healing rate.\n//\n// 0x494EB8\nint partyMemberRestingHeal(int a1)\n{\n    int v1 = a1 / 3;\n    if (v1 == 0) {\n        return 0;\n    }\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n        if (PID_TYPE(partyMember->object->pid) == OBJ_TYPE_CRITTER) {\n            int healingRate = critterGetStat(partyMember->object, STAT_HEALING_RATE);\n            critter_adjust_hits(partyMember->object, v1 * healingRate);\n        }\n    }\n\n    return 1;\n}\n\n// 0x494F24\nObject* partyMemberFindObjFromPid(int pid)\n{\n    for (int index = 0; index < partyMemberCount; index++) {\n        Object* object = partyMemberList[index].object;\n        if (object->pid == pid) {\n            return object;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x494F64\nbool isPotentialPartyMember(Object* object)\n{\n    for (int index = 0; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n        if (partyMember->object->pid == partyMemberPidList[index]) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// Returns `true` if specified object is a party member.\n//\n// 0x494FC4\nbool isPartyMember(Object* object)\n{\n    if (object == NULL) {\n        return false;\n    }\n\n    if (object->id < 18000) {\n        return false;\n    }\n\n    bool isPartyMember = false;\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        if (partyMemberList[index].object == object) {\n            isPartyMember = true;\n            break;\n        }\n    }\n\n    return isPartyMember;\n}\n\n// Returns number of active critters in the party.\n//\n// 0x495010\nint getPartyMemberCount()\n{\n    int count = partyMemberCount;\n\n    for (int index = 1; index < partyMemberCount; index++) {\n        Object* object = partyMemberList[index].object;\n\n        if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER || critter_is_dead(object) || (object->flags & OBJECT_HIDDEN) != 0) {\n            count--;\n        }\n    }\n\n    return count;\n}\n\n// 0x495070\nstatic int partyMemberNewObjID()\n{\n    // 0x519DC0\n    static int curID = 20000;\n\n    Object* object;\n\n    do {\n        curID++;\n\n        object = obj_find_first();\n        while (object != NULL) {\n            if (object->id == curID) {\n                break;\n            }\n\n            Inventory* inventory = &(object->data.inventory);\n\n            int index;\n            for (index = 0; index < inventory->length; index++) {\n                InventoryItem* inventoryItem = &(inventory->items[index]);\n                Object* item = inventoryItem->item;\n                if (item->id == curID) {\n                    break;\n                }\n\n                if (partyMemberNewObjIDRecurseFind(item, curID)) {\n                    break;\n                }\n            }\n\n            if (index < inventory->length) {\n                break;\n            }\n\n            object = obj_find_next();\n        }\n    } while (object != NULL);\n\n    curID++;\n\n    return curID;\n}\n\n// 0x4950F4\nstatic int partyMemberNewObjIDRecurseFind(Object* obj, int objectId)\n{\n    Inventory* inventory = &(obj->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        if (inventoryItem->item->id == objectId) {\n            return 1;\n        }\n\n        if (partyMemberNewObjIDRecurseFind(inventoryItem->item, objectId)) {\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x495140\nint partyMemberPrepItemSaveAll()\n{\n    for (int partyMemberIndex = 0; partyMemberIndex < partyMemberCount; partyMemberIndex++) {\n        PartyMember* partyMember = &(partyMemberList[partyMemberIndex]);\n\n        Inventory* inventory = &(partyMember->object->data.inventory);\n        for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) {\n            InventoryItem* inventoryItem = &(inventory->items[inventoryItemIndex]);\n            partyMemberPrepItemSave(inventoryItem->item);\n        }\n    }\n\n    return 0;\n}\n\n// 0x495198\nstatic int partyMemberPrepItemSave(Object* object)\n{\n    if (object->sid != -1) {\n        Script* script;\n        if (scr_ptr(object->sid, &script) == -1) {\n            GNWSystemError(\"\\n  Error!: partyMemberPrepItemSaveAll: Can't find script!\");\n            exit(1);\n        }\n\n        script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n    }\n\n    Inventory* inventory = &(object->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        partyMemberPrepItemSave(inventoryItem->item);\n    }\n\n    return 0;\n}\n\n// 0x495234\nstatic int partyMemberItemSave(Object* object)\n{\n    if (object->sid != -1) {\n        Script* script;\n        if (scr_ptr(object->sid, &script) == -1) {\n            GNWSystemError(\"\\n  Error!: partyMemberItemSave: Can't find script!\");\n            exit(1);\n        }\n\n        if (object->id < 20000) {\n            script->field_1C = partyMemberNewObjID();\n            object->id = script->field_1C;\n        }\n\n        PartyMember* node = (PartyMember*)mem_malloc(sizeof(*node));\n        if (node == NULL) {\n            GNWSystemError(\"\\n  Error!: partyMemberItemSave: Out of memory!\");\n            exit(1);\n        }\n\n        node->object = object;\n\n        node->script = (Script*)mem_malloc(sizeof(*script));\n        if (node->script == NULL) {\n            GNWSystemError(\"\\n  Error!: partyMemberItemSave: Out of memory!\");\n            exit(1);\n        }\n\n        memcpy(node->script, script, sizeof(*script));\n\n        if (script->localVarsCount != 0 && script->localVarsOffset != -1) {\n            node->vars = (int*)mem_malloc(sizeof(*node->vars) * script->localVarsCount);\n            if (node->vars == NULL) {\n                GNWSystemError(\"\\n  Error!: partyMemberItemSave: Out of memory!\");\n                exit(1);\n            }\n\n            memcpy(node->vars, map_local_vars + script->localVarsOffset, sizeof(int) * script->localVarsCount);\n        } else {\n            node->vars = NULL;\n        }\n\n        PartyMember* temp = itemSaveListHead;\n        itemSaveListHead = node;\n        node->next = temp;\n    }\n\n    Inventory* inventory = &(object->data.inventory);\n    for (int index = 0; index < inventory->length; index++) {\n        InventoryItem* inventoryItem = &(inventory->items[index]);\n        partyMemberItemSave(inventoryItem->item);\n    }\n\n    return 0;\n}\n\n// 0x495388\nstatic int partyMemberItemRecover(PartyMember* partyMember)\n{\n    int sid = -1;\n    if (scr_new(&sid, SCRIPT_TYPE_ITEM) == -1) {\n        GNWSystemError(\"\\n  Error!: partyMemberItemRecover: Can't create script!\");\n        exit(1);\n    }\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        GNWSystemError(\"\\n  Error!: partyMemberItemRecover: Can't find script!\");\n        exit(1);\n    }\n\n    memcpy(script, partyMember->script, sizeof(*script));\n\n    partyMember->object->sid = partyMemberItemCount | (SCRIPT_TYPE_ITEM << 24);\n    script->sid = partyMemberItemCount | (SCRIPT_TYPE_ITEM << 24);\n\n    script->program = NULL;\n    script->flags &= ~(SCRIPT_FLAG_0x01 | SCRIPT_FLAG_0x04 | SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n\n    partyMemberItemCount++;\n\n    mem_free(partyMember->script);\n    partyMember->script = NULL;\n\n    if (partyMember->vars != NULL) {\n        script->localVarsOffset = map_malloc_local_var(script->localVarsCount);\n        memcpy(map_local_vars + script->localVarsOffset, partyMember->vars, sizeof(int) * script->localVarsCount);\n    }\n\n    return 0;\n}\n\n// 0x4954C4\nstatic int partyMemberClearItemList()\n{\n    while (itemSaveListHead != NULL) {\n        PartyMember* node = itemSaveListHead;\n        itemSaveListHead = itemSaveListHead->next;\n\n        if (node->script != NULL) {\n            mem_free(node->script);\n        }\n\n        if (node->vars != NULL) {\n            mem_free(node->vars);\n        }\n\n        mem_free(node);\n    }\n\n    partyMemberItemCount = 20000;\n\n    return 0;\n}\n\n// Returns best skill of the specified party member.\n//\n// 0x495520\nint partyMemberSkill(Object* object)\n{\n    int bestSkill = SKILL_SMALL_GUNS;\n\n    if (object == NULL) {\n        return bestSkill;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        return bestSkill;\n    }\n\n    int bestValue = 0;\n    for (int skill = 0; skill < SKILL_COUNT; skill++) {\n        int value = skill_level(object, skill);\n        if (value > bestValue) {\n            bestSkill = skill;\n            bestValue = value;\n        }\n    }\n\n    return bestSkill;\n}\n\n// Returns party member with highest skill level.\n//\n// 0x495560\nObject* partyMemberWithHighestSkill(int skill)\n{\n    int bestValue = 0;\n    Object* bestPartyMember = NULL;\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        Object* object = partyMemberList[index].object;\n        if ((object->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n            int value = skill_level(object, skill);\n            if (value > bestValue) {\n                bestValue = value;\n                bestPartyMember = object;\n            }\n        }\n    }\n\n    return bestPartyMember;\n}\n\n// Returns highest skill level in party.\n//\n// 0x4955C8\nint partyMemberHighestSkillLevel(int skill)\n{\n    int bestValue = 0;\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        Object* object = partyMemberList[index].object;\n        if ((object->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n            int value = skill_level(object, skill);\n            if (value > bestValue) {\n                bestValue = value;\n            }\n        }\n    }\n\n    return bestValue;\n}\n\n// 0x495620\nstatic int partyFixMultipleMembers()\n{\n    debug_printf(\"\\n\\n\\n[Party Members]:\");\n\n    int critterCount = 0;\n    for (Object* obj = obj_find_first(); obj != NULL; obj = obj_find_next()) {\n        if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n            critterCount++;\n        }\n\n        bool isPartyMember = false;\n        for (int index = 1; index < partyMemberMaxCount; index++) {\n            if (obj->pid == partyMemberPidList[index]) {\n                isPartyMember = true;\n                break;\n            }\n        }\n\n        if (!isPartyMember) {\n            continue;\n        }\n\n        debug_printf(\"\\n   PM: %s\", critter_name(obj));\n\n        bool v19 = false;\n        if (obj->sid == -1) {\n            v19 = true;\n        } else {\n            Object* v7 = NULL;\n            for (int i = 0; i < partyMemberCount; i++) {\n                if (obj->pid == partyMemberList[i].object->pid) {\n                    v7 = partyMemberList[i].object;\n                    break;\n                }\n            }\n\n            if (v7 != NULL && obj != v7) {\n                if (v7->sid == obj->sid) {\n                    obj->sid = -1;\n                }\n                v19 = true;\n            }\n        }\n\n        if (!v19) {\n            continue;\n        }\n\n        Object* v10 = NULL;\n        for (int i = 0; i < partyMemberCount; i++) {\n            if (obj->pid == partyMemberList[i].object->pid) {\n                v10 = partyMemberList[i].object;\n            }\n        }\n\n        // TODO: Probably wrong.\n        if (obj == v10) {\n            debug_printf(\"\\nError: Attempting to destroy evil critter doppleganger FAILED!\");\n            continue;\n        }\n\n        debug_printf(\"\\nDestroying evil critter doppleganger!\");\n\n        if (obj->sid != -1) {\n            scr_remove(obj->sid);\n            obj->sid = -1;\n        } else {\n            if (queue_remove_this(obj, EVENT_TYPE_SCRIPT) == -1) {\n                debug_printf(\"\\nERROR Removing Timed Events on FIX remove!!\\n\");\n            }\n        }\n\n        obj_erase_object(obj, NULL);\n    }\n\n    for (int index = 0; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n\n        Script* script;\n        if (scr_ptr(partyMember->object->sid, &script) != -1) {\n            script->owner = partyMember->object;\n        } else {\n            debug_printf(\"\\nError: Failed to fix party member critter scripts!\");\n        }\n    }\n\n    debug_printf(\"\\nTotal Critter Count: %d\\n\\n\", critterCount);\n\n    return 0;\n}\n\n// 0x495870\nvoid partyMemberSaveProtos()\n{\n    for (int index = 1; index < partyMemberMaxCount; index++) {\n        int pid = partyMemberPidList[index];\n        if (pid != -1) {\n            proto_save_pid(pid);\n        }\n    }\n}\n\n// 0x4958B0\nbool partyMemberHasAIDisposition(Object* critter, int disposition)\n{\n    if (critter == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (disposition == -1 || disposition > 5) {\n        return false;\n    }\n\n    PartyMemberAI* aiOptions;\n    if (partyMemberGetAIOptions(critter, &aiOptions) == -1) {\n        return false;\n    }\n\n    return aiOptions->disposition[disposition + 1];\n}\n\n// 0x495920\nbool partyMemberHasAIBurstValue(Object* object, int areaAttackMode)\n{\n    if (object == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (areaAttackMode >= AREA_ATTACK_MODE_COUNT) {\n        return false;\n    }\n\n    PartyMemberAI* aiOptions;\n    if (partyMemberGetAIOptions(object, &aiOptions) == -1) {\n        return false;\n    }\n\n    return aiOptions->area_attack_mode[areaAttackMode];\n}\n\n// 0x495980\nbool partyMemberHasAIRunAwayValue(Object* object, int runAwayMode)\n{\n    if (object == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (runAwayMode >= RUN_AWAY_MODE_COUNT) {\n        return false;\n    }\n\n    PartyMemberAI* aiOptions;\n    if (partyMemberGetAIOptions(object, &aiOptions) == -1) {\n        return false;\n    }\n\n    return aiOptions->run_away_mode[runAwayMode + 1];\n}\n\n// 0x4959E0\nbool partyMemberHasAIWeaponPrefValue(Object* object, int bestWeapon)\n{\n    if (object == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (bestWeapon >= BEST_WEAPON_COUNT) {\n        return false;\n    }\n\n    PartyMemberAI* aiOptions;\n    if (partyMemberGetAIOptions(object, &aiOptions) == -1) {\n        return false;\n    }\n\n    return aiOptions->best_weapon[bestWeapon];\n}\n\n// 0x495A40\nbool partyMemberHasAIDistancePrefValue(Object* object, int distanceMode)\n{\n    if (object == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (distanceMode >= DISTANCE_COUNT) {\n        return false;\n    }\n\n    PartyMemberAI* aiOptions;\n    if (partyMemberGetAIOptions(object, &aiOptions) == -1) {\n        return false;\n    }\n\n    return aiOptions->distance_mode[distanceMode];\n}\n\n// 0x495AA0\nbool partyMemberHasAIAttackWhoValue(Object* object, int attackWho)\n{\n    if (object == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (attackWho >= ATTACK_WHO_COUNT) {\n        return false;\n    }\n\n    PartyMemberAI* aiOptions;\n    if (partyMemberGetAIOptions(object, &aiOptions) == -1) {\n        return false;\n    }\n\n    return aiOptions->attack_who[attackWho];\n}\n\n// 0x495B00\nbool partyMemberHasAIChemUseValue(Object* object, int chemUse)\n{\n    if (object == NULL) {\n        return false;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        return false;\n    }\n\n    if (chemUse >= CHEM_USE_COUNT) {\n        return false;\n    }\n\n    PartyMemberAI* aiOptions;\n    if (partyMemberGetAIOptions(object, &aiOptions) == -1) {\n        return false;\n    }\n\n    return aiOptions->chem_use[chemUse];\n}\n\n// partyMemberIncLevels\n// 0x495B60\nint partyMemberIncLevels()\n{\n    int i;\n    PartyMember* partyMember;\n    Object* obj;\n    PartyMemberAI* aiOptions;\n    const char* name;\n    int j;\n    int v0;\n    PartyMemberLevelUpInfo* levelUpInfo;\n    int v24;\n    char* text;\n    MessageListItem msg;\n    char str[260];\n    Rect v19;\n\n    v0 = -1;\n    for (i = 1; i < partyMemberCount; i++) {\n        partyMember = &(partyMemberList[i]);\n        obj = partyMember->object;\n\n        if (partyMemberGetAIOptions(obj, &aiOptions) == -1) {\n            break;\n        }\n\n        if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n            continue;\n        }\n\n        name = critter_name(obj);\n        debug_printf(\"\\npartyMemberIncLevels: %s\", name);\n\n        if (aiOptions->level_up_every == 0) {\n            continue;\n        }\n\n        for (j = 1; j < partyMemberMaxCount; j++) {\n            if (partyMemberPidList[j] == obj->pid) {\n                v0 = j;\n            }\n        }\n\n        if (v0 == -1) {\n            continue;\n        }\n\n        if (stat_pc_get(PC_STAT_LEVEL) < aiOptions->level_minimum) {\n            continue;\n        }\n\n        levelUpInfo = &(partyMemberLevelUpInfoList[v0]);\n\n        if (levelUpInfo->field_0 >= aiOptions->level_pids_num) {\n            continue;\n        }\n\n        levelUpInfo->field_4++;\n\n        v24 = levelUpInfo->field_4 % aiOptions->level_pids_num;\n        debug_printf(\"pm: levelMod: %d, Lvl: %d, Early: %d, Every: %d\", v24, levelUpInfo->field_4, levelUpInfo->field_8, aiOptions->level_up_every);\n\n        if (v24 != 0 || levelUpInfo->field_8 == 0) {\n            if (levelUpInfo->field_8 == 0) {\n                if (v24 == 0 || roll_random(0, 100) <= 100 * v24 / aiOptions->level_up_every) {\n                    levelUpInfo->field_0++;\n                    if (v24 != 0) {\n                        levelUpInfo->field_8 = 1;\n                    }\n\n                    if (partyMemberCopyLevelInfo(obj, aiOptions->level_pids[levelUpInfo->field_0]) == -1) {\n                        return -1;\n                    }\n\n                    name = critter_name(obj);\n                    // %s has gained in some abilities.\n                    text = getmsg(&misc_message_file, &msg, 9000);\n                    sprintf(str, text, name);\n                    display_print(str);\n\n                    debug_printf(str);\n\n                    // Individual message\n                    msg.num = 9000 + 10 * v0 + levelUpInfo->field_0 - 1;\n                    if (message_search(&misc_message_file, &msg)) {\n                        name = critter_name(obj);\n                        sprintf(str, msg.text, name);\n                        text_object_create(obj, str, 101, colorTable[0x7FFF], colorTable[0], &v19);\n                        tile_refresh_rect(&v19, obj->elevation);\n                    }\n                }\n            }\n        } else {\n            levelUpInfo->field_8 = 0;\n        }\n    }\n\n    return 0;\n}\n\n// 0x495EA8\nstatic int partyMemberCopyLevelInfo(Object* critter, int a2)\n{\n    if (critter == NULL) {\n        return -1;\n    }\n\n    if (a2 == -1) {\n        return -1;\n    }\n\n    Proto* proto1;\n    if (proto_ptr(critter->pid, &proto1) == -1) {\n        return -1;\n    }\n\n    Proto* proto2;\n    if (proto_ptr(a2, &proto2) == -1) {\n        return -1;\n    }\n\n    Object* item2 = inven_right_hand(critter);\n    invenUnwieldFunc(critter, 1, 0);\n\n    Object* armor = inven_worn(critter);\n    adjust_ac(critter, armor, NULL);\n    item_remove_mult(critter, armor, 1);\n\n    int maxHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS);\n    critter_adjust_hits(critter, maxHp);\n\n    for (int stat = 0; stat < SPECIAL_STAT_COUNT; stat++) {\n        proto1->critter.data.baseStats[stat] = proto2->critter.data.baseStats[stat];\n    }\n\n    for (int stat = 0; stat < SPECIAL_STAT_COUNT; stat++) {\n        proto1->critter.data.bonusStats[stat] = proto2->critter.data.bonusStats[stat];\n    }\n\n    for (int skill = 0; skill < SKILL_COUNT; skill++) {\n        proto1->critter.data.skills[skill] = proto2->critter.data.skills[skill];\n    }\n\n    critter->data.critter.hp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS);\n\n    if (armor != NULL) {\n        item_add_force(critter, armor, 1);\n        inven_wield(critter, armor, 0);\n    }\n\n    if (item2 != NULL) {\n        invenWieldFunc(critter, item2, 0, false);\n    }\n\n    return 0;\n}\n\n// Returns `true` if any party member that can be healed thru the rest is\n// wounded.\n//\n// This function is used to determine if any party member needs healing thru\n// the \"Rest until party healed\", therefore it excludes robots in the party\n// (they cannot be healed by resting) and dude (he/she has it's own \"Rest\n// until healed\" option).\n//\n// 0x496058\nbool partyMemberNeedsHealing()\n{\n    for (int index = 1; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n        Object* object = partyMember->object;\n\n        if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) continue;\n        if (critter_is_dead(object)) continue;\n        if ((object->flags & OBJECT_HIDDEN) != 0) continue;\n        if (critterGetKillType(object) == KILL_TYPE_ROBOT) continue;\n\n        int currentHp = critter_get_hits(object);\n        int maximumHp = critterGetStat(object, STAT_MAXIMUM_HIT_POINTS);\n        if (currentHp < maximumHp) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// Returns maximum amount of damage of any party member that can be healed thru\n// the rest.\n//\n// 0x4960DC\nint partyMemberMaxHealingNeeded()\n{\n    int maxWound = 0;\n\n    for (int index = 1; index < partyMemberCount; index++) {\n        PartyMember* partyMember = &(partyMemberList[index]);\n        Object* object = partyMember->object;\n\n        if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) continue;\n        if (critter_is_dead(object)) continue;\n        if ((object->flags & OBJECT_HIDDEN) != 0) continue;\n        if (critterGetKillType(object) == KILL_TYPE_ROBOT) continue;\n\n        int currentHp = critter_get_hits(object);\n        int maximumHp = critterGetStat(object, STAT_MAXIMUM_HIT_POINTS);\n        int wound = maximumHp - currentHp;\n        if (wound > 0) {\n            if (wound > maxWound) {\n                maxWound = wound;\n            }\n        }\n    }\n\n    return maxWound;\n}\n"
  },
  {
    "path": "src/game/party.h",
    "content": "#ifndef FALLOUT_GAME_PARTY_H_\n#define FALLOUT_GAME_PARTY_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/object_types.h\"\n\nextern int partyMemberMaxCount;\nextern int* partyMemberPidList;\n\nint partyMember_init();\nvoid partyMember_reset();\nvoid partyMember_exit();\nint partyMemberAdd(Object* object);\nint partyMemberRemove(Object* object);\nint partyMemberPrepSave();\nint partyMemberUnPrepSave();\nint partyMemberSave(File* stream);\nint partyMemberPrepLoad();\nint partyMemberRecoverLoad();\nint partyMemberLoad(File* stream);\nvoid partyMemberClear();\nint partyMemberSyncPosition();\nint partyMemberRestingHeal(int a1);\nObject* partyMemberFindObjFromPid(int a1);\nbool isPotentialPartyMember(Object* object);\nbool isPartyMember(Object* object);\nint getPartyMemberCount();\nint partyMemberPrepItemSaveAll();\nint partyMemberSkill(Object* object);\nObject* partyMemberWithHighestSkill(int skill);\nint partyMemberHighestSkillLevel(int skill);\nvoid partyMemberSaveProtos();\nbool partyMemberHasAIDisposition(Object* object, int disposition);\nbool partyMemberHasAIBurstValue(Object* object, int areaAttackMode);\nbool partyMemberHasAIRunAwayValue(Object* object, int runAwayMode);\nbool partyMemberHasAIWeaponPrefValue(Object* object, int bestWeapon);\nbool partyMemberHasAIDistancePrefValue(Object* object, int distanceMode);\nbool partyMemberHasAIAttackWhoValue(Object* object, int attackWho);\nbool partyMemberHasAIChemUseValue(Object* object, int chemUse);\nint partyMemberIncLevels();\nbool partyMemberNeedsHealing();\nint partyMemberMaxHealingNeeded();\n\n#endif /* FALLOUT_GAME_PARTY_H_ */\n"
  },
  {
    "path": "src/game/perk.c",
    "content": "#include \"game/perk.h\"\n\n#include <stdio.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/message.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/party.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n\ntypedef struct PerkDescription {\n    char* name;\n    char* description;\n    int frmId;\n    int maxRank;\n    int minLevel;\n    int stat;\n    int statModifier;\n    int param1;\n    int value1;\n    int field_24;\n    int param2;\n    int value2;\n    int stats[PRIMARY_STAT_COUNT];\n} PerkDescription;\n\nstatic bool perk_can_add(Object* critter, int perk);\nstatic void perk_defaults();\n\n// 0x519DCC\nstatic PerkDescription perk_data[PERK_COUNT] = {\n    { NULL, NULL, 72, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 5, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 73, 1, 15, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 },\n    { NULL, NULL, 74, 3, 3, 11, 2, -1, 0, 0, -1, 0, 6, 0, 0, 0, 0, 6, 0 },\n    { NULL, NULL, 75, 2, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 },\n    { NULL, NULL, 76, 2, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 6 },\n    { NULL, NULL, 77, 1, 15, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 6, 7, 0 },\n    { NULL, NULL, 78, 3, 3, 13, 2, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 79, 3, 3, 14, 2, -1, 0, 0, -1, 0, 0, 0, 6, 0, 0, 0, 0 },\n    { NULL, NULL, 80, 3, 6, 15, 5, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 6 },\n    { NULL, NULL, 81, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 82, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 6, 0, 0, 0 },\n    { NULL, NULL, 83, 2, 6, 31, 15, -1, 0, 0, -1, 0, 0, 0, 6, 0, 4, 0, 0 },\n    { NULL, NULL, 84, 3, 3, 24, 10, -1, 0, 0, -1, 0, 0, 0, 6, 0, 0, 0, 6 },\n    { NULL, NULL, 85, 3, 3, 12, 50, -1, 0, 0, -1, 0, 6, 0, 6, 0, 0, 0, 0 },\n    { NULL, NULL, 86, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 6, 0, 0 },\n    { NULL, NULL, 87, 1, 6, -1, 0, 8, 50, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 },\n    { NULL, NULL, 88, 1, 3, -1, 0, 17, 40, 0, -1, 0, 0, 0, 6, 0, 6, 0, 0 },\n    { NULL, NULL, 89, 1, 12, -1, 0, 15, 75, 0, -1, 0, 0, 0, 0, 7, 0, 0, 0 },\n    { NULL, NULL, 90, 3, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 6, 0, 0 },\n    { NULL, NULL, 91, 2, 3, -1, 0, 6, 40, 0, -1, 0, 0, 7, 0, 0, 5, 6, 0 },\n    { NULL, NULL, 92, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 8 },\n    { NULL, NULL, 93, 1, 9, 16, 20, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 4, 6 },\n    { NULL, NULL, 94, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 5, 0, 0 },\n    { NULL, NULL, 95, 1, 24, -1, 0, 3, 80, 0, -1, 0, 8, 0, 0, 0, 0, 8, 0 },\n    { NULL, NULL, 96, 1, 24, -1, 0, 0, 80, 0, -1, 0, 0, 8, 0, 0, 0, 8, 0 },\n    { NULL, NULL, 97, 1, 18, -1, 0, 8, 80, 2, 3, 80, 0, 0, 0, 0, 0, 10, 0 },\n    { NULL, NULL, 98, 2, 12, 8, 1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 },\n    { NULL, NULL, 99, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 100, 2, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 4, 0, 0, 0, 0 },\n    { NULL, NULL, 101, 1, 9, 9, 5, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 },\n    { NULL, NULL, 102, 2, 6, 32, 25, -1, 0, 0, -1, 0, 0, 0, 3, 0, 0, 0, 0 },\n    { NULL, NULL, 103, 1, 12, -1, 0, 13, 40, 1, 12, 40, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 104, 1, 12, -1, 0, 6, 40, 1, 7, 40, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 105, 1, 12, -1, 0, 10, 50, 2, 9, 50, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 106, 1, 9, -1, 0, 14, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 107, 3, 6, -1, 0, -1, 0, 0, -1, 0, -9, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 108, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 4, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 109, 1, 15, -1, 0, 10, 80, 0, -1, 0, 0, 0, 0, 0, 0, 8, 0 },\n    { NULL, NULL, 110, 1, 6, -1, 0, 8, 60, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 111, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 10, 0, 0, 0 },\n    { NULL, NULL, 112, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 8 },\n    { NULL, NULL, 113, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 114, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 5, 0, 0, 0, 0 },\n    { NULL, NULL, 115, 2, 6, -1, 0, 17, 40, 0, -1, 0, 0, 0, 6, 0, 0, 0, 0 },\n    { NULL, NULL, 116, 1, 310, -1, 0, 17, 25, 0, -1, 0, 0, 0, 0, 0, 5, 0, 0 },\n    { NULL, NULL, 117, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 118, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 4 },\n    { NULL, NULL, 119, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 120, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 },\n    { NULL, NULL, 121, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 4, 0, 0 },\n    { NULL, NULL, 122, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 4, 0, 0 },\n    { NULL, NULL, 123, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 124, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 125, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 126, -1, 1, -1, 0, -1, 0, 0, -1, 0, -2, 0, -2, 0, 0, -3, 0 },\n    { NULL, NULL, 127, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -3, -2, 0 },\n    { NULL, NULL, 128, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -2, 0, 0 },\n    { NULL, NULL, 129, -1, 1, 31, -20, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 130, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 131, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 132, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 133, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 134, -1, 1, 31, 30, -1, 0, 0, -1, 0, 3, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 135, -1, 1, 31, 20, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 136, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 137, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 138, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 139, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 140, -1, 1, 31, 60, -1, 0, 0, -1, 0, 4, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 141, -1, 1, 31, 75, -1, 0, 0, -1, 0, 4, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 136, -1, 1, 8, -1, -1, 0, 0, -1, 0, -1, -1, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 149, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, -2, 0, 0, -1, 0, -1 },\n    { NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 2, 0, 0, 0 },\n    { NULL, NULL, 158, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 157, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 157, -1, 1, 3, -1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 168, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 168, -1, 1, 3, -1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 172, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 155, 1, 6, -1, 0, -1, 0, 0, -1, 0, -10, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 156, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 122, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 6, 0, 0 },\n    { NULL, NULL, 39, 1, 9, -1, 0, 11, 75, 0, -1, 0, 0, 0, 0, 0, 0, 4, 0 },\n    { NULL, NULL, 44, 1, 6, -1, 0, 16, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 0, 1, 12, -1, 0, -1, 0, 0, -1, 0, -10, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 1, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, -10, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 2, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, -10, 0, 0, 0, 0 },\n    { NULL, NULL, 3, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, -10, 0, 0, 0 },\n    { NULL, NULL, 4, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -10, 0, 0 },\n    { NULL, NULL, 5, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, -10, 0 },\n    { NULL, NULL, 6, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -10 },\n    { NULL, NULL, 160, 1, 6, -1, 0, 10, 50, 2, 0x4000000, 50, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 161, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 159, 1, 12, -1, 0, 3, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 163, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 5, 0, 0, 5, 0 },\n    { NULL, NULL, 162, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 6, 0, 0, 0 },\n    { NULL, NULL, 164, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 5 },\n    { NULL, NULL, 165, 1, 12, -1, 0, 7, 60, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 166, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, -10, 0, 0, 0 },\n    { NULL, NULL, 43, 1, 6, -1, 0, 15, 50, 2, 14, 50, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 167, 1, 6, 12, 50, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 169, 1, 9, -1, 0, 1, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 170, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 },\n    { NULL, NULL, 121, 1, 6, -1, 0, 15, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 171, 1, 3, -1, 0, -1, 0, 0, -1, 0, 6, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 38, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 173, 1, 12, -1, 0, -1, 0, 0, -1, 0, -7, 0, 0, 0, 0, 5, 0 },\n    { NULL, NULL, 104, -1, 1, -1, 0, 7, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 142, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 142, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 52, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 52, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 104, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 104, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 35, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 35, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n    { NULL, NULL, 64, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 },\n};\n\n// An array of perk ranks for each party member.\n//\n// 0x51C120\nstatic PerkRankData* perkLevelDataList = NULL;\n\n// Amount of experience points granted when player selected \"Here and now\"\n// perk.\n//\n// 0x51C124\nstatic int hereAndNowExps = 0;\n\n// perk.msg\n//\n// 0x6642D4\nstatic MessageList perk_message_file;\n\n// 0x4965A0\nint perk_init()\n{\n    perkLevelDataList = (PerkRankData*)mem_malloc(sizeof(*perkLevelDataList) * partyMemberMaxCount);\n    if (perkLevelDataList == NULL) {\n        return -1;\n    }\n\n    perk_defaults();\n\n    if (!message_init(&perk_message_file)) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"perk.msg\");\n\n    if (!message_load(&perk_message_file, path)) {\n        return -1;\n    }\n\n    for (int perk = 0; perk < PERK_COUNT; perk++) {\n        MessageListItem messageListItem;\n\n        messageListItem.num = 101 + perk;\n        if (message_search(&perk_message_file, &messageListItem)) {\n            perk_data[perk].name = messageListItem.text;\n        }\n\n        messageListItem.num = 1101 + perk;\n        if (message_search(&perk_message_file, &messageListItem)) {\n            perk_data[perk].description = messageListItem.text;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4966B0\nvoid perk_reset()\n{\n    perk_defaults();\n}\n\n// 0x4966B8\nvoid perk_exit()\n{\n    message_exit(&perk_message_file);\n\n    if (perkLevelDataList != NULL) {\n        mem_free(perkLevelDataList);\n        perkLevelDataList = NULL;\n    }\n}\n\n// 0x4966E4\nint perk_load(File* stream)\n{\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        PerkRankData* ranksData = &(perkLevelDataList[index]);\n        for (int perk = 0; perk < PERK_COUNT; perk++) {\n            if (db_freadInt(stream, &(ranksData->ranks[perk])) == -1) {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x496738\nint perk_save(File* stream)\n{\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        PerkRankData* ranksData = &(perkLevelDataList[index]);\n        for (int perk = 0; perk < PERK_COUNT; perk++) {\n            if (db_fwriteInt(stream, ranksData->ranks[perk]) == -1) {\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// perkGetLevelData\n// 0x49678C\nPerkRankData* perkGetLevelData(Object* critter)\n{\n    if (critter == obj_dude) {\n        return perkLevelDataList;\n    }\n\n    for (int index = 1; index < partyMemberMaxCount; index++) {\n        if (critter->pid == partyMemberPidList[index]) {\n            return perkLevelDataList + index;\n        }\n    }\n\n    debug_printf(\"\\nError: perkGetLevelData: Can't find party member match!\");\n\n    return perkLevelDataList;\n}\n\n// 0x49680C\nstatic bool perk_can_add(Object* critter, int perk)\n{\n    if (!perkIsValid(perk)) {\n        return false;\n    }\n\n    PerkDescription* perkDescription = &(perk_data[perk]);\n\n    if (perkDescription->maxRank == -1) {\n        return false;\n    }\n\n    PerkRankData* ranksData = perkGetLevelData(critter);\n    if (ranksData->ranks[perk] >= perkDescription->maxRank) {\n        return false;\n    }\n\n    if (critter == obj_dude) {\n        if (stat_pc_get(PC_STAT_LEVEL) < perkDescription->minLevel) {\n            return false;\n        }\n    }\n\n    bool v1 = true;\n\n    int param1 = perkDescription->param1;\n    if (param1 != -1) {\n        bool isVariable = false;\n        if ((param1 & 0x4000000) != 0) {\n            isVariable = true;\n            param1 &= ~0x4000000;\n        }\n\n        int value1 = perkDescription->value1;\n        if (value1 < 0) {\n            if (isVariable) {\n                if (game_get_global_var(param1) >= value1) {\n                    v1 = false;\n                }\n            } else {\n                if (skill_level(critter, param1) >= -value1) {\n                    v1 = false;\n                }\n            }\n        } else {\n            if (isVariable) {\n                if (game_get_global_var(param1) < value1) {\n                    v1 = false;\n                }\n            } else {\n                if (skill_level(critter, param1) < value1) {\n                    v1 = false;\n                }\n            }\n        }\n    }\n\n    if (!v1 || perkDescription->field_24 == 2) {\n        if (perkDescription->field_24 == 0) {\n            return false;\n        }\n\n        if (!v1 && perkDescription->field_24 == 2) {\n            return false;\n        }\n\n        int param2 = perkDescription->param2;\n        bool isVariable = false;\n        if (param2 != -1) {\n            if ((param2 & 0x4000000) != 0) {\n                isVariable = true;\n                param2 &= ~0x4000000;\n            }\n        }\n\n        if (param2 == -1) {\n            return false;\n        }\n\n        int value2 = perkDescription->value2;\n        if (value2 < 0) {\n            if (isVariable) {\n                if (game_get_global_var(param2) >= value2) {\n                    return false;\n                }\n            } else {\n                if (skill_level(critter, param2) >= -value2) {\n                    return false;\n                }\n            }\n        } else {\n            if (isVariable) {\n                if (game_get_global_var(param2) < value2) {\n                    return false;\n                }\n            } else {\n                if (skill_level(critter, param2) < value2) {\n                    return false;\n                }\n            }\n        }\n    }\n\n    for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {\n        if (perkDescription->stats[stat] < 0) {\n            if (critterGetStat(critter, stat) >= -perkDescription->stats[stat]) {\n                return false;\n            }\n        } else {\n            if (critterGetStat(critter, stat) < perkDescription->stats[stat]) {\n                return false;\n            }\n        }\n    }\n\n    return true;\n}\n\n// Resets party member perks.\n//\n// 0x496A0C\nstatic void perk_defaults()\n{\n    for (int index = 0; index < partyMemberMaxCount; index++) {\n        PerkRankData* ranksData = &(perkLevelDataList[index]);\n        for (int perk = 0; perk < PERK_COUNT; perk++) {\n            ranksData->ranks[perk] = 0;\n        }\n    }\n}\n\n// 0x496A5C\nint perk_add(Object* critter, int perk)\n{\n    if (!perkIsValid(perk)) {\n        return -1;\n    }\n\n    if (!perk_can_add(critter, perk)) {\n        return -1;\n    }\n\n    PerkRankData* ranksData = perkGetLevelData(critter);\n    ranksData->ranks[perk] += 1;\n\n    perk_add_effect(critter, perk);\n\n    return 0;\n}\n\n// perk_add_force\n// 0x496A9C\nint perk_add_force(Object* critter, int perk)\n{\n    if (!perkIsValid(perk)) {\n        return -1;\n    }\n\n    PerkRankData* ranksData = perkGetLevelData(critter);\n    int value = ranksData->ranks[perk];\n\n    int maxRank = perk_data[perk].maxRank;\n\n    if (maxRank != -1 && value >= maxRank) {\n        return -1;\n    }\n\n    ranksData->ranks[perk] += 1;\n\n    perk_add_effect(critter, perk);\n\n    return 0;\n}\n\n// perk_sub\n// 0x496AFC\nint perk_sub(Object* critter, int perk)\n{\n    if (!perkIsValid(perk)) {\n        return -1;\n    }\n\n    PerkRankData* ranksData = perkGetLevelData(critter);\n    int value = ranksData->ranks[perk];\n\n    if (value < 1) {\n        return -1;\n    }\n\n    ranksData->ranks[perk] -= 1;\n\n    perk_remove_effect(critter, perk);\n\n    return 0;\n}\n\n// Returns perks available to pick.\n//\n// 0x496B44\nint perk_make_list(Object* critter, int* perks)\n{\n    int count = 0;\n    for (int perk = 0; perk < PERK_COUNT; perk++) {\n        if (perk_can_add(critter, perk)) {\n            perks[count] = perk;\n            count++;\n        }\n    }\n    return count;\n}\n\n// has_perk\n// 0x496B78\nint perk_level(Object* critter, int perk)\n{\n    if (!perkIsValid(perk)) {\n        return 0;\n    }\n\n    PerkRankData* ranksData = perkGetLevelData(critter);\n    return ranksData->ranks[perk];\n}\n\n// 0x496B90\nchar* perk_name(int perk)\n{\n    if (!perkIsValid(perk)) {\n        return NULL;\n    }\n    return perk_data[perk].name;\n}\n\n// 0x496BB4\nchar* perk_description(int perk)\n{\n    if (!perkIsValid(perk)) {\n        return NULL;\n    }\n    return perk_data[perk].description;\n}\n\n// 0x496BD8\nint perk_skilldex_fid(int perk)\n{\n    if (!perkIsValid(perk)) {\n        return 0;\n    }\n    return perk_data[perk].frmId;\n}\n\n// perk_add_effect\n// 0x496BFC\nvoid perk_add_effect(Object* critter, int perk)\n{\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        debug_printf(\"\\nERROR: perk_add_effect: Was called on non-critter!\");\n        return;\n    }\n\n    if (!perkIsValid(perk)) {\n        return;\n    }\n\n    PerkDescription* perkDescription = &(perk_data[perk]);\n\n    if (perkDescription->stat != -1) {\n        int value = stat_get_bonus(critter, perkDescription->stat);\n        stat_set_bonus(critter, perkDescription->stat, value + perkDescription->statModifier);\n    }\n\n    if (perk == PERK_HERE_AND_NOW) {\n        PerkRankData* ranksData = perkGetLevelData(critter);\n        ranksData->ranks[PERK_HERE_AND_NOW] -= 1;\n\n        int level = stat_pc_get(PC_STAT_LEVEL);\n\n        hereAndNowExps = statPcMinExpForLevel(level + 1) - stat_pc_get(PC_STAT_EXPERIENCE);\n        statPCAddExperienceCheckPMs(hereAndNowExps, false);\n\n        ranksData->ranks[PERK_HERE_AND_NOW] += 1;\n    }\n\n    if (perkDescription->maxRank == -1) {\n        for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {\n            int value = stat_get_bonus(critter, stat);\n            stat_set_bonus(critter, stat, value + perkDescription->stats[stat]);\n        }\n    }\n}\n\n// perk_remove_effect\n// 0x496CE0\nvoid perk_remove_effect(Object* critter, int perk)\n{\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        debug_printf(\"\\nERROR: perk_remove_effect: Was called on non-critter!\");\n        return;\n    }\n\n    if (!perkIsValid(perk)) {\n        return;\n    }\n\n    PerkDescription* perkDescription = &(perk_data[perk]);\n\n    if (perkDescription->stat != -1) {\n        int value = stat_get_bonus(critter, perkDescription->stat);\n        stat_set_bonus(critter, perkDescription->stat, value - perkDescription->statModifier);\n    }\n\n    if (perk == PERK_HERE_AND_NOW) {\n        int xp = stat_pc_get(PC_STAT_EXPERIENCE);\n        stat_pc_set(PC_STAT_EXPERIENCE, xp - hereAndNowExps);\n    }\n\n    if (perkDescription->maxRank == -1) {\n        for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) {\n            int value = stat_get_bonus(critter, stat);\n            stat_set_bonus(critter, stat, value - perkDescription->stats[stat]);\n        }\n    }\n}\n\n// Returns modifier to specified skill accounting for perks.\n//\n// 0x496DD0\nint perk_adjust_skill(Object* critter, int skill)\n{\n    int modifier = 0;\n\n    switch (skill) {\n    case SKILL_FIRST_AID:\n        if (perkHasRank(critter, PERK_MEDIC)) {\n            modifier += 10;\n        }\n\n        if (perkHasRank(critter, PERK_VAULT_CITY_TRAINING)) {\n            modifier += 5;\n        }\n\n        break;\n    case SKILL_DOCTOR:\n        if (perkHasRank(critter, PERK_MEDIC)) {\n            modifier += 10;\n        }\n\n        if (perkHasRank(critter, PERK_LIVING_ANATOMY)) {\n            modifier += 10;\n        }\n\n        if (perkHasRank(critter, PERK_VAULT_CITY_TRAINING)) {\n            modifier += 5;\n        }\n\n        break;\n    case SKILL_SNEAK:\n        if (perkHasRank(critter, PERK_GHOST)) {\n            int lightIntensity = obj_get_visible_light(obj_dude);\n            if (lightIntensity > 45875) {\n                modifier += 20;\n            }\n        }\n        // FALLTHROUGH\n    case SKILL_LOCKPICK:\n    case SKILL_STEAL:\n    case SKILL_TRAPS:\n        if (perkHasRank(critter, PERK_THIEF)) {\n            modifier += 10;\n        }\n\n        if (skill == SKILL_LOCKPICK || skill == SKILL_STEAL) {\n            if (perkHasRank(critter, PERK_MASTER_THIEF)) {\n                modifier += 15;\n            }\n        }\n\n        if (skill == SKILL_STEAL) {\n            if (perkHasRank(critter, PERK_HARMLESS)) {\n                modifier += 20;\n            }\n        }\n\n        break;\n    case SKILL_SCIENCE:\n    case SKILL_REPAIR:\n        if (perkHasRank(critter, PERK_MR_FIXIT)) {\n            modifier += 10;\n        }\n\n        break;\n    case SKILL_SPEECH:\n        if (perkHasRank(critter, PERK_SPEAKER)) {\n            modifier += 20;\n        }\n\n        if (perkHasRank(critter, PERK_EXPERT_EXCREMENT_EXPEDITOR)) {\n            modifier += 5;\n        }\n\n        // FALLTHROUGH\n    case SKILL_BARTER:\n        if (perkHasRank(critter, PERK_NEGOTIATOR)) {\n            modifier += 10;\n        }\n\n        if (skill == SKILL_BARTER) {\n            if (perkHasRank(critter, PERK_SALESMAN)) {\n                modifier += 20;\n            }\n        }\n\n        break;\n    case SKILL_GAMBLING:\n        if (perkHasRank(critter, PERK_GAMBLER)) {\n            modifier += 20;\n        }\n\n        break;\n    case SKILL_OUTDOORSMAN:\n        if (perkHasRank(critter, PERK_RANGER)) {\n            modifier += 15;\n        }\n\n        if (perkHasRank(critter, PERK_SURVIVALIST)) {\n            modifier += 25;\n        }\n\n        break;\n    }\n\n    return modifier;\n}\n"
  },
  {
    "path": "src/game/perk.h",
    "content": "#ifndef FALLOUT_GAME_PERK_H_\n#define FALLOUT_GAME_PERK_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/object_types.h\"\n#include \"game/perk_defs.h\"\n\ntypedef struct PerkRankData {\n    int ranks[PERK_COUNT];\n} PerkRankData;\n\nint perk_init();\nvoid perk_reset();\nvoid perk_exit();\nint perk_load(File* stream);\nint perk_save(File* stream);\nPerkRankData* perkGetLevelData(Object* critter);\nint perk_add(Object* critter, int perk);\nint perk_add_force(Object* critter, int perk);\nint perk_sub(Object* critter, int perk);\nint perk_make_list(Object* critter, int* perks);\nint perk_level(Object* critter, int perk);\nchar* perk_name(int perk);\nchar* perk_description(int perk);\nint perk_skilldex_fid(int perk);\nvoid perk_add_effect(Object* critter, int perk);\nvoid perk_remove_effect(Object* critter, int perk);\nint perk_adjust_skill(Object* critter, int skill);\n\n// Returns true if perk is valid.\nstatic inline bool perkIsValid(int perk)\n{\n    return perk >= 0 && perk < PERK_COUNT;\n}\n\n// Returns true if critter has at least one rank in specified perk.\n//\n// NOTE: Most perks have only 1 rank, which means dude either have perk, or\n// not.\n//\n// On the other hand, there are several places in editor, where they made two\n// consequtive calls to [perk_level], first to check for presence, then get\n// the actual value for displaying. So a macro could exist, or this very\n// function, but due to similarity to [perk_level] it could have been\n// collapsed by compiler.\nstatic inline bool perkHasRank(Object* critter, int perk)\n{\n    return perk_level(critter, perk) != 0;\n}\n\n#endif /* FALLOUT_GAME_PERK_H_ */\n"
  },
  {
    "path": "src/game/perk_defs.h",
    "content": "#ifndef PERK_DEFS_H\n#define PERK_DEFS_H\n\ntypedef enum Perk {\n    PERK_AWARENESS,\n    PERK_BONUS_HTH_ATTACKS,\n    PERK_BONUS_HTH_DAMAGE,\n    PERK_BONUS_MOVE,\n    PERK_BONUS_RANGED_DAMAGE,\n    PERK_BONUS_RATE_OF_FIRE,\n    PERK_EARLIER_SEQUENCE,\n    PERK_FASTER_HEALING,\n    PERK_MORE_CRITICALS,\n    PERK_NIGHT_VISION,\n    PERK_PRESENCE,\n    PERK_RAD_RESISTANCE,\n    PERK_TOUGHNESS,\n    PERK_STRONG_BACK,\n    PERK_SHARPSHOOTER,\n    PERK_SILENT_RUNNING,\n    PERK_SURVIVALIST,\n    PERK_MASTER_TRADER,\n    PERK_EDUCATED,\n    PERK_HEALER,\n    PERK_FORTUNE_FINDER,\n    PERK_BETTER_CRITICALS,\n    PERK_EMPATHY,\n    PERK_SLAYER,\n    PERK_SNIPER,\n    PERK_SILENT_DEATH,\n    PERK_ACTION_BOY,\n    PERK_MENTAL_BLOCK,\n    PERK_LIFEGIVER,\n    PERK_DODGER,\n    PERK_SNAKEATER,\n    PERK_MR_FIXIT,\n    PERK_MEDIC,\n    PERK_MASTER_THIEF,\n    PERK_SPEAKER,\n    PERK_HEAVE_HO,\n    PERK_FRIENDLY_FOE,\n    PERK_PICKPOCKET,\n    PERK_GHOST,\n    PERK_CULT_OF_PERSONALITY,\n    PERK_SCROUNGER,\n    PERK_EXPLORER,\n    PERK_FLOWER_CHILD,\n    PERK_PATHFINDER,\n    PERK_ANIMAL_FRIEND,\n    PERK_SCOUT,\n    PERK_MYSTERIOUS_STRANGER,\n    PERK_RANGER,\n    PERK_QUICK_POCKETS,\n    PERK_SMOOTH_TALKER,\n    PERK_SWIFT_LEARNER,\n    PERK_TAG,\n    PERK_MUTATE,\n    PERK_NUKA_COLA_ADDICTION,\n    PERK_BUFFOUT_ADDICTION,\n    PERK_MENTATS_ADDICTION,\n    PERK_PSYCHO_ADDICTION,\n    PERK_RADAWAY_ADDICTION,\n    PERK_WEAPON_LONG_RANGE,\n    PERK_WEAPON_ACCURATE,\n    PERK_WEAPON_PENETRATE,\n    PERK_WEAPON_KNOCKBACK,\n    PERK_POWERED_ARMOR,\n    PERK_COMBAT_ARMOR,\n    PERK_WEAPON_SCOPE_RANGE,\n    PERK_WEAPON_FAST_RELOAD,\n    PERK_WEAPON_NIGHT_SIGHT,\n    PERK_WEAPON_FLAMEBOY,\n    PERK_ARMOR_ADVANCED_I,\n    PERK_ARMOR_ADVANCED_II,\n    PERK_JET_ADDICTION,\n    PERK_TRAGIC_ADDICTION,\n    PERK_ARMOR_CHARISMA,\n    PERK_GECKO_SKINNING,\n    PERK_DERMAL_IMPACT_ARMOR,\n    PERK_DERMAL_IMPACT_ASSAULT_ENHANCEMENT,\n    PERK_PHOENIX_ARMOR_IMPLANTS,\n    PERK_PHOENIX_ASSAULT_ENHANCEMENT,\n    PERK_VAULT_CITY_INOCULATIONS,\n    PERK_ADRENALINE_RUSH,\n    PERK_CAUTIOUS_NATURE,\n    PERK_COMPREHENSION,\n    PERK_DEMOLITION_EXPERT,\n    PERK_GAMBLER,\n    PERK_GAIN_STRENGTH,\n    PERK_GAIN_PERCEPTION,\n    PERK_GAIN_ENDURANCE,\n    PERK_GAIN_CHARISMA,\n    PERK_GAIN_INTELLIGENCE,\n    PERK_GAIN_AGILITY,\n    PERK_GAIN_LUCK,\n    PERK_HARMLESS,\n    PERK_HERE_AND_NOW,\n    PERK_HTH_EVADE,\n    PERK_KAMA_SUTRA_MASTER,\n    PERK_KARMA_BEACON,\n    PERK_LIGHT_STEP,\n    PERK_LIVING_ANATOMY,\n    PERK_MAGNETIC_PERSONALITY,\n    PERK_NEGOTIATOR,\n    PERK_PACK_RAT,\n    PERK_PYROMANIAC,\n    PERK_QUICK_RECOVERY,\n    PERK_SALESMAN,\n    PERK_STONEWALL,\n    PERK_THIEF,\n    PERK_WEAPON_HANDLING,\n    PERK_VAULT_CITY_TRAINING,\n    PERK_ALCOHOL_RAISED_HIT_POINTS,\n    PERK_ALCOHOL_RAISED_HIT_POINTS_II,\n    PERK_ALCOHOL_LOWERED_HIT_POINTS,\n    PERK_ALCOHOL_LOWERED_HIT_POINTS_II,\n    PERK_AUTODOC_RAISED_HIT_POINTS,\n    PERK_AUTODOC_RAISED_HIT_POINTS_II,\n    PERK_AUTODOC_LOWERED_HIT_POINTS,\n    PERK_AUTODOC_LOWERED_HIT_POINTS_II,\n    PERK_EXPERT_EXCREMENT_EXPEDITOR,\n    PERK_WEAPON_ENHANCED_KNOCKOUT,\n    PERK_JINXED,\n    PERK_COUNT,\n} Perk;\n\n#endif /* PERK_DEFS_H */\n"
  },
  {
    "path": "src/game/pipboy.c",
    "content": "#include \"game/pipboy.h\"\n\n#include <ctype.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/automap.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/config.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/cycle.h\"\n#include \"game/bmpdlog.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gmovie.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"game/wordwrap.h\"\n#include \"game/worldmap.h\"\n\n#define PIPBOY_RAND_MAX 32767\n\n#define PIPBOY_WINDOW_WIDTH 640\n#define PIPBOY_WINDOW_HEIGHT 480\n\n#define PIPBOY_WINDOW_DAY_X 20\n#define PIPBOY_WINDOW_DAY_Y 17\n\n#define PIPBOY_WINDOW_MONTH_X 46\n#define PIPBOY_WINDOW_MONTH_Y 18\n\n#define PIPBOY_WINDOW_YEAR_X 83\n#define PIPBOY_WINDOW_YEAR_Y 17\n\n#define PIPBOY_WINDOW_TIME_X 155\n#define PIPBOY_WINDOW_TIME_Y 17\n\n#define PIPBOY_HOLODISK_LINES_MAX 35\n\n#define PIPBOY_WINDOW_CONTENT_VIEW_X 254\n#define PIPBOY_WINDOW_CONTENT_VIEW_Y 46\n#define PIPBOY_WINDOW_CONTENT_VIEW_WIDTH 374\n#define PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT 410\n\n#define PIPBOY_IDLE_TIMEOUT 120000\n\n#define PIPBOY_BOMB_COUNT 16\n\n#define BACK_BUTTON_INDEX 22\n\ntypedef enum Holiday {\n    HOLIDAY_NEW_YEAR,\n    HOLIDAY_VALENTINES_DAY,\n    HOLIDAY_FOOLS_DAY,\n    HOLIDAY_SHIPPING_DAY,\n    HOLIDAY_INDEPENDENCE_DAY,\n    HOLIDAY_HALLOWEEN,\n    HOLIDAY_THANKSGIVING_DAY,\n    HOLIDAY_CRISTMAS,\n    HOLIDAY_COUNT,\n} Holiday;\n\n// Options used to render Pipboy texts.\ntypedef enum PipboyTextOptions {\n    // Specifies that text should be rendered in the center of the Pipboy\n    // monitor.\n    //\n    // This option is mutually exclusive with other alignment options.\n    PIPBOY_TEXT_ALIGNMENT_CENTER = 0x02,\n\n    // Specifies that text should be rendered in the beginning of the right\n    // column in two-column layout.\n    //\n    // This option is mutually exclusive with other alignment options.\n    PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN = 0x04,\n\n    // Specifies that text should be rendered in the center of the left column.\n    //\n    // This option is mutually exclusive with other alignment options.\n    PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER = 0x10,\n\n    // Specifies that text should be rendered in the center of the right\n    // column.\n    //\n    // This option is mutually exclusive with other alignment options.\n    PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER = 0x20,\n\n    // Specifies that text should rendered with underline.\n    PIPBOY_TEXT_STYLE_UNDERLINE = 0x08,\n\n    // Specifies that text should rendered with strike-through line.\n    PIPBOY_TEXT_STYLE_STRIKE_THROUGH = 0x40,\n\n    // Specifies that text should be rendered with no (minimal) indentation.\n    PIPBOY_TEXT_NO_INDENT = 0x80,\n} PipboyTextOptions;\n\ntypedef enum PipboyRestDuration {\n    PIPBOY_REST_DURATION_TEN_MINUTES,\n    PIPBOY_REST_DURATION_THIRTY_MINUTES,\n    PIPBOY_REST_DURATION_ONE_HOUR,\n    PIPBOY_REST_DURATION_TWO_HOURS,\n    PIPBOY_REST_DURATION_THREE_HOURS,\n    PIPBOY_REST_DURATION_FOUR_HOURS,\n    PIPBOY_REST_DURATION_FIVE_HOURS,\n    PIPBOY_REST_DURATION_SIX_HOURS,\n    PIPBOY_REST_DURATION_UNTIL_MORNING,\n    PIPBOY_REST_DURATION_UNTIL_NOON,\n    PIPBOY_REST_DURATION_UNTIL_EVENING,\n    PIPBOY_REST_DURATION_UNTIL_MIDNIGHT,\n    PIPBOY_REST_DURATION_UNTIL_HEALED,\n    PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED,\n    PIPBOY_REST_DURATION_COUNT,\n    PIPBOY_REST_DURATION_COUNT_WITHOUT_PARTY = PIPBOY_REST_DURATION_COUNT - 1,\n} PipboyRestDuration;\n\ntypedef enum PipboyFrm {\n    PIPBOY_FRM_LITTLE_RED_BUTTON_UP,\n    PIPBOY_FRM_LITTLE_RED_BUTTON_DOWN,\n    PIPBOY_FRM_NUMBERS,\n    PIPBOY_FRM_BACKGROUND,\n    PIPBOY_FRM_NOTE,\n    PIPBOY_FRM_MONTHS,\n    PIPBOY_FRM_NOTE_NUMBERS,\n    PIPBOY_FRM_ALARM_DOWN,\n    PIPBOY_FRM_ALARM_UP,\n    PIPBOY_FRM_LOGO,\n    PIPBOY_FRM_BOMB,\n    PIPBOY_FRM_COUNT,\n} PipboyFrm;\n\n// Provides metadata information on quests.\n//\n// Loaded from `data/quest.txt`.\ntypedef struct QuestDescription {\n    int location;\n    int description;\n    int gvar;\n    int displayThreshold;\n    int completedThreshold;\n} QuestDescription;\n\n// Provides metadata information on holodisks.\n//\n// Loaded from `data/holodisk.txt`.\ntypedef struct HolodiskDescription {\n    int gvar;\n    int name;\n    int description;\n} HolodiskDescription;\n\ntypedef struct HolidayDescription {\n    short month;\n    short day;\n    short textId;\n} HolidayDescription;\n\ntypedef struct STRUCT_664350 {\n    char* name;\n    short field_4;\n    short field_6;\n} STRUCT_664350;\n\ntypedef struct PipboyBomb {\n    int x;\n    int y;\n    float field_8;\n    float field_C;\n    unsigned char field_10;\n} PipboyBomb;\n\nstatic int StartPipboy(int intent);\nstatic void EndPipboy();\nstatic void pip_num(int value, int digits, int x, int y);\nstatic void pip_date();\nstatic void pip_print(const char* text, int a2, int a3);\nstatic void pip_back(int a1);\nstatic void PipStatus(int a1);\nstatic void ListStatLines(int a1);\nstatic void ShowHoloDisk();\nstatic int ListHoloDiskTitles(int a1);\nstatic int qscmp(const void* a1, const void* a2);\nstatic void PipAutomaps(int a1);\nstatic int PrintAMelevList(int a1);\nstatic int PrintAMList(int a1);\nstatic void PipArchives(int a1);\nstatic int ListArchive(int a1);\nstatic void PipAlarm(int a1);\nstatic void DrawAlarmText(int a1);\nstatic void DrawAlrmHitPnts();\nstatic void AddHotLines(int a1, int a2, bool add_back_button);\nstatic void NixHotLines();\nstatic bool TimedRest(int hours, int minutes, int kind);\nstatic bool Check4Health(int a1);\nstatic bool AddHealth();\nstatic void ClacTime(int* hours, int* minutes, int wakeUpHour);\nstatic int ScreenSaver();\nstatic int quest_init();\nstatic void quest_exit();\nstatic int quest_qsort_compare(const void* a1, const void* a2);\nstatic int holodisks_init();\nstatic void holodisks_exit();\n\n// 0x496FC0\nstatic const Rect pip_rect = {\n    PIPBOY_WINDOW_CONTENT_VIEW_X,\n    PIPBOY_WINDOW_CONTENT_VIEW_Y,\n    PIPBOY_WINDOW_CONTENT_VIEW_X + PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n    PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n};\n\n// 0x496FD0\nstatic const int pipgrphs[PIPBOY_FRM_COUNT] = {\n    8,\n    9,\n    82,\n    127,\n    128,\n    129,\n    130,\n    131,\n    132,\n    133,\n    226,\n};\n\n// 0x51C128\nstatic QuestDescription* quests = NULL;\n\n// 0x51C12C\nstatic int quest_count = 0;\n\n// 0x51C130\nstatic HolodiskDescription* holodisks = NULL;\n\n// 0x51C134\nstatic int holodisks_count = 0;\n\n// Number of rest options available.\n//\n// 0x51C138\nstatic int currentAlarmTypeCount = PIPBOY_REST_DURATION_COUNT;\n\n// 0x51C13C\nstatic bool bk_enable = false;\n\n// 0x51C140\nstatic HolidayDescription SpclDate[HOLIDAY_COUNT] = {\n    { 1, 1, 100 },\n    { 2, 14, 101 },\n    { 4, 1, 102 },\n    { 7, 4, 104 },\n    { 10, 6, 103 },\n    { 10, 31, 105 },\n    { 11, 28, 106 },\n    { 12, 25, 107 },\n};\n\n// 0x51C170\nPipboyRenderProc* PipFnctn[5] = {\n    PipStatus,\n    PipAutomaps,\n    PipArchives,\n    PipAlarm,\n    PipAlarm,\n};\n\n// 0x6642E0\nstatic Size ginfo[PIPBOY_FRM_COUNT];\n\n// 0x664338\nstatic MessageListItem pipmesg;\n\n// pipboy.msg\n//\n// 0x664348\nstatic MessageList pipboy_message_file;\n\n// 0x664350\nstatic STRUCT_664350 sortlist[24];\n\n// quests.msg\n//\n// 0x664410\nstatic MessageList quest_message_file;\n\n// 0x664418\nstatic int statcount;\n\n// 0x66441C\nstatic unsigned char* scrn_buf;\n\n// 0x664420\nstatic unsigned char* pipbmp[PIPBOY_FRM_COUNT];\n\n// 0x66444C\nstatic int holocount;\n\n// 0x664450\nstatic int mouse_y;\n\n// 0x664454\nstatic int mouse_x;\n\n// 0x664458\nstatic unsigned int wait_time;\n\n// Index of the last page when rendering holodisk content.\n//\n// 0x66445C\nstatic int holopages;\n\n// 0x664460\nstatic int HotLines[23];\n\n// 0x6644BC\nstatic int old_mouse_x;\n\n// 0x6644C0\nstatic int old_mouse_y;\n\n// 0x6644C4\nstatic int pip_win;\n\n// 0x6644C8\nstatic CacheEntry* grphkey[PIPBOY_FRM_COUNT];\n\n// 0x6644F4\nstatic int holodisk;\n\n// 0x6644F8\nstatic int hot_line_count;\n\n// 0x6644FC\nstatic int savefont;\n\n// 0x664500\nstatic bool proc_bail_flag;\n\n// 0x664504\nstatic int amlst_mode;\n\n// 0x664508\nstatic int crnt_func;\n\n// 0x66450C\nstatic int actcnt;\n\n// 0x664510\nstatic int hot_line_start;\n\n// 0x664514\nstatic int cursor_line;\n\n// 0x664518\nstatic int rest_time;\n\n// 0x66451C\nstatic int amcty_indx;\n\n// 0x664520\nstatic int view_page;\n\n// 0x664524\nstatic int bottom_line;\n\n// 0x664528\nstatic unsigned char hot_back_line;\n\n// 0x664529\nstatic unsigned char holo_flag;\n\n// 0x66452A\nstatic unsigned char stat_flag;\n\n// 0x497004\nint pipboy(int intent)\n{\n    if (!wmMapPipboyActive()) {\n        // You aren't wearing the pipboy!\n        const char* text = getmsg(&misc_message_file, &pipmesg, 7000);\n        dialog_out(text, NULL, 0, 192, 135, colorTable[32328], NULL, colorTable[32328], 1);\n        return 0;\n    }\n\n    intent = StartPipboy(intent);\n    if (intent == -1) {\n        return -1;\n    }\n\n    mouse_get_position(&old_mouse_x, &old_mouse_y);\n    wait_time = get_time();\n\n    while (true) {\n        int keyCode = get_input();\n\n        if (intent == PIPBOY_OPEN_INTENT_REST) {\n            keyCode = 504;\n            intent = PIPBOY_OPEN_INTENT_UNSPECIFIED;\n        }\n\n        mouse_get_position(&mouse_x, &mouse_y);\n\n        if (keyCode != -1 || mouse_x != old_mouse_x || mouse_y != old_mouse_y) {\n            wait_time = get_time();\n            old_mouse_x = mouse_x;\n            old_mouse_y = mouse_y;\n        } else {\n            if (get_time() - wait_time > PIPBOY_IDLE_TIMEOUT) {\n                ScreenSaver();\n\n                wait_time = get_time();\n                mouse_get_position(&old_mouse_x, &old_mouse_y);\n            }\n        }\n\n        if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n            game_quit_with_confirm();\n            break;\n        }\n\n        if (keyCode == 503 || keyCode == KEY_ESCAPE || keyCode == KEY_RETURN || keyCode == KEY_UPPERCASE_P || keyCode == KEY_LOWERCASE_P || game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        if (keyCode == KEY_F12) {\n            dump_screen();\n        } else if (keyCode >= 500 && keyCode <= 504) {\n            crnt_func = keyCode - 500;\n            PipFnctn[crnt_func](1024);\n        } else if (keyCode >= 505 && keyCode <= 527) {\n            PipFnctn[crnt_func](keyCode - 506);\n        } else if (keyCode == 528) {\n            PipFnctn[crnt_func](1025);\n        } else if (keyCode == KEY_PAGE_DOWN) {\n            PipFnctn[crnt_func](1026);\n        } else if (keyCode == KEY_PAGE_UP) {\n            PipFnctn[crnt_func](1027);\n        }\n\n        if (proc_bail_flag) {\n            break;\n        }\n    }\n\n    EndPipboy();\n\n    return 0;\n}\n\n// 0x497228\nstatic int StartPipboy(int intent)\n{\n    bk_enable = map_disable_bk_processes();\n\n    cycle_disable();\n    gmouse_3d_off();\n    disable_box_bar_win();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    currentAlarmTypeCount = PIPBOY_REST_DURATION_COUNT_WITHOUT_PARTY;\n\n    if (getPartyMemberCount() > 1 && partyMemberNeedsHealing()) {\n        currentAlarmTypeCount = PIPBOY_REST_DURATION_COUNT;\n    }\n\n    savefont = text_curr();\n    text_font(101);\n\n    proc_bail_flag = 0;\n    rest_time = 0;\n    cursor_line = 0;\n    hot_line_count = 0;\n    bottom_line = PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT / text_height() - 1;\n    hot_line_start = 0;\n    hot_back_line = 0;\n\n    if (holodisks_init() == -1) {\n        return -1;\n    }\n\n    if (!message_init(&pipboy_message_file)) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"pipboy.msg\");\n\n    if (!(message_load(&pipboy_message_file, path))) {\n        return -1;\n    }\n\n    int index;\n    for (index = 0; index < PIPBOY_FRM_COUNT; index++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, pipgrphs[index], 0, 0, 0);\n        pipbmp[index] = art_lock(fid, &(grphkey[index]), &(ginfo[index].width), &(ginfo[index].height));\n        if (pipbmp[index] == NULL) {\n            break;\n        }\n    }\n\n    if (index != PIPBOY_FRM_COUNT) {\n        debug_printf(\"\\n** Error loading pipboy graphics! **\\n\");\n\n        while (--index >= 0) {\n            art_ptr_unlock(grphkey[index]);\n        }\n\n        return -1;\n    }\n\n    int pipboyWindowX = 0;\n    int pipboyWindowY = 0;\n    pip_win = win_add(pipboyWindowX, pipboyWindowY, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_HEIGHT, colorTable[0], WINDOW_FLAG_0x10);\n    if (pip_win == -1) {\n        debug_printf(\"\\n** Error opening pipboy window! **\\n\");\n        for (int index = 0; index < PIPBOY_FRM_COUNT; index++) {\n            art_ptr_unlock(grphkey[index]);\n        }\n        return -1;\n    }\n\n    scrn_buf = win_get_buf(pip_win);\n    memcpy(scrn_buf, pipbmp[PIPBOY_FRM_BACKGROUND], PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_HEIGHT);\n\n    pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y);\n    pip_date();\n\n    int alarmButton = win_register_button(pip_win,\n        124,\n        13,\n        ginfo[PIPBOY_FRM_ALARM_UP].width,\n        ginfo[PIPBOY_FRM_ALARM_UP].height,\n        -1,\n        -1,\n        -1,\n        504,\n        pipbmp[PIPBOY_FRM_ALARM_UP],\n        pipbmp[PIPBOY_FRM_ALARM_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (alarmButton != -1) {\n        win_register_button_sound_func(alarmButton, gsound_med_butt_press, gsound_med_butt_release);\n    }\n\n    int y = 341;\n    int eventCode = 500;\n    for (int index = 0; index < 5; index += 1) {\n        if (index != 1) {\n            int btn = win_register_button(pip_win,\n                53,\n                y,\n                ginfo[PIPBOY_FRM_LITTLE_RED_BUTTON_UP].width,\n                ginfo[PIPBOY_FRM_LITTLE_RED_BUTTON_UP].height,\n                -1,\n                -1,\n                -1,\n                eventCode,\n                pipbmp[PIPBOY_FRM_LITTLE_RED_BUTTON_UP],\n                pipbmp[PIPBOY_FRM_LITTLE_RED_BUTTON_DOWN],\n                NULL,\n                BUTTON_FLAG_TRANSPARENT);\n            if (btn != -1) {\n                win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release);\n            }\n\n            eventCode += 1;\n        }\n\n        y += 27;\n    }\n\n    if (intent == PIPBOY_OPEN_INTENT_REST) {\n        if (!critter_can_obj_dude_rest()) {\n            trans_buf_to_buf(\n                pipbmp[PIPBOY_FRM_LOGO],\n                ginfo[PIPBOY_FRM_LOGO].width,\n                ginfo[PIPBOY_FRM_LOGO].height,\n                ginfo[PIPBOY_FRM_LOGO].width,\n                scrn_buf + PIPBOY_WINDOW_WIDTH * 156 + 323,\n                PIPBOY_WINDOW_WIDTH);\n\n            int month;\n            int day;\n            int year;\n            game_time_date(&month, &day, &year);\n\n            int holiday = 0;\n            for (; holiday < HOLIDAY_COUNT; holiday += 1) {\n                const HolidayDescription* holidayDescription = &(SpclDate[holiday]);\n                if (holidayDescription->month == month && holidayDescription->day == day) {\n                    break;\n                }\n            }\n\n            if (holiday != HOLIDAY_COUNT) {\n                const HolidayDescription* holidayDescription = &(SpclDate[holiday]);\n                const char* holidayName = getmsg(&pipboy_message_file, &pipmesg, holidayDescription->textId);\n                char holidayNameCopy[256];\n                strcpy(holidayNameCopy, holidayName);\n\n                int len = text_width(holidayNameCopy);\n                text_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * (ginfo[PIPBOY_FRM_LOGO].height + 174) + 6 + ginfo[PIPBOY_FRM_LOGO].width / 2 + 323 - len / 2,\n                    holidayNameCopy,\n                    350,\n                    PIPBOY_WINDOW_WIDTH,\n                    colorTable[992]);\n            }\n\n            win_draw(pip_win);\n\n            gsound_play_sfx_file(\"iisxxxx1\");\n\n            const char* text = getmsg(&pipboy_message_file, &pipmesg, 215);\n            dialog_out(text, NULL, 0, 192, 135, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE);\n\n            intent = PIPBOY_OPEN_INTENT_UNSPECIFIED;\n        }\n    } else {\n        trans_buf_to_buf(\n            pipbmp[PIPBOY_FRM_LOGO],\n            ginfo[PIPBOY_FRM_LOGO].width,\n            ginfo[PIPBOY_FRM_LOGO].height,\n            ginfo[PIPBOY_FRM_LOGO].width,\n            scrn_buf + PIPBOY_WINDOW_WIDTH * 156 + 323,\n            PIPBOY_WINDOW_WIDTH);\n\n        int month;\n        int day;\n        int year;\n        game_time_date(&month, &day, &year);\n\n        int holiday;\n        for (holiday = 0; holiday < HOLIDAY_COUNT; holiday += 1) {\n            const HolidayDescription* holidayDescription = &(SpclDate[holiday]);\n            if (holidayDescription->month == month && holidayDescription->day == day) {\n                break;\n            }\n        }\n\n        if (holiday != HOLIDAY_COUNT) {\n            const HolidayDescription* holidayDescription = &(SpclDate[holiday]);\n            const char* holidayName = getmsg(&pipboy_message_file, &pipmesg, holidayDescription->textId);\n            char holidayNameCopy[256];\n            strcpy(holidayNameCopy, holidayName);\n\n            int length = text_width(holidayNameCopy);\n            text_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * (ginfo[PIPBOY_FRM_LOGO].height + 174) + 6 + ginfo[PIPBOY_FRM_LOGO].width / 2 + 323 - length / 2,\n                holidayNameCopy,\n                350,\n                PIPBOY_WINDOW_WIDTH,\n                colorTable[992]);\n        }\n\n        win_draw(pip_win);\n    }\n\n    if (quest_init() == -1) {\n        return -1;\n    }\n\n    gsound_play_sfx_file(\"pipon\");\n    win_draw(pip_win);\n\n    return intent;\n}\n\n// 0x497828\nstatic void EndPipboy()\n{\n    bool showScriptMessages = false;\n    configGetBool(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages);\n\n    if (showScriptMessages) {\n        debug_printf(\"\\nScript <Map Update>\");\n    }\n\n    scr_exec_map_update_scripts();\n\n    win_delete(pip_win);\n\n    message_exit(&pipboy_message_file);\n\n    // NOTE: Uninline.\n    holodisks_exit();\n\n    for (int index = 0; index < PIPBOY_FRM_COUNT; index++) {\n        art_ptr_unlock(grphkey[index]);\n    }\n\n    NixHotLines();\n\n    text_font(savefont);\n\n    if (bk_enable) {\n        map_enable_bk_processes();\n    }\n\n    cycle_enable();\n    enable_box_bar_win();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n    intface_redraw();\n\n    // NOTE: Uninline.\n    quest_exit();\n}\n\n// 0x497918\nvoid pip_init()\n{\n}\n\n// 0x49791C\nstatic void pip_num(int value, int digits, int x, int y)\n{\n    int offset = PIPBOY_WINDOW_WIDTH * y + x + 9 * (digits - 1);\n\n    for (int index = 0; index < digits; index++) {\n        buf_to_buf(pipbmp[PIPBOY_FRM_NUMBERS] + 9 * (value % 10), 9, 17, 360, scrn_buf + offset, PIPBOY_WINDOW_WIDTH);\n        offset -= 9;\n        value /= 10;\n    }\n}\n\n// 0x4979B4\nstatic void pip_date()\n{\n    int day;\n    int month;\n    int year;\n\n    game_time_date(&month, &day, &year);\n    pip_num(day, 2, PIPBOY_WINDOW_DAY_X, PIPBOY_WINDOW_DAY_Y);\n\n    buf_to_buf(pipbmp[PIPBOY_FRM_MONTHS] + 435 * (month - 1), 29, 14, 29, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_MONTH_Y + PIPBOY_WINDOW_MONTH_X, PIPBOY_WINDOW_WIDTH);\n\n    pip_num(year, 4, PIPBOY_WINDOW_YEAR_X, PIPBOY_WINDOW_YEAR_Y);\n}\n\n// 0x497A40\nstatic void pip_print(const char* text, int flags, int color)\n{\n    if ((flags & PIPBOY_TEXT_STYLE_UNDERLINE) != 0) {\n        color |= FONT_UNDERLINE;\n    }\n\n    int left = 8;\n    if ((flags & PIPBOY_TEXT_NO_INDENT) != 0) {\n        left -= 7;\n    }\n\n    int length = text_width(text);\n\n    if ((flags & PIPBOY_TEXT_ALIGNMENT_CENTER) != 0) {\n        left = (350 - length) / 2;\n    } else if ((flags & PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN) != 0) {\n        left += 175;\n    } else if ((flags & PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER) != 0) {\n        left += 86 - length + 16;\n    } else if ((flags & PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER) != 0) {\n        left += 260 - length;\n    }\n\n    text_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * (cursor_line * text_height() + PIPBOY_WINDOW_CONTENT_VIEW_Y) + PIPBOY_WINDOW_CONTENT_VIEW_X + left, text, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_WIDTH, color);\n\n    if ((flags & PIPBOY_TEXT_STYLE_STRIKE_THROUGH) != 0) {\n        int top = cursor_line * text_height() + 49;\n        draw_line(scrn_buf, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_X + left, top, PIPBOY_WINDOW_CONTENT_VIEW_X + left + length, top, color);\n    }\n\n    if (cursor_line < bottom_line) {\n        cursor_line += 1;\n    }\n}\n\n// 0x497B64\nstatic void pip_back(int color)\n{\n    if (bottom_line >= 0) {\n        cursor_line = bottom_line;\n    }\n\n    buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH);\n\n    // BACK\n    const char* text = getmsg(&pipboy_message_file, &pipmesg, 201);\n    pip_print(text, PIPBOY_TEXT_ALIGNMENT_CENTER, color);\n}\n\n// NOTE: Collapsed.\n//\n// 0x497BD4\nint _save_pipboy(File* stream)\n{\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x497BD4.\nint save_pipboy(File* stream)\n{\n    return _save_pipboy(stream);\n}\n\n// NOTE: Uncollapsed 0x497BD4.\nint load_pipboy(File* stream)\n{\n    return _save_pipboy(stream);\n}\n\n// 0x497BD8\nstatic void PipStatus(int a1)\n{\n    if (a1 == 1024) {\n        NixHotLines();\n        buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n            PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n            PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n            PIPBOY_WINDOW_WIDTH,\n            scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n            PIPBOY_WINDOW_WIDTH);\n        if (bottom_line >= 0) {\n            cursor_line = 0;\n        }\n\n        holo_flag = 0;\n        holodisk = -1;\n        holocount = 0;\n        view_page = 0;\n        stat_flag = 0;\n\n        for (int index = 0; index < holodisks_count; index += 1) {\n            HolodiskDescription* holodisk_entry = &(holodisks[index]);\n            if (game_global_vars[holodisk_entry->gvar] != 0) {\n                holocount += 1;\n                break;\n            }\n        }\n\n        ListStatLines(-1);\n\n        if (statcount == 0) {\n            const char* text = getmsg(&pipboy_message_file, &pipmesg, 203);\n            pip_print(text, 0, colorTable[992]);\n        }\n\n        holocount = ListHoloDiskTitles(-1);\n\n        win_draw_rect(pip_win, &pip_rect);\n        AddHotLines(2, statcount + holocount + 1, false);\n        win_draw(pip_win);\n        return;\n    }\n\n    if (stat_flag == 0 && holo_flag == 0) {\n        if (statcount != 0 && mouse_x < 429) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n                PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n                PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n                PIPBOY_WINDOW_WIDTH,\n                scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n                PIPBOY_WINDOW_WIDTH);\n            ListStatLines(a1);\n            ListHoloDiskTitles(-1);\n            win_draw_rect(pip_win, &pip_rect);\n            pause_for_tocks(200);\n            stat_flag = 1;\n        } else {\n            if (holocount != 0 && holocount >= a1 && mouse_x > 429) {\n                gsound_play_sfx_file(\"ib1p1xx1\");\n                holodisk = 0;\n\n                int index = 0;\n                for (; index < holodisks_count; index += 1) {\n                    HolodiskDescription* holodisk_entry = &(holodisks[index]);\n                    if (game_global_vars[holodisk_entry->gvar] > 0) {\n                        if (a1 - 1 == holodisk) {\n                            break;\n                        }\n                        holodisk += 1;\n                    }\n                }\n                holodisk = index;\n\n                buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n                    PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n                    PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n                    PIPBOY_WINDOW_WIDTH,\n                    scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n                    PIPBOY_WINDOW_WIDTH);\n                ListHoloDiskTitles(holodisk);\n                ListStatLines(-1);\n                win_draw_rect(pip_win, &pip_rect);\n                pause_for_tocks(200);\n                NixHotLines();\n                ShowHoloDisk();\n                AddHotLines(0, 0, true);\n                holo_flag = 1;\n            }\n        }\n    }\n\n    if (stat_flag == 0) {\n        if (holo_flag == 0 || a1 < 1025 || a1 > 1027) {\n            return;\n        }\n\n        if ((mouse_x > 459 && a1 != 1027) || a1 == 1026) {\n            if (holopages <= view_page) {\n                if (a1 != 1026) {\n                    gsound_play_sfx_file(\"ib1p1xx1\");\n                    buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH);\n\n                    if (bottom_line >= 0) {\n                        cursor_line = bottom_line;\n                    }\n\n                    // Back\n                    const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 201);\n                    pip_print(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]);\n\n                    if (bottom_line >= 0) {\n                        cursor_line = bottom_line;\n                    }\n\n                    // Done\n                    const char* text2 = getmsg(&pipboy_message_file, &pipmesg, 214);\n                    pip_print(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]);\n\n                    win_draw_rect(pip_win, &pip_rect);\n                    pause_for_tocks(200);\n                    PipStatus(1024);\n                }\n            } else {\n                gsound_play_sfx_file(\"ib1p1xx1\");\n                buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH);\n\n                if (bottom_line >= 0) {\n                    cursor_line = bottom_line;\n                }\n\n                // Back\n                const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 201);\n                pip_print(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]);\n\n                if (bottom_line >= 0) {\n                    cursor_line = bottom_line;\n                }\n\n                // More\n                const char* text2 = getmsg(&pipboy_message_file, &pipmesg, 200);\n                pip_print(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]);\n\n                win_draw_rect(pip_win, &pip_rect);\n                pause_for_tocks(200);\n\n                view_page += 1;\n\n                ShowHoloDisk();\n            }\n            return;\n        }\n\n        if (a1 == 1027) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH);\n\n            if (bottom_line >= 0) {\n                cursor_line = bottom_line;\n            }\n\n            // Back\n            const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 201);\n            pip_print(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]);\n\n            if (bottom_line >= 0) {\n                cursor_line = bottom_line;\n            }\n\n            // More\n            const char* text2 = getmsg(&pipboy_message_file, &pipmesg, 200);\n            pip_print(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]);\n\n            win_draw_rect(pip_win, &pip_rect);\n            pause_for_tocks(200);\n\n            view_page -= 1;\n\n            if (view_page < 0) {\n                PipStatus(1024);\n                return;\n            }\n        } else {\n            if (mouse_x > 395) {\n                return;\n            }\n\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH);\n\n            if (bottom_line >= 0) {\n                cursor_line = bottom_line;\n            }\n\n            // Back\n            const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 201);\n            pip_print(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]);\n\n            if (bottom_line >= 0) {\n                cursor_line = bottom_line;\n            }\n\n            // More\n            const char* text2 = getmsg(&pipboy_message_file, &pipmesg, 200);\n            pip_print(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]);\n\n            win_draw_rect(pip_win, &pip_rect);\n            pause_for_tocks(200);\n\n            if (view_page <= 0) {\n                PipStatus(1024);\n                return;\n            }\n\n            view_page -= 1;\n        }\n\n        ShowHoloDisk();\n        return;\n    }\n\n    if (a1 == 1025) {\n        gsound_play_sfx_file(\"ib1p1xx1\");\n        pip_back(colorTable[32747]);\n        win_draw_rect(pip_win, &pip_rect);\n        pause_for_tocks(200);\n        PipStatus(1024);\n    }\n\n    if (a1 <= statcount) {\n        gsound_play_sfx_file(\"ib1p1xx1\");\n\n        int v13 = 0;\n        int index = 0;\n        for (; index < quest_count; index++) {\n            QuestDescription* questDescription = &(quests[index]);\n            if (questDescription->displayThreshold <= game_global_vars[questDescription->gvar]) {\n                if (v13 == a1 - 1) {\n                    break;\n                }\n\n                v13 += 1;\n\n                // Skip quests in the same location.\n                //\n                // FIXME: This code should be identical to the one in the\n                // `ListStatLines`. See buffer overread\n                // bug involved.\n                for (; index < quest_count; index++) {\n                    if (quests[index].location != quests[index + 1].location) {\n                        break;\n                    }\n                }\n            }\n        }\n\n        NixHotLines();\n        buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n            PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n            PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n            PIPBOY_WINDOW_WIDTH,\n            scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n            PIPBOY_WINDOW_WIDTH);\n        if (bottom_line >= 0) {\n            cursor_line = 0;\n        }\n\n        if (bottom_line >= 1) {\n            cursor_line = 1;\n        }\n\n        AddHotLines(0, 0, true);\n\n        QuestDescription* questDescription = &(quests[index]);\n\n        const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 210);\n        const char* text2 = getmsg(&map_msg_file, &pipmesg, questDescription->location);\n        char formattedText[1024];\n        sprintf(formattedText, \"%s %s\", text2, text1);\n        pip_print(formattedText, PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]);\n\n        if (bottom_line >= 3) {\n            cursor_line = 3;\n        }\n\n        int number = 1;\n        for (; index < quest_count; index++) {\n            QuestDescription* questDescription = &(quests[index]);\n            if (game_global_vars[questDescription->gvar] >= questDescription->displayThreshold) {\n                const char* text = getmsg(&quest_message_file, &pipmesg, questDescription->description);\n                char formattedText[1024];\n                sprintf(formattedText, \"%d. %s\", number, text);\n                number += 1;\n\n                short beginnings[WORD_WRAP_MAX_COUNT];\n                short count;\n                if (word_wrap(formattedText, 350, beginnings, &count) == 0) {\n                    for (int line = 0; line < count - 1; line += 1) {\n                        char* beginning = formattedText + beginnings[line];\n                        char* ending = formattedText + beginnings[line + 1];\n                        char c = *ending;\n                        *ending = '\\0';\n\n                        int flags;\n                        int color;\n                        if (game_global_vars[questDescription->gvar] < questDescription->completedThreshold) {\n                            flags = 0;\n                            color = colorTable[992];\n                        } else {\n                            flags = PIPBOY_TEXT_STYLE_STRIKE_THROUGH;\n                            color = colorTable[8804];\n                        }\n\n                        pip_print(beginning, flags, color);\n\n                        *ending = c;\n                        cursor_line += 1;\n                    }\n                } else {\n                    debug_printf(\"\\n ** Word wrap error in pipboy! **\\n\");\n                }\n            }\n\n            if (index != quest_count - 1) {\n                QuestDescription* nextQuestDescription = &(quests[index + 1]);\n                if (questDescription->location != nextQuestDescription->location) {\n                    break;\n                }\n            }\n        }\n\n        pip_back(colorTable[992]);\n        win_draw_rect(pip_win, &pip_rect);\n        stat_flag = 1;\n    }\n}\n\n// [a1] is likely selected location, or -1 if nothing is selected\n//\n// 0x498734\nstatic void ListStatLines(int a1)\n{\n    if (bottom_line >= 0) {\n        cursor_line = 0;\n    }\n\n    int flags = holocount != 0 ? PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER : PIPBOY_TEXT_ALIGNMENT_CENTER;\n    flags |= PIPBOY_TEXT_STYLE_UNDERLINE;\n\n    // STATUS\n    const char* statusText = getmsg(&pipboy_message_file, &pipmesg, 202);\n    pip_print(statusText, flags, colorTable[992]);\n\n    if (bottom_line >= 2) {\n        cursor_line = 2;\n    }\n\n    statcount = 0;\n\n    for (int index = 0; index < quest_count; index += 1) {\n        QuestDescription* quest = &(quests[index]);\n        if (quest->displayThreshold > game_global_vars[quest->gvar]) {\n            continue;\n        }\n\n        int color = (cursor_line - 1) / 2 == (a1 - 1) ? colorTable[32747] : colorTable[992];\n\n        // Render location.\n        const char* questLocation = getmsg(&map_msg_file, &pipmesg, quest->location);\n        pip_print(questLocation, 0, color);\n\n        cursor_line += 1;\n        statcount += 1;\n\n        // Skip quests in the same location.\n        //\n        // FIXME: There is a buffer overread bug at the end of the loop. It does\n        // not manifest because dynamically allocated memory blocks have special\n        // footer guard. Location field is the first in the struct and matches\n        // size of the guard. So on the final iteration it compares location of\n        // the last quest with this special guard (0xBEEFCAFE).\n        for (; index < quest_count; index++) {\n            if (quests[index].location != quests[index + 1].location) {\n                break;\n            }\n        }\n    }\n}\n\n// 0x4988A0\nstatic void ShowHoloDisk()\n{\n    buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n        PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n        PIPBOY_WINDOW_WIDTH,\n        scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_WIDTH);\n\n    if (bottom_line >= 0) {\n        cursor_line = 0;\n    }\n\n    HolodiskDescription* holodisk_entry = &(holodisks[holodisk]);\n\n    int holodiskTextId;\n    int linesCount = 0;\n\n    holopages = 0;\n\n    for (holodiskTextId = holodisk_entry->description; holodiskTextId < holodisk_entry->description + 500; holodiskTextId += 1) {\n        const char* text = getmsg(&pipboy_message_file, &pipmesg, holodiskTextId);\n        if (strcmp(text, \"**END-DISK**\") == 0) {\n            break;\n        }\n\n        linesCount += 1;\n        if (linesCount >= PIPBOY_HOLODISK_LINES_MAX) {\n            linesCount = 0;\n            holopages += 1;\n        }\n    }\n\n    if (holodiskTextId >= holodisk_entry->description + 500) {\n        debug_printf(\"\\nPIPBOY: #1 Holodisk text end not found!\\n\");\n    }\n\n    holodiskTextId = holodisk_entry->description;\n\n    if (view_page != 0) {\n        int page = 0;\n        int numberOfLines = 0;\n        for (; holodiskTextId < holodiskTextId + 500; holodiskTextId += 1) {\n            const char* line = getmsg(&pipboy_message_file, &pipmesg, holodiskTextId);\n            if (strcmp(line, \"**END-DISK**\") == 0) {\n                debug_printf(\"\\nPIPBOY: Premature page end in holodisk page search!\\n\");\n                break;\n            }\n\n            numberOfLines += 1;\n            if (numberOfLines >= PIPBOY_HOLODISK_LINES_MAX) {\n                page += 1;\n                if (page >= view_page) {\n                    break;\n                }\n\n                numberOfLines = 0;\n            }\n        }\n\n        holodiskTextId += 1;\n\n        if (holodiskTextId >= holodisk_entry->description + 500) {\n            debug_printf(\"\\nPIPBOY: #2 Holodisk text end not found!\\n\");\n        }\n    } else {\n        const char* name = getmsg(&pipboy_message_file, &pipmesg, holodisk_entry->name);\n        pip_print(name, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]);\n    }\n\n    if (holopages != 0) {\n        // of\n        const char* of = getmsg(&pipboy_message_file, &pipmesg, 212);\n        char formattedText[60]; // TODO: Size is probably wrong.\n        sprintf(formattedText, \"%d %s %d\", view_page + 1, of, holopages + 1);\n\n        int len = text_width(of);\n        text_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * 47 + 616 + 604 - len, formattedText, 350, PIPBOY_WINDOW_WIDTH, colorTable[992]);\n    }\n\n    if (bottom_line >= 3) {\n        cursor_line = 3;\n    }\n\n    for (int line = 0; line < PIPBOY_HOLODISK_LINES_MAX; line += 1) {\n        const char* text = getmsg(&pipboy_message_file, &pipmesg, holodiskTextId);\n        if (strcmp(text, \"**END-DISK**\") == 0) {\n            break;\n        }\n\n        if (strcmp(text, \"**END-PAR**\") == 0) {\n            cursor_line += 1;\n        } else {\n            pip_print(text, PIPBOY_TEXT_NO_INDENT, colorTable[992]);\n        }\n\n        holodiskTextId += 1;\n    }\n\n    int moreOrDoneTextId;\n    if (holopages <= view_page) {\n        if (bottom_line >= 0) {\n            cursor_line = bottom_line;\n        }\n\n        const char* back = getmsg(&pipboy_message_file, &pipmesg, 201);\n        pip_print(back, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]);\n\n        if (bottom_line >= 0) {\n            cursor_line = bottom_line;\n        }\n\n        moreOrDoneTextId = 214;\n    } else {\n        if (bottom_line >= 0) {\n            cursor_line = bottom_line;\n        }\n\n        const char* back = getmsg(&pipboy_message_file, &pipmesg, 201);\n        pip_print(back, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]);\n\n        if (bottom_line >= 0) {\n            cursor_line = bottom_line;\n        }\n\n        moreOrDoneTextId = 200;\n    }\n\n    const char* moreOrDoneText = getmsg(&pipboy_message_file, &pipmesg, moreOrDoneTextId);\n    pip_print(moreOrDoneText, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]);\n    win_draw(pip_win);\n}\n\n// 0x498C40\nstatic int ListHoloDiskTitles(int a1)\n{\n    if (bottom_line >= 2) {\n        cursor_line = 2;\n    }\n\n    int knownHolodisksCount = 0;\n    for (int index = 0; index < holodisks_count; index++) {\n        HolodiskDescription* holodisk_entry = &(holodisks[index]);\n        if (game_global_vars[holodisk_entry->gvar] != 0) {\n            int color;\n            if ((cursor_line - 2) / 2 == a1) {\n                color = colorTable[32747];\n            } else {\n                color = colorTable[992];\n            }\n\n            const char* text = getmsg(&pipboy_message_file, &pipmesg, holodisk_entry->name);\n            pip_print(text, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN, color);\n\n            cursor_line++;\n            knownHolodisksCount++;\n        }\n    }\n\n    if (knownHolodisksCount != 0) {\n        if (bottom_line >= 0) {\n            cursor_line = 0;\n        }\n\n        const char* text = getmsg(&pipboy_message_file, &pipmesg, 211); // DATA\n        pip_print(text, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]);\n    }\n\n    return knownHolodisksCount;\n}\n\n// 0x498D34\nstatic int qscmp(const void* a1, const void* a2)\n{\n    STRUCT_664350* v1 = (STRUCT_664350*)a1;\n    STRUCT_664350* v2 = (STRUCT_664350*)a2;\n\n    return strcmp(v1->name, v2->name);\n}\n\n// 0x498D40\nstatic void PipAutomaps(int a1)\n{\n    if (a1 == 1024) {\n        NixHotLines();\n        buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n            PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n            PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n            PIPBOY_WINDOW_WIDTH,\n            scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n            PIPBOY_WINDOW_WIDTH);\n\n        if (bottom_line >= 0) {\n            cursor_line = 0;\n        }\n\n        const char* title = getmsg(&pipboy_message_file, &pipmesg, 205);\n        pip_print(title, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]);\n\n        actcnt = PrintAMList(-1);\n\n        AddHotLines(2, actcnt, 0);\n\n        win_draw_rect(pip_win, &pip_rect);\n        amlst_mode = 0;\n        return;\n    }\n\n    if (amlst_mode != 0) {\n        if (a1 == 1025 || a1 <= -1) {\n            PipAutomaps(1024);\n            gsound_play_sfx_file(\"ib1p1xx1\");\n        }\n\n        if (a1 >= 1 && a1 <= actcnt + 3) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            PrintAMelevList(a1);\n            draw_top_down_map_pipboy(pip_win, sortlist[a1 - 1].field_6, sortlist[a1 - 1].field_4);\n            win_draw_rect(pip_win, &pip_rect);\n        }\n\n        return;\n    }\n\n    if (a1 > 0 && a1 <= actcnt) {\n        gsound_play_sfx_file(\"ib1p1xx1\");\n        NixHotLines();\n        PrintAMList(a1);\n        win_draw_rect(pip_win, &pip_rect);\n        amcty_indx = sortlist[a1 - 1].field_4;\n        actcnt = PrintAMelevList(1);\n        AddHotLines(0, actcnt + 2, 1);\n        draw_top_down_map_pipboy(pip_win, sortlist[0].field_6, sortlist[0].field_4);\n        win_draw_rect(pip_win, &pip_rect);\n        amlst_mode = 1;\n    }\n}\n\n// 0x498F30\nstatic int PrintAMelevList(int a1)\n{\n    AutomapHeader* automapHeader;\n    if (ReadAMList(&automapHeader) == -1) {\n        return -1;\n    }\n\n    int v4 = 0;\n    for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        if (automapHeader->offsets[amcty_indx][elevation] > 0) {\n            sortlist[v4].name = map_get_elev_idx(amcty_indx, elevation);\n            sortlist[v4].field_4 = elevation;\n            sortlist[v4].field_6 = amcty_indx;\n            v4++;\n        }\n    }\n\n    int mapCount = wmMapMaxCount();\n    for (int map = 0; map < mapCount; map++) {\n        if (map == amcty_indx) {\n            continue;\n        }\n\n        if (get_map_idx_same(amcty_indx, map) == -1) {\n            continue;\n        }\n\n        for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n            if (automapHeader->offsets[map][elevation] > 0) {\n                sortlist[v4].name = map_get_elev_idx(map, elevation);\n                sortlist[v4].field_4 = elevation;\n                sortlist[v4].field_6 = map;\n                v4++;\n            }\n        }\n    }\n\n    buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n        PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n        PIPBOY_WINDOW_WIDTH,\n        scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_WIDTH);\n\n    if (bottom_line >= 0) {\n        cursor_line = 0;\n    }\n\n    const char* msg = getmsg(&pipboy_message_file, &pipmesg, 205);\n    pip_print(msg, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]);\n\n    if (bottom_line >= 2) {\n        cursor_line = 2;\n    }\n\n    const char* name = map_get_description_idx(amcty_indx);\n    pip_print(name, PIPBOY_TEXT_ALIGNMENT_CENTER, colorTable[992]);\n\n    if (bottom_line >= 4) {\n        cursor_line = 4;\n    }\n\n    int selectedPipboyLine = (a1 - 1) * 2;\n\n    for (int index = 0; index < v4; index++) {\n        int color;\n        if (cursor_line - 4 == selectedPipboyLine) {\n            color = colorTable[32747];\n        } else {\n            color = colorTable[992];\n        }\n\n        pip_print(sortlist[index].name, 0, color);\n        cursor_line++;\n    }\n\n    pip_back(colorTable[992]);\n\n    return v4;\n}\n\n// 0x499150\nstatic int PrintAMList(int a1)\n{\n    AutomapHeader* automapHeader;\n    if (ReadAMList(&automapHeader) == -1) {\n        return -1;\n    }\n\n    int count = 0;\n    int index = 0;\n\n    int mapCount = wmMapMaxCount();\n    for (int map = 0; map < mapCount; map++) {\n        int elevation;\n        for (elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n            if (automapHeader->offsets[map][elevation] > 0) {\n                if (automapDisplayMap(map) == 0) {\n                    break;\n                }\n            }\n        }\n\n        if (elevation < ELEVATION_COUNT) {\n            int v7;\n            if (count != 0) {\n                v7 = 0;\n                for (int index = 0; index < count; index++) {\n                    if (is_map_idx_same(map, sortlist[index].field_4)) {\n                        break;\n                    }\n\n                    v7++;\n                }\n            } else {\n                v7 = 0;\n            }\n\n            if (v7 == count) {\n                sortlist[count].name = map_get_short_name(map);\n                sortlist[count].field_4 = map;\n                count++;\n            }\n        }\n    }\n\n    if (count != 0) {\n        if (count > 1) {\n            qsort(sortlist, count, sizeof(*sortlist), qscmp);\n        }\n\n        buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n            PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n            PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n            PIPBOY_WINDOW_WIDTH,\n            scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n            PIPBOY_WINDOW_WIDTH);\n\n        if (bottom_line >= 0) {\n            cursor_line = 0;\n        }\n\n        const char* msg = getmsg(&pipboy_message_file, &pipmesg, 205);\n        pip_print(msg, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]);\n\n        if (bottom_line >= 2) {\n            cursor_line = 2;\n        }\n\n        for (int index = 0; index < count; index++) {\n            int color;\n            if (cursor_line - 1 == a1) {\n                color = colorTable[32747];\n            } else {\n                color = colorTable[992];\n            }\n\n            pip_print(sortlist[index].name, 0, color);\n            cursor_line++;\n        }\n    }\n\n    return count;\n}\n\n// 0x49932C\nstatic void PipArchives(int a1)\n{\n    if (a1 == 1024) {\n        NixHotLines();\n        view_page = ListArchive(-1);\n        AddHotLines(2, view_page, false);\n    } else if (a1 >= 0 && a1 <= view_page) {\n        gsound_play_sfx_file(\"ib1p1xx1\");\n\n        ListArchive(a1);\n\n        int movie;\n        for (movie = 2; movie < 16; movie++) {\n            if (gmovie_has_been_played(movie)) {\n                a1--;\n                if (a1 <= 0) {\n                    break;\n                }\n            }\n        }\n\n        if (movie <= MOVIE_COUNT) {\n            gmovie_play(movie, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC);\n        } else {\n            debug_printf(\"\\n ** Selected movie not found in list! **\\n\");\n        }\n\n        text_font(101);\n\n        wait_time = get_time();\n        ListArchive(-1);\n    }\n}\n\n// 0x4993DC\nstatic int ListArchive(int a1)\n{\n    const char* text;\n    int i;\n    int v12;\n    int msg_num;\n    int v5;\n    int v8;\n    int v9;\n\n    buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n        PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n        PIPBOY_WINDOW_WIDTH,\n        scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_WIDTH);\n\n    if (bottom_line >= 0) {\n        cursor_line = 0;\n    }\n\n    // VIDEO ARCHIVES\n    text = getmsg(&pipboy_message_file, &pipmesg, 206);\n    pip_print(text, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]);\n\n    if (bottom_line >= 2) {\n        cursor_line = 2;\n    }\n\n    v5 = 0;\n    v12 = a1 - 1;\n\n    // 502 - Elder Speech\n    // ...\n    // 516 - Credits\n    msg_num = 502;\n\n    for (i = 2; i < 16; i++) {\n        if (gmovie_has_been_played(i)) {\n            v8 = v5++;\n            if (v8 == v12) {\n                v9 = colorTable[32747];\n            } else {\n                v9 = colorTable[992];\n            }\n\n            text = getmsg(&pipboy_message_file, &pipmesg, msg_num);\n            pip_print(text, 0, v9);\n\n            cursor_line++;\n        }\n\n        msg_num++;\n    }\n\n    win_draw_rect(pip_win, &pip_rect);\n\n    return v5;\n}\n\n// 0x499518\nstatic void PipAlarm(int a1)\n{\n    if (a1 == 1024) {\n        if (critter_can_obj_dude_rest()) {\n            NixHotLines();\n            DrawAlarmText(0);\n            AddHotLines(5, currentAlarmTypeCount, false);\n        } else {\n            gsound_play_sfx_file(\"iisxxxx1\");\n\n            // You cannot rest at this location!\n            const char* text = getmsg(&pipboy_message_file, &pipmesg, 215);\n            dialog_out(text, NULL, 0, 192, 135, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE);\n        }\n    } else if (a1 >= 4 && a1 <= 17) {\n        gsound_play_sfx_file(\"ib1p1xx1\");\n\n        DrawAlarmText(a1 - 3);\n\n        int duration = a1 - 4;\n        int minutes = 0;\n        int hours = 0;\n        int v10 = 0;\n\n        switch (duration) {\n        case PIPBOY_REST_DURATION_TEN_MINUTES:\n            TimedRest(0, 10, 0);\n            break;\n        case PIPBOY_REST_DURATION_THIRTY_MINUTES:\n            TimedRest(0, 30, 0);\n            break;\n        case PIPBOY_REST_DURATION_ONE_HOUR:\n        case PIPBOY_REST_DURATION_TWO_HOURS:\n        case PIPBOY_REST_DURATION_THREE_HOURS:\n        case PIPBOY_REST_DURATION_FOUR_HOURS:\n        case PIPBOY_REST_DURATION_FIVE_HOURS:\n        case PIPBOY_REST_DURATION_SIX_HOURS:\n            TimedRest(duration - 1, 0, 0);\n            break;\n        case PIPBOY_REST_DURATION_UNTIL_MORNING:\n            ClacTime(&hours, &minutes, 8);\n            TimedRest(hours, minutes, 0);\n            break;\n        case PIPBOY_REST_DURATION_UNTIL_NOON:\n            ClacTime(&hours, &minutes, 12);\n            TimedRest(hours, minutes, 0);\n            break;\n        case PIPBOY_REST_DURATION_UNTIL_EVENING:\n            ClacTime(&hours, &minutes, 18);\n            TimedRest(hours, minutes, 0);\n            break;\n        case PIPBOY_REST_DURATION_UNTIL_MIDNIGHT:\n            ClacTime(&hours, &minutes, 0);\n            if (TimedRest(hours, minutes, 0) == 0) {\n                pip_num(0, 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y);\n            }\n            win_draw(pip_win);\n            break;\n        case PIPBOY_REST_DURATION_UNTIL_HEALED:\n        case PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED:\n            TimedRest(0, 0, duration);\n            break;\n        }\n\n        gsound_play_sfx_file(\"ib2lu1x1\");\n\n        DrawAlarmText(0);\n    }\n}\n\n// 0x4996B4\nstatic void DrawAlarmText(int a1)\n{\n    const char* text;\n\n    buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n        PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n        PIPBOY_WINDOW_WIDTH,\n        scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_WIDTH);\n\n    if (bottom_line >= 0) {\n        cursor_line = 0;\n    }\n\n    // ALARM CLOCK\n    text = getmsg(&pipboy_message_file, &pipmesg, 300);\n    pip_print(text, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]);\n\n    if (bottom_line >= 5) {\n        cursor_line = 5;\n    }\n\n    DrawAlrmHitPnts();\n\n    // NOTE: I don't know if this +1 was a result of compiler optimization or it\n    // was written like this in the first place.\n    for (int option = 1; option < currentAlarmTypeCount + 1; option++) {\n        // 302 - Rest for ten minutes\n        // ...\n        // 315 - Rest until party is healed\n        text = getmsg(&pipboy_message_file, &pipmesg, 302 + option - 1);\n        int color = option == a1 ? colorTable[32747] : colorTable[992];\n\n        pip_print(text, 0, color);\n\n        cursor_line++;\n    }\n\n    win_draw_rect(pip_win, &pip_rect);\n}\n\n// 0x4997B8\nstatic void DrawAlrmHitPnts()\n{\n    int max_hp;\n    int cur_hp;\n    char* text;\n    char msg[64];\n    int len;\n\n    buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + 66 * PIPBOY_WINDOW_WIDTH + 254,\n        350,\n        10,\n        PIPBOY_WINDOW_WIDTH,\n        scrn_buf + 66 * PIPBOY_WINDOW_WIDTH + 254,\n        PIPBOY_WINDOW_WIDTH);\n\n    max_hp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n    cur_hp = critter_get_hits(obj_dude);\n    text = getmsg(&pipboy_message_file, &pipmesg, 301); // Hit Points\n    sprintf(msg, \"%s %d/%d\", text, cur_hp, max_hp);\n    len = text_width(msg);\n    text_to_buf(scrn_buf + 66 * PIPBOY_WINDOW_WIDTH + 254 + (350 - len) / 2, msg, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_WIDTH, colorTable[992]);\n}\n\n// 0x4998C0\nstatic void AddHotLines(int start, int count, bool add_back_button)\n{\n    text_font(101);\n\n    int height = text_height();\n\n    hot_line_start = start;\n    hot_line_count = count;\n\n    if (count != 0) {\n        int y = start * height + PIPBOY_WINDOW_CONTENT_VIEW_Y;\n        int eventCode = start + 505;\n        for (int index = start; index < 22; index++) {\n            if (hot_line_count + hot_line_start <= index) {\n                break;\n            }\n\n            HotLines[index] = win_register_button(pip_win, 254, y, 350, height, -1, -1, -1, eventCode, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT);\n            y += height * 2;\n            eventCode += 1;\n        }\n    }\n\n    if (add_back_button) {\n        HotLines[BACK_BUTTON_INDEX] = win_register_button(pip_win, 254, height * bottom_line + PIPBOY_WINDOW_CONTENT_VIEW_Y, 350, height, -1, -1, -1, 528, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT);\n    }\n}\n\n// 0x4999C0\nstatic void NixHotLines()\n{\n    if (hot_line_count != 0) {\n        // NOTE: There is a buffer overread bug. In original binary it leads to\n        // reading continuation (from 0x6644B8 onwards), which finally destroys\n        // button in `pip_win` (id #3), which corresponds to Skilldex\n        // button. Other buttons can be destroyed depending on the last mouse\n        // position. I was not able to replicate this exact behaviour with MSVC.\n        // So here is a small fix, which is an exception to \"Do not fix vanilla\n        // bugs\" strategy.\n        //\n        // TODO: Reevaluate after merging back button into `HotLines`.\n        int end = hot_line_start + hot_line_count;\n        if (end > 22) {\n            end = 22;\n        }\n\n        for (int index = hot_line_start; index < end; index++) {\n            win_delete_button(HotLines[index]);\n        }\n    }\n\n    if (hot_back_line) {\n        win_delete_button(HotLines[BACK_BUTTON_INDEX]);\n    }\n\n    hot_line_count = 0;\n    hot_back_line = 0;\n}\n\n// 0x499A24\nstatic bool TimedRest(int hours, int minutes, int duration)\n{\n    gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH);\n\n    bool rc = false;\n\n    if (duration == 0) {\n        int hoursInMinutes = hours * 60;\n        double v1 = (double)hoursInMinutes + (double)minutes;\n        double v2 = v1 * (1.0 / 1440.0) * 3.5 + 0.25;\n        double v3 = (double)minutes / v1 * v2;\n        if (minutes != 0) {\n            int gameTime = game_time();\n\n            double v4 = v3 * 20.0;\n            int v5 = 0;\n            for (int v5 = 0; v5 < (int)v4; v5++) {\n                if (rc) {\n                    break;\n                }\n\n                unsigned int start = get_time();\n\n                unsigned int v6 = (unsigned int)((double)v5 / v4 * ((double)minutes * 600.0) + (double)gameTime);\n                unsigned int nextEventTime = queue_next_time();\n                if (v6 >= nextEventTime) {\n                    gameTimeSetTime(nextEventTime + 1);\n                    if (queue_process()) {\n                        rc = true;\n                        debug_printf(\"PIPBOY: Returning from Queue trigger...\\n\");\n                        proc_bail_flag = 1;\n                        break;\n                    }\n\n                    if (game_user_wants_to_quit != 0) {\n                        rc = true;\n                    }\n                }\n\n                if (!rc) {\n                    gameTimeSetTime(v6);\n                    if (get_input() == KEY_ESCAPE || game_user_wants_to_quit != 0) {\n                        rc = true;\n                    }\n\n                    pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y);\n                    pip_date();\n                    win_draw(pip_win);\n\n                    while (elapsed_time(start) < 50) {\n                    }\n                }\n            }\n\n            if (!rc) {\n                gameTimeSetTime(gameTime + 600 * minutes);\n\n                if (Check4Health(minutes)) {\n                    // NOTE: Uninline.\n                    AddHealth();\n                }\n            }\n\n            pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y);\n            pip_date();\n            DrawAlrmHitPnts();\n            win_draw(pip_win);\n        }\n\n        if (hours != 0 && !rc) {\n            int gameTime = game_time();\n            double v7 = (v2 - v3) * 20.0;\n\n            for (int hour = 0; hour < (int)v7; hour++) {\n                if (rc) {\n                    break;\n                }\n\n                unsigned int start = get_time();\n\n                if (get_input() == KEY_ESCAPE || game_user_wants_to_quit != 0) {\n                    rc = true;\n                }\n\n                unsigned int v8 = (unsigned int)((double)hour / v7 * (hours * GAME_TIME_TICKS_PER_HOUR) + gameTime);\n                unsigned int nextEventTime = queue_next_time();\n                if (!rc && v8 >= nextEventTime) {\n                    gameTimeSetTime(nextEventTime + 1);\n\n                    if (queue_process()) {\n                        rc = true;\n                        debug_printf(\"PIPBOY: Returning from Queue trigger...\\n\");\n                        proc_bail_flag = 1;\n                        break;\n                    }\n\n                    if (game_user_wants_to_quit != 0) {\n                        rc = true;\n                    }\n                }\n\n                if (!rc) {\n                    gameTimeSetTime(v8);\n\n                    int healthToAdd = (int)((double)hoursInMinutes / v7);\n                    if (Check4Health(healthToAdd)) {\n                        // NOTE: Uninline.\n                        AddHealth();\n                    }\n\n                    pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y);\n                    pip_date();\n                    DrawAlrmHitPnts();\n                    win_draw(pip_win);\n\n                    while (elapsed_time(start) < 50) {\n                    }\n                }\n            }\n\n            if (!rc) {\n                gameTimeSetTime(gameTime + GAME_TIME_TICKS_PER_HOUR * hours);\n            }\n\n            pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y);\n            pip_date();\n            DrawAlrmHitPnts();\n            win_draw(pip_win);\n        }\n    } else if (duration == PIPBOY_REST_DURATION_UNTIL_HEALED || duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) {\n        int currentHp = critter_get_hits(obj_dude);\n        int maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n        if (currentHp != maxHp\n            || (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED && partyMemberNeedsHealing())) {\n            // First pass - healing dude is the top priority.\n            int hpToHeal = maxHp - currentHp;\n            int healingRate = critterGetStat(obj_dude, STAT_HEALING_RATE);\n            int hoursToHeal = (int)((double)hpToHeal / (double)healingRate * 3.0);\n            while (!rc && hoursToHeal != 0) {\n                if (hoursToHeal <= 24) {\n                    rc = TimedRest(hoursToHeal, 0, 0);\n                    hoursToHeal = 0;\n                } else {\n                    rc = TimedRest(24, 0, 0);\n                    hoursToHeal -= 24;\n                }\n            }\n\n            // Second pass - attempt to heal delayed damage to dude (via poison\n            // or radiation), and remaining party members. This process is\n            // performed in 3 hour increments.\n            currentHp = critter_get_hits(obj_dude);\n            maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n            hpToHeal = maxHp - currentHp;\n\n            if (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) {\n                int partyHpToHeal = partyMemberMaxHealingNeeded();\n                if (partyHpToHeal > hpToHeal) {\n                    hpToHeal = partyHpToHeal;\n                }\n            }\n\n            while (!rc && hpToHeal != 0) {\n                currentHp = critter_get_hits(obj_dude);\n                maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n                hpToHeal = maxHp - currentHp;\n\n                if (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) {\n                    int partyHpToHeal = partyMemberMaxHealingNeeded();\n                    if (partyHpToHeal > hpToHeal) {\n                        hpToHeal = partyHpToHeal;\n                    }\n                }\n\n                rc = TimedRest(3, 0, 0);\n            }\n        } else {\n            // No one needs healing.\n            gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n            return rc;\n        }\n    }\n\n    int gameTime = game_time();\n    int nextEventGameTime = queue_next_time();\n    if (gameTime > nextEventGameTime) {\n        if (queue_process()) {\n            debug_printf(\"PIPBOY: Returning from Queue trigger...\\n\");\n            proc_bail_flag = 1;\n            rc = true;\n        }\n    }\n\n    pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y);\n    pip_date();\n    win_draw(pip_win);\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    return rc;\n}\n\n// 0x499FCC\nstatic bool Check4Health(int a1)\n{\n    rest_time += a1;\n\n    if (rest_time < 180) {\n        return false;\n    }\n\n    debug_printf(\"\\n health added!\\n\");\n    rest_time = 0;\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x49A008\nstatic bool AddHealth()\n{\n    partyMemberRestingHeal(3);\n\n    int currentHp = critter_get_hits(obj_dude);\n    int maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n    return currentHp == maxHp;\n}\n\n// Returns [hours] and [minutes] needed to rest until [wakeUpHour].\n//\n// 0x49A03C\nstatic void ClacTime(int* hours, int* minutes, int wakeUpHour)\n{\n    int gameTimeHour = game_time_hour();\n\n    *hours = gameTimeHour / 100;\n    *minutes = gameTimeHour % 100;\n\n    if (*hours != wakeUpHour || *minutes != 0) {\n        *hours = wakeUpHour - *hours;\n        if (*hours < 0) {\n            *hours += 24;\n            if (*minutes != 0) {\n                *hours -= 1;\n                *minutes = 60 - *minutes;\n            }\n        } else {\n            if (*minutes != 0) {\n                *hours -= 1;\n                *minutes = 60 - *minutes;\n                if (*hours < 0) {\n                    *hours = 23;\n                }\n            }\n        }\n    } else {\n        *hours = 24;\n    }\n}\n\n// 0x49A0C8\nstatic int ScreenSaver()\n{\n    PipboyBomb bombs[PIPBOY_BOMB_COUNT];\n\n    mouse_get_position(&old_mouse_x, &old_mouse_y);\n\n    for (int index = 0; index < PIPBOY_BOMB_COUNT; index += 1) {\n        bombs[index].field_10 = 0;\n    }\n\n    gmouse_disable(0);\n\n    unsigned char* buf = (unsigned char*)mem_malloc(412 * 374);\n    if (buf == NULL) {\n        return -1;\n    }\n\n    buf_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n        PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n        PIPBOY_WINDOW_WIDTH,\n        buf,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH);\n\n    buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n        PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n        PIPBOY_WINDOW_WIDTH,\n        scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_WIDTH);\n\n    int v31 = 50;\n    while (true) {\n        unsigned int time = get_time();\n\n        mouse_get_position(&mouse_x, &mouse_y);\n        if (get_input() != -1 || old_mouse_x != mouse_x || old_mouse_y != mouse_y) {\n            break;\n        }\n\n        double random = roll_random(0, PIPBOY_RAND_MAX);\n\n        // TODO: Figure out what this constant means. Probably somehow related\n        // to PIPBOY_RAND_MAX.\n        if (random < 3047.3311) {\n            int index = 0;\n            for (; index < PIPBOY_BOMB_COUNT; index += 1) {\n                if (bombs[index].field_10 == 0) {\n                    break;\n                }\n            }\n\n            if (index < PIPBOY_BOMB_COUNT) {\n                PipboyBomb* bomb = &(bombs[index]);\n                int v27 = (350 - ginfo[PIPBOY_FRM_BOMB].width / 4) + (406 - ginfo[PIPBOY_FRM_BOMB].height / 4);\n                int v5 = (int)((double)roll_random(0, PIPBOY_RAND_MAX) / (double)PIPBOY_RAND_MAX * (double)v27);\n                int v6 = ginfo[PIPBOY_FRM_BOMB].height / 4;\n                if (PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT - v6 >= v5) {\n                    bomb->x = 602;\n                    bomb->y = v5 + 48;\n                } else {\n                    bomb->x = v5 - (PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT - v6) + PIPBOY_WINDOW_CONTENT_VIEW_X + ginfo[PIPBOY_FRM_BOMB].width / 4;\n                    bomb->y = PIPBOY_WINDOW_CONTENT_VIEW_Y - ginfo[PIPBOY_FRM_BOMB].height + 2;\n                }\n\n                bomb->field_10 = 1;\n                bomb->field_8 = (float)((double)roll_random(0, PIPBOY_RAND_MAX) * (2.75 / PIPBOY_RAND_MAX) + 0.15);\n                bomb->field_C = 0;\n            }\n        }\n\n        if (v31 == 0) {\n            buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n                PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n                PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n                PIPBOY_WINDOW_WIDTH,\n                scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n                PIPBOY_WINDOW_WIDTH);\n        }\n\n        for (int index = 0; index < PIPBOY_BOMB_COUNT; index++) {\n            PipboyBomb* bomb = &(bombs[index]);\n            if (bomb->field_10 != 1) {\n                continue;\n            }\n\n            int srcWidth = ginfo[PIPBOY_FRM_BOMB].width;\n            int srcHeight = ginfo[PIPBOY_FRM_BOMB].height;\n            int destX = bomb->x;\n            int destY = bomb->y;\n            int srcY = 0;\n            int srcX = 0;\n\n            if (destX >= PIPBOY_WINDOW_CONTENT_VIEW_X) {\n                if (destX + ginfo[PIPBOY_FRM_BOMB].width >= 604) {\n                    srcWidth = 604 - destX;\n                    if (srcWidth < 1) {\n                        bomb->field_10 = 0;\n                    }\n                }\n            } else {\n                srcX = PIPBOY_WINDOW_CONTENT_VIEW_X - destX;\n                if (srcX >= ginfo[PIPBOY_FRM_BOMB].width) {\n                    bomb->field_10 = 0;\n                }\n                destX = PIPBOY_WINDOW_CONTENT_VIEW_X;\n                srcWidth = ginfo[PIPBOY_FRM_BOMB].width - srcX;\n            }\n\n            if (destY >= PIPBOY_WINDOW_CONTENT_VIEW_Y) {\n                if (destY + ginfo[PIPBOY_FRM_BOMB].height >= 452) {\n                    srcHeight = 452 - destY;\n                    if (srcHeight < 1) {\n                        bomb->field_10 = 0;\n                    }\n                }\n            } else {\n                if (destY + ginfo[PIPBOY_FRM_BOMB].height < PIPBOY_WINDOW_CONTENT_VIEW_Y) {\n                    bomb->field_10 = 0;\n                }\n\n                srcY = PIPBOY_WINDOW_CONTENT_VIEW_Y - destY;\n                srcHeight = ginfo[PIPBOY_FRM_BOMB].height - srcY;\n                destY = PIPBOY_WINDOW_CONTENT_VIEW_Y;\n            }\n\n            if (bomb->field_10 == 1 && v31 == 0) {\n                trans_buf_to_buf(\n                    pipbmp[PIPBOY_FRM_BOMB] + ginfo[PIPBOY_FRM_BOMB].width * srcY + srcX,\n                    srcWidth,\n                    srcHeight,\n                    ginfo[PIPBOY_FRM_BOMB].width,\n                    scrn_buf + PIPBOY_WINDOW_WIDTH * destY + destX,\n                    PIPBOY_WINDOW_WIDTH);\n            }\n\n            bomb->field_C += bomb->field_8;\n            if (bomb->field_C >= 1.0) {\n                bomb->x = (int)((float)bomb->x - bomb->field_C);\n                bomb->y = (int)((float)bomb->y + bomb->field_C);\n                bomb->field_C = 0.0;\n            }\n        }\n\n        if (v31 != 0) {\n            v31 -= 1;\n        } else {\n            win_draw_rect(pip_win, &pip_rect);\n            while (elapsed_time(time) < 50) {\n            }\n        }\n    }\n\n    buf_to_buf(buf,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n        PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT,\n        PIPBOY_WINDOW_CONTENT_VIEW_WIDTH,\n        scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X,\n        PIPBOY_WINDOW_WIDTH);\n\n    mem_free(buf);\n\n    win_draw_rect(pip_win, &pip_rect);\n    gmouse_enable();\n\n    return 0;\n}\n\n// 0x49A5D4\nstatic int quest_init()\n{\n    // NOTE: Uninline.\n    quest_exit();\n\n    if (!message_init(&quest_message_file)) {\n        return -1;\n    }\n\n    if (!message_load(&quest_message_file, \"game\\\\quests.msg\")) {\n        return -1;\n    }\n\n    File* stream = db_fopen(\"data\\\\quests.txt\", \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    char string[256];\n    while (db_fgets(string, 256, stream)) {\n        const char* delim = \" \\t,\";\n        char* tok;\n        QuestDescription entry;\n\n        char* pch = string;\n        while (isspace(*pch)) {\n            pch++;\n        }\n\n        if (*pch == '#') {\n            continue;\n        }\n\n        tok = strtok(pch, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.location = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.description = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.gvar = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.displayThreshold = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.completedThreshold = atoi(tok);\n\n        QuestDescription* entries = (QuestDescription*)mem_realloc(quests, sizeof(*quests) * (quest_count + 1));\n        if (entries == NULL) {\n            goto err;\n        }\n\n        memcpy(&(entries[quest_count]), &entry, sizeof(entry));\n\n        quests = entries;\n        quest_count++;\n    }\n\n    qsort(quests, quest_count, sizeof(*quests), quest_qsort_compare);\n\n    db_fclose(stream);\n\n    return 0;\n\nerr:\n\n    db_fclose(stream);\n\n    return -1;\n}\n\n// 0x49A7E4\nstatic void quest_exit()\n{\n    if (quests != NULL) {\n        mem_free(quests);\n        quests = NULL;\n    }\n\n    quest_count = 0;\n\n    message_exit(&quest_message_file);\n}\n\n// 0x49A818\nstatic int quest_qsort_compare(const void* a1, const void* a2)\n{\n    QuestDescription* quest1 = (QuestDescription*)a1;\n    QuestDescription* quest2 = (QuestDescription*)a2;\n    return quest1->location - quest2->location;\n}\n\n// 0x49A824\nstatic int holodisks_init()\n{\n    // NOTE: Uninline.\n    holodisks_exit();\n\n    File* stream = db_fopen(\"data\\\\holodisk.txt\", \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    char str[256];\n    while (db_fgets(str, sizeof(str), stream)) {\n        const char* delim = \" \\t,\";\n        char* tok;\n        HolodiskDescription entry;\n\n        char* ch = str;\n        while (isspace(*ch)) {\n            ch++;\n        }\n\n        if (*ch == '#') {\n            continue;\n        }\n\n        tok = strtok(ch, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.gvar = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.name = atoi(tok);\n\n        tok = strtok(NULL, delim);\n        if (tok == NULL) {\n            continue;\n        }\n\n        entry.description = atoi(tok);\n\n        HolodiskDescription* entries = (HolodiskDescription*)mem_realloc(holodisks, sizeof(*holodisks) * (holodisks_count + 1));\n        if (entries == NULL) {\n            goto err;\n        }\n\n        memcpy(&(entries[holodisks_count]), &entry, sizeof(*holodisks));\n\n        holodisks = entries;\n        holodisks_count++;\n    }\n\n    db_fclose(stream);\n\n    return 0;\n\nerr:\n\n    db_fclose(stream);\n\n    return -1;\n}\n\n// 0x49A968\nstatic void holodisks_exit()\n{\n    if (holodisks != NULL) {\n        mem_free(holodisks);\n        holodisks = NULL;\n    }\n\n    holodisks_count = 0;\n}\n"
  },
  {
    "path": "src/game/pipboy.h",
    "content": "#ifndef FALLOUT_GAME_PIPBOY_H_\n#define FALLOUT_GAME_PIPBOY_H_\n\n#include <stdbool.h>\n\n#include \"game/art.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/message.h\"\n\ntypedef enum PipboyOpenIntent {\n    PIPBOY_OPEN_INTENT_UNSPECIFIED = 0,\n    PIPBOY_OPEN_INTENT_REST = 1,\n} PipboyOpenIntent;\n\ntypedef void(PipboyRenderProc)(int a1);\n\nPipboyRenderProc* PipFnctn[5];\n\nint pipboy(int intent);\nvoid pip_init();\nint save_pipboy(File* stream);\nint load_pipboy(File* stream);\n\n#endif /* FALLOUT_GAME_PIPBOY_H_ */\n"
  },
  {
    "path": "src/game/protinst.c",
    "content": "#include \"game/protinst.h\"\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/palette.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/tile.h\"\n#include \"game/worldmap.h\"\n\nstatic int obj_use_book(Object* item_obj);\nstatic int obj_use_flare(Object* critter_obj, Object* item_obj);\nstatic int obj_use_explosive(Object* explosive);\nstatic int obj_use_misc_item(Object* item_obj);\nstatic int protinstTestDroppedExplosive(Object* a1);\nstatic int protinst_default_use_item(Object* a1, Object* a2, Object* item);\nstatic int set_door_state_open(Object* a1, Object* a2);\nstatic int set_door_state_closed(Object* a1, Object* a2);\nstatic int check_door_state(Object* a1, Object* a2);\n\n// 0x49A9A0\nint obj_sid(Object* object, int* sidPtr)\n{\n    *sidPtr = object->sid;\n    if (*sidPtr == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x49A9B4\nint obj_new_sid(Object* object, int* sidPtr)\n{\n    *sidPtr = -1;\n\n    Proto* proto;\n    if (proto_ptr(object->pid, &proto) == -1) {\n        return -1;\n    }\n\n    int sid;\n    int objectType = PID_TYPE(object->pid);\n    if (objectType < OBJ_TYPE_TILE) {\n        sid = proto->sid;\n    } else if (objectType == OBJ_TYPE_TILE) {\n        sid = proto->tile.sid;\n    } else if (objectType == OBJ_TYPE_MISC) {\n        sid = -1;\n    } else {\n        assert(false && \"Should be unreachable\");\n    }\n\n    if (sid == -1) {\n        return -1;\n    }\n\n    int scriptType = SID_TYPE(sid);\n    if (scr_new(sidPtr, scriptType) == -1) {\n        return -1;\n    }\n\n    Script* script;\n    if (scr_ptr(*sidPtr, &script) == -1) {\n        return -1;\n    }\n\n    script->field_14 = sid & 0xFFFFFF;\n\n    if (objectType == OBJ_TYPE_CRITTER) {\n        object->field_80 = script->field_14;\n    }\n\n    if (scriptType == SCRIPT_TYPE_SPATIAL) {\n        script->sp.built_tile = builtTileCreate(object->tile, object->elevation);\n        script->sp.radius = 3;\n    }\n\n    if (object->id == -1) {\n        object->id = new_obj_id();\n    }\n\n    script->field_1C = object->id;\n    script->owner = object;\n\n    scr_find_str_run_info(sid & 0xFFFFFF, &(script->field_50), *sidPtr);\n\n    return 0;\n}\n\n// 0x49AAC0\nint obj_new_sid_inst(Object* obj, int scriptType, int a3)\n{\n    if (a3 == -1) {\n        return -1;\n    }\n\n    int sid;\n    if (scr_new(&sid, scriptType) == -1) {\n        return -1;\n    }\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        return -1;\n    }\n\n    script->field_14 = a3;\n    if (scriptType == SCRIPT_TYPE_SPATIAL) {\n        script->sp.built_tile = builtTileCreate(obj->tile, obj->elevation);\n        script->sp.radius = 3;\n    }\n\n    obj->sid = sid;\n\n    obj->id = new_obj_id();\n    script->field_1C = obj->id;\n\n    script->owner = obj;\n\n    scr_find_str_run_info(a3 & 0xFFFFFF, &(script->field_50), sid);\n\n    if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n        obj->field_80 = script->field_14;\n    }\n\n    return 0;\n}\n\n// 0x49AC3C\nint obj_look_at(Object* a1, Object* a2)\n{\n    return obj_look_at_func(a1, a2, display_print);\n}\n\n// 0x49AC4C\nint obj_look_at_func(Object* a1, Object* a2, void (*a3)(char* string))\n{\n    if (critter_is_dead(a1)) {\n        return -1;\n    }\n\n    if (FID_TYPE(a2->fid) == OBJ_TYPE_TILE) {\n        return -1;\n    }\n\n    Proto* proto;\n    if (proto_ptr(a2->pid, &proto) == -1) {\n        return -1;\n    }\n\n    bool scriptOverrides = false;\n\n    if (a2->sid != -1) {\n        scr_set_objs(a2->sid, a1, a2);\n        exec_script_proc(a2->sid, SCRIPT_PROC_LOOK_AT);\n\n        Script* script;\n        if (scr_ptr(a2->sid, &script) == -1) {\n            return -1;\n        }\n\n        scriptOverrides = script->scriptOverrides;\n    }\n\n    if (!scriptOverrides) {\n        MessageListItem messageListItem;\n\n        if (PID_TYPE(a2->pid) == OBJ_TYPE_CRITTER && critter_is_dead(a2)) {\n            messageListItem.num = 491 + roll_random(0, 1);\n        } else {\n            messageListItem.num = 490;\n        }\n\n        if (message_search(&proto_main_msg_file, &messageListItem)) {\n            const char* objectName = object_name(a2);\n\n            char formattedText[260];\n            sprintf(formattedText, messageListItem.text, objectName);\n\n            a3(formattedText);\n        }\n    }\n\n    return -1;\n}\n\n// 0x49AD78\nint obj_examine(Object* a1, Object* a2)\n{\n    return obj_examine_func(a1, a2, display_print);\n}\n\n// Performs examine (reading description) action and passes resulting text\n// to given callback.\n//\n// [critter] is a critter who's performing an action. Can be NULL.\n// [fn] can be called up to three times when [a2] is an ammo.\n//\n// 0x49AD88\nint obj_examine_func(Object* critter, Object* target, void (*fn)(char* string))\n{\n    if (critter_is_dead(critter)) {\n        return -1;\n    }\n\n    if (FID_TYPE(target->fid) == OBJ_TYPE_TILE) {\n        return -1;\n    }\n\n    bool scriptOverrides = false;\n    if (target->sid != -1) {\n        scr_set_objs(target->sid, critter, target);\n        exec_script_proc(target->sid, SCRIPT_PROC_DESCRIPTION);\n\n        Script* script;\n        if (scr_ptr(target->sid, &script) == -1) {\n            return -1;\n        }\n\n        scriptOverrides = script->scriptOverrides;\n    }\n\n    if (!scriptOverrides) {\n        char* description = object_description(target);\n        if (description != NULL && strcmp(description, proto_none_str) == 0) {\n            description = NULL;\n        }\n\n        if (description == NULL || *description == '\\0') {\n            MessageListItem messageListItem;\n            messageListItem.num = 493;\n            if (!message_search(&proto_main_msg_file, &messageListItem)) {\n                debug_printf(\"\\nError: Can't find msg num!\");\n            }\n            fn(messageListItem.text);\n        } else {\n            if (PID_TYPE(target->pid) != OBJ_TYPE_CRITTER || !critter_is_dead(target)) {\n                fn(description);\n            }\n        }\n    }\n\n    if (critter == NULL || critter != obj_dude) {\n        return 0;\n    }\n\n    char formattedText[260];\n\n    int type = PID_TYPE(target->pid);\n    if (type == OBJ_TYPE_CRITTER) {\n        if (target != obj_dude && perk_level(obj_dude, PERK_AWARENESS) && !critter_is_dead(target)) {\n            MessageListItem hpMessageListItem;\n\n            if (critter_body_type(target) != BODY_TYPE_BIPED) {\n                // It has %d/%d hps\n                hpMessageListItem.num = 537;\n            } else {\n                // 535: He has %d/%d hps\n                // 536: She has %d/%d hps\n                hpMessageListItem.num = 535 + critterGetStat(target, STAT_GENDER);\n            }\n\n            Object* item2 = inven_right_hand(target);\n            if (item2 != NULL && item_get_type(item2) != ITEM_TYPE_WEAPON) {\n                item2 = NULL;\n            }\n\n            if (!message_search(&proto_main_msg_file, &hpMessageListItem)) {\n                debug_printf(\"\\nError: Can't find msg num!\");\n                exit(1);\n            }\n\n            if (item2 != NULL) {\n                MessageListItem weaponMessageListItem;\n\n                if (item_w_caliber(item2) != 0) {\n                    weaponMessageListItem.num = 547; // and is wielding a %s with %d/%d shots of %s.\n                } else {\n                    weaponMessageListItem.num = 546; // and is wielding a %s.\n                }\n\n                if (!message_search(&proto_main_msg_file, &weaponMessageListItem)) {\n                    debug_printf(\"\\nError: Can't find msg num!\");\n                    exit(1);\n                }\n\n                char format[80];\n                sprintf(format, \"%s%s\", hpMessageListItem.text, weaponMessageListItem.text);\n\n                if (item_w_caliber(item2) != 0) {\n                    const int ammoTypePid = item_w_ammo_pid(item2);\n                    const char* ammoName = proto_name(ammoTypePid);\n                    const int ammoCapacity = item_w_max_ammo(item2);\n                    const int ammoQuantity = item_w_curr_ammo(item2);\n                    const char* weaponName = object_name(item2);\n                    const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);\n                    const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);\n                    sprintf(formattedText,\n                        format,\n                        currentHitPoints,\n                        maxiumHitPoints,\n                        weaponName,\n                        ammoQuantity,\n                        ammoCapacity,\n                        ammoName);\n                } else {\n                    const char* weaponName = object_name(item2);\n                    const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);\n                    const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);\n                    sprintf(formattedText,\n                        format,\n                        currentHitPoints,\n                        maxiumHitPoints,\n                        weaponName);\n                }\n            } else {\n                MessageListItem endingMessageListItem;\n\n                if (critter_is_crippled(target)) {\n                    endingMessageListItem.num = 544; // ,\n                } else {\n                    endingMessageListItem.num = 545; // .\n                }\n\n                if (!message_search(&proto_main_msg_file, &endingMessageListItem)) {\n                    debug_printf(\"\\nError: Can't find msg num!\");\n                    exit(1);\n                }\n\n                const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);\n                const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);\n                sprintf(formattedText, hpMessageListItem.text, currentHitPoints, maxiumHitPoints);\n                strcat(formattedText, endingMessageListItem.text);\n            }\n        } else {\n            int v12 = 0;\n            if (critter_is_crippled(target)) {\n                v12 -= 2;\n            }\n\n            int v16;\n\n            const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);\n            const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);\n            if (currentHitPoints <= 0 || critter_is_dead(target)) {\n                v16 = 0;\n            } else if (currentHitPoints == maxiumHitPoints) {\n                v16 = 4;\n            } else {\n                v16 = (currentHitPoints * 3) / maxiumHitPoints + 1;\n            }\n\n            MessageListItem hpMessageListItem;\n            hpMessageListItem.num = 500 + v16;\n            if (!message_search(&proto_main_msg_file, &hpMessageListItem)) {\n                debug_printf(\"\\nError: Can't find msg num!\");\n                exit(1);\n            }\n\n            if (v16 > 4) {\n                // Error: lookup_val out of range\n                hpMessageListItem.num = 550;\n                if (!message_search(&proto_main_msg_file, &hpMessageListItem)) {\n                    debug_printf(\"\\nError: Can't find msg num!\");\n                    exit(1);\n                }\n\n                debug_printf(hpMessageListItem.text);\n                return 0;\n            }\n\n            MessageListItem v66;\n            if (target == obj_dude) {\n                // You look %s\n                v66.num = 520 + v12;\n                if (!message_search(&proto_main_msg_file, &v66)) {\n                    debug_printf(\"\\nError: Can't find msg num!\");\n                    exit(1);\n                }\n\n                sprintf(formattedText, v66.text, hpMessageListItem.text);\n            } else {\n                // %s %s\n                v66.num = 521 + v12;\n                if (!message_search(&proto_main_msg_file, &v66)) {\n                    debug_printf(\"\\nError: Can't find msg num!\");\n                    exit(1);\n                }\n\n                MessageListItem v63;\n                v63.num = 522 + critterGetStat(target, STAT_GENDER);\n                if (!message_search(&proto_main_msg_file, &v63)) {\n                    debug_printf(\"\\nError: Can't find msg num!\");\n                    exit(1);\n                }\n\n                sprintf(formattedText, v63.text, hpMessageListItem.text);\n            }\n        }\n\n        if (critter_is_crippled(target)) {\n            const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS);\n            const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS);\n\n            MessageListItem v63;\n            v63.num = maxiumHitPoints >= currentHitPoints ? 531 : 530;\n\n            if (target == obj_dude) {\n                v63.num += 2;\n            }\n\n            if (!message_search(&proto_main_msg_file, &v63)) {\n                debug_printf(\"\\nError: Can't find msg num!\");\n                exit(1);\n            }\n\n            strcat(formattedText, v63.text);\n        }\n\n        fn(formattedText);\n    } else if (type == OBJ_TYPE_SCENERY) {\n        if (target->pid == PROTO_ID_CAR) {\n            MessageListItem carMessageListItem;\n            carMessageListItem.num = 549; // The car is running at %d%% power.\n\n            int car = game_get_global_var(GVAR_PLAYER_GOT_CAR);\n            if (car == 0) {\n                carMessageListItem.num = 548; // The car doesn't look like it's working right now.\n            }\n\n            if (!message_search(&proto_main_msg_file, &carMessageListItem)) {\n                debug_printf(\"\\nError: Can't find msg num!\");\n                exit(1);\n            }\n\n            if (car != 0) {\n                sprintf(formattedText, carMessageListItem.text, 100 * wmCarGasAmount() / 80000);\n            } else {\n                strcpy(formattedText, carMessageListItem.text);\n            }\n\n            fn(formattedText);\n        }\n    } else if (type == OBJ_TYPE_ITEM) {\n        int itemType = item_get_type(target);\n        if (itemType == ITEM_TYPE_WEAPON) {\n            if (item_w_caliber(target) != 0) {\n                MessageListItem weaponMessageListItem;\n                weaponMessageListItem.num = 526;\n\n                if (!message_search(&proto_main_msg_file, &weaponMessageListItem)) {\n                    debug_printf(\"\\nError: Can't find msg num!\");\n                    exit(1);\n                }\n\n                int ammoTypePid = item_w_ammo_pid(target);\n                const char* ammoName = proto_name(ammoTypePid);\n                int ammoCapacity = item_w_max_ammo(target);\n                int ammoQuantity = item_w_curr_ammo(target);\n                sprintf(formattedText, weaponMessageListItem.text, ammoQuantity, ammoCapacity, ammoName);\n                fn(formattedText);\n            }\n        } else if (itemType == ITEM_TYPE_AMMO) {\n            MessageListItem ammoMessageListItem;\n            ammoMessageListItem.num = 510;\n\n            if (!message_search(&proto_main_msg_file, &ammoMessageListItem)) {\n                debug_printf(\"\\nError: Can't find msg num!\");\n                exit(1);\n            }\n\n            sprintf(formattedText,\n                ammoMessageListItem.text,\n                item_a_ac_adjust(target));\n            fn(formattedText);\n\n            ammoMessageListItem.num++;\n            if (!message_search(&proto_main_msg_file, &ammoMessageListItem)) {\n                debug_printf(\"\\nError: Can't find msg num!\");\n                exit(1);\n            }\n\n            sprintf(formattedText,\n                ammoMessageListItem.text,\n                item_a_dr_adjust(target));\n            fn(formattedText);\n\n            ammoMessageListItem.num++;\n            if (!message_search(&proto_main_msg_file, &ammoMessageListItem)) {\n                debug_printf(\"\\nError: Can't find msg num!\");\n                exit(1);\n            }\n\n            sprintf(formattedText,\n                ammoMessageListItem.text,\n                item_a_dam_mult(target),\n                item_a_dam_div(target));\n            fn(formattedText);\n        }\n    }\n\n    return 0;\n}\n\n// 0x49B650\nint obj_pickup(Object* critter, Object* item)\n{\n    bool overriden = false;\n\n    if (item->sid != -1) {\n        scr_set_objs(item->sid, critter, item);\n        exec_script_proc(item->sid, SCRIPT_PROC_PICKUP);\n\n        Script* script;\n        if (scr_ptr(item->sid, &script) == -1) {\n            return -1;\n        }\n\n        overriden = script->scriptOverrides;\n    }\n\n    if (!overriden) {\n        int rc;\n        if (item->pid == PROTO_ID_MONEY) {\n            int amount = item_caps_get_amount(item);\n            if (amount <= 0) {\n                amount = 1;\n            }\n\n            rc = item_add_mult(critter, item, amount);\n            if (rc == 0) {\n                item_caps_set_amount(item, 0);\n            }\n        } else {\n            rc = item_add_mult(critter, item, 1);\n        }\n\n        if (rc == 0) {\n            Rect rect;\n            obj_disconnect(item, &rect);\n            tile_refresh_rect(&rect, item->elevation);\n        } else {\n            MessageListItem messageListItem;\n            // You cannot pick up that item. You are at your maximum weight capacity.\n            messageListItem.num = 905;\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x49B73C\nint obj_remove_from_inven(Object* critter, Object* item)\n{\n    Rect updatedRect;\n    int fid;\n    int v11 = 0;\n    if (inven_right_hand(critter) == item) {\n        if (critter != obj_dude || intface_is_item_right_hand()) {\n            fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, FID_ANIM_TYPE(critter->fid), 0, critter->rotation);\n            obj_change_fid(critter, fid, &updatedRect);\n            v11 = 2;\n        } else {\n            v11 = 1;\n        }\n    } else if (inven_left_hand(critter) == item) {\n        if (critter == obj_dude && !intface_is_item_right_hand()) {\n            fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, FID_ANIM_TYPE(critter->fid), 0, critter->rotation);\n            obj_change_fid(critter, fid, &updatedRect);\n            v11 = 2;\n        } else {\n            v11 = 1;\n        }\n    } else if (inven_worn(critter) == item) {\n        if (critter == obj_dude) {\n            int v5 = 1;\n\n            Proto* proto;\n            if (proto_ptr(0x1000000, &proto) != -1) {\n                v5 = proto->fid;\n            }\n\n            fid = art_id(OBJ_TYPE_CRITTER, v5, FID_ANIM_TYPE(critter->fid), (critter->fid & 0xF000) >> 12, critter->rotation);\n            obj_change_fid(critter, fid, &updatedRect);\n            v11 = 3;\n        }\n    }\n\n    int rc = item_remove_mult(critter, item, 1);\n\n    if (v11 >= 2) {\n        tile_refresh_rect(&updatedRect, critter->elevation);\n    }\n\n    if (v11 <= 2 && critter == obj_dude) {\n        intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n    }\n\n    return rc;\n}\n\n// 0x49B8B0\nint obj_drop(Object* a1, Object* a2)\n{\n    if (a2 == NULL) {\n        return -1;\n    }\n\n    bool scriptOverrides = false;\n    if (a1->sid != -1) {\n        scr_set_objs(a1->sid, a2, NULL);\n        exec_script_proc(a1->sid, SCRIPT_PROC_IS_DROPPING);\n\n        Script* scr;\n        if (scr_ptr(a1->sid, &scr) == -1) {\n            return -1;\n        }\n\n        scriptOverrides = scr->scriptOverrides;\n    }\n\n    if (scriptOverrides) {\n        return 0;\n    }\n\n    if (a2->sid != -1) {\n        scr_set_objs(a2->sid, a1, a2);\n        exec_script_proc(a2->sid, SCRIPT_PROC_DROP);\n\n        Script* scr;\n        if (scr_ptr(a2->sid, &scr) == -1) {\n            return -1;\n        }\n\n        scriptOverrides = scr->scriptOverrides;\n    }\n\n    if (scriptOverrides) {\n        return 0;\n    }\n\n    if (obj_remove_from_inven(a1, a2) == 0) {\n        Object* owner = obj_top_environment(a1);\n        if (owner == NULL) {\n            owner = a1;\n        }\n\n        Rect updatedRect;\n        obj_connect(a2, owner->tile, owner->elevation, &updatedRect);\n        tile_refresh_rect(&updatedRect, owner->elevation);\n    }\n\n    return 0;\n}\n\n// 0x49B9A0\nint obj_destroy(Object* obj)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    int elev;\n    Object* owner = obj->owner;\n    if (owner != NULL) {\n        obj_remove_from_inven(owner, obj);\n    } else {\n        elev = obj->elevation;\n    }\n\n    queue_remove(obj);\n\n    Rect rect;\n    obj_erase_object(obj, &rect);\n\n    if (owner == NULL) {\n        tile_refresh_rect(&rect, elev);\n    }\n\n    return 0;\n}\n\n// Read a book.\n//\n// 0x49B9F0\nstatic int obj_use_book(Object* book)\n{\n    MessageListItem messageListItem;\n\n    int messageId = -1;\n    int skill;\n\n    switch (book->pid) {\n    case PROTO_ID_BIG_BOOK_OF_SCIENCE:\n        // You learn new science information.\n        messageId = 802;\n        skill = SKILL_SCIENCE;\n        break;\n    case PROTO_ID_DEANS_ELECTRONICS:\n        // You learn a lot about repairing broken electronics.\n        messageId = 803;\n        skill = SKILL_REPAIR;\n        break;\n    case PROTO_ID_FIRST_AID_BOOK:\n        // You learn new ways to heal injury.\n        messageId = 804;\n        skill = SKILL_FIRST_AID;\n        break;\n    case PROTO_ID_SCOUT_HANDBOOK:\n        // You learn a lot about wilderness survival.\n        messageId = 806;\n        skill = SKILL_OUTDOORSMAN;\n        break;\n    case PROTO_ID_GUNS_AND_BULLETS:\n        // You learn how to handle your guns better.\n        messageId = 805;\n        skill = SKILL_SMALL_GUNS;\n        break;\n    }\n\n    if (messageId == -1) {\n        return -1;\n    }\n\n    if (isInCombat()) {\n        // You cannot do that in combat.\n        messageListItem.num = 902;\n        if (message_search(&proto_main_msg_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n\n        return 0;\n    }\n\n    int increase = (100 - skill_level(obj_dude, skill)) / 10;\n    if (increase <= 0) {\n        messageId = 801;\n    } else {\n        if (perk_level(obj_dude, PERK_COMPREHENSION)) {\n            increase = 150 * increase / 100;\n        }\n\n        for (int i = 0; i < increase; i++) {\n            skill_inc_point_force(obj_dude, skill);\n        }\n    }\n\n    palette_fade_to(black_palette);\n\n    int intelligence = critterGetStat(obj_dude, STAT_INTELLIGENCE);\n    inc_game_time_in_seconds(3600 * (11 - intelligence));\n\n    scr_exec_map_update_scripts();\n\n    palette_fade_to(cmap);\n\n    // You read the book.\n    messageListItem.num = 800;\n    if (message_search(&proto_main_msg_file, &messageListItem)) {\n        display_print(messageListItem.text);\n    }\n\n    messageListItem.num = messageId;\n    if (message_search(&proto_main_msg_file, &messageListItem)) {\n        display_print(messageListItem.text);\n    }\n\n    return 1;\n}\n\n// Light a flare.\n//\n// 0x49BBA8\nstatic int obj_use_flare(Object* critter_obj, Object* flare)\n{\n    MessageListItem messageListItem;\n\n    if (flare->pid != PROTO_ID_FLARE) {\n        return -1;\n    }\n\n    if ((flare->flags & OBJECT_USED) != 0) {\n        if (critter_obj == obj_dude) {\n            // The flare is already lit.\n            messageListItem.num = 588;\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n    } else {\n        if (critter_obj == obj_dude) {\n            // You light the flare.\n            messageListItem.num = 588;\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n\n        flare->pid = PROTO_ID_LIT_FLARE;\n\n        obj_set_light(flare, 8, 0x10000, NULL);\n        queue_add(72000, flare, NULL, EVENT_TYPE_FLARE);\n    }\n\n    return 0;\n}\n\n// 0x49BC60\nint obj_use_radio(Object* item)\n{\n    Script* scr;\n\n    if (item->sid == -1) {\n        return -1;\n    }\n\n    scr_set_objs(item->sid, obj_dude, item);\n    exec_script_proc(item->sid, SCRIPT_PROC_USE);\n\n    if (scr_ptr(item->sid, &scr) == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x49BCB4\nstatic int obj_use_explosive(Object* explosive)\n{\n    MessageListItem messageListItem;\n\n    int pid = explosive->pid;\n    if (pid != PROTO_ID_DYNAMITE_I\n        && pid != PROTO_ID_PLASTIC_EXPLOSIVES_I\n        && pid != PROTO_ID_DYNAMITE_II\n        && pid != PROTO_ID_PLASTIC_EXPLOSIVES_II) {\n        return -1;\n    }\n\n    if ((explosive->flags & OBJECT_USED) != 0) {\n        // The timer is already ticking.\n        messageListItem.num = 590;\n        if (message_search(&proto_main_msg_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n    } else {\n        int seconds = inven_set_timer(explosive);\n        if (seconds != -1) {\n            // You set the timer.\n            messageListItem.num = 589;\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n\n            if (pid == PROTO_ID_DYNAMITE_I) {\n                explosive->pid = PROTO_ID_DYNAMITE_II;\n            } else if (pid == PROTO_ID_PLASTIC_EXPLOSIVES_I) {\n                explosive->pid = PROTO_ID_PLASTIC_EXPLOSIVES_II;\n            }\n\n            int delay = 10 * seconds;\n\n            int roll;\n            if (perkHasRank(obj_dude, PERK_DEMOLITION_EXPERT)) {\n                roll = ROLL_SUCCESS;\n            } else {\n                roll = skill_result(obj_dude, SKILL_TRAPS, 0, NULL);\n            }\n\n            int eventType;\n            switch (roll) {\n            case ROLL_CRITICAL_FAILURE:\n                delay = 0;\n                eventType = EVENT_TYPE_EXPLOSION_FAILURE;\n                break;\n            case ROLL_FAILURE:\n                eventType = EVENT_TYPE_EXPLOSION_FAILURE;\n                delay /= 2;\n                break;\n            default:\n                eventType = EVENT_TYPE_EXPLOSION;\n                break;\n            }\n\n            queue_add(delay, explosive, NULL, eventType);\n        }\n    }\n\n    return 2;\n}\n\n// Recharge car with given item\n// Returns -1 when car cannot be recharged with given item.\n// Returns 1 when car is recharged.\n//\n// 0x49BDE8\nint obj_use_power_on_car(Object* item)\n{\n    // 0x49A990\n    static MessageListItem messageListItem;\n\n    int messageNum;\n\n    bool isEnergy = false;\n    int energyDensity;\n\n    switch (item->pid) {\n    case PROTO_ID_SMALL_ENERGY_CELL:\n        energyDensity = 16000;\n        isEnergy = true;\n        break;\n    case PROTO_ID_MICRO_FUSION_CELL:\n        energyDensity = 40000;\n        isEnergy = true;\n        break;\n    }\n\n    if (!isEnergy) {\n        return -1;\n    }\n\n    if (wmCarGasAmount() < CAR_FUEL_MAX) {\n        int energy = item_w_curr_ammo(item) * energyDensity;\n        int capacity = item_w_max_ammo(item);\n\n        // NOTE: that function will never return -1\n        if (wmCarFillGas(energy / capacity) == -1) {\n            return -1;\n        }\n\n        // You charge the car with more power.\n        messageNum = 595;\n    } else {\n        // The car is already full of power.\n        messageNum = 596;\n    }\n\n    char* text = getmsg(&proto_main_msg_file, &messageListItem, messageNum);\n    display_print(text);\n\n    return 1;\n}\n\n// 0x49BE88\nstatic int obj_use_misc_item(Object* item)\n{\n\n    if (item == NULL) {\n        return -1;\n    }\n\n    switch (item->pid) {\n    case PROTO_ID_RAMIREZ_BOX_CLOSED:\n    case PROTO_ID_RAIDERS_MAP:\n    case PROTO_ID_CATS_PAW_ISSUE_5:\n    case PROTO_ID_PIP_BOY_LINGUAL_ENHANCER:\n    case PROTO_ID_SURVEY_MAP:\n    case PROTO_ID_PIP_BOY_MEDICAL_ENHANCER:\n        if (item->sid == -1) {\n            return 1;\n        }\n\n        scr_set_objs(item->sid, obj_dude, item);\n        exec_script_proc(item->sid, SCRIPT_PROC_USE);\n\n        Script* scr;\n        if (scr_ptr(item->sid, &scr) == -1) {\n            return -1;\n        }\n\n        return 1;\n    }\n\n    return -1;\n}\n\n// 0x49BF38\nint protinst_use_item(Object* critter, Object* item)\n{\n    int rc;\n    MessageListItem messageListItem;\n\n    switch (item_get_type(item)) {\n    case ITEM_TYPE_DRUG:\n        rc = -1;\n        break;\n    case ITEM_TYPE_WEAPON:\n    case ITEM_TYPE_MISC:\n        rc = obj_use_book(item);\n        if (rc != -1) {\n            break;\n        }\n\n        rc = obj_use_flare(critter, item);\n        if (rc == 0) {\n            break;\n        }\n\n        rc = obj_use_misc_item(item);\n        if (rc != -1) {\n            break;\n        }\n\n        rc = obj_use_radio(item);\n        if (rc == 0) {\n            break;\n        }\n\n        rc = obj_use_explosive(item);\n        if (rc == 0 || rc == 2) {\n            break;\n        }\n\n        // TODO: Not sure about these two conditions.\n        if (item_m_uses_charges(item)) {\n            rc = item_m_use_charged_item(critter, item);\n            if (rc == 0) {\n                break;\n            }\n        }\n        // FALLTHROUGH\n    default:\n        // That does nothing\n        messageListItem.num = 582;\n        if (message_search(&proto_main_msg_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n\n        rc = -1;\n    }\n\n    return rc;\n}\n\n// 0x49BFE8\nstatic int protinstTestDroppedExplosive(Object* a1)\n{\n    if (a1->pid == PROTO_ID_DYNAMITE_II || a1->pid == PROTO_ID_PLASTIC_EXPLOSIVES_II) {\n        Attack attack;\n        combat_ctd_init(&attack, obj_dude, 0, HIT_MODE_PUNCH, HIT_LOCATION_TORSO);\n        attack.attackerFlags = DAM_HIT;\n        attack.tile = obj_dude->tile;\n        compute_explosion_on_extras(&attack, 0, 0, 1);\n\n        int team = obj_dude->data.critter.combat.team;\n        Object* v2 = NULL;\n        for (int index = 0; index < attack.extrasLength; index++) {\n            Object* v5 = attack.extras[index];\n            if (v5 != obj_dude\n                && v5->data.critter.combat.team != team\n                && stat_result(v5, STAT_PERCEPTION, 0, NULL) >= 2) {\n                critter_set_who_hit_me(v5, obj_dude);\n                if (v2 == NULL) {\n                    v2 = v5;\n                }\n            }\n        }\n\n        if (v2 != NULL && !isInCombat()) {\n            STRUCT_664980 attack;\n            attack.attacker = v2;\n            attack.defender = obj_dude;\n            attack.actionPointsBonus = 0;\n            attack.accuracyBonus = 0;\n            attack.minDamage = 0;\n            attack.maxDamage = 99999;\n            attack.field_1C = 0;\n            scripts_request_combat(&attack);\n        }\n    }\n    return 0;\n}\n\n// 0x49C124\nint obj_use_item(Object* a1, Object* a2)\n{\n    int rc = protinst_use_item(a1, a2);\n    if (rc == 1 || rc == 2) {\n        Object* root = obj_top_environment(a2);\n        if (root != NULL) {\n            int flags = a2->flags & OBJECT_IN_ANY_HAND;\n            item_remove_mult(root, a2, 1);\n            Object* v8 = item_replace(root, a2, flags);\n            if (root == obj_dude) {\n                int leftItemAction;\n                int rightItemAction;\n                intface_get_item_states(&leftItemAction, &rightItemAction);\n                if (v8 == NULL) {\n                    if ((flags & OBJECT_IN_LEFT_HAND) != 0) {\n                        leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                    } else if ((flags & OBJECT_IN_RIGHT_HAND) != 0) {\n                        rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                    } else {\n                        leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                        rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                    }\n                }\n                intface_update_items(false, leftItemAction, rightItemAction);\n            }\n        }\n\n        if (rc == 1) {\n            obj_destroy(a2);\n        } else if (rc == 2 && root != NULL) {\n            Rect updatedRect;\n            obj_connect(a2, root->tile, root->elevation, &updatedRect);\n            tile_refresh_rect(&updatedRect, root->elevation);\n            protinstTestDroppedExplosive(a2);\n        }\n\n        rc = 0;\n    }\n\n    scr_exec_map_update_scripts();\n\n    return rc;\n}\n\n// 0x49C240\nstatic int protinst_default_use_item(Object* a1, Object* a2, Object* item)\n{\n    char formattedText[90];\n    MessageListItem messageListItem;\n\n    int rc;\n    switch (item_get_type(item)) {\n    case ITEM_TYPE_DRUG:\n        if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) {\n            if (a1 == obj_dude) {\n                // That does nothing\n                messageListItem.num = 582;\n                if (message_search(&proto_main_msg_file, &messageListItem)) {\n                    display_print(messageListItem.text);\n                }\n            }\n            return -1;\n        }\n\n        if (critter_is_dead(a2)) {\n            // 583: To your dismay, you realize that it is already dead.\n            // 584: As you reach down, you realize that it is already dead.\n            // 585: Alas, you are too late.\n            // 586: That won't work on the dead.\n            messageListItem.num = 583 + roll_random(0, 3);\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n            return -1;\n        }\n\n        rc = item_d_take_drug(a2, item);\n\n        if (a1 == obj_dude && a2 != obj_dude) {\n            // TODO: Looks like there is bug in this branch, message 580 will never be shown,\n            // as we can only be here when target is not dude.\n\n            // 580: You use the %s.\n            // 581: You use the %s on %s.\n            messageListItem.num = 580 + (a2 != obj_dude);\n            if (!message_search(&proto_main_msg_file, &messageListItem)) {\n                return -1;\n            }\n\n            sprintf(formattedText, messageListItem.text, object_name(item), object_name(a2));\n            display_print(formattedText);\n        }\n\n        if (a2 == obj_dude) {\n            intface_update_hit_points(true);\n        }\n\n        return rc;\n    case ITEM_TYPE_AMMO:\n        rc = obj_use_power_on_car(item);\n        if (rc == 1) {\n            return 1;\n        }\n        break;\n    case ITEM_TYPE_WEAPON:\n    case ITEM_TYPE_MISC:\n        rc = obj_use_flare(a1, item);\n        if (rc == 0) {\n            return 0;\n        }\n        break;\n    }\n\n    messageListItem.num = 582;\n    if (message_search(&proto_main_msg_file, &messageListItem)) {\n        sprintf(formattedText, messageListItem.text);\n        display_print(formattedText);\n    }\n    return -1;\n}\n\n// 0x49C3CC\nint protinst_use_item_on(Object* a1, Object* a2, Object* item)\n{\n    int messageId = -1;\n    int criticalChanceModifier = 0;\n    int skill = -1;\n\n    switch (item->pid) {\n    case PROTO_ID_DOCTORS_BAG:\n        // The supplies in the Doctor's Bag run out.\n        messageId = 900;\n        criticalChanceModifier = 20;\n        skill = SKILL_DOCTOR;\n        break;\n    case PROTO_ID_FIRST_AID_KIT:\n        // The supplies in the First Aid Kit run out.\n        messageId = 901;\n        criticalChanceModifier = 20;\n        skill = SKILL_FIRST_AID;\n        break;\n    case PROTO_ID_PARAMEDICS_BAG:\n        // The supplies in the Paramedic's Bag run out.\n        messageId = 910;\n        criticalChanceModifier = 40;\n        skill = SKILL_DOCTOR;\n        break;\n    case PROTO_ID_FIELD_MEDIC_FIRST_AID_KIT:\n        // The supplies in the Field Medic First Aid Kit run out.\n        messageId = 911;\n        criticalChanceModifier = 40;\n        skill = SKILL_FIRST_AID;\n        break;\n    }\n\n    if (skill == -1) {\n        Script* script;\n\n        if (item->sid == -1) {\n            if (a2->sid == -1) {\n                return protinst_default_use_item(a1, a2, item);\n            }\n\n            scr_set_objs(a2->sid, a1, item);\n            exec_script_proc(a2->sid, SCRIPT_PROC_USE_OBJ_ON);\n\n            if (scr_ptr(a2->sid, &script) == -1) {\n                return -1;\n            }\n\n            if (!script->scriptOverrides) {\n                return protinst_default_use_item(a1, a2, item);\n            }\n        } else {\n            scr_set_objs(item->sid, a1, a2);\n            exec_script_proc(item->sid, SCRIPT_PROC_USE_OBJ_ON);\n\n            if (scr_ptr(item->sid, &script) == -1) {\n                return -1;\n            }\n\n            if (script->field_28 == 0) {\n                if (a2->sid == -1) {\n                    return protinst_default_use_item(a1, a2, item);\n                }\n\n                scr_set_objs(a2->sid, a1, item);\n                exec_script_proc(a2->sid, SCRIPT_PROC_USE_OBJ_ON);\n\n                Script* script;\n                if (scr_ptr(a2->sid, &script) == -1) {\n                    return -1;\n                }\n\n                if (!script->scriptOverrides) {\n                    return protinst_default_use_item(a1, a2, item);\n                }\n            }\n        }\n\n        return script->field_28;\n    }\n\n    if (isInCombat()) {\n        MessageListItem messageListItem;\n        // You cannot do that in combat.\n        messageListItem.num = 902;\n        if (a1 == obj_dude) {\n            if (message_search(&proto_main_msg_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n        return -1;\n    }\n\n    if (skill_use(a1, a2, skill, criticalChanceModifier) != 0) {\n        return 0;\n    }\n\n    if (roll_random(1, 10) != 1) {\n        return 0;\n    }\n\n    MessageListItem messageListItem;\n    messageListItem.num = messageId;\n    if (a1 == obj_dude) {\n        if (message_search(&proto_main_msg_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n    }\n\n    return 1;\n}\n\n// 0x49C5FC\nint obj_use_item_on(Object* a1, Object* a2, Object* a3)\n{\n    int rc = protinst_use_item_on(a1, a2, a3);\n\n    if (rc == 1) {\n        if (a1 != NULL) {\n            int flags = a3->flags & OBJECT_IN_ANY_HAND;\n            item_remove_mult(a1, a3, 1);\n\n            Object* v7 = item_replace(a1, a3, flags);\n\n            int leftItemAction;\n            int rightItemAction;\n            if (a1 == obj_dude) {\n                intface_get_item_states(&leftItemAction, &rightItemAction);\n            }\n\n            if (v7 == NULL) {\n                if ((flags & OBJECT_IN_LEFT_HAND) != 0) {\n                    leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                } else if ((flags & OBJECT_IN_RIGHT_HAND) != 0) {\n                    rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                } else {\n                    leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                    rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT;\n                }\n            }\n\n            intface_update_items(false, leftItemAction, rightItemAction);\n        }\n\n        obj_destroy(a3);\n\n        rc = 0;\n    }\n\n    scr_exec_map_update_scripts();\n\n    return rc;\n}\n\n// 0x49C6BC\nint check_scenery_ap_cost(Object* obj, Object* a2)\n{\n    if (!isInCombat()) {\n        return 0;\n    }\n\n    int actionPoints = obj->data.critter.combat.ap;\n    if (actionPoints >= 3) {\n        obj->data.critter.combat.ap = actionPoints - 3;\n\n        if (obj == obj_dude) {\n            intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move);\n        }\n\n        return 0;\n    }\n\n    MessageListItem messageListItem;\n    // You don't have enough action points.\n    messageListItem.num = 700;\n\n    if (obj == obj_dude) {\n        if (message_search(&proto_main_msg_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n    }\n\n    return -1;\n}\n\n// 0x49C740\nint obj_use(Object* a1, Object* a2)\n{\n    int type = FID_TYPE(a2->fid);\n    if (a1 == obj_dude) {\n        if (type != OBJ_TYPE_SCENERY) {\n            return -1;\n        }\n    } else {\n        if (type != OBJ_TYPE_SCENERY) {\n            return 0;\n        }\n    }\n\n    Proto* sceneryProto;\n    if (proto_ptr(a2->pid, &sceneryProto) == -1) {\n        return -1;\n    }\n\n    if (PID_TYPE(a2->pid) == OBJ_TYPE_SCENERY && sceneryProto->scenery.type == SCENERY_TYPE_DOOR) {\n        return obj_use_door(a1, a2, 0);\n    }\n\n    bool scriptOverrides = false;\n\n    if (a2->sid != -1) {\n        scr_set_objs(a2->sid, a1, a2);\n        exec_script_proc(a2->sid, SCRIPT_PROC_USE);\n\n        Script* script;\n        if (scr_ptr(a2->sid, &script) == -1) {\n            return -1;\n        }\n\n        scriptOverrides = script->scriptOverrides;\n    }\n\n    if (!scriptOverrides) {\n        if (PID_TYPE(a2->pid) == OBJ_TYPE_SCENERY) {\n            if (sceneryProto->scenery.type == SCENERY_TYPE_LADDER_DOWN) {\n                if (obj_use_ladder_top(a1, a2, 0) == 0) {\n                    scriptOverrides = true;\n                }\n            } else if (sceneryProto->scenery.type == SCENERY_TYPE_LADDER_UP) {\n                if (obj_use_ladder_bottom(a1, a2, 0) == 0) {\n                    scriptOverrides = true;\n                }\n            } else if (sceneryProto->scenery.type == SCENERY_TYPE_STAIRS) {\n                if (obj_use_stairs(a1, a2, 0) == 0) {\n                    scriptOverrides = true;\n                }\n            }\n        }\n    }\n\n    if (!scriptOverrides) {\n        if (a1 == obj_dude) {\n            // You see: %s\n            MessageListItem messageListItem;\n            messageListItem.num = 480;\n            if (!message_search(&proto_main_msg_file, &messageListItem)) {\n                return -1;\n            }\n\n            char formattedText[260];\n            const char* name = object_name(a2);\n            sprintf(formattedText, messageListItem.text, name);\n            display_print(formattedText);\n        }\n    }\n\n    scr_exec_map_update_scripts();\n\n    return 0;\n}\n\n// 0x49C900\nint obj_use_ladder_top(Object* a1, Object* ladder, int a3)\n{\n    int builtTile = ladder->data.scenery.ladder.destinationBuiltTile;\n    if (builtTile == -1) {\n        return -1;\n    }\n\n    int tile = builtTileGetTile(builtTile);\n    int elevation = builtTileGetElevation(builtTile);\n    if (ladder->data.scenery.ladder.destinationMap != 0) {\n        MapTransition transition;\n        memset(&transition, 0, sizeof(transition));\n\n        transition.map = ladder->data.scenery.ladder.destinationMap;\n        transition.elevation = elevation;\n        transition.tile = tile;\n        transition.rotation = builtTileGetRotation(builtTile);\n\n        map_leave_map(&transition);\n\n        wmMapMarkMapEntranceState(transition.map, elevation, 1);\n    } else {\n        Rect updatedRect;\n        if (obj_move_to_tile(a1, tile, elevation, &updatedRect) == -1) {\n            return -1;\n        }\n\n        tile_refresh_rect(&updatedRect, map_elevation);\n    }\n\n    return 0;\n}\n\n// 0x49C9A4\nint obj_use_ladder_bottom(Object* a1, Object* ladder, int a3)\n{\n    int builtTile = ladder->data.scenery.ladder.destinationBuiltTile;\n    if (builtTile == -1) {\n        return -1;\n    }\n\n    int tile = builtTileGetTile(builtTile);\n    int elevation = builtTileGetElevation(builtTile);\n    if (ladder->data.scenery.ladder.destinationMap != 0) {\n        MapTransition transition;\n        memset(&transition, 0, sizeof(transition));\n\n        transition.map = ladder->data.scenery.ladder.destinationMap;\n        transition.elevation = elevation;\n        transition.tile = tile;\n        transition.rotation = builtTileGetRotation(builtTile);\n\n        map_leave_map(&transition);\n\n        wmMapMarkMapEntranceState(transition.map, elevation, 1);\n    } else {\n        Rect updatedRect;\n        if (obj_move_to_tile(a1, tile, elevation, &updatedRect) == -1) {\n            return -1;\n        }\n\n        tile_refresh_rect(&updatedRect, map_elevation);\n    }\n\n    return 0;\n}\n\n// 0x49CA48\nint obj_use_stairs(Object* a1, Object* stairs, int a3)\n{\n    int builtTile = stairs->data.scenery.stairs.destinationBuiltTile;\n    if (builtTile == -1) {\n        return -1;\n    }\n\n    int tile = builtTileGetTile(builtTile);\n    int elevation = builtTileGetElevation(builtTile);\n    if (stairs->data.scenery.stairs.destinationMap > 0) {\n        MapTransition transition;\n        memset(&transition, 0, sizeof(transition));\n\n        transition.map = stairs->data.scenery.stairs.destinationMap;\n        transition.elevation = elevation;\n        transition.tile = tile;\n        transition.rotation = builtTileGetRotation(builtTile);\n\n        map_leave_map(&transition);\n\n        wmMapMarkMapEntranceState(transition.map, elevation, 1);\n    } else {\n        Rect updatedRect;\n        if (obj_move_to_tile(a1, tile, elevation, &updatedRect) == -1) {\n            return -1;\n        }\n\n        tile_refresh_rect(&updatedRect, map_elevation);\n    }\n\n    return 0;\n}\n\n// 0x49CAF4\nstatic int set_door_state_open(Object* a1, Object* a2)\n{\n    a1->data.scenery.door.openFlags |= 0x01;\n    return 0;\n}\n\n// 0x49CB04\nstatic int set_door_state_closed(Object* a1, Object* a2)\n{\n    a1->data.scenery.door.openFlags &= ~0x01;\n    return 0;\n}\n\n// 0x49CB14\nstatic int check_door_state(Object* a1, Object* a2)\n{\n    if ((a1->data.scenery.door.openFlags & 0x01) == 0) {\n        a1->flags &= ~OBJECT_OPEN_DOOR;\n\n        obj_rebuild_all_light();\n        tile_refresh_display();\n\n        if (a1->frame == 0) {\n            return 0;\n        }\n\n        CacheEntry* artHandle;\n        Art* art = art_ptr_lock(a1->fid, &artHandle);\n        if (art == NULL) {\n            return -1;\n        }\n\n        Rect dirty;\n        Rect temp;\n\n        obj_bound(a1, &dirty);\n\n        for (int frame = a1->frame - 1; frame >= 0; frame--) {\n            int x;\n            int y;\n            art_frame_hot(art, frame, a1->rotation, &x, &y);\n            obj_offset(a1, -x, -y, &temp);\n        }\n\n        obj_set_frame(a1, 0, &temp);\n        rect_min_bound(&dirty, &temp, &dirty);\n\n        tile_refresh_rect(&dirty, map_elevation);\n\n        art_ptr_unlock(artHandle);\n        return 0;\n    } else {\n        a1->flags |= OBJECT_OPEN_DOOR;\n\n        obj_rebuild_all_light();\n        tile_refresh_display();\n\n        CacheEntry* artHandle;\n        Art* art = art_ptr_lock(a1->fid, &artHandle);\n        if (art == NULL) {\n            return -1;\n        }\n\n        int frameCount = art_frame_max_frame(art);\n        if (a1->frame == frameCount - 1) {\n            art_ptr_unlock(artHandle);\n            return 0;\n        }\n\n        Rect dirty;\n        Rect temp;\n\n        obj_bound(a1, &dirty);\n\n        for (int frame = a1->frame + 1; frame < frameCount; frame++) {\n            int x;\n            int y;\n            art_frame_hot(art, frame, a1->rotation, &x, &y);\n            obj_offset(a1, x, y, &temp);\n        }\n\n        obj_set_frame(a1, frameCount - 1, &temp);\n        rect_min_bound(&dirty, &temp, &dirty);\n\n        tile_refresh_rect(&dirty, map_elevation);\n\n        art_ptr_unlock(artHandle);\n        return 0;\n    }\n}\n\n// 0x49CCB8\nint obj_use_door(Object* a1, Object* a2, int a3)\n{\n    if (obj_is_locked(a2)) {\n        const char* sfx = gsnd_build_open_sfx_name(a2, SCENERY_SOUND_EFFECT_LOCKED);\n        gsound_play_sfx_file(sfx);\n    }\n\n    bool scriptOverrides = false;\n    if (a2->sid != -1) {\n        scr_set_objs(a2->sid, a1, a2);\n        exec_script_proc(a2->sid, SCRIPT_PROC_USE);\n\n        Script* script;\n        if (scr_ptr(a2->sid, &script) == -1) {\n            return -1;\n        }\n\n        scriptOverrides = script->scriptOverrides;\n    }\n\n    if (!scriptOverrides) {\n        int start;\n        int end;\n        int step;\n        if (a2->frame != 0) {\n            if (obj_blocking_at(NULL, a2->tile, a2->elevation) != 0) {\n                MessageListItem messageListItem;\n                char* text = getmsg(&proto_main_msg_file, &messageListItem, 597);\n                display_print(text);\n                return -1;\n            }\n            start = 1;\n            end = (a3 == 0) - 1;\n            step = -1;\n        } else {\n            if (a2->data.scenery.door.openFlags & 0x01) {\n                return -1;\n            }\n\n            start = 0;\n            end = (a3 != 0) + 1;\n            step = 1;\n        }\n\n        register_begin(ANIMATION_REQUEST_RESERVED);\n\n        for (int i = start; i != end; i += step) {\n            if (i != 0) {\n                if (a3 == 0) {\n                    register_object_call(a2, a2, set_door_state_closed, -1);\n                }\n\n                const char* sfx = gsnd_build_open_sfx_name(a2, SCENERY_SOUND_EFFECT_CLOSED);\n                register_object_play_sfx(a2, sfx, -1);\n\n                register_object_animate_reverse(a2, ANIM_STAND, 0);\n            } else {\n                if (a3 == 0) {\n                    register_object_call(a2, a2, set_door_state_open, -1);\n                }\n\n                const char* sfx = gsnd_build_open_sfx_name(a2, SCENERY_SOUND_EFFECT_CLOSED);\n                register_object_play_sfx(a2, sfx, -1);\n\n                register_object_animate(a2, ANIM_STAND, 0);\n            }\n        }\n\n        register_object_must_call(a2, a2, check_door_state, -1);\n\n        register_end();\n    }\n\n    return 0;\n}\n\n// 0x49CE7C\nint obj_use_container(Object* critter, Object* item)\n{\n    if (FID_TYPE(item->fid) != OBJ_TYPE_ITEM) {\n        return -1;\n    }\n\n    Proto* itemProto;\n    if (proto_ptr(item->pid, &itemProto) == -1) {\n        return -1;\n    }\n\n    if (itemProto->item.type != ITEM_TYPE_CONTAINER) {\n        return -1;\n    }\n\n    if (obj_is_locked(item)) {\n        const char* sfx = gsnd_build_open_sfx_name(item, SCENERY_SOUND_EFFECT_LOCKED);\n        gsound_play_sfx_file(sfx);\n\n        if (critter == obj_dude) {\n            MessageListItem messageListItem;\n            // It is locked.\n            messageListItem.num = 487;\n            if (!message_search(&proto_main_msg_file, &messageListItem)) {\n                return -1;\n            }\n\n            display_print(messageListItem.text);\n        }\n\n        return -1;\n    }\n\n    bool overriden = false;\n    if (item->sid != -1) {\n        scr_set_objs(item->sid, critter, item);\n        exec_script_proc(item->sid, SCRIPT_PROC_USE);\n\n        Script* script;\n        if (scr_ptr(item->sid, &script) == -1) {\n            return -1;\n        }\n\n        overriden = script->scriptOverrides;\n    }\n\n    if (overriden) {\n        return -1;\n    }\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n\n    if (item->frame == 0) {\n        const char* sfx = gsnd_build_open_sfx_name(item, SCENERY_SOUND_EFFECT_OPEN);\n        register_object_play_sfx(item, sfx, 0);\n        register_object_animate(item, ANIM_STAND, 0);\n    } else {\n        const char* sfx = gsnd_build_open_sfx_name(item, SCENERY_SOUND_EFFECT_CLOSED);\n        register_object_play_sfx(item, sfx, 0);\n        register_object_animate_reverse(item, ANIM_STAND, 0);\n    }\n\n    register_end();\n\n    if (critter == obj_dude) {\n        MessageListItem messageListItem;\n        messageListItem.num = item->frame != 0\n            ? 486 // You search the %s.\n            : 485; // You close the %s.\n        if (!message_search(&proto_main_msg_file, &messageListItem)) {\n            return -1;\n        }\n\n        char formattedText[260];\n        const char* objectName = object_name(item);\n        sprintf(formattedText, messageListItem.text, objectName);\n        display_print(formattedText);\n    }\n\n    return 0;\n}\n\n// 0x49D078\nint obj_use_skill_on(Object* source, Object* target, int skill)\n{\n    if (obj_lock_is_jammed(target)) {\n        if (source == obj_dude) {\n            MessageListItem messageListItem;\n            messageListItem.num = 2001;\n            if (message_search(&misc_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n        }\n        return -1;\n    }\n\n    Proto* proto;\n    if (proto_ptr(target->pid, &proto) == -1) {\n        return -1;\n    }\n\n    bool scriptOverrides = false;\n    if (target->sid != -1) {\n        scr_set_objs(target->sid, source, target);\n        scr_set_action_num(target->sid, skill);\n        exec_script_proc(target->sid, SCRIPT_PROC_USE_SKILL_ON);\n\n        Script* script;\n        if (scr_ptr(target->sid, &script) == -1) {\n            return -1;\n        }\n\n        scriptOverrides = script->scriptOverrides;\n    }\n\n    if (!scriptOverrides) {\n        skill_use(source, target, skill, 0);\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x49D140\nbool obj_is_a_portal(Object* obj)\n{\n    if (obj == NULL) {\n        return false;\n    }\n\n    Proto* proto;\n    if (proto_ptr(obj->pid, &proto) == -1) {\n        return false;\n    }\n\n    return proto->scenery.type == SCENERY_TYPE_DOOR;\n}\n\n// 0x49D178\nbool obj_is_lockable(Object* obj)\n{\n    Proto* proto;\n\n    if (obj == NULL) {\n        return false;\n    }\n\n    if (proto_ptr(obj->pid, &proto) == -1) {\n        return false;\n    }\n\n    switch (PID_TYPE(obj->pid)) {\n    case OBJ_TYPE_ITEM:\n        if (proto->item.type == ITEM_TYPE_CONTAINER) {\n            return true;\n        }\n        break;\n    case OBJ_TYPE_SCENERY:\n        if (proto->scenery.type == SCENERY_TYPE_DOOR) {\n            return true;\n        }\n        break;\n    }\n\n    return false;\n}\n\n// 0x49D1C8\nbool obj_is_locked(Object* obj)\n{\n    if (obj == NULL) {\n        return false;\n    }\n\n    ObjectData* data = &(obj->data);\n    switch (PID_TYPE(obj->pid)) {\n    case OBJ_TYPE_ITEM:\n        return data->flags & CONTAINER_FLAG_LOCKED;\n    case OBJ_TYPE_SCENERY:\n        return data->scenery.door.openFlags & DOOR_FLAG_LOCKED;\n    }\n\n    return false;\n}\n\n// 0x49D20C\nint obj_lock(Object* object)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    switch (PID_TYPE(object->pid)) {\n    case OBJ_TYPE_ITEM:\n        object->data.flags |= OBJ_LOCKED;\n        break;\n    case OBJ_TYPE_SCENERY:\n        object->data.scenery.door.openFlags |= OBJ_LOCKED;\n        break;\n    default:\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x49D250\nint obj_unlock(Object* object)\n{\n    if (object == NULL) {\n        return -1;\n    }\n\n    switch (PID_TYPE(object->pid)) {\n    case OBJ_TYPE_ITEM:\n        object->data.flags &= ~OBJ_LOCKED;\n        return 0;\n    case OBJ_TYPE_SCENERY:\n        object->data.scenery.door.openFlags &= ~OBJ_LOCKED;\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x49D294\nbool obj_is_openable(Object* obj)\n{\n    Proto* proto;\n\n    if (obj == NULL) {\n        return false;\n    }\n\n    if (proto_ptr(obj->pid, &proto) == -1) {\n        return false;\n    }\n\n    switch (PID_TYPE(obj->pid)) {\n    case OBJ_TYPE_ITEM:\n        if (proto->item.type == ITEM_TYPE_CONTAINER) {\n            return true;\n        }\n        break;\n    case OBJ_TYPE_SCENERY:\n        if (proto->scenery.type == SCENERY_TYPE_DOOR) {\n            return true;\n        }\n        break;\n    }\n\n    return false;\n}\n\n// 0x49D2E4\nint obj_is_open(Object* object)\n{\n    return object->frame != 0;\n}\n\n// 0x49D2F4\nint obj_toggle_open(Object* obj)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (!obj_is_openable(obj)) {\n        return -1;\n    }\n\n    if (obj_is_locked(obj)) {\n        return -1;\n    }\n\n    obj_unjam_lock(obj);\n\n    register_begin(ANIMATION_REQUEST_RESERVED);\n\n    if (obj->frame != 0) {\n        register_object_must_call(obj, obj, set_door_state_closed, -1);\n\n        const char* sfx = gsnd_build_open_sfx_name(obj, SCENERY_SOUND_EFFECT_CLOSED);\n        register_object_play_sfx(obj, sfx, -1);\n\n        register_object_animate_reverse(obj, ANIM_STAND, 0);\n    } else {\n        register_object_must_call(obj, obj, set_door_state_open, -1);\n\n        const char* sfx = gsnd_build_open_sfx_name(obj, SCENERY_SOUND_EFFECT_OPEN);\n        register_object_play_sfx(obj, sfx, -1);\n        register_object_animate(obj, ANIM_STAND, 0);\n    }\n\n    register_object_must_call(obj, obj, check_door_state, -1);\n\n    register_end();\n\n    return 0;\n}\n\n// 0x49D3D8\nint obj_open(Object* obj)\n{\n    if (obj->frame == 0) {\n        obj_toggle_open(obj);\n    }\n\n    return 0;\n}\n\n// 0x49D3F4\nint obj_close(Object* obj)\n{\n    if (obj->frame != 0) {\n        obj_toggle_open(obj);\n    }\n\n    return 0;\n}\n\n// 0x49D410\nbool obj_lock_is_jammed(Object* obj)\n{\n    if (!obj_is_lockable(obj)) {\n        return false;\n    }\n\n    if (PID_TYPE(obj->pid) == OBJ_TYPE_SCENERY) {\n        if ((obj->data.scenery.door.openFlags & OBJ_JAMMED) != 0) {\n            return true;\n        }\n    } else {\n        if ((obj->data.flags & OBJ_JAMMED) != 0) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// jam_lock\n// 0x49D448\nint obj_jam_lock(Object* obj)\n{\n    if (!obj_is_lockable(obj)) {\n        return -1;\n    }\n\n    ObjectData* data = &(obj->data);\n    switch (PID_TYPE(obj->pid)) {\n    case OBJ_TYPE_ITEM:\n        data->flags |= CONTAINER_FLAG_JAMMED;\n        break;\n    case OBJ_TYPE_SCENERY:\n        data->scenery.door.openFlags |= DOOR_FLAG_JAMMGED;\n        break;\n    }\n\n    return 0;\n}\n\n// 0x49D480\nint obj_unjam_lock(Object* obj)\n{\n    if (!obj_is_lockable(obj)) {\n        return -1;\n    }\n\n    ObjectData* data = &(obj->data);\n    switch (PID_TYPE(obj->pid)) {\n    case OBJ_TYPE_ITEM:\n        data->flags &= ~CONTAINER_FLAG_JAMMED;\n        break;\n    case OBJ_TYPE_SCENERY:\n        data->scenery.door.openFlags &= ~DOOR_FLAG_JAMMGED;\n        break;\n    }\n\n    return 0;\n}\n\n// 0x49D4B8\nint obj_unjam_all_locks()\n{\n    Object* obj = obj_find_first();\n    while (obj != NULL) {\n        obj_unjam_lock(obj);\n        obj = obj_find_next();\n    }\n\n    return 0;\n}\n\n// critter_attempt_placement\n// 0x49D4D4\nint obj_attempt_placement(Object* obj, int tile, int elevation, int a4)\n{\n    if (tile == -1) {\n        return -1;\n    }\n\n    int newTile = tile;\n    if (obj_blocking_at(NULL, tile, elevation) != NULL) {\n        int v6 = a4;\n        if (a4 < 1) {\n            v6 = 1;\n        }\n\n        int attempts = 0;\n        while (v6 < 7) {\n            attempts++;\n            if (attempts >= 100) {\n                break;\n            }\n\n            for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n                newTile = tile_num_in_direction(tile, rotation, v6);\n                if (obj_blocking_at(NULL, newTile, elevation) == NULL && v6 > 1 && make_path(obj_dude, obj_dude->tile, newTile, NULL, 0) != 0) {\n                    break;\n                }\n            }\n\n            v6++;\n        }\n\n        if (a4 != 1 && v6 > a4 + 2) {\n            for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {\n                int candidate = tile_num_in_direction(tile, rotation, 1);\n                if (obj_blocking_at(NULL, candidate, elevation) == NULL) {\n                    newTile = candidate;\n                    break;\n                }\n            }\n        }\n    }\n\n    Rect updatedRect;\n    obj_turn_on(obj, &updatedRect);\n\n    Rect temp;\n    if (obj_move_to_tile(obj, newTile, elevation, &temp) != -1) {\n        rect_min_bound(&updatedRect, &temp, &updatedRect);\n\n        if (elevation == map_elevation) {\n            tile_refresh_rect(&updatedRect, elevation);\n        }\n    }\n\n    return 0;\n}\n\n// 0x49D628\nint objPMAttemptPlacement(Object* obj, int tile, int elevation)\n{\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (tile == -1) {\n        return -1;\n    }\n\n    int v9 = tile;\n    int v7 = 0;\n    if (!wmEvalTileNumForPlacement(tile)) {\n        v9 = obj_dude->tile;\n        for (int v4 = 1; v4 <= 100; v4++) {\n            // TODO: Check.\n            v7++;\n            v9 = tile_num_in_direction(v9, v7 % ROTATION_COUNT, 1);\n            if (wmEvalTileNumForPlacement(v9) != 0) {\n                break;\n            }\n\n            if (tile_dist(obj_dude->tile, v9) > 8) {\n                v9 = tile;\n                break;\n            }\n        }\n    }\n\n    obj_turn_on(obj, NULL);\n    obj_move_to_tile(obj, v9, elevation, NULL);\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/protinst.h",
    "content": "#ifndef FALLOUT_GAME_PROTINST_H_\n#define FALLOUT_GAME_PROTINST_H_\n\n#include <stdbool.h>\n\n#include \"game/object_types.h\"\n\nint obj_sid(Object* object, int* sidPtr);\nint obj_new_sid(Object* object, int* sidPtr);\nint obj_new_sid_inst(Object* obj, int a2, int a3);\nint obj_look_at(Object* a1, Object* a2);\nint obj_look_at_func(Object* a1, Object* a2, void (*a3)(char* string));\nint obj_examine(Object* a1, Object* a2);\nint obj_examine_func(Object* critter, Object* target, void (*fn)(char* string));\nint obj_pickup(Object* critter, Object* item);\nint obj_remove_from_inven(Object* critter, Object* item);\nint obj_drop(Object* a1, Object* a2);\nint obj_destroy(Object* obj);\nint obj_use_radio(Object* item_obj);\nint obj_use_power_on_car(Object* ammo);\nint protinst_use_item(Object* a1, Object* a2);\nint obj_use_item(Object* a1, Object* a2);\nint protinst_use_item_on(Object* a1, Object* a2, Object* item);\nint obj_use_item_on(Object* a1, Object* a2, Object* a3);\nint check_scenery_ap_cost(Object* obj, Object* a2);\nint obj_use(Object* a1, Object* a2);\nint obj_use_ladder_top(Object* a1, Object* ladder, int a3);\nint obj_use_ladder_bottom(Object* a1, Object* ladder, int a3);\nint obj_use_stairs(Object* a1, Object* stairs, int a3);\nint obj_use_door(Object* a1, Object* a2, int a3);\nint obj_use_container(Object* critter, Object* item);\nint obj_use_skill_on(Object* a1, Object* a2, int skill);\nbool obj_is_a_portal(Object* obj);\nbool obj_is_lockable(Object* obj);\nbool obj_is_locked(Object* obj);\nint obj_lock(Object* obj);\nint obj_unlock(Object* obj);\nbool obj_is_openable(Object* obj);\nint obj_is_open(Object* obj);\nint obj_toggle_open(Object* obj);\nint obj_open(Object* obj);\nint obj_close(Object* obj);\nbool obj_lock_is_jammed(Object* obj);\nint obj_jam_lock(Object* obj);\nint obj_unjam_lock(Object* obj);\nint obj_unjam_all_locks();\nint obj_attempt_placement(Object* obj, int tile, int elevation, int a4);\nint objPMAttemptPlacement(Object* obj, int tile, int elevation);\n\n#endif /* FALLOUT_GAME_PROTINST_H_ */\n"
  },
  {
    "path": "src/game/proto.c",
    "content": "#include \"game/proto.h\"\n\n#include <direct.h>\n#include <stdio.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"game/art.h\"\n#include \"game/editor.h\"\n#include \"game/combat.h\"\n#include \"game/config.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/dialog.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmovie.h\"\n#include \"game/intface.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/trait.h\"\n\nstatic char* proto_get_msg_info(int pid, int message);\nstatic int proto_read_CombatData(CritterCombatData* data, File* stream);\nstatic int proto_write_CombatData(CritterCombatData* data, File* stream);\nstatic int proto_read_item_data(ItemProtoData* item_data, int type, File* stream);\nstatic int proto_read_scenery_data(SceneryProtoData* scenery_data, int type, File* stream);\nstatic int proto_read_protoSubNode(Proto* buf, File* stream);\nstatic int proto_write_item_data(ItemProtoData* item_data, int type, File* stream);\nstatic int proto_write_scenery_data(SceneryProtoData* scenery_data, int type, File* stream);\nstatic int proto_write_protoSubNode(Proto* buf, File* stream);\nstatic void proto_remove_some_list(int type);\nstatic void proto_remove_list(int type);\nstatic int proto_new_id(int a1);\n\n// 0x51C18C\nchar cd_path_base[MAX_PATH];\n\n// 0x51C290\nstatic ProtoList protolists[11] = {\n    { 0, 0, 0, 1 },\n    { 0, 0, 0, 1 },\n    { 0, 0, 0, 1 },\n    { 0, 0, 0, 1 },\n    { 0, 0, 0, 1 },\n    { 0, 0, 0, 1 },\n    { 0, 0, 0, 1 },\n    { 0, 0, 0, 0 },\n    { 0, 0, 0, 0 },\n    { 0, 0, 0, 0 },\n    { 0, 0, 0, 0 },\n};\n\n// 0x51C340\nstatic const size_t proto_sizes[11] = {\n    sizeof(ItemProto), // 0x84\n    sizeof(CritterProto), // 0x1A0\n    sizeof(SceneryProto), // 0x38\n    sizeof(WallProto), // 0x24\n    sizeof(TileProto), // 0x1C\n    sizeof(MiscProto), // 0x1C\n    0,\n    0,\n    0,\n    0,\n    0,\n};\n\n// 0x51C36C\nstatic int protos_been_initialized = 0;\n\n// obj_dude_proto\n// 0x51C370\nstatic CritterProto pc_proto = {\n    0x1000000,\n    -1,\n    0x1000001,\n    0,\n    0,\n    0x20000000,\n    0,\n    -1,\n    0,\n    { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 18, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 23, 0 },\n    { 0 },\n    { 0 },\n    0,\n    0,\n    0,\n    0,\n    -1,\n    0,\n    0,\n};\n\n// 0x51C534\nchar proto_path_base[] = \"proto\\\\\";\n\n// 0x664530\nchar* mp_perk_code_strs[1 + PERK_COUNT];\n\n// 0x66470C\nchar* mp_critter_stats_list[2 + STAT_COUNT];\n\n// Message list by object type\n// 0 - pro_item.msg\n// 1 - pro_crit.msg\n// 2 - pro_scen.msg\n// 3 - pro_wall.msg\n// 4 - pro_tile.msg\n// 5 - pro_misc.msg\n//\n// 0x6647AC\nMessageList proto_msg_files[6];\n\n// 0x6647DC\nchar* race_type_strs[RACE_TYPE_COUNT];\n\n// 0x6647E4\nchar* scenery_pro_type[SCENERY_TYPE_COUNT];\n\n// proto.msg\n//\n// 0x6647FC\nMessageList proto_main_msg_file;\n\n// 0x664804\nchar* item_pro_material[MATERIAL_TYPE_COUNT];\n\n// \"<None>\" from proto.msg\n//\n// 0x664824\nchar* proto_none_str;\n\n// 0x664828\nchar* body_type_strs[BODY_TYPE_COUNT];\n\n// 0x664834\nchar* item_pro_type[ITEM_TYPE_COUNT];\n\n// 0x66484C\nchar* damage_code_strs[DAMAGE_TYPE_COUNT];\n\n// 0x66486C\nchar* cal_type_strs[CALIBER_TYPE_COUNT];\n\n// Perk names.\n//\n// 0x6648B8\nchar** perk_code_strs;\n\n// Stat names.\n//\n// 0x6648BC\nchar** critter_stats_list;\n\n// NOTE: Inlined.\n//\n// 0x49E270\nvoid proto_make_path(char* path, int pid)\n{\n    strcpy(path, cd_path_base);\n    strcat(path, proto_path_base);\n    if (pid != -1) {\n        strcat(path, art_dir(PID_TYPE(pid)));\n    }\n}\n\n// Append proto file name to proto_path from proto.lst.\n//\n// 0x49E758\nint proto_list_str(int pid, char* proto_path)\n{\n    if (pid == -1) {\n        return -1;\n    }\n\n    if (proto_path == NULL) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    proto_make_path(path, pid);\n    strcat(path, \"\\\\\");\n    strcat(path, art_dir(PID_TYPE(pid)));\n    strcat(path, \".lst\");\n\n    File* stream = db_fopen(path, \"rt\");\n\n    int i = 1;\n    char string[256];\n    while (db_fgets(string, sizeof(string), stream)) {\n        if (i == (pid & 0xFFFFFF)) {\n            break;\n        }\n\n        i++;\n    }\n\n    db_fclose(stream);\n\n    if (i != (pid & 0xFFFFFF)) {\n        return -1;\n    }\n\n    char* pch = strchr(string, ' ');\n    if (pch != NULL) {\n        *pch = '\\0';\n    }\n\n    pch = strchr(string, '\\n');\n    if (pch != NULL) {\n        *pch = '\\0';\n    }\n\n    strcpy(proto_path, string);\n\n    return 0;\n}\n\n// 0x49E99C\nbool proto_action_can_use(int pid)\n{\n    Proto* proto;\n    if (proto_ptr(pid, &proto) == -1) {\n        return false;\n    }\n\n    if ((proto->item.extendedFlags & 0x0800) != 0) {\n        return true;\n    }\n\n    if (PID_TYPE(pid) == OBJ_TYPE_ITEM && proto->item.type == ITEM_TYPE_CONTAINER) {\n        return true;\n    }\n\n    return false;\n}\n\n// 0x49E9DC\nbool proto_action_can_use_on(int pid)\n{\n    Proto* proto;\n    if (proto_ptr(pid, &proto) == -1) {\n        return false;\n    }\n\n    if ((proto->item.extendedFlags & 0x1000) != 0) {\n        return true;\n    }\n\n    if (PID_TYPE(pid) == OBJ_TYPE_ITEM && proto->item.type == ITEM_TYPE_DRUG) {\n        return true;\n    }\n\n    return false;\n}\n\n// 0x49EA24\nbool proto_action_can_talk_to(int pid)\n{\n    Proto* proto;\n    if (proto_ptr(pid, &proto) == -1) {\n        return false;\n    }\n\n    if (PID_TYPE(pid) == OBJ_TYPE_CRITTER) {\n        return true;\n    }\n\n    if (proto->critter.extendedFlags & 0x4000) {\n        return true;\n    }\n\n    return false;\n}\n\n// Likely returns true if item with given pid can be picked up.\n//\n// 0x49EA5C\nint proto_action_can_pickup(int pid)\n{\n    if (PID_TYPE(pid) != OBJ_TYPE_ITEM) {\n        return false;\n    }\n\n    Proto* proto;\n    if (proto_ptr(pid, &proto) == -1) {\n        return false;\n    }\n\n    if (proto->item.type == ITEM_TYPE_CONTAINER) {\n        return (proto->item.extendedFlags & 0x8000) != 0;\n    }\n\n    return true;\n}\n\n// 0x49EAA4\nstatic char* proto_get_msg_info(int pid, int message)\n{\n    char* v1 = proto_none_str;\n\n    Proto* proto;\n    if (proto_ptr(pid, &proto) != -1) {\n        if (proto->messageId != -1) {\n            MessageList* messageList = &(proto_msg_files[PID_TYPE(pid)]);\n\n            MessageListItem messageListItem;\n            messageListItem.num = proto->messageId + message;\n            if (message_search(messageList, &messageListItem)) {\n                v1 = messageListItem.text;\n            }\n        }\n    }\n\n    return v1;\n}\n\n// 0x49EAFC\nchar* proto_name(int pid)\n{\n    if (pid == 0x1000000) {\n        return critter_name(obj_dude);\n    }\n\n    return proto_get_msg_info(pid, PROTOTYPE_MESSAGE_NAME);\n}\n\n// 0x49EB1C\nchar* proto_description(int pid)\n{\n    return proto_get_msg_info(pid, PROTOTYPE_MESSAGE_DESCRIPTION);\n}\n\n// 0x49EDB4\nint proto_critter_init(Proto* a1, int a2)\n{\n    if (!protos_been_initialized) {\n        return -1;\n    }\n\n    int v1 = a2 & 0xFFFFFF;\n\n    a1->pid = -1;\n    a1->messageId = 100 * v1;\n    a1->fid = art_id(OBJ_TYPE_CRITTER, v1 - 1, 0, 0, 0);\n    a1->critter.lightDistance = 0;\n    a1->critter.lightIntensity = 0;\n    a1->critter.flags = 0x20000000;\n    a1->critter.extendedFlags = 0x6000;\n    a1->critter.sid = -1;\n    a1->critter.data.flags = 0;\n    a1->critter.data.bodyType = 0;\n    a1->critter.headFid = -1;\n    a1->critter.aiPacket = 1;\n    if (!art_exists(a1->fid)) {\n        a1->fid = art_id(OBJ_TYPE_CRITTER, 0, 0, 0, 0);\n    }\n\n    CritterProtoData* data = &(a1->critter.data);\n    data->experience = 60;\n    data->killType = 0;\n    data->damageType = 0;\n    stat_set_defaults(data);\n    skill_set_defaults(data);\n\n    return 0;\n}\n\n// 0x49EEA4\nvoid clear_pupdate_data(Object* obj)\n{\n    // NOTE: Original code is slightly different. It uses loop to zero object\n    // data byte by byte.\n    memset(&(obj->data), 0, sizeof(obj->data));\n}\n\n// 0x49EEB8\nstatic int proto_read_CombatData(CritterCombatData* data, File* stream)\n{\n    if (db_freadInt(stream, &(data->damageLastTurn)) == -1) return -1;\n    if (db_freadInt(stream, &(data->maneuver)) == -1) return -1;\n    if (db_freadInt(stream, &(data->ap)) == -1) return -1;\n    if (db_freadInt(stream, &(data->results)) == -1) return -1;\n    if (db_freadInt(stream, &(data->aiPacket)) == -1) return -1;\n    if (db_freadInt(stream, &(data->team)) == -1) return -1;\n    if (db_freadInt(stream, &(data->whoHitMeCid)) == -1) return -1;\n\n    return 0;\n}\n\n// 0x49EF40\nstatic int proto_write_CombatData(CritterCombatData* data, File* stream)\n{\n    if (db_fwriteInt(stream, data->damageLastTurn) == -1) return -1;\n    if (db_fwriteInt(stream, data->maneuver) == -1) return -1;\n    if (db_fwriteInt(stream, data->ap) == -1) return -1;\n    if (db_fwriteInt(stream, data->results) == -1) return -1;\n    if (db_fwriteInt(stream, data->aiPacket) == -1) return -1;\n    if (db_fwriteInt(stream, data->team) == -1) return -1;\n    if (db_fwriteInt(stream, data->whoHitMeCid) == -1) return -1;\n\n    return 0;\n}\n\n// 0x49F004\nint proto_read_protoUpdateData(Object* obj, File* stream)\n{\n    Proto* proto;\n\n    Inventory* inventory = &(obj->data.inventory);\n    if (db_freadInt(stream, &(inventory->length)) == -1) return -1;\n    if (db_freadInt(stream, &(inventory->capacity)) == -1) return -1;\n    // TODO: See below.\n    if (db_freadInt(stream, (int*)&(inventory->items)) == -1) return -1;\n\n    if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n        if (db_freadInt(stream, &(obj->data.critter.field_0)) == -1) return -1;\n        if (proto_read_CombatData(&(obj->data.critter.combat), stream) == -1) return -1;\n        if (db_freadInt(stream, &(obj->data.critter.hp)) == -1) return -1;\n        if (db_freadInt(stream, &(obj->data.critter.radiation)) == -1) return -1;\n        if (db_freadInt(stream, &(obj->data.critter.poison)) == -1) return -1;\n    } else {\n        if (db_freadInt(stream, &(obj->data.flags)) == -1) return -1;\n\n        if (obj->data.flags == 0xCCCCCCCC) {\n            debug_printf(\"\\nNote: Reading pud: updated_flags was un-Set!\");\n            obj->data.flags = 0;\n        }\n\n        switch (PID_TYPE(obj->pid)) {\n        case OBJ_TYPE_ITEM:\n            if (proto_ptr(obj->pid, &proto) == -1) return -1;\n\n            switch (proto->item.type) {\n            case ITEM_TYPE_WEAPON:\n                if (db_freadInt(stream, &(obj->data.item.weapon.ammoQuantity)) == -1) return -1;\n                if (db_freadInt(stream, &(obj->data.item.weapon.ammoTypePid)) == -1) return -1;\n                break;\n            case ITEM_TYPE_AMMO:\n                if (db_freadInt(stream, &(obj->data.item.ammo.quantity)) == -1) return -1;\n                break;\n            case ITEM_TYPE_MISC:\n                if (db_freadInt(stream, &(obj->data.item.misc.charges)) == -1) return -1;\n                break;\n            case ITEM_TYPE_KEY:\n                if (db_freadInt(stream, &(obj->data.item.key.keyCode)) == -1) return -1;\n                break;\n            default:\n                break;\n            }\n\n            break;\n        case OBJ_TYPE_SCENERY:\n            if (proto_ptr(obj->pid, &proto) == -1) return -1;\n\n            switch (proto->scenery.type) {\n            case SCENERY_TYPE_DOOR:\n                if (db_freadInt(stream, &(obj->data.scenery.door.openFlags)) == -1) return -1;\n                break;\n            case SCENERY_TYPE_STAIRS:\n                if (db_freadInt(stream, &(obj->data.scenery.stairs.destinationBuiltTile)) == -1) return -1;\n                if (db_freadInt(stream, &(obj->data.scenery.stairs.destinationMap)) == -1) return -1;\n                break;\n            case SCENERY_TYPE_ELEVATOR:\n                if (db_freadInt(stream, &(obj->data.scenery.elevator.type)) == -1) return -1;\n                if (db_freadInt(stream, &(obj->data.scenery.elevator.level)) == -1) return -1;\n                break;\n            case SCENERY_TYPE_LADDER_UP:\n                if (map_data.version == 19) {\n                    if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationBuiltTile)) == -1) return -1;\n                } else {\n                    if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationMap)) == -1) return -1;\n                    if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationBuiltTile)) == -1) return -1;\n                }\n                break;\n            case SCENERY_TYPE_LADDER_DOWN:\n                if (map_data.version == 19) {\n                    if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationBuiltTile)) == -1) return -1;\n                } else {\n                    if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationMap)) == -1) return -1;\n                    if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationBuiltTile)) == -1) return -1;\n                }\n                break;\n            }\n\n            break;\n        case OBJ_TYPE_MISC:\n            if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) {\n                if (db_freadInt(stream, &(obj->data.misc.map)) == -1) return -1;\n                if (db_freadInt(stream, &(obj->data.misc.tile)) == -1) return -1;\n                if (db_freadInt(stream, &(obj->data.misc.elevation)) == -1) return -1;\n                if (db_freadInt(stream, &(obj->data.misc.rotation)) == -1) return -1;\n            }\n            break;\n        }\n    }\n\n    return 0;\n}\n\n// 0x49F428\nint proto_write_protoUpdateData(Object* obj, File* stream)\n{\n    Proto* proto;\n\n    ObjectData* data = &(obj->data);\n    if (db_fwriteInt(stream, data->inventory.length) == -1) return -1;\n    if (db_fwriteInt(stream, data->inventory.capacity) == -1) return -1;\n    // TODO: Why do we need to write address of pointer? That probably means\n    // this field is shared with something else.\n    if (db_fwriteInt(stream, (intptr_t)data->inventory.items) == -1) return -1;\n\n    if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n        if (db_fwriteInt(stream, data->flags) == -1) return -1;\n        if (proto_write_CombatData(&(obj->data.critter.combat), stream) == -1) return -1;\n        if (db_fwriteInt(stream, data->critter.hp) == -1) return -1;\n        if (db_fwriteInt(stream, data->critter.radiation) == -1) return -1;\n        if (db_fwriteInt(stream, data->critter.poison) == -1) return -1;\n    } else {\n        if (db_fwriteInt(stream, data->flags) == -1) return -1;\n\n        switch (PID_TYPE(obj->pid)) {\n        case OBJ_TYPE_ITEM:\n            if (proto_ptr(obj->pid, &proto) == -1) return -1;\n\n            switch (proto->item.type) {\n            case ITEM_TYPE_WEAPON:\n                if (db_fwriteInt(stream, data->item.weapon.ammoQuantity) == -1) return -1;\n                if (db_fwriteInt(stream, data->item.weapon.ammoTypePid) == -1) return -1;\n                break;\n            case ITEM_TYPE_AMMO:\n                if (db_fwriteInt(stream, data->item.ammo.quantity) == -1) return -1;\n                break;\n            case ITEM_TYPE_MISC:\n                if (db_fwriteInt(stream, data->item.misc.charges) == -1) return -1;\n                break;\n            case ITEM_TYPE_KEY:\n                if (db_fwriteInt(stream, data->item.key.keyCode) == -1) return -1;\n                break;\n            }\n            break;\n        case OBJ_TYPE_SCENERY:\n            if (proto_ptr(obj->pid, &proto) == -1) return -1;\n\n            switch (proto->scenery.type) {\n            case SCENERY_TYPE_DOOR:\n                if (db_fwriteInt(stream, data->scenery.door.openFlags) == -1) return -1;\n                break;\n            case SCENERY_TYPE_STAIRS:\n                if (db_fwriteInt(stream, data->scenery.stairs.destinationBuiltTile) == -1) return -1;\n                if (db_fwriteInt(stream, data->scenery.stairs.destinationMap) == -1) return -1;\n                break;\n            case SCENERY_TYPE_ELEVATOR:\n                if (db_fwriteInt(stream, data->scenery.elevator.type) == -1) return -1;\n                if (db_fwriteInt(stream, data->scenery.elevator.level) == -1) return -1;\n                break;\n            case SCENERY_TYPE_LADDER_UP:\n                if (db_fwriteInt(stream, data->scenery.ladder.destinationMap) == -1) return -1;\n                if (db_fwriteInt(stream, data->scenery.ladder.destinationBuiltTile) == -1) return -1;\n                break;\n            case SCENERY_TYPE_LADDER_DOWN:\n                if (db_fwriteInt(stream, data->scenery.ladder.destinationMap) == -1) return -1;\n                if (db_fwriteInt(stream, data->scenery.ladder.destinationBuiltTile) == -1) return -1;\n                break;\n            default:\n                break;\n            }\n            break;\n        case OBJ_TYPE_MISC:\n            if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) {\n                if (db_fwriteInt(stream, data->misc.map) == -1) return -1;\n                if (db_fwriteInt(stream, data->misc.tile) == -1) return -1;\n                if (db_fwriteInt(stream, data->misc.elevation) == -1) return -1;\n                if (db_fwriteInt(stream, data->misc.rotation) == -1) return -1;\n            }\n            break;\n        default:\n            break;\n        }\n    }\n\n    return 0;\n}\n\n// 0x49F73C\nint proto_update_gen(Object* obj)\n{\n    Proto* proto;\n\n    if (!protos_been_initialized) {\n        return -1;\n    }\n\n    ObjectData* data = &(obj->data);\n    data->inventory.length = 0;\n    data->inventory.capacity = 0;\n    data->inventory.items = NULL;\n\n    if (proto_ptr(obj->pid, &proto) == -1) {\n        return -1;\n    }\n\n    switch (PID_TYPE(obj->pid)) {\n    case OBJ_TYPE_ITEM:\n        switch (proto->item.type) {\n        case ITEM_TYPE_CONTAINER:\n            data->flags = 0;\n            break;\n        case ITEM_TYPE_WEAPON:\n            data->item.weapon.ammoQuantity = proto->item.data.weapon.ammoCapacity;\n            data->item.weapon.ammoTypePid = proto->item.data.weapon.ammoTypePid;\n            break;\n        case ITEM_TYPE_AMMO:\n            data->item.ammo.quantity = proto->item.data.ammo.quantity;\n            break;\n        case ITEM_TYPE_MISC:\n            data->item.misc.charges = proto->item.data.misc.charges;\n            break;\n        case ITEM_TYPE_KEY:\n            data->item.key.keyCode = proto->item.data.key.keyCode;\n            break;\n        }\n        break;\n    case OBJ_TYPE_SCENERY:\n        switch (proto->scenery.type) {\n        case SCENERY_TYPE_DOOR:\n            data->scenery.door.openFlags = proto->scenery.data.door.openFlags;\n            break;\n        case SCENERY_TYPE_STAIRS:\n            data->scenery.stairs.destinationBuiltTile = proto->scenery.data.stairs.field_0;\n            data->scenery.stairs.destinationMap = proto->scenery.data.stairs.field_4;\n            break;\n        case SCENERY_TYPE_ELEVATOR:\n            data->scenery.elevator.type = proto->scenery.data.elevator.type;\n            data->scenery.elevator.level = proto->scenery.data.elevator.level;\n            break;\n        case SCENERY_TYPE_LADDER_UP:\n        case SCENERY_TYPE_LADDER_DOWN:\n            data->scenery.ladder.destinationMap = proto->scenery.data.ladder.field_0;\n            break;\n        }\n        break;\n    case OBJ_TYPE_MISC:\n        if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) {\n            data->misc.tile = -1;\n            data->misc.elevation = 0;\n            data->misc.rotation = 0;\n            data->misc.map = -1;\n        }\n        break;\n    default:\n        break;\n    }\n\n    return 0;\n}\n\n// 0x49F8A0\nint proto_update_init(Object* obj)\n{\n    if (!protos_been_initialized) {\n        return -1;\n    }\n\n    if (obj == NULL) {\n        return -1;\n    }\n\n    if (obj->pid == -1) {\n        return -1;\n    }\n\n    for (int i = 0; i < 14; i++) {\n        obj->field_2C_array[i] = 0;\n    }\n\n    if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) {\n        return proto_update_gen(obj);\n    }\n\n    ObjectData* data = &(obj->data);\n    data->inventory.length = 0;\n    data->inventory.capacity = 0;\n    data->inventory.items = NULL;\n    combat_data_init(obj);\n    data->critter.hp = critterGetStat(obj, STAT_MAXIMUM_HIT_POINTS);\n    data->critter.combat.ap = critterGetStat(obj, STAT_MAXIMUM_ACTION_POINTS);\n    stat_recalc_derived(obj);\n    obj->data.critter.combat.whoHitMe = NULL;\n\n    Proto* proto;\n    if (proto_ptr(obj->pid, &proto) != -1) {\n        data->critter.combat.aiPacket = proto->critter.aiPacket;\n        data->critter.combat.team = proto->critter.team;\n    }\n\n    return 0;\n}\n\n// 0x49F984\nint proto_dude_update_gender()\n{\n    Proto* proto;\n    if (proto_ptr(0x1000000, &proto) == -1) {\n        return -1;\n    }\n\n    int nativeLook = DUDE_NATIVE_LOOK_TRIBAL;\n    if (gmovie_has_been_played(MOVIE_VSUIT)) {\n        nativeLook = DUDE_NATIVE_LOOK_JUMPSUIT;\n    }\n\n    int frmId;\n    if (critterGetStat(obj_dude, STAT_GENDER) == GENDER_MALE) {\n        frmId = art_vault_person_nums[nativeLook][GENDER_MALE];\n    } else {\n        frmId = art_vault_person_nums[nativeLook][GENDER_FEMALE];\n    }\n\n    art_vault_guy_num = frmId;\n\n    if (inven_worn(obj_dude) == NULL) {\n        int v1 = 0;\n        if (inven_right_hand(obj_dude) != NULL || inven_left_hand(obj_dude) != NULL) {\n            v1 = (obj_dude->fid & 0xF000) >> 12;\n        }\n\n        int fid = art_id(OBJ_TYPE_CRITTER, art_vault_guy_num, 0, v1, 0);\n        obj_change_fid(obj_dude, fid, NULL);\n    }\n\n    proto->fid = art_id(OBJ_TYPE_CRITTER, art_vault_guy_num, 0, 0, 0);\n\n    return 0;\n}\n\n// 0x49FA64\nint proto_dude_init(const char* path)\n{\n    // 0x51C538\n    static int init_true = 0;\n\n    // 0x51C53C\n    static int retval = 0;\n\n    pc_proto.fid = art_id(OBJ_TYPE_CRITTER, art_vault_guy_num, 0, 0, 0);\n\n    if (init_true) {\n        obj_inven_free(&(obj_dude->data.inventory));\n    }\n\n    init_true = 1;\n\n    Proto* proto;\n    if (proto_ptr(0x1000000, &proto) == -1) {\n        return -1;\n    }\n\n    proto_ptr(obj_dude->pid, &proto);\n\n    proto_update_init(obj_dude);\n    obj_dude->data.critter.combat.aiPacket = 0;\n    obj_dude->data.critter.combat.team = 0;\n    ResetPlayer();\n\n    if (pc_load_data(path) == -1) {\n        retval = -1;\n    }\n\n    proto->critter.data.baseStats[STAT_DAMAGE_RESISTANCE_EMP] = 100;\n    proto->critter.data.bodyType = 0;\n    proto->critter.data.experience = 0;\n    proto->critter.data.killType = 0;\n    proto->critter.data.damageType = 0;\n\n    proto_dude_update_gender();\n    inven_reset_dude();\n\n    if ((obj_dude->flags & OBJECT_FLAT) != 0) {\n        obj_toggle_flat(obj_dude, NULL);\n    }\n\n    if ((obj_dude->flags & OBJECT_NO_BLOCK) != 0) {\n        obj_dude->flags &= ~OBJECT_NO_BLOCK;\n    }\n\n    stat_recalc_derived(obj_dude);\n    critter_adjust_hits(obj_dude, 10000);\n\n    if (retval) {\n        debug_printf(\"\\n ** Error in proto_dude_init()! **\\n\");\n    }\n\n    return 0;\n}\n\n// 0x49FFD8\nint proto_data_member(int pid, int member, ProtoDataMemberValue* value)\n{\n    Proto* proto;\n    if (proto_ptr(pid, &proto) == -1) {\n        return -1;\n    }\n\n    switch (PID_TYPE(pid)) {\n    case OBJ_TYPE_ITEM:\n        switch (member) {\n        case ITEM_DATA_MEMBER_PID:\n            value->integerValue = proto->pid;\n            break;\n        case ITEM_DATA_MEMBER_NAME:\n            // NOTE: uninline\n            value->stringValue = proto_name(proto->scenery.pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case ITEM_DATA_MEMBER_DESCRIPTION:\n            // NOTE: Uninline.\n            value->stringValue = proto_description(proto->pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case ITEM_DATA_MEMBER_FID:\n            value->integerValue = proto->fid;\n            break;\n        case ITEM_DATA_MEMBER_LIGHT_DISTANCE:\n            value->integerValue = proto->item.lightDistance;\n            break;\n        case ITEM_DATA_MEMBER_LIGHT_INTENSITY:\n            value->integerValue = proto->item.lightIntensity;\n            break;\n        case ITEM_DATA_MEMBER_FLAGS:\n            value->integerValue = proto->item.flags;\n            break;\n        case ITEM_DATA_MEMBER_EXTENDED_FLAGS:\n            value->integerValue = proto->item.extendedFlags;\n            break;\n        case ITEM_DATA_MEMBER_SID:\n            value->integerValue = proto->item.sid;\n            break;\n        case ITEM_DATA_MEMBER_TYPE:\n            value->integerValue = proto->item.type;\n            break;\n        case ITEM_DATA_MEMBER_MATERIAL:\n            value->integerValue = proto->item.material;\n            break;\n        case ITEM_DATA_MEMBER_SIZE:\n            value->integerValue = proto->item.size;\n            break;\n        case ITEM_DATA_MEMBER_WEIGHT:\n            value->integerValue = proto->item.weight;\n            break;\n        case ITEM_DATA_MEMBER_COST:\n            value->integerValue = proto->item.cost;\n            break;\n        case ITEM_DATA_MEMBER_INVENTORY_FID:\n            value->integerValue = proto->item.inventoryFid;\n            break;\n        case ITEM_DATA_MEMBER_WEAPON_RANGE:\n            if (proto->item.type == ITEM_TYPE_WEAPON) {\n                value->integerValue = proto->item.data.weapon.maxRange1;\n            }\n            break;\n        default:\n            debug_printf(\"\\n\\tError: Unimp'd data member in member in proto_data_member!\");\n            break;\n        }\n        break;\n    case OBJ_TYPE_CRITTER:\n        switch (member) {\n        case CRITTER_DATA_MEMBER_PID:\n            value->integerValue = proto->critter.pid;\n            break;\n        case CRITTER_DATA_MEMBER_NAME:\n            // NOTE: Uninline.\n            value->stringValue = proto_name(proto->critter.pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case CRITTER_DATA_MEMBER_DESCRIPTION:\n            // NOTE: Uninline.\n            value->stringValue = proto_description(proto->critter.pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case CRITTER_DATA_MEMBER_FID:\n            value->integerValue = proto->critter.fid;\n            break;\n        case CRITTER_DATA_MEMBER_LIGHT_DISTANCE:\n            value->integerValue = proto->critter.lightDistance;\n            break;\n        case CRITTER_DATA_MEMBER_LIGHT_INTENSITY:\n            value->integerValue = proto->critter.lightIntensity;\n            break;\n        case CRITTER_DATA_MEMBER_FLAGS:\n            value->integerValue = proto->critter.flags;\n            break;\n        case CRITTER_DATA_MEMBER_EXTENDED_FLAGS:\n            value->integerValue = proto->critter.extendedFlags;\n            break;\n        case CRITTER_DATA_MEMBER_SID:\n            value->integerValue = proto->critter.sid;\n            break;\n        case CRITTER_DATA_MEMBER_HEAD_FID:\n            value->integerValue = proto->critter.headFid;\n            break;\n        case CRITTER_DATA_MEMBER_BODY_TYPE:\n            value->integerValue = proto->critter.data.bodyType;\n            break;\n        default:\n            debug_printf(\"\\n\\tError: Unimp'd data member in member in proto_data_member!\");\n            break;\n        }\n        break;\n    case OBJ_TYPE_SCENERY:\n        switch (member) {\n        case SCENERY_DATA_MEMBER_PID:\n            value->integerValue = proto->scenery.pid;\n            break;\n        case SCENERY_DATA_MEMBER_NAME:\n            // NOTE: Uninline.\n            value->stringValue = proto_name(proto->scenery.pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case SCENERY_DATA_MEMBER_DESCRIPTION:\n            // NOTE: Uninline.\n            value->stringValue = proto_description(proto->scenery.pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case SCENERY_DATA_MEMBER_FID:\n            value->integerValue = proto->scenery.fid;\n            break;\n        case SCENERY_DATA_MEMBER_LIGHT_DISTANCE:\n            value->integerValue = proto->scenery.lightDistance;\n            break;\n        case SCENERY_DATA_MEMBER_LIGHT_INTENSITY:\n            value->integerValue = proto->scenery.lightIntensity;\n            break;\n        case SCENERY_DATA_MEMBER_FLAGS:\n            value->integerValue = proto->scenery.flags;\n            break;\n        case SCENERY_DATA_MEMBER_EXTENDED_FLAGS:\n            value->integerValue = proto->scenery.extendedFlags;\n            break;\n        case SCENERY_DATA_MEMBER_SID:\n            value->integerValue = proto->scenery.sid;\n            break;\n        case SCENERY_DATA_MEMBER_TYPE:\n            value->integerValue = proto->scenery.type;\n            break;\n        case SCENERY_DATA_MEMBER_MATERIAL:\n            value->integerValue = proto->scenery.field_2C;\n            break;\n        default:\n            debug_printf(\"\\n\\tError: Unimp'd data member in member in proto_data_member!\");\n            break;\n        }\n        break;\n    case OBJ_TYPE_WALL:\n        switch (member) {\n        case WALL_DATA_MEMBER_PID:\n            value->integerValue = proto->wall.pid;\n            break;\n        case WALL_DATA_MEMBER_NAME:\n            // NOTE: Uninline.\n            value->stringValue = proto_name(proto->wall.pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case WALL_DATA_MEMBER_DESCRIPTION:\n            // NOTE: Uninline.\n            value->stringValue = proto_description(proto->wall.pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case WALL_DATA_MEMBER_FID:\n            value->integerValue = proto->wall.fid;\n            break;\n        case WALL_DATA_MEMBER_LIGHT_DISTANCE:\n            value->integerValue = proto->wall.lightDistance;\n            break;\n        case WALL_DATA_MEMBER_LIGHT_INTENSITY:\n            value->integerValue = proto->wall.lightIntensity;\n            break;\n        case WALL_DATA_MEMBER_FLAGS:\n            value->integerValue = proto->wall.flags;\n            break;\n        case WALL_DATA_MEMBER_EXTENDED_FLAGS:\n            value->integerValue = proto->wall.extendedFlags;\n            break;\n        case WALL_DATA_MEMBER_SID:\n            value->integerValue = proto->wall.sid;\n            break;\n        case WALL_DATA_MEMBER_MATERIAL:\n            value->integerValue = proto->wall.material;\n            break;\n        default:\n            debug_printf(\"\\n\\tError: Unimp'd data member in member in proto_data_member!\");\n            break;\n        }\n        break;\n    case OBJ_TYPE_TILE:\n        debug_printf(\"\\n\\tError: Unimp'd data member in member in proto_data_member!\");\n        break;\n    case OBJ_TYPE_MISC:\n        switch (member) {\n        case MISC_DATA_MEMBER_PID:\n            value->integerValue = proto->misc.pid;\n            break;\n        case MISC_DATA_MEMBER_NAME:\n            // NOTE: Uninline.\n            value->stringValue = proto_name(proto->misc.pid);\n            return PROTO_DATA_MEMBER_TYPE_STRING;\n        case MISC_DATA_MEMBER_DESCRIPTION:\n            // NOTE: Uninline.\n            value->stringValue = proto_description(proto->misc.pid);\n            // FIXME: Errornously report type as int, should be string.\n            return PROTO_DATA_MEMBER_TYPE_INT;\n        case MISC_DATA_MEMBER_FID:\n            value->integerValue = proto->misc.fid;\n            return 1;\n        case MISC_DATA_MEMBER_LIGHT_DISTANCE:\n            value->integerValue = proto->misc.lightDistance;\n            return 1;\n        case MISC_DATA_MEMBER_LIGHT_INTENSITY:\n            value->integerValue = proto->misc.lightIntensity;\n            break;\n        case MISC_DATA_MEMBER_FLAGS:\n            value->integerValue = proto->misc.flags;\n            break;\n        case MISC_DATA_MEMBER_EXTENDED_FLAGS:\n            value->integerValue = proto->misc.extendedFlags;\n            break;\n        default:\n            debug_printf(\"\\n\\tError: Unimp'd data member in member in proto_data_member!\");\n            break;\n        }\n        break;\n    }\n\n    return PROTO_DATA_MEMBER_TYPE_INT;\n}\n\n// 0x4A0390\nint proto_init()\n{\n    char* master_patches;\n    int len;\n    MessageListItem messageListItem;\n    char path[MAX_PATH];\n    int i;\n\n    if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &master_patches)) {\n        return -1;\n    }\n\n    sprintf(path, \"%s\\\\proto\", master_patches);\n    len = strlen(path);\n\n    mkdir(path);\n\n    strcpy(path + len, \"\\\\critters\");\n    mkdir(path);\n\n    strcpy(path + len, \"\\\\items\");\n    mkdir(path);\n\n    // TODO: Get rid of cast.\n    proto_critter_init((Proto*)&pc_proto, 0x1000000);\n\n    pc_proto.pid = 0x1000000;\n    pc_proto.fid = art_id(OBJ_TYPE_CRITTER, 1, 0, 0, 0);\n\n    obj_dude->pid = 0x1000000;\n    obj_dude->sid = 1;\n\n    for (i = 0; i < 6; i++) {\n        proto_remove_list(i);\n    }\n\n    proto_header_load();\n\n    protos_been_initialized = 1;\n\n    proto_dude_init(\"premade\\\\player.gcd\");\n\n    for (i = 0; i < 6; i++) {\n        if (!message_init(&(proto_msg_files[i]))) {\n            debug_printf(\"\\nError: Initing proto message files!\");\n            return -1;\n        }\n    }\n\n    for (i = 0; i < 6; i++) {\n        sprintf(path, \"%spro_%.4s%s\", msg_path, art_dir(i), \".msg\");\n\n        if (!message_load(&(proto_msg_files[i]), path)) {\n            debug_printf(\"\\nError: Loading proto message files!\");\n            return -1;\n        }\n    }\n\n    mp_critter_stats_list[0] = \"Drug Stat (Special)\";\n    mp_critter_stats_list[1] = \"None\";\n    critter_stats_list = &(mp_critter_stats_list[2]);\n    for (i = 0; i < STAT_COUNT; i++) {\n        critter_stats_list[i] = stat_name(i);\n        if (critter_stats_list[i] == NULL) {\n            debug_printf(\"\\nError: Finding stat names!\");\n            return -1;\n        }\n    }\n\n    mp_perk_code_strs[0] = \"None\";\n    perk_code_strs = &(mp_perk_code_strs[1]);\n    for (i = 0; i < PERK_COUNT; i++) {\n        mp_perk_code_strs[i] = perk_name(i);\n        if (mp_perk_code_strs[i] == NULL) {\n            debug_printf(\"\\nError: Finding perk names!\");\n            return -1;\n        }\n    }\n\n    if (!message_init(&proto_main_msg_file)) {\n        debug_printf(\"\\nError: Initing main proto message file!\");\n        return -1;\n    }\n\n    sprintf(path, \"%sproto.msg\", msg_path);\n\n    if (!message_load(&proto_main_msg_file, path)) {\n        debug_printf(\"\\nError: Loading main proto message file!\");\n        return -1;\n    }\n\n    proto_none_str = getmsg(&proto_main_msg_file, &messageListItem, 10);\n\n    // material type names\n    for (i = 0; i < MATERIAL_TYPE_COUNT; i++) {\n        item_pro_material[i] = getmsg(&proto_main_msg_file, &messageListItem, 100 + i);\n    }\n\n    // item type names\n    for (i = 0; i < ITEM_TYPE_COUNT; i++) {\n        item_pro_type[i] = getmsg(&proto_main_msg_file, &messageListItem, 150 + i);\n    }\n\n    // scenery type names\n    for (i = 0; i < SCENERY_TYPE_COUNT; i++) {\n        scenery_pro_type[i] = getmsg(&proto_main_msg_file, &messageListItem, 200 + i);\n    }\n\n    // damage code types\n    for (i = 0; i < DAMAGE_TYPE_COUNT; i++) {\n        damage_code_strs[i] = getmsg(&proto_main_msg_file, &messageListItem, 250 + i);\n    }\n\n    // caliber types\n    for (i = 0; i < CALIBER_TYPE_COUNT; i++) {\n        cal_type_strs[i] = getmsg(&proto_main_msg_file, &messageListItem, 300 + i);\n    }\n\n    // race types\n    for (i = 0; i < RACE_TYPE_COUNT; i++) {\n        race_type_strs[i] = getmsg(&proto_main_msg_file, &messageListItem, 350 + i);\n    }\n\n    // body types\n    for (i = 0; i < BODY_TYPE_COUNT; i++) {\n        body_type_strs[i] = getmsg(&proto_main_msg_file, &messageListItem, 400 + i);\n    }\n\n    return 0;\n}\n\n// 0x4A0814\nvoid proto_reset()\n{\n    int i;\n\n    // TODO: Get rid of cast.\n    proto_critter_init((Proto*)&pc_proto, 0x1000000);\n    pc_proto.pid = 0x1000000;\n    pc_proto.fid = art_id(OBJ_TYPE_CRITTER, 1, 0, 0, 0);\n\n    obj_dude->pid = 0x1000000;\n    obj_dude->sid = -1;\n    obj_dude->flags &= ~OBJECT_FLAG_0xFC000;\n\n    for (i = 0; i < 6; i++) {\n        proto_remove_list(i);\n    }\n\n    proto_header_load();\n\n    protos_been_initialized = 1;\n    proto_dude_init(\"premade\\\\player.gcd\");\n}\n\n// 0x4A0898\nvoid proto_exit()\n{\n    int i;\n\n    for (i = 0; i < 6; i++) {\n        proto_remove_list(i);\n    }\n\n    for (i = 0; i < 6; i++) {\n        message_exit(&(proto_msg_files[i]));\n    }\n\n    message_exit(&proto_main_msg_file);\n}\n\n// Count .pro lines in .lst files.\n//\n// 0x4A08E0\nint proto_header_load()\n{\n    for (int index = 0; index < 6; index++) {\n        ProtoList* ptr = &(protolists[index]);\n        ptr->head = NULL;\n        ptr->tail = NULL;\n        ptr->length = 0;\n        ptr->max_entries_num = 1;\n\n        char path[MAX_PATH];\n        proto_make_path(path, index << 24);\n        strcat(path, \"\\\\\");\n        strcat(path, art_dir(index));\n        strcat(path, \".lst\");\n\n        File* stream = db_fopen(path, \"rt\");\n        if (stream == NULL) {\n            return -1;\n        }\n\n        int ch = '\\0';\n        while (1) {\n            ch = db_fgetc(stream);\n            if (ch == -1) {\n                break;\n            }\n\n            if (ch == '\\n') {\n                ptr->max_entries_num++;\n            }\n        }\n\n        if (ch != '\\n') {\n            ptr->max_entries_num++;\n        }\n\n        db_fclose(stream);\n    }\n\n    return 0;\n}\n\n// 0x4A0AEC\nstatic int proto_read_item_data(ItemProtoData* item_data, int type, File* stream)\n{\n    switch (type) {\n    case ITEM_TYPE_ARMOR:\n        if (db_freadInt(stream, &(item_data->armor.armorClass)) == -1) return -1;\n        if (db_freadIntCount(stream, item_data->armor.damageResistance, 7) == -1) return -1;\n        if (db_freadIntCount(stream, item_data->armor.damageThreshold, 7) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->armor.perk)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->armor.maleFid)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->armor.femaleFid)) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_CONTAINER:\n        if (db_freadInt(stream, &(item_data->container.maxSize)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->container.openFlags)) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_DRUG:\n        if (db_freadInt(stream, &(item_data->drug.stat[0])) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->drug.stat[1])) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->drug.stat[2])) == -1) return -1;\n        if (db_freadIntCount(stream, item_data->drug.amount, 3) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->drug.duration1)) == -1) return -1;\n        if (db_freadIntCount(stream, item_data->drug.amount1, 3) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->drug.duration2)) == -1) return -1;\n        if (db_freadIntCount(stream, item_data->drug.amount2, 3) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->drug.addictionChance)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->drug.withdrawalEffect)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->drug.withdrawalOnset)) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_WEAPON:\n        if (db_freadInt(stream, &(item_data->weapon.animationCode)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.minDamage)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.maxDamage)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.damageType)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.maxRange1)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.maxRange2)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.projectilePid)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.minStrength)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.actionPointCost1)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.actionPointCost2)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.criticalFailureType)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.perk)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.rounds)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.caliber)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.ammoTypePid)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->weapon.ammoCapacity)) == -1) return -1;\n        if (db_freadByte(stream, &(item_data->weapon.soundCode)) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_AMMO:\n        if (db_freadInt(stream, &(item_data->ammo.caliber)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->ammo.quantity)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->ammo.armorClassModifier)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->ammo.damageResistanceModifier)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->ammo.damageMultiplier)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->ammo.damageDivisor)) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_MISC:\n        if (db_freadInt(stream, &(item_data->misc.powerTypePid)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->misc.powerType)) == -1) return -1;\n        if (db_freadInt(stream, &(item_data->misc.charges)) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_KEY:\n        if (db_freadInt(stream, &(item_data->key.keyCode)) == -1) return -1;\n\n        return 0;\n    }\n\n    return 0;\n}\n\n// 0x4A0ED0\nstatic int proto_read_scenery_data(SceneryProtoData* scenery_data, int type, File* stream)\n{\n    switch (type) {\n    case SCENERY_TYPE_DOOR:\n        if (db_freadInt(stream, &(scenery_data->door.openFlags)) == -1) return -1;\n        if (db_freadInt(stream, &(scenery_data->door.keyCode)) == -1) return -1;\n\n        return 0;\n    case SCENERY_TYPE_STAIRS:\n        if (db_freadInt(stream, &(scenery_data->stairs.field_0)) == -1) return -1;\n        if (db_freadInt(stream, &(scenery_data->stairs.field_4)) == -1) return -1;\n\n        return 0;\n    case SCENERY_TYPE_ELEVATOR:\n        if (db_freadInt(stream, &(scenery_data->elevator.type)) == -1) return -1;\n        if (db_freadInt(stream, &(scenery_data->elevator.level)) == -1) return -1;\n\n        return 0;\n    case SCENERY_TYPE_LADDER_UP:\n    case SCENERY_TYPE_LADDER_DOWN:\n        if (db_freadInt(stream, &(scenery_data->ladder.field_0)) == -1) return -1;\n\n        return 0;\n    case SCENERY_TYPE_GENERIC:\n        if (db_freadInt(stream, &(scenery_data->generic.field_0)) == -1) return -1;\n\n        return 0;\n    }\n\n    return 0;\n}\n\n// read .pro file\n// 0x4A0FA0\nstatic int proto_read_protoSubNode(Proto* proto, File* stream)\n{\n    if (db_freadInt(stream, &(proto->pid)) == -1) return -1;\n    if (db_freadInt(stream, &(proto->messageId)) == -1) return -1;\n    if (db_freadInt(stream, &(proto->fid)) == -1) return -1;\n\n    switch (PID_TYPE(proto->pid)) {\n    case OBJ_TYPE_ITEM:\n        if (db_freadInt(stream, &(proto->item.lightDistance)) == -1) return -1;\n        if (db_freadLong(stream, &(proto->item.lightIntensity)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->item.flags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->item.extendedFlags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->item.sid)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->item.type)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->item.material)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->item.size)) == -1) return -1;\n        if (db_freadLong(stream, &(proto->item.weight)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->item.cost)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->item.inventoryFid)) == -1) return -1;\n        if (db_freadByte(stream, &(proto->item.field_80)) == -1) return -1;\n        if (proto_read_item_data(&(proto->item.data), proto->item.type, stream) == -1) return -1;\n\n        return 0;\n    case OBJ_TYPE_CRITTER:\n        if (db_freadInt(stream, &(proto->critter.lightDistance)) == -1) return -1;\n        if (db_freadLong(stream, &(proto->critter.lightIntensity)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->critter.flags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->critter.extendedFlags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->critter.sid)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->critter.headFid)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->critter.aiPacket)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->critter.team)) == -1) return -1;\n\n        if (critter_read_data(stream, &(proto->critter.data)) == -1) return -1;\n\n        return 0;\n    case OBJ_TYPE_SCENERY:\n        if (db_freadInt(stream, &(proto->scenery.lightDistance)) == -1) return -1;\n        if (db_freadLong(stream, &(proto->scenery.lightIntensity)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->scenery.flags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->scenery.extendedFlags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->scenery.sid)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->scenery.type)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->scenery.field_2C)) == -1) return -1;\n        if (db_freadByte(stream, &(proto->scenery.field_34)) == -1) return -1;\n        if (proto_read_scenery_data(&(proto->scenery.data), proto->scenery.type, stream) == -1) return -1;\n        return 0;\n    case OBJ_TYPE_WALL:\n        if (db_freadInt(stream, &(proto->wall.lightDistance)) == -1) return -1;\n        if (db_freadLong(stream, &(proto->wall.lightIntensity)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->wall.flags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->wall.extendedFlags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->wall.sid)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->wall.material)) == -1) return -1;\n\n        return 0;\n    case OBJ_TYPE_TILE:\n        if (db_freadInt(stream, &(proto->tile.flags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->tile.extendedFlags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->tile.sid)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->tile.material)) == -1) return -1;\n\n        return 0;\n    case OBJ_TYPE_MISC:\n        if (db_freadInt(stream, &(proto->misc.lightDistance)) == -1) return -1;\n        if (db_freadLong(stream, &(proto->misc.lightIntensity)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->misc.flags)) == -1) return -1;\n        if (db_freadInt(stream, &(proto->misc.extendedFlags)) == -1) return -1;\n\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x4A1390\nstatic int proto_write_item_data(ItemProtoData* item_data, int type, File* stream)\n{\n    switch (type) {\n    case ITEM_TYPE_ARMOR:\n        if (db_fwriteInt(stream, item_data->armor.armorClass) == -1) return -1;\n        if (db_fwriteIntCount(stream, item_data->armor.damageResistance, 7) == -1) return -1;\n        if (db_fwriteIntCount(stream, item_data->armor.damageThreshold, 7) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->armor.perk) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->armor.maleFid) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->armor.femaleFid) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_CONTAINER:\n        if (db_fwriteInt(stream, item_data->container.maxSize) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->container.openFlags) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_DRUG:\n        if (db_fwriteInt(stream, item_data->drug.stat[0]) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->drug.stat[1]) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->drug.stat[2]) == -1) return -1;\n        if (db_fwriteIntCount(stream, item_data->drug.amount, 3) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->drug.duration1) == -1) return -1;\n        if (db_fwriteIntCount(stream, item_data->drug.amount1, 3) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->drug.duration2) == -1) return -1;\n        if (db_fwriteIntCount(stream, item_data->drug.amount2, 3) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->drug.addictionChance) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->drug.withdrawalEffect) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->drug.withdrawalOnset) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_WEAPON:\n        if (db_fwriteInt(stream, item_data->weapon.animationCode) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.maxDamage) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.minDamage) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.damageType) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.maxRange1) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.maxRange2) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.projectilePid) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.minStrength) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.actionPointCost1) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.actionPointCost2) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.criticalFailureType) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.perk) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.rounds) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.caliber) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.ammoTypePid) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->weapon.ammoCapacity) == -1) return -1;\n        if (db_fwriteByte(stream, item_data->weapon.soundCode) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_AMMO:\n        if (db_fwriteInt(stream, item_data->ammo.caliber) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->ammo.quantity) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->ammo.armorClassModifier) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->ammo.damageResistanceModifier) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->ammo.damageMultiplier) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->ammo.damageDivisor) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_MISC:\n        if (db_fwriteInt(stream, item_data->misc.powerTypePid) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->misc.powerType) == -1) return -1;\n        if (db_fwriteInt(stream, item_data->misc.charges) == -1) return -1;\n\n        return 0;\n    case ITEM_TYPE_KEY:\n        if (db_fwriteInt(stream, item_data->key.keyCode) == -1) return -1;\n\n        return 0;\n    }\n\n    return 0;\n}\n\n// 0x4A16E4\nstatic int proto_write_scenery_data(SceneryProtoData* scenery_data, int type, File* stream)\n{\n    switch (type) {\n    case SCENERY_TYPE_DOOR:\n        if (db_fwriteInt(stream, scenery_data->door.openFlags) == -1) return -1;\n        if (db_fwriteInt(stream, scenery_data->door.keyCode) == -1) return -1;\n\n        return 0;\n    case SCENERY_TYPE_STAIRS:\n        if (db_fwriteInt(stream, scenery_data->stairs.field_0) == -1) return -1;\n        if (db_fwriteInt(stream, scenery_data->stairs.field_4) == -1) return -1;\n\n        return 0;\n    case SCENERY_TYPE_ELEVATOR:\n        if (db_fwriteInt(stream, scenery_data->elevator.type) == -1) return -1;\n        if (db_fwriteInt(stream, scenery_data->elevator.level) == -1) return -1;\n\n        return 0;\n    case SCENERY_TYPE_LADDER_UP:\n    case SCENERY_TYPE_LADDER_DOWN:\n        if (db_fwriteInt(stream, scenery_data->ladder.field_0) == -1) return -1;\n\n        return 0;\n    case SCENERY_TYPE_GENERIC:\n        if (db_fwriteInt(stream, scenery_data->generic.field_0) == -1) return -1;\n\n        return 0;\n    }\n\n    return 0;\n}\n\n// 0x4A17B4\nstatic int proto_write_protoSubNode(Proto* proto, File* stream)\n{\n    if (db_fwriteInt(stream, proto->pid) == -1) return -1;\n    if (db_fwriteInt(stream, proto->messageId) == -1) return -1;\n    if (db_fwriteInt(stream, proto->fid) == -1) return -1;\n\n    switch (PID_TYPE(proto->pid)) {\n    case OBJ_TYPE_ITEM:\n        if (db_fwriteInt(stream, proto->item.lightDistance) == -1) return -1;\n        if (db_fwriteLong(stream, proto->item.lightIntensity) == -1) return -1;\n        if (db_fwriteInt(stream, proto->item.flags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->item.extendedFlags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->item.sid) == -1) return -1;\n        if (db_fwriteInt(stream, proto->item.type) == -1) return -1;\n        if (db_fwriteInt(stream, proto->item.material) == -1) return -1;\n        if (db_fwriteInt(stream, proto->item.size) == -1) return -1;\n        if (db_fwriteLong(stream, proto->item.weight) == -1) return -1;\n        if (db_fwriteInt(stream, proto->item.cost) == -1) return -1;\n        if (db_fwriteInt(stream, proto->item.inventoryFid) == -1) return -1;\n        if (db_fwriteByte(stream, proto->item.field_80) == -1) return -1;\n        if (proto_write_item_data(&(proto->item.data), proto->item.type, stream) == -1) return -1;\n\n        return 0;\n    case OBJ_TYPE_CRITTER:\n        if (db_fwriteInt(stream, proto->critter.lightDistance) == -1) return -1;\n        if (db_fwriteLong(stream, proto->critter.lightIntensity) == -1) return -1;\n        if (db_fwriteInt(stream, proto->critter.flags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->critter.extendedFlags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->critter.sid) == -1) return -1;\n        if (db_fwriteInt(stream, proto->critter.headFid) == -1) return -1;\n        if (db_fwriteInt(stream, proto->critter.aiPacket) == -1) return -1;\n        if (db_fwriteInt(stream, proto->critter.team) == -1) return -1;\n        if (critter_write_data(stream, &(proto->critter.data)) == -1) return -1;\n\n        return 0;\n    case OBJ_TYPE_SCENERY:\n        if (db_fwriteInt(stream, proto->scenery.lightDistance) == -1) return -1;\n        if (db_fwriteLong(stream, proto->scenery.lightIntensity) == -1) return -1;\n        if (db_fwriteInt(stream, proto->scenery.flags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->scenery.extendedFlags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->scenery.sid) == -1) return -1;\n        if (db_fwriteInt(stream, proto->scenery.type) == -1) return -1;\n        if (db_fwriteInt(stream, proto->scenery.field_2C) == -1) return -1;\n        if (db_fwriteByte(stream, proto->scenery.field_34) == -1) return -1;\n        if (proto_write_scenery_data(&(proto->scenery.data), proto->scenery.type, stream) == -1) return -1;\n    case OBJ_TYPE_WALL:\n        if (db_fwriteInt(stream, proto->wall.lightDistance) == -1) return -1;\n        if (db_fwriteLong(stream, proto->wall.lightIntensity) == -1) return -1;\n        if (db_fwriteInt(stream, proto->wall.flags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->wall.extendedFlags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->wall.sid) == -1) return -1;\n        if (db_fwriteInt(stream, proto->wall.material) == -1) return -1;\n\n        return 0;\n    case OBJ_TYPE_TILE:\n        if (db_fwriteInt(stream, proto->tile.flags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->tile.extendedFlags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->tile.sid) == -1) return -1;\n        if (db_fwriteInt(stream, proto->tile.material) == -1) return -1;\n\n        return 0;\n    case OBJ_TYPE_MISC:\n        if (db_fwriteInt(stream, proto->misc.lightDistance) == -1) return -1;\n        if (db_fwriteLong(stream, proto->misc.lightIntensity) == -1) return -1;\n        if (db_fwriteInt(stream, proto->misc.flags) == -1) return -1;\n        if (db_fwriteInt(stream, proto->misc.extendedFlags) == -1) return -1;\n\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x4A1B30\nint proto_save_pid(int pid)\n{\n    Proto* proto;\n    if (proto_ptr(pid, &proto) == -1) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    proto_make_path(path, pid);\n    strcat(path, \"\\\\\");\n\n    proto_list_str(pid, path + strlen(path));\n\n    File* stream = db_fopen(path, \"wb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int rc = proto_write_protoSubNode(proto, stream);\n\n    db_fclose(stream);\n\n    return rc;\n}\n\n// 0x4A1C3C\nint proto_load_pid(int pid, Proto** protoPtr)\n{\n    char path[MAX_PATH];\n    proto_make_path(path, pid);\n    strcat(path, \"\\\\\");\n\n    if (proto_list_str(pid, path + strlen(path)) == -1) {\n        return -1;\n    }\n\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        debug_printf(\"\\nError: Can't fopen proto!\\n\");\n        *protoPtr = NULL;\n        return -1;\n    }\n\n    if (proto_find_free_subnode(PID_TYPE(pid), protoPtr) == -1) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    if (proto_read_protoSubNode(*protoPtr, stream) != 0) {\n        db_fclose(stream);\n        return -1;\n    }\n\n    db_fclose(stream);\n    return 0;\n}\n\n// 0x4A1D98\nint proto_find_free_subnode(int type, Proto** protoPtr)\n{\n    size_t size = (type >= 0 && type < 11) ? proto_sizes[type] : 0;\n\n    Proto* proto = (Proto*)mem_malloc(size);\n    *protoPtr = proto;\n    if (proto == NULL) {\n        return -1;\n    }\n\n    ProtoList* protoList = &(protolists[type]);\n    ProtoListExtent* protoListExtent = protoList->tail;\n\n    if (protoList->head != NULL) {\n        if (protoListExtent->length == PROTO_LIST_EXTENT_SIZE) {\n            ProtoListExtent* newExtent = protoListExtent->next = (ProtoListExtent*)mem_malloc(sizeof(ProtoListExtent));\n            if (protoListExtent == NULL) {\n                mem_free(proto);\n                *protoPtr = NULL;\n                return -1;\n            }\n\n            newExtent->length = 0;\n            newExtent->next = NULL;\n\n            protoList->tail = newExtent;\n            protoList->length++;\n\n            protoListExtent = newExtent;\n        }\n    } else {\n        protoListExtent = (ProtoListExtent*)mem_malloc(sizeof(ProtoListExtent));\n        if (protoListExtent == NULL) {\n            mem_free(proto);\n            *protoPtr = NULL;\n            return -1;\n        }\n\n        protoListExtent->next = NULL;\n        protoListExtent->length = 0;\n\n        protoList->length = 1;\n        protoList->tail = protoListExtent;\n        protoList->head = protoListExtent;\n    }\n\n    protoListExtent->proto[protoListExtent->length] = proto;\n    protoListExtent->length++;\n\n    return 0;\n}\n\n// Evict top most proto cache block.\n//\n// 0x4A2040\nstatic void proto_remove_some_list(int type)\n{\n    ProtoList* protoList = &(protolists[type]);\n    ProtoListExtent* protoListExtent = protoList->head;\n    if (protoListExtent != NULL) {\n        protoList->length--;\n        protoList->head = protoListExtent->next;\n\n        for (int index = 0; index < protoListExtent->length; index++) {\n            mem_free(protoListExtent->proto[index]);\n        }\n\n        mem_free(protoListExtent);\n    }\n}\n\n// Clear proto cache of given type.\n//\n// 0x4A2094\nstatic void proto_remove_list(int type)\n{\n    ProtoList* protoList = &(protolists[type]);\n\n    ProtoListExtent* curr = protoList->head;\n    while (curr != NULL) {\n        ProtoListExtent* next = curr->next;\n        for (int index = 0; index < curr->length; index++) {\n            mem_free(curr->proto[index]);\n        }\n        mem_free(curr);\n        curr = next;\n    }\n\n    protoList->head = NULL;\n    protoList->tail = NULL;\n    protoList->length = 0;\n}\n\n// Clear all proto cache.\n//\n// 0x4A20F4\nvoid proto_remove_all()\n{\n    for (int index = 0; index < 6; index++) {\n        proto_remove_list(index);\n    }\n}\n\n// 0x4A2108\nint proto_ptr(int pid, Proto** protoPtr)\n{\n    *protoPtr = NULL;\n\n    if (pid == -1) {\n        return -1;\n    }\n\n    if (pid == 0x1000000) {\n        *protoPtr = (Proto*)&pc_proto;\n        return 0;\n    }\n\n    ProtoList* protoList = &(protolists[PID_TYPE(pid)]);\n    ProtoListExtent* protoListExtent = protoList->head;\n    while (protoListExtent != NULL) {\n        for (int index = 0; index < protoListExtent->length; index++) {\n            Proto* proto = (Proto*)protoListExtent->proto[index];\n            if (pid == proto->pid) {\n                *protoPtr = proto;\n                return 0;\n            }\n        }\n        protoListExtent = protoListExtent->next;\n    }\n\n    if (protoList->head != NULL && protoList->tail != NULL) {\n        if (PROTO_LIST_EXTENT_SIZE * protoList->length - (PROTO_LIST_EXTENT_SIZE - protoList->tail->length) > PROTO_LIST_MAX_ENTRIES) {\n            proto_remove_some_list(PID_TYPE(pid));\n        }\n    }\n\n    return proto_load_pid(pid, protoPtr);\n}\n\n// 0x4A21DC\nstatic int proto_new_id(int a1)\n{\n    int result = protolists[a1].max_entries_num;\n    protolists[a1].max_entries_num = result + 1;\n\n    return result;\n}\n\n// 0x4A2214\nint proto_max_id(int a1)\n{\n    return protolists[a1].max_entries_num;\n}\n\n// 0x4A22C0\nint ResetPlayer()\n{\n    Proto* proto;\n    proto_ptr(obj_dude->pid, &proto);\n\n    stat_pc_set_defaults();\n    stat_set_defaults(&(proto->critter.data));\n    critter_reset();\n    editor_reset();\n    skill_set_defaults(&(proto->critter.data));\n    skill_reset();\n    perk_reset();\n    trait_reset();\n    stat_recalc_derived(obj_dude);\n    return 0;\n}\n"
  },
  {
    "path": "src/game/proto.h",
    "content": "#ifndef FALLOUT_GAME_PROTO_H_\n#define FALLOUT_GAME_PROTO_H_\n\n#include \"plib/db/db.h\"\n#include \"game/message.h\"\n#include \"game/object_types.h\"\n#include \"game/perk_defs.h\"\n#include \"game/proto_types.h\"\n#include \"game/skill_defs.h\"\n#include \"game/stat_defs.h\"\n\ntypedef enum ItemDataMember {\n    ITEM_DATA_MEMBER_PID = 0,\n    ITEM_DATA_MEMBER_NAME = 1,\n    ITEM_DATA_MEMBER_DESCRIPTION = 2,\n    ITEM_DATA_MEMBER_FID = 3,\n    ITEM_DATA_MEMBER_LIGHT_DISTANCE = 4,\n    ITEM_DATA_MEMBER_LIGHT_INTENSITY = 5,\n    ITEM_DATA_MEMBER_FLAGS = 6,\n    ITEM_DATA_MEMBER_EXTENDED_FLAGS = 7,\n    ITEM_DATA_MEMBER_SID = 8,\n    ITEM_DATA_MEMBER_TYPE = 9,\n    ITEM_DATA_MEMBER_MATERIAL = 11,\n    ITEM_DATA_MEMBER_SIZE = 12,\n    ITEM_DATA_MEMBER_WEIGHT = 13,\n    ITEM_DATA_MEMBER_COST = 14,\n    ITEM_DATA_MEMBER_INVENTORY_FID = 15,\n    ITEM_DATA_MEMBER_WEAPON_RANGE = 555,\n} ItemDataMember;\n\ntypedef enum CritterDataMember {\n    CRITTER_DATA_MEMBER_PID = 0,\n    CRITTER_DATA_MEMBER_NAME = 1,\n    CRITTER_DATA_MEMBER_DESCRIPTION = 2,\n    CRITTER_DATA_MEMBER_FID = 3,\n    CRITTER_DATA_MEMBER_LIGHT_DISTANCE = 4,\n    CRITTER_DATA_MEMBER_LIGHT_INTENSITY = 5,\n    CRITTER_DATA_MEMBER_FLAGS = 6,\n    CRITTER_DATA_MEMBER_EXTENDED_FLAGS = 7,\n    CRITTER_DATA_MEMBER_SID = 8,\n    CRITTER_DATA_MEMBER_DATA = 9,\n    CRITTER_DATA_MEMBER_HEAD_FID = 10,\n    CRITTER_DATA_MEMBER_BODY_TYPE = 11,\n} CritterDataMember;\n\ntypedef enum SceneryDataMember {\n    SCENERY_DATA_MEMBER_PID = 0,\n    SCENERY_DATA_MEMBER_NAME = 1,\n    SCENERY_DATA_MEMBER_DESCRIPTION = 2,\n    SCENERY_DATA_MEMBER_FID = 3,\n    SCENERY_DATA_MEMBER_LIGHT_DISTANCE = 4,\n    SCENERY_DATA_MEMBER_LIGHT_INTENSITY = 5,\n    SCENERY_DATA_MEMBER_FLAGS = 6,\n    SCENERY_DATA_MEMBER_EXTENDED_FLAGS = 7,\n    SCENERY_DATA_MEMBER_SID = 8,\n    SCENERY_DATA_MEMBER_TYPE = 9,\n    SCENERY_DATA_MEMBER_DATA = 10,\n    SCENERY_DATA_MEMBER_MATERIAL = 11,\n} SceneryDataMember;\n\ntypedef enum WallDataMember {\n    WALL_DATA_MEMBER_PID = 0,\n    WALL_DATA_MEMBER_NAME = 1,\n    WALL_DATA_MEMBER_DESCRIPTION = 2,\n    WALL_DATA_MEMBER_FID = 3,\n    WALL_DATA_MEMBER_LIGHT_DISTANCE = 4,\n    WALL_DATA_MEMBER_LIGHT_INTENSITY = 5,\n    WALL_DATA_MEMBER_FLAGS = 6,\n    WALL_DATA_MEMBER_EXTENDED_FLAGS = 7,\n    WALL_DATA_MEMBER_SID = 8,\n    WALL_DATA_MEMBER_MATERIAL = 9,\n} WallDataMember;\n\ntypedef enum MiscDataMember {\n    MISC_DATA_MEMBER_PID = 0,\n    MISC_DATA_MEMBER_NAME = 1,\n    MISC_DATA_MEMBER_DESCRIPTION = 2,\n    MISC_DATA_MEMBER_FID = 3,\n    MISC_DATA_MEMBER_LIGHT_DISTANCE = 4,\n    MISC_DATA_MEMBER_LIGHT_INTENSITY = 5,\n    MISC_DATA_MEMBER_FLAGS = 6,\n    MISC_DATA_MEMBER_EXTENDED_FLAGS = 7,\n} MiscDataMember;\n\ntypedef enum ProtoDataMemberType {\n    PROTO_DATA_MEMBER_TYPE_INT = 1,\n    PROTO_DATA_MEMBER_TYPE_STRING = 2,\n} ProtoDataMemberType;\n\ntypedef union ProtoDataMemberValue {\n    int integerValue;\n    char* stringValue;\n} ProtoDataMemberValue;\n\ntypedef enum PrototypeMessage {\n    PROTOTYPE_MESSAGE_NAME,\n    PROTOTYPE_MESSAGE_DESCRIPTION,\n} PrototypeMesage;\n\nextern char cd_path_base[];\nextern char proto_path_base[];\n\nextern char* mp_perk_code_strs[1 + PERK_COUNT];\nextern char* mp_critter_stats_list[2 + STAT_COUNT];\nextern MessageList proto_msg_files[6];\nextern char* race_type_strs[2];\nextern char* scenery_pro_type[6];\nextern MessageList proto_main_msg_file;\nextern char* item_pro_material[8];\nextern char* proto_none_str;\nextern char* body_type_strs[3];\nextern char* item_pro_type[7];\nextern char* damage_code_strs[7];\nextern char* cal_type_strs[19];\nextern char** perk_code_strs;\nextern char** critter_stats_list;\n\nvoid proto_make_path(char* path, int pid);\nint proto_list_str(int pid, char* proto_path);\nbool proto_action_can_use(int pid);\nbool proto_action_can_use_on(int pid);\nbool proto_action_can_talk_to(int pid);\nint proto_action_can_pickup(int pid);\nchar* proto_name(int pid);\nchar* proto_description(int pid);\nint proto_critter_init(Proto* a1, int a2);\nvoid clear_pupdate_data(Object* obj);\nint proto_read_protoUpdateData(Object* obj, File* stream);\nint proto_write_protoUpdateData(Object* obj, File* stream);\nint proto_update_gen(Object* obj);\nint proto_update_init(Object* obj);\nint proto_dude_update_gender();\nint proto_dude_init(const char* path);\nint proto_data_member(int pid, int member, ProtoDataMemberValue* value);\nint proto_init();\nvoid proto_reset();\nvoid proto_exit();\nint proto_header_load();\nint proto_save_pid(int pid);\nint proto_load_pid(int pid, Proto** out_proto);\nint proto_find_free_subnode(int type, Proto** out_ptr);\nvoid proto_remove_all();\nint proto_ptr(int pid, Proto** out_proto);\nint proto_max_id(int a1);\nint ResetPlayer();\n\n#endif /* FALLOUT_GAME_PROTO_H_ */\n"
  },
  {
    "path": "src/game/proto_types.h",
    "content": "#ifndef PROTO_TYPES_H\n#define PROTO_TYPES_H\n\n// Number of prototypes in prototype extent.\n#define PROTO_LIST_EXTENT_SIZE 16\n\n// Max number of prototypes of one type to be stored in prototype cache lists.\n// Once this value is reached the top most proto extent is removed from the\n// cache list.\n//\n// See:\n// - [sub_4A2108]\n// - [sub_4A2040]\n#define PROTO_LIST_MAX_ENTRIES 512\n\n#define WEAPON_TWO_HAND 0x00000200\n\nenum {\n    GENDER_MALE,\n    GENDER_FEMALE,\n    GENDER_COUNT,\n};\n\nenum {\n    ITEM_TYPE_ARMOR,\n    ITEM_TYPE_CONTAINER,\n    ITEM_TYPE_DRUG,\n    ITEM_TYPE_WEAPON,\n    ITEM_TYPE_AMMO,\n    ITEM_TYPE_MISC,\n    ITEM_TYPE_KEY,\n    ITEM_TYPE_COUNT,\n};\n\nenum {\n    SCENERY_TYPE_DOOR,\n    SCENERY_TYPE_STAIRS,\n    SCENERY_TYPE_ELEVATOR,\n    SCENERY_TYPE_LADDER_UP,\n    SCENERY_TYPE_LADDER_DOWN,\n    SCENERY_TYPE_GENERIC,\n    SCENERY_TYPE_COUNT,\n};\n\nenum {\n    MATERIAL_TYPE_GLASS,\n    MATERIAL_TYPE_METAL,\n    MATERIAL_TYPE_PLASTIC,\n    MATERIAL_TYPE_WOOD,\n    MATERIAL_TYPE_DIRT,\n    MATERIAL_TYPE_STONE,\n    MATERIAL_TYPE_CEMENT,\n    MATERIAL_TYPE_LEATHER,\n    MATERIAL_TYPE_COUNT,\n};\n\nenum {\n    DAMAGE_TYPE_NORMAL,\n    DAMAGE_TYPE_LASER,\n    DAMAGE_TYPE_FIRE,\n    DAMAGE_TYPE_PLASMA,\n    DAMAGE_TYPE_ELECTRICAL,\n    DAMAGE_TYPE_EMP,\n    DAMAGE_TYPE_EXPLOSION,\n    DAMAGE_TYPE_COUNT,\n};\n\nenum {\n    CALIBER_TYPE_NONE,\n    CALIBER_TYPE_ROCKET,\n    CALIBER_TYPE_FLAMETHROWER_FUEL,\n    CALIBER_TYPE_C_ENERGY_CELL,\n    CALIBER_TYPE_D_ENERGY_CELL,\n    CALIBER_TYPE_223,\n    CALIBER_TYPE_5_MM,\n    CALIBER_TYPE_40_CAL,\n    CALIBER_TYPE_10_MM,\n    CALIBER_TYPE_44_CAL,\n    CALIBER_TYPE_14_MM,\n    CALIBER_TYPE_12_GAUGE,\n    CALIBER_TYPE_9_MM,\n    CALIBER_TYPE_BB,\n    CALIBER_TYPE_45_CAL,\n    CALIBER_TYPE_2_MM,\n    CALIBER_TYPE_4_7_MM_CASELESS,\n    CALIBER_TYPE_NH_NEEDLER,\n    CALIBER_TYPE_7_62,\n    CALIBER_TYPE_COUNT,\n};\n\nenum {\n    RACE_TYPE_CAUCASIAN,\n    RACE_TYPE_AFRICAN,\n    RACE_TYPE_COUNT,\n};\n\nenum {\n    BODY_TYPE_BIPED,\n    BODY_TYPE_QUADRUPED,\n    BODY_TYPE_ROBOTIC,\n    BODY_TYPE_COUNT,\n};\n\nenum {\n    KILL_TYPE_MAN,\n    KILL_TYPE_WOMAN,\n    KILL_TYPE_CHILD,\n    KILL_TYPE_SUPER_MUTANT,\n    KILL_TYPE_GHOUL,\n    KILL_TYPE_BRAHMIN,\n    KILL_TYPE_RADSCORPION,\n    KILL_TYPE_RAT,\n    KILL_TYPE_FLOATER,\n    KILL_TYPE_CENTAUR,\n    KILL_TYPE_ROBOT,\n    KILL_TYPE_DOG,\n    KILL_TYPE_MANTIS,\n    KILL_TYPE_DEATH_CLAW,\n    KILL_TYPE_PLANT,\n    KILL_TYPE_GECKO,\n    KILL_TYPE_ALIEN,\n    KILL_TYPE_GIANT_ANT,\n    KILL_TYPE_BIG_BAD_BOSS,\n    KILL_TYPE_COUNT,\n};\n\nenum {\n    PROTO_ID_POWER_ARMOR = 3,\n    PROTO_ID_SMALL_ENERGY_CELL = 38,\n    PROTO_ID_MICRO_FUSION_CELL = 39,\n    PROTO_ID_STIMPACK = 40,\n    PROTO_ID_MONEY = 41,\n    PROTO_ID_FIRST_AID_KIT = 47,\n    PROTO_ID_RADAWAY = 48,\n    PROTO_ID_DYNAMITE_I = 51,\n    PROTO_ID_GEIGER_COUNTER_I = 52,\n    PROTO_ID_MENTATS = 53,\n    PROTO_ID_STEALTH_BOY_I = 54,\n    PROTO_ID_MOTION_SENSOR = 59,\n    PROTO_ID_BIG_BOOK_OF_SCIENCE = 73,\n    PROTO_ID_DEANS_ELECTRONICS = 76,\n    PROTO_ID_FLARE = 79,\n    PROTO_ID_FIRST_AID_BOOK = 80,\n    PROTO_ID_PLASTIC_EXPLOSIVES_I = 85,\n    PROTO_ID_SCOUT_HANDBOOK = 86,\n    PROTO_ID_BUFF_OUT = 87,\n    PROTO_ID_DOCTORS_BAG = 91,\n    PROTO_ID_GUNS_AND_BULLETS = 102,\n    PROTO_ID_NUKA_COLA = 106,\n    PROTO_ID_PSYCHO = 110,\n    PROTO_ID_BEER = 124,\n    PROTO_ID_BOOZE = 125,\n    PROTO_ID_SUPER_STIMPACK = 144,\n    PROTO_ID_MOLOTOV_COCKTAIL = 159,\n    PROTO_ID_LIT_FLARE = 205,\n    PROTO_ID_DYNAMITE_II = 206, // armed\n    PROTO_ID_GEIGER_COUNTER_II = 207,\n    PROTO_ID_PLASTIC_EXPLOSIVES_II = 209, // armed\n    PROTO_ID_STEALTH_BOY_II = 210,\n    PROTO_ID_HARDENED_POWER_ARMOR = 232,\n    PROTO_ID_JET = 259,\n    PROTO_ID_JET_ANTIDOTE = 260,\n    PROTO_ID_HEALING_POWDER = 273,\n    PROTO_ID_DECK_OF_TRAGIC_CARDS = 304,\n    PROTO_ID_CATS_PAW_ISSUE_5 = 331,\n    PROTO_ID_ADVANCED_POWER_ARMOR = 348,\n    PROTO_ID_ADVANCED_POWER_ARMOR_MK_II = 349,\n    PROTO_ID_SHIV = 383,\n    PROTO_ID_SOLAR_SCORCHER = 390,\n    PROTO_ID_SUPER_CATTLE_PROD = 399,\n    PROTO_ID_MEGA_POWER_FIST = 407,\n    PROTO_ID_FIELD_MEDIC_FIRST_AID_KIT = 408,\n    PROTO_ID_PARAMEDICS_BAG = 409,\n    PROTO_ID_RAMIREZ_BOX_CLOSED = 431,\n    PROTO_ID_MIRRORED_SHADES = 433,\n    PROTO_ID_RAIDERS_MAP = 444,\n    PROTO_ID_CAR_TRUNK = 455,\n    PROTO_ID_PIP_BOY_LINGUAL_ENHANCER = 499,\n    PROTO_ID_PIP_BOY_MEDICAL_ENHANCER = 516,\n    PROTO_ID_SURVEY_MAP = 523,\n};\n\n#define PROTO_ID_0x1000098 0x1000098\n#define PROTO_ID_0x10001E0 0x10001E0\n#define PROTO_ID_0x2000031 0x2000031\n#define PROTO_ID_0x2000158 0x2000158\n#define PROTO_ID_CAR 0x20003F1\n#define PROTO_ID_0x200050D 0x200050D\n#define PROTO_ID_0x2000099 0x2000099\n#define PROTO_ID_0x20001A5 0x20001A5\n#define PROTO_ID_0x20001D6 0x20001D6\n#define PROTO_ID_0x20001EB 0x20001EB\n#define FID_0x20001F5 0x20001F5\n// first exit grid\n#define PROTO_ID_0x5000010 0x5000010\n// last exit grid\n#define PROTO_ID_0x5000017 0x5000017\n\ntypedef enum ItemProtoFlags {\n    ItemProtoFlags_0x08 = 0x08,\n    ItemProtoFlags_0x10 = 0x10,\n    ItemProtoFlags_0x1000 = 0x1000,\n    ItemProtoFlags_0x8000 = 0x8000,\n    ItemProtoFlags_0x20000000 = 0x20000000,\n    ItemProtoFlags_0x80000000 = 0x80000000,\n} ItemProtoFlags;\n\ntypedef enum ItemProtoExtendedFlags {\n    ItemProtoExtendedFlags_BigGun = 0x0100,\n    ItemProtoExtendedFlags_IsTwoHanded = 0x0200,\n    ItemProtoExtendedFlags_0x0800 = 0x0800,\n    ItemProtoExtendedFlags_0x1000 = 0x1000,\n    ItemProtoExtendedFlags_0x2000 = 0x2000,\n    ItemProtoExtendedFlags_0x8000 = 0x8000,\n\n    // This flag is used on weapons to indicate that's an natural (integral)\n    // part of it's owner, for example Claw, or Robot's Rocket Launcher. Items\n    // with this flag on do count toward total weight and cannot be dropped.\n    ItemProtoExtendedFlags_NaturalWeapon = 0x08000000,\n} ItemProtoExtendedFlags;\n\ntypedef struct {\n    int armorClass; // d.ac\n    int damageResistance[7]; // d.dam_resist\n    int damageThreshold[7]; // d.dam_thresh\n    int perk; // d.perk\n    int maleFid; // d.male_fid\n    int femaleFid; // d.female_fid\n} ProtoItemArmorData;\n\ntypedef struct {\n    int maxSize; // d.max_size\n    int openFlags; // d.open_flags\n} ProtoItemContainerData;\n\ntypedef struct {\n    int stat[3]; // d.stat\n    int amount[3]; // d.amount\n    int duration1; // d.duration1\n    int amount1[3]; // d.amount1\n    int duration2; // d.duration2\n    int amount2[3]; // d.amount2\n    int addictionChance; // d.addiction_chance\n    int withdrawalEffect; // d.withdrawal_effect\n    int withdrawalOnset; // d.withdrawal_onset\n} ProtoItemDrugData;\n\ntypedef struct {\n    int animationCode; // d.animation_code\n    int minDamage; // d.min_damage\n    int maxDamage; // d.max_damage\n    int damageType; // d.dt\n    int maxRange1; // d.max_range1\n    int maxRange2; // d.max_range2\n    int projectilePid; // d.proj_pid\n    int minStrength; // d.min_st\n    int actionPointCost1; // d.mp_cost1\n    int actionPointCost2; // d.mp_cost2\n    int criticalFailureType; // d.crit_fail_table\n    int perk; // d.perk\n    int rounds; // d.rounds\n    int caliber; // d.caliber\n    int ammoTypePid; // d.ammo_type_pid\n    int ammoCapacity; // d.max_ammo\n    unsigned char soundCode; // d.sound_id\n} ProtoItemWeaponData;\n\ntypedef struct {\n    int caliber; // d.caliber\n    int quantity; // d.quantity\n    int armorClassModifier; // d.ac_adjust\n    int damageResistanceModifier; // d.dr_adjust\n    int damageMultiplier; // d.dam_mult\n    int damageDivisor; // d.dam_div\n} ProtoItemAmmoData;\n\ntypedef struct {\n    int powerTypePid; // d.power_type_pid\n    int powerType; // d.power_type\n    int charges; // d.charges\n} ProtoItemMiscData;\n\ntypedef struct {\n    int keyCode; // d.key_code\n} ProtoItemKeyData;\n\ntypedef struct ItemProtoData {\n    union {\n        struct {\n            int field_0;\n            int field_4;\n            int field_8; // max charges\n            int field_C;\n            int field_10;\n            int field_14;\n            int field_18;\n        } unknown;\n        ProtoItemArmorData armor;\n        ProtoItemContainerData container;\n        ProtoItemDrugData drug;\n        ProtoItemWeaponData weapon;\n        ProtoItemAmmoData ammo;\n        ProtoItemMiscData misc;\n        ProtoItemKeyData key;\n    };\n} ItemProtoData;\n\ntypedef struct ItemProto {\n    int pid; // pid\n    int messageId; // message_num\n    int fid; // fid\n    int lightDistance; // light_distance\n    int lightIntensity; // light_intensity\n    int flags; // flags\n    int extendedFlags; // flags_ext\n    int sid; // sid\n    int type; // type\n    ItemProtoData data; // d\n    int material; // material\n    int size; // size\n    int weight; // weight\n    int cost; // cost\n    int inventoryFid; // inv_fid\n    unsigned char field_80;\n} ItemProto;\n\nstatic_assert(sizeof(ItemProto) == 0x84, \"wrong size\");\n\ntypedef struct CritterProtoData {\n    int flags; // d.flags\n    int baseStats[35]; // d.stat_base\n    int bonusStats[35]; // d.stat_bonus\n    int skills[18]; // d.stat_points\n    int bodyType; // d.body\n    int experience;\n    int killType;\n    // Looks like this is the \"native\" damage type when critter is unarmed.\n    int damageType;\n} CritterProtoData;\n\ntypedef struct CritterProto {\n    int pid; // pid\n    int messageId; // message_num\n    int fid; // fid\n    int lightDistance; // light_distance\n    int lightIntensity; // light_intensity\n    int flags; // flags\n    int extendedFlags; // flags_ext\n    int sid; // sid\n    CritterProtoData data; // d\n    int headFid; // head_fid\n    int aiPacket; // ai_packet\n    int team; // team_num\n} CritterProto;\n\nstatic_assert(sizeof(CritterProto) == 0x1A0, \"wrong size\");\n\ntypedef struct {\n    int openFlags; // d.open_flags\n    int keyCode; // d.key_code\n} SceneryProtoDoorData;\n\ntypedef struct {\n    int field_0; // d.lower_tile\n    int field_4; // d.upper_tile\n} SceneryProtoStairsData;\n\ntypedef struct {\n    int type;\n    int level;\n} SceneryProtoElevatorData;\n\ntypedef struct {\n    int field_0;\n} SceneryProtoLadderData;\n\ntypedef struct {\n    int field_0;\n} SceneryProtoGenericData;\n\ntypedef struct SceneryProtoData {\n    union {\n        SceneryProtoDoorData door;\n        SceneryProtoStairsData stairs;\n        SceneryProtoElevatorData elevator;\n        SceneryProtoLadderData ladder;\n        SceneryProtoGenericData generic;\n    };\n} SceneryProtoData;\n\ntypedef struct SceneryProto {\n    int pid; // id\n    int messageId; // message_num\n    int fid; // fid\n    int lightDistance; // light_distance\n    int lightIntensity; // light_intensity\n    int flags; // flags\n    int extendedFlags; // flags_ext\n    int sid; // sid\n    int type; // type\n    SceneryProtoData data;\n    int field_2C; // material\n    int field_30; //\n    unsigned char field_34;\n} SceneryProto;\n\nstatic_assert(sizeof(SceneryProto) == 0x38, \"wrong size\");\n\ntypedef struct WallProto {\n    int pid; // id\n    int messageId; // message_num\n    int fid; // fid\n    int lightDistance; // light_distance\n    int lightIntensity; // light_intensity\n    int flags; // flags\n    int extendedFlags; // flags_ext\n    int sid; // sid\n    int material; // material\n} WallProto;\n\nstatic_assert(sizeof(WallProto) == 0x24, \"wrong size\");\n\ntypedef struct TileProto {\n    int pid; // id\n    int messageId; // message_num\n    int fid; // fid\n    int flags; // flags\n    int extendedFlags; // flags_ext\n    int sid; // sid\n    int material; // material\n} TileProto;\n\nstatic_assert(sizeof(TileProto) == 0x1C, \"wrong size\");\n\ntypedef struct MiscProto {\n    int pid; // id\n    int messageId; // message_num\n    int fid; // fid\n    int lightDistance; // light_distance\n    int lightIntensity; // light_intensity\n    int flags; // flags\n    int extendedFlags; // flags_ext\n} MiscProto;\n\nstatic_assert(sizeof(MiscProto) == 0x1C, \"wrong size\");\n\ntypedef union Proto {\n    struct {\n        int pid; // pid\n        int messageId; // message_num\n        int fid; // fid\n\n        // TODO: Move to NonTile props?\n        int lightDistance;\n        int lightIntensity;\n        int flags;\n        int extendedFlags;\n        int sid;\n    };\n    ItemProto item;\n    CritterProto critter;\n    SceneryProto scenery;\n    WallProto wall;\n    TileProto tile;\n    MiscProto misc;\n} Proto;\n\ntypedef struct ProtoListExtent {\n    Proto* proto[PROTO_LIST_EXTENT_SIZE];\n    // Number of protos in the extent\n    int length;\n    struct ProtoListExtent* next;\n} ProtoListExtent;\n\ntypedef struct ProtoList {\n    ProtoListExtent* head;\n    ProtoListExtent* tail;\n    // Number of extents in the list.\n    int length;\n    // Number of lines in proto/{type}/{type}.lst.\n    int max_entries_num;\n} ProtoList;\n\n#endif /* PROTO_TYPES_H */\n"
  },
  {
    "path": "src/game/queue.c",
    "content": "#include \"game/queue.h\"\n\n#include \"game/actions.h\"\n#include \"game/critter.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/gsound.h\"\n#include \"game/item.h\"\n#include \"game/map.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/scripts.h\"\n\ntypedef struct QueueListNode {\n    // TODO: Make unsigned.\n    int time;\n    int type;\n    Object* owner;\n    void* data;\n    struct QueueListNode* next;\n} QueueListNode;\n\nstatic int queue_destroy(Object* obj, void* data);\nstatic int queue_explode(Object* obj, void* data);\nstatic int queue_explode_exit(Object* obj, void* data);\nstatic int queue_do_explosion(Object* obj, bool a2);\nstatic int queue_premature(Object* obj, void* data);\n\n// Last queue list node found during [queue_find_first] and\n// [queue_find_next] calls.\n//\n// 0x51C690\nstatic QueueListNode* tmpQNode = NULL;\n\n// 0x6648C0\nstatic QueueListNode* queue;\n\n// 0x51C540\nEventTypeDescription q_func[EVENT_TYPE_COUNT] = {\n    { item_d_process, mem_free, item_d_load, item_d_save, true, item_d_clear },\n    { critter_wake_up, NULL, NULL, NULL, true, critter_wake_clear },\n    { item_wd_process, mem_free, item_wd_load, item_wd_save, true, item_wd_clear },\n    { script_q_process, mem_free, script_q_load, script_q_save, true, NULL },\n    { gtime_q_process, NULL, NULL, NULL, true, NULL },\n    { critter_check_poison, NULL, NULL, NULL, false, NULL },\n    { critter_process_rads, mem_free, critter_load_rads, critter_save_rads, false, NULL },\n    { queue_destroy, NULL, NULL, NULL, true, queue_destroy },\n    { queue_explode, NULL, NULL, NULL, true, queue_explode_exit },\n    { item_m_trickle, NULL, NULL, NULL, true, item_m_turn_off_from_queue },\n    { critter_sneak_check, NULL, NULL, NULL, true, critter_sneak_clear },\n    { queue_premature, NULL, NULL, NULL, true, queue_explode_exit },\n    { scr_map_q_process, NULL, NULL, NULL, true, NULL },\n    { gsound_sfx_q_process, mem_free, NULL, NULL, true, NULL },\n};\n\n// 0x4A2320\nvoid queue_init()\n{\n    queue = NULL;\n}\n\n// NOTE: Uncollapsed 0x4A2330.\n//\n// 0x4A2330\nint queue_reset()\n{\n    queue_clear();\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x4A2330.\n//\n// 0x4A2330\nint queue_exit()\n{\n    queue_clear();\n    return 0;\n}\n\n// 0x4A2338\nint queue_load(File* stream)\n{\n    int count;\n    if (db_freadInt(stream, &count) == -1) {\n        return -1;\n    }\n\n    QueueListNode* oldListHead = queue;\n    queue = NULL;\n\n    QueueListNode** nextPtr = &queue;\n\n    int rc = 0;\n    for (int index = 0; index < count; index += 1) {\n        QueueListNode* queueListNode = (QueueListNode*)mem_malloc(sizeof(*queueListNode));\n        if (queueListNode == NULL) {\n            rc = -1;\n            break;\n        }\n\n        if (db_freadInt(stream, &(queueListNode->time)) == -1) {\n            mem_free(queueListNode);\n            rc = -1;\n            break;\n        }\n\n        if (db_freadInt(stream, &(queueListNode->type)) == -1) {\n            mem_free(queueListNode);\n            rc = -1;\n            break;\n        }\n\n        int objectId;\n        if (db_freadInt(stream, &objectId) == -1) {\n            mem_free(queueListNode);\n            rc = -1;\n            break;\n        }\n\n        Object* obj;\n        if (objectId == -2) {\n            obj = NULL;\n        } else {\n            obj = obj_find_first();\n            while (obj != NULL) {\n                obj = inven_find_id(obj, objectId);\n                if (obj != NULL) {\n                    break;\n                }\n                obj = obj_find_next();\n            }\n        }\n\n        queueListNode->owner = obj;\n\n        EventTypeDescription* eventTypeDescription = &(q_func[queueListNode->type]);\n        if (eventTypeDescription->readProc != NULL) {\n            if (eventTypeDescription->readProc(stream, &(queueListNode->data)) == -1) {\n                mem_free(queueListNode);\n                rc = -1;\n                break;\n            }\n        } else {\n            queueListNode->data = NULL;\n        }\n\n        queueListNode->next = NULL;\n\n        *nextPtr = queueListNode;\n        nextPtr = &(queueListNode->next);\n    }\n\n    if (rc == -1) {\n        while (queue != NULL) {\n            QueueListNode* next = queue->next;\n\n            EventTypeDescription* eventTypeDescription = &(q_func[queue->type]);\n            if (eventTypeDescription->freeProc != NULL) {\n                eventTypeDescription->freeProc(queue->data);\n            }\n\n            mem_free(queue);\n\n            queue = next;\n        }\n    }\n\n    if (oldListHead != NULL) {\n        QueueListNode** v13 = &queue;\n        QueueListNode* v15;\n        do {\n            while (true) {\n                QueueListNode* v14 = *v13;\n                if (v14 == NULL) {\n                    break;\n                }\n\n                if (v14->time > oldListHead->time) {\n                    break;\n                }\n\n                v13 = &(v14->next);\n            }\n            v15 = oldListHead->next;\n            oldListHead->next = *v13;\n            *v13 = oldListHead;\n            oldListHead = v15;\n        } while (v15 != NULL);\n    }\n\n    return rc;\n}\n\n// 0x4A24E0\nint queue_save(File* stream)\n{\n    QueueListNode* queueListNode;\n\n    int count = 0;\n\n    queueListNode = queue;\n    while (queueListNode != NULL) {\n        count += 1;\n        queueListNode = queueListNode->next;\n    }\n\n    if (db_fwriteInt(stream, count) == -1) {\n        return -1;\n    }\n\n    queueListNode = queue;\n    while (queueListNode != NULL) {\n        Object* object = queueListNode->owner;\n        int objectId = object != NULL ? object->id : -2;\n\n        if (db_fwriteInt(stream, queueListNode->time) == -1) {\n            return -1;\n        }\n\n        if (db_fwriteInt(stream, queueListNode->type) == -1) {\n            return -1;\n        }\n\n        if (db_fwriteInt(stream, objectId) == -1) {\n            return -1;\n        }\n\n        EventTypeDescription* eventTypeDescription = &(q_func[queueListNode->type]);\n        if (eventTypeDescription->writeProc != NULL) {\n            if (eventTypeDescription->writeProc(stream, queueListNode->data) == -1) {\n                return -1;\n            }\n        }\n\n        queueListNode = queueListNode->next;\n    }\n\n    return 0;\n}\n\n// 0x4A258C\nint queue_add(int delay, Object* obj, void* data, int eventType)\n{\n    QueueListNode* newQueueListNode = (QueueListNode*)mem_malloc(sizeof(QueueListNode));\n    if (newQueueListNode == NULL) {\n        return -1;\n    }\n\n    int v1 = game_time();\n    int v2 = v1 + delay;\n    newQueueListNode->time = v2;\n    newQueueListNode->type = eventType;\n    newQueueListNode->owner = obj;\n    newQueueListNode->data = data;\n\n    if (obj != NULL) {\n        obj->flags |= OBJECT_USED;\n    }\n\n    QueueListNode** v3 = &queue;\n\n    if (queue != NULL) {\n        QueueListNode* v4;\n\n        do {\n            v4 = *v3;\n            if (v2 < v4->time) {\n                break;\n            }\n            v3 = &(v4->next);\n        } while (v4->next != NULL);\n    }\n\n    newQueueListNode->next = *v3;\n    *v3 = newQueueListNode;\n\n    return 0;\n}\n\n// 0x4A25F4\nint queue_remove(Object* owner)\n{\n    QueueListNode* queueListNode = queue;\n    QueueListNode** queueListNodePtr = &queue;\n\n    while (queueListNode) {\n        if (queueListNode->owner == owner) {\n            QueueListNode* temp = queueListNode;\n\n            queueListNode = queueListNode->next;\n            *queueListNodePtr = queueListNode;\n\n            EventTypeDescription* eventTypeDescription = &(q_func[temp->type]);\n            if (eventTypeDescription->freeProc != NULL) {\n                eventTypeDescription->freeProc(temp->data);\n            }\n\n            mem_free(temp);\n        } else {\n            queueListNodePtr = &(queueListNode->next);\n            queueListNode = queueListNode->next;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4A264C\nint queue_remove_this(Object* owner, int eventType)\n{\n    QueueListNode* queueListNode = queue;\n    QueueListNode** queueListNodePtr = &queue;\n\n    while (queueListNode) {\n        if (queueListNode->owner == owner && queueListNode->type == eventType) {\n            QueueListNode* temp = queueListNode;\n\n            queueListNode = queueListNode->next;\n            *queueListNodePtr = queueListNode;\n\n            EventTypeDescription* eventTypeDescription = &(q_func[temp->type]);\n            if (eventTypeDescription->freeProc != NULL) {\n                eventTypeDescription->freeProc(temp->data);\n            }\n\n            mem_free(temp);\n        } else {\n            queueListNodePtr = &(queueListNode->next);\n            queueListNode = queueListNode->next;\n        }\n    }\n\n    return 0;\n}\n\n// Returns true if there is at least one event of given type scheduled.\n//\n// 0x4A26A8\nbool queue_find(Object* owner, int eventType)\n{\n    QueueListNode* queueListEvent = queue;\n    while (queueListEvent != NULL) {\n        if (owner == queueListEvent->owner && eventType == queueListEvent->type) {\n            return true;\n        }\n\n        queueListEvent = queueListEvent->next;\n    }\n\n    return false;\n}\n\n// 0x4A26D0\nint queue_process()\n{\n    int time = game_time();\n    int v1 = 0;\n\n    while (queue != NULL) {\n        QueueListNode* queueListNode = queue;\n        if (time < queueListNode->time || v1 != 0) {\n            break;\n        }\n\n        queue = queueListNode->next;\n\n        EventTypeDescription* eventTypeDescription = &(q_func[queueListNode->type]);\n        v1 = eventTypeDescription->handlerProc(queueListNode->owner, queueListNode->data);\n\n        if (eventTypeDescription->freeProc != NULL) {\n            eventTypeDescription->freeProc(queueListNode->data);\n        }\n\n        mem_free(queueListNode);\n    }\n\n    return v1;\n}\n\n// 0x4A2748\nvoid queue_clear()\n{\n    QueueListNode* queueListNode = queue;\n    while (queueListNode != NULL) {\n        QueueListNode* next = queueListNode->next;\n\n        EventTypeDescription* eventTypeDescription = &(q_func[queueListNode->type]);\n        if (eventTypeDescription->freeProc != NULL) {\n            eventTypeDescription->freeProc(queueListNode->data);\n        }\n\n        mem_free(queueListNode);\n\n        queueListNode = next;\n    }\n\n    queue = NULL;\n}\n\n// 0x4A2790\nvoid queue_clear_type(int eventType, QueueEventHandler* fn)\n{\n    QueueListNode** ptr = &queue;\n    QueueListNode* curr = *ptr;\n\n    while (curr != NULL) {\n        if (eventType == curr->type) {\n            QueueListNode* tmp = curr;\n\n            *ptr = curr->next;\n            curr = *ptr;\n\n            if (fn != NULL && fn(tmp->owner, tmp->data) != 1) {\n                *ptr = tmp;\n                ptr = &(tmp->next);\n            } else {\n                EventTypeDescription* eventTypeDescription = &(q_func[tmp->type]);\n                if (eventTypeDescription->freeProc != NULL) {\n                    eventTypeDescription->freeProc(tmp->data);\n                }\n\n                mem_free(tmp);\n            }\n        } else {\n            ptr = &(curr->next);\n            curr = *ptr;\n        }\n    }\n}\n\n// TODO: Make unsigned.\n//\n// 0x4A2808\nint queue_next_time()\n{\n    if (queue == NULL) {\n        return 0;\n    }\n\n    return queue->time;\n}\n\n// 0x4A281C\nstatic int queue_destroy(Object* obj, void* data)\n{\n    obj_destroy(obj);\n    return 1;\n}\n\n// 0x4A2828\nstatic int queue_explode(Object* obj, void* data)\n{\n    return queue_do_explosion(obj, true);\n}\n\n// 0x4A2830\nstatic int queue_explode_exit(Object* obj, void* data)\n{\n    return queue_do_explosion(obj, false);\n}\n\n// 0x4A2834\nstatic int queue_do_explosion(Object* explosive, bool a2)\n{\n    int tile;\n    int elevation;\n\n    Object* owner = obj_top_environment(explosive);\n    if (owner) {\n        tile = owner->tile;\n        elevation = owner->elevation;\n    } else {\n        tile = explosive->tile;\n        elevation = explosive->elevation;\n    }\n\n    int maxDamage;\n    int minDamage;\n    if (explosive->pid == PROTO_ID_DYNAMITE_I || explosive->pid == PROTO_ID_DYNAMITE_II) {\n        // Dynamite\n        minDamage = 30;\n        maxDamage = 50;\n    } else {\n        // Plastic explosive\n        minDamage = 40;\n        maxDamage = 80;\n    }\n\n    // FIXME: I guess this is a little bit wrong, dude can never be null, I\n    // guess it needs to check if owner is dude.\n    if (obj_dude != NULL) {\n        if (perkHasRank(obj_dude, PERK_DEMOLITION_EXPERT)) {\n            maxDamage += 10;\n            minDamage += 10;\n        }\n    }\n\n    if (action_explode(tile, elevation, minDamage, maxDamage, obj_dude, a2) == -2) {\n        queue_add(50, explosive, NULL, EVENT_TYPE_EXPLOSION);\n    } else {\n        obj_destroy(explosive);\n    }\n\n    return 1;\n}\n\n// 0x4A28E4\nstatic int queue_premature(Object* obj, void* data)\n{\n    MessageListItem msg;\n\n    // Due to your inept handling, the explosive detonates prematurely.\n    msg.num = 4000;\n    if (message_search(&misc_message_file, &msg)) {\n        display_print(msg.text);\n    }\n\n    return queue_do_explosion(obj, true);\n}\n\n// 0x4A2920\nvoid queue_leaving_map()\n{\n    for (int eventType = 0; eventType < EVENT_TYPE_COUNT; eventType++) {\n        EventTypeDescription* eventTypeDescription = &(q_func[eventType]);\n        if (eventTypeDescription->field_10) {\n            queue_clear_type(eventType, eventTypeDescription->field_14);\n        }\n    }\n}\n\n// 0x4A294C\nbool queue_is_empty()\n{\n    return queue == NULL;\n}\n\n// 0x4A295C\nvoid* queue_find_first(Object* owner, int eventType)\n{\n    QueueListNode* queueListNode = queue;\n    while (queueListNode != NULL) {\n        if (owner == queueListNode->owner && eventType == queueListNode->type) {\n            tmpQNode = queueListNode;\n            return queueListNode->data;\n        }\n        queueListNode = queueListNode->next;\n    }\n\n    tmpQNode = NULL;\n    return NULL;\n}\n\n// 0x4A2994\nvoid* queue_find_next(Object* owner, int eventType)\n{\n    if (tmpQNode != NULL) {\n        QueueListNode* queueListNode = tmpQNode->next;\n        while (queueListNode != NULL) {\n            if (owner == queueListNode->owner && eventType == queueListNode->type) {\n                tmpQNode = queueListNode;\n                return queueListNode->data;\n            }\n            queueListNode = queueListNode->next;\n        }\n    }\n\n    tmpQNode = NULL;\n\n    return NULL;\n}\n"
  },
  {
    "path": "src/game/queue.h",
    "content": "#ifndef FALLOUT_GAME_QUEUE_H_\n#define FALLOUT_GAME_QUEUE_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/object_types.h\"\n\ntypedef enum EventType {\n    EVENT_TYPE_DRUG = 0,\n    EVENT_TYPE_KNOCKOUT = 1,\n    EVENT_TYPE_WITHDRAWAL = 2,\n    EVENT_TYPE_SCRIPT = 3,\n    EVENT_TYPE_GAME_TIME = 4,\n    EVENT_TYPE_POISON = 5,\n    EVENT_TYPE_RADIATION = 6,\n    EVENT_TYPE_FLARE = 7,\n    EVENT_TYPE_EXPLOSION = 8,\n    EVENT_TYPE_ITEM_TRICKLE = 9,\n    EVENT_TYPE_SNEAK = 10,\n    EVENT_TYPE_EXPLOSION_FAILURE = 11,\n    EVENT_TYPE_MAP_UPDATE_EVENT = 12,\n    EVENT_TYPE_GSOUND_SFX_EVENT = 13,\n    EVENT_TYPE_COUNT,\n} EventType;\n\ntypedef struct DrugEffectEvent {\n    int drugPid;\n    int stats[3];\n    int modifiers[3];\n} DrugEffectEvent;\n\ntypedef struct WithdrawalEvent {\n    int field_0;\n    int pid;\n    int perk;\n} WithdrawalEvent;\n\ntypedef struct ScriptEvent {\n    int sid;\n    int fixedParam;\n} ScriptEvent;\n\ntypedef struct RadiationEvent {\n    int radiationLevel;\n    int isHealing;\n} RadiationEvent;\n\ntypedef struct AmbientSoundEffectEvent {\n    int ambientSoundEffectIndex;\n} AmbientSoundEffectEvent;\n\ntypedef int QueueEventHandler(Object* owner, void* data);\ntypedef void QueueEventDataFreeProc(void* data);\ntypedef int QueueEventDataReadProc(File* stream, void** dataPtr);\ntypedef int QueueEventDataWriteProc(File* stream, void* data);\n\ntypedef struct EventTypeDescription {\n    QueueEventHandler* handlerProc;\n    QueueEventDataFreeProc* freeProc;\n    QueueEventDataReadProc* readProc;\n    QueueEventDataWriteProc* writeProc;\n    bool field_10;\n    QueueEventHandler* field_14;\n} EventTypeDescription;\n\nextern EventTypeDescription q_func[EVENT_TYPE_COUNT];\n\nvoid queue_init();\nint queue_reset();\nint queue_exit();\nint queue_load(File* stream);\nint queue_save(File* stream);\nint queue_add(int delay, Object* owner, void* data, int eventType);\nint queue_remove(Object* owner);\nint queue_remove_this(Object* owner, int eventType);\nbool queue_find(Object* owner, int eventType);\nint queue_process();\nvoid queue_clear();\nvoid queue_clear_type(int eventType, QueueEventHandler* fn);\nint queue_next_time();\nvoid queue_leaving_map();\nbool queue_is_empty();\nvoid* queue_find_first(Object* owner, int eventType);\nvoid* queue_find_next(Object* owner, int eventType);\n\n#endif /* FALLOUT_GAME_QUEUE_H_ */\n"
  },
  {
    "path": "src/game/reaction.c",
    "content": "#include \"game/reaction.h\"\n\n#include \"game/scripts.h\"\n\n// 0x4A29D0\nint reaction_set(Object* critter, int value)\n{\n    scr_set_local_var(critter->sid, 0, value);\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4A29E4\nint level_to_reaction()\n{\n    return 0;\n}\n\n// 0x4A29E8\nint reaction_lookup_internal(int a1)\n{\n    if (a1 > 10) {\n        return NPC_REACTION_GOOD;\n    } else if (a1 > -10) {\n        return NPC_REACTION_NEUTRAL;\n    } else if (a1 > -25) {\n        return NPC_REACTION_BAD;\n    } else if (a1 > -50) {\n        return NPC_REACTION_BAD;\n    } else if (a1 > -75) {\n        return NPC_REACTION_BAD;\n    } else {\n        return NPC_REACTION_BAD;\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4A2A2C\nint reaction_to_level_internal(int sid, int reaction)\n{\n    int level;\n\n    if (reaction <= -75) {\n        level = -4;\n    } else if (reaction <= -50) {\n        level = -3;\n    } else if (reaction <= -25) {\n        level = -2;\n    } else if (reaction <= -10) {\n        level = -1;\n    } else if (reaction <= 10) {\n        level = 0;\n    } else if (reaction <= 25) {\n        level = 1;\n    } else if (reaction <= 50) {\n        level = 2;\n    } else if (reaction <= 75) {\n        level = 3;\n    } else {\n        level = 4;\n    }\n\n    scr_set_local_var(sid, 1, level);\n\n    return reaction_lookup_internal(reaction);\n}\n\n// NOTE: From OS X binary.\nint reaction_to_level(int a1)\n{\n    return reaction_lookup_internal(a1);\n}\n\n// 0x4A2B28\nint reaction_get(Object* critter)\n{\n    int reactionValue;\n\n    if (scr_get_local_var(critter->sid, 0, &reactionValue) == -1) {\n        return -1;\n    }\n\n    return reactionValue;\n}\n\n// NOTE: From OS X binary.\nint reaction_roll()\n{\n    return 0;\n}\n\n// TODO: Accepts 3 parameters, check `op_reaction_influence`.\n//\n// 0x4A29F0\nint reaction_influence()\n{\n    return 0;\n}\n"
  },
  {
    "path": "src/game/reaction.h",
    "content": "#ifndef FALLOUT_GAME_REACTION_H_\n#define FALLOUT_GAME_REACTION_H_\n\n#include \"game/object_types.h\"\n\ntypedef enum NpcReaction {\n    NPC_REACTION_BAD,\n    NPC_REACTION_NEUTRAL,\n    NPC_REACTION_GOOD,\n} NpcReaction;\n\nint reaction_set(Object* critter, int value);\nint level_to_reaction();\nint reaction_lookup_internal(int a1);\nint reaction_to_level_internal(int sid, int reaction);\nint reaction_to_level(int a1);\nint reaction_get(Object* critter);\nint reaction_roll();\nint reaction_influence();\n\n#endif /* FALLOUT_GAME_REACTION_H_ */\n"
  },
  {
    "path": "src/game/roll.c",
    "content": "#include \"game/roll.h\"\n\n#include <limits.h>\n#include <stdlib.h>\n\n// clang-format off\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n#include <timeapi.h>\n// clang-format on\n\n#include \"plib/gnw/debug.h\"\n#include \"game/scripts.h\"\n\nstatic int ran1(int max);\nstatic void init_random();\nstatic int random_seed();\nstatic void seed_generator(int seed);\nstatic unsigned int timer_read();\nstatic void check_chi_squared();\n\n// 0x51C694\nstatic int iy = 0;\n\n// 0x6648D0\nstatic int iv[32];\n\n// 0x664950\nstatic int idum;\n\n// 0x4A2FE0\nvoid roll_init()\n{\n    // NOTE: Uninline.\n    init_random();\n\n    check_chi_squared();\n}\n\n// NOTE: Uncollapsed 0x4A2FFC.\nint roll_reset()\n{\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x4A2FFC.\nint roll_exit()\n{\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x4A2FFC.\nint roll_save(File* stream)\n{\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x4A2FFC.\nint roll_load(File* stream)\n{\n    return 0;\n}\n\n// Rolls d% against [difficulty].\n//\n// 0x4A3000\nint roll_check(int difficulty, int criticalSuccessModifier, int* howMuchPtr)\n{\n    int delta = difficulty - roll_random(1, 100);\n    int result = roll_check_critical(delta, criticalSuccessModifier);\n\n    if (howMuchPtr != NULL) {\n        *howMuchPtr = delta;\n    }\n\n    return result;\n}\n\n// Translates raw d% result into [Roll] constants, possibly upgrading to\n// criticals (starting from day 2).\n//\n// 0x4A3030\nint roll_check_critical(int delta, int criticalSuccessModifier)\n{\n    int gameTime = game_time();\n\n    int roll;\n    if (delta < 0) {\n        roll = ROLL_FAILURE;\n\n        if ((gameTime / GAME_TIME_TICKS_PER_DAY) >= 1) {\n            // 10% to become critical failure.\n            if (roll_random(1, 100) <= -delta / 10) {\n                roll = ROLL_CRITICAL_FAILURE;\n            }\n        }\n    } else {\n        roll = ROLL_SUCCESS;\n\n        if ((gameTime / GAME_TIME_TICKS_PER_DAY) >= 1) {\n            // 10% + modifier to become critical success.\n            if (roll_random(1, 100) <= delta / 10 + criticalSuccessModifier) {\n                roll = ROLL_CRITICAL_SUCCESS;\n            }\n        }\n    }\n\n    return roll;\n}\n\n// 0x4A30C0\nint roll_random(int min, int max)\n{\n    int result;\n\n    if (min <= max) {\n        result = min + ran1(max - min + 1);\n    } else {\n        result = max + ran1(min - max + 1);\n    }\n\n    if (result < min || result > max) {\n        debug_printf(\"Random number %d is not in range %d to %d\", result, min, max);\n        result = min;\n    }\n\n    return result;\n}\n\n// 0x4A30FC\nstatic int ran1(int max)\n{\n    int v1 = 16807 * (idum % 127773) - 2836 * (idum / 127773);\n\n    if (v1 < 0) {\n        v1 += 0x7FFFFFFF;\n    }\n\n    if (v1 < 0) {\n        v1 += 0x7FFFFFFF;\n    }\n\n    int v2 = iy & 0x1F;\n    int v3 = iv[v2];\n    iv[v2] = v1;\n    iy = v3;\n    idum = v1;\n\n    return v3 % max;\n}\n\n// NOTE: Inlined.\n//\n// 0x4A3174\nstatic void init_random()\n{\n    srand(timer_read());\n    seed_generator(random_seed());\n}\n\n// 0x4A31A0\nvoid roll_set_seed(int seed)\n{\n    if (seed == -1) {\n        // NOTE: Uninline.\n        seed = random_seed();\n    }\n\n    seed_generator(seed);\n}\n\n// 0x4A31C4\nstatic int random_seed()\n{\n    int high = rand() << 16;\n    int low = rand();\n\n    return (high + low) & INT_MAX;\n}\n\n// 0x4A31E0\nstatic void seed_generator(int seed)\n{\n    int num = seed;\n    if (num < 1) {\n        num = 1;\n    }\n\n    for (int index = 40; index > 0; index--) {\n        num = 16807 * (num % 127773) - 2836 * (num / 127773);\n\n        if (num < 0) {\n            num &= INT_MAX;\n        }\n\n        if (index < 32) {\n            iv[index] = num;\n        }\n    }\n\n    iy = iv[0];\n    idum = num;\n}\n\n// Provides seed for random number generator.\n//\n// 0x4A3258\nstatic unsigned int timer_read()\n{\n    return timeGetTime();\n}\n\n// 0x4A3264\nstatic void check_chi_squared()\n{\n    int results[25];\n\n    for (int index = 0; index < 25; index++) {\n        results[index] = 0;\n    }\n\n    for (int attempt = 0; attempt < 100000; attempt++) {\n        int value = roll_random(1, 25);\n        if (value - 1 < 0) {\n            debug_printf(\"I made a negative number %d\\n\", value - 1);\n        }\n\n        results[value - 1]++;\n    }\n\n    double v1 = 0.0;\n\n    for (int index = 0; index < 25; index++) {\n        double v2 = ((double)results[index] - 4000.0) * ((double)results[index] - 4000.0) / 4000.0;\n        v1 += v2;\n    }\n\n    debug_printf(\"Chi squared is %f, P = %f at 0.05\\n\", v1, 4000.0);\n\n    if (v1 < 36.42) {\n        debug_printf(\"Sequence is random, 95%% confidence.\\n\");\n    } else {\n        debug_printf(\"Warning! Sequence is not random, 95%% confidence.\\n\");\n    }\n}\n"
  },
  {
    "path": "src/game/roll.h",
    "content": "#ifndef FALLOUT_GAME_ROLL_H_\n#define FALLOUT_GAME_ROLL_H_\n\n#include \"plib/db/db.h\"\n\ntypedef enum Roll {\n    ROLL_CRITICAL_FAILURE,\n    ROLL_FAILURE,\n    ROLL_SUCCESS,\n    ROLL_CRITICAL_SUCCESS,\n} Roll;\n\nvoid roll_init();\nint roll_reset();\nint roll_exit();\nint roll_save(File* stream);\nint roll_load(File* stream);\nint roll_check(int difficulty, int criticalSuccessModifier, int* howMuchPtr);\nint roll_check_critical(int delta, int criticalSuccessModifier);\nint roll_random(int min, int max);\nvoid roll_set_seed(int seed);\n\n#endif /* FALLOUT_GAME_ROLL_H_ */\n"
  },
  {
    "path": "src/game/scripts.c",
    "content": "#include \"game/scripts.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n\n#include \"int/window.h\"\n#include \"game/actions.h\"\n#include \"game/automap.h\"\n#include \"game/combat.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/dialog.h\"\n#include \"game/elevator.h\"\n#include \"game/endgame.h\"\n#include \"int/export.h\"\n#include \"game/game.h\"\n#include \"game/gdialog.h\"\n#include \"game/gmouse.h\"\n#include \"game/gmovie.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/queue.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/intrface.h\"\n#include \"game/worldmap.h\"\n\n#define SCRIPT_LIST_EXTENT_SIZE 16\n\ntypedef struct ScriptListExtent {\n    Script scripts[SCRIPT_LIST_EXTENT_SIZE];\n    // Number of scripts in the extent\n    int length;\n    struct ScriptListExtent* next;\n} ScriptListExtent;\n\nstatic_assert(sizeof(ScriptListExtent) == 0xE08, \"wrong size\");\n\ntypedef struct ScriptList {\n    ScriptListExtent* head;\n    ScriptListExtent* tail;\n    // Number of extents in the script list.\n    int length;\n    int nextScriptId;\n} ScriptList;\n\nstatic_assert(sizeof(ScriptList) == 0x10, \"wrong size\");\n\ntypedef struct ScriptState {\n    unsigned int requests;\n    STRUCT_664980 combatState1;\n    STRUCT_664980 combatState2;\n    int elevatorType;\n    int elevatorLevel;\n    int explosionTile;\n    int explosionElevation;\n    int explosionMinDamage;\n    int explosionMaxDamage;\n    Object* dialogTarget;\n    Object* lootingBy;\n    Object* lootingFrom;\n    Object* stealingBy;\n    Object* stealingFrom;\n} ScriptState;\n\nstatic void doBkProcesses();\nstatic void script_chk_critters();\nstatic void script_chk_timed_events();\nstatic int scr_build_lookup_table(Script* scr);\nstatic int scrInitListInfo();\nstatic int scrExitListInfo();\nstatic int scr_index_to_name(int scriptIndex, char* name);\nstatic int scr_header_load();\nstatic int scr_write_ScriptSubNode(Script* scr, File* stream);\nstatic int scr_write_ScriptNode(ScriptListExtent* a1, File* stream);\nstatic int scr_read_ScriptSubNode(Script* scr, File* stream);\nstatic int scr_read_ScriptNode(ScriptListExtent* a1, File* stream);\nstatic int scr_new_id(int scriptType);\nstatic void scrExecMapProcScripts(int a1);\n\n// Number of lines in scripts.lst\n//\n// 0x51C6AC\nint num_script_indexes = 0;\n\n// 0x51C6B0\nstatic int scr_find_first_idx = 0;\n\n// 0x51C6B4\nstatic ScriptListExtent* scr_find_first_ptr = NULL;\n\n// 0x51C6B8\nstatic int scr_find_first_elev = 0;\n\n// 0x51C6BC\nstatic bool scrSpatialsEnabled = true;\n\n// 0x51C6C0\nstatic ScriptList scriptlists[SCRIPT_TYPE_COUNT];\n\n// 0x51C710\nstatic char script_path_base[] = \"scripts\\\\\";\n\n// 0x51C714\nstatic bool script_engine_running = false;\n\n// 0x51C718\nstatic int script_engine_run_critters = 0;\n\n// 0x51C71C\nstatic int script_engine_game_mode = 0;\n\n// Game time in ticks (1/10 second).\n//\n// 0x51C720\nstatic int fallout_game_time = 302400;\n\n// 0x51C724\nstatic int days_in_month[12] = {\n    31, // Jan\n    28, // Feb\n    31, // Mar\n    30, // Apr\n    31, // May\n    30, // Jun\n    31, // Jul\n    31, // Aug\n    30, // Sep\n    31, // Oct\n    30, // Nov\n    31, // Dec\n};\n\n// 0x51C758\nstatic const char* procTableStrs[SCRIPT_PROC_COUNT] = {\n    \"no_p_proc\",\n    \"start\",\n    \"spatial_p_proc\",\n    \"description_p_proc\",\n    \"pickup_p_proc\",\n    \"drop_p_proc\",\n    \"use_p_proc\",\n    \"use_obj_on_p_proc\",\n    \"use_skill_on_p_proc\",\n    \"none_x_bad\",\n    \"none_x_bad\",\n    \"talk_p_proc\",\n    \"critter_p_proc\",\n    \"combat_p_proc\",\n    \"damage_p_proc\",\n    \"map_enter_p_proc\",\n    \"map_exit_p_proc\",\n    \"create_p_proc\",\n    \"destroy_p_proc\",\n    \"none_x_bad\",\n    \"none_x_bad\",\n    \"look_at_p_proc\",\n    \"timed_event_p_proc\",\n    \"map_update_p_proc\",\n    \"push_p_proc\",\n    \"is_dropping_p_proc\",\n    \"combat_is_starting_p_proc\",\n    \"combat_is_over_p_proc\",\n};\n\n// scripts.lst\n//\n// 0x51C7C8\nstatic ScriptsListEntry* scriptListInfo = NULL;\n\n// 0x51C7CC\nstatic int maxScriptNum = 0;\n\n// 0x51C7E8\nObject* scrQueueTestObj = NULL;\n\n// 0x51C7EC\nint scrQueueTestValue = 0;\n\n// 0x664954\nstatic ScriptState scriptState;\n\n// 0x6649D4\nMessageList script_dialog_msgs[1450];\n\n// scr.msg\n//\n// 0x667724\nMessageList script_message_file;\n\n// TODO: Make unsigned.\n//\n// Returns game time in ticks (1/10 second).\n//\n// 0x4A3330\nint game_time()\n{\n    return fallout_game_time;\n}\n\n// 0x4A3338\nvoid game_time_date(int* monthPtr, int* dayPtr, int* yearPtr)\n{\n    int year = (fallout_game_time / GAME_TIME_TICKS_PER_DAY + 24) / 365 + 2241;\n    int month = 6;\n    int day = (fallout_game_time / GAME_TIME_TICKS_PER_DAY + 24) % 365;\n\n    while (1) {\n        int daysInMonth = days_in_month[month];\n        if (day < daysInMonth) {\n            break;\n        }\n\n        month++;\n        day -= daysInMonth;\n\n        if (month == 12) {\n            year++;\n            month = 0;\n        }\n    }\n\n    if (dayPtr != NULL) {\n        *dayPtr = day + 1;\n    }\n\n    if (monthPtr != NULL) {\n        *monthPtr = month + 1;\n    }\n\n    if (yearPtr != NULL) {\n        *yearPtr = year;\n    }\n}\n\n// Returns game hour/minute in military format (hhmm).\n//\n// Examples:\n// - 8:00 A.M. -> 800\n// - 3:00 P.M. -> 1500\n// - 11:59 P.M. -> 2359\n//\n// game_time_hour\n// 0x4A33C8\nint game_time_hour()\n{\n    return 100 * ((fallout_game_time / 600) / 60 % 24) + (fallout_game_time / 600) % 60;\n}\n\n// Returns time string (h:mm)\n//\n// 0x4A3420\nchar* game_time_hour_str()\n{\n    // 0x66772C\n    static char hour_str[7];\n\n    sprintf(hour_str, \"%d:%02d\", (fallout_game_time / 600) / 60 % 24, (fallout_game_time / 600) % 60);\n    return hour_str;\n}\n\n// TODO: Make unsigned.\n//\n// 0x4A347C\nvoid gameTimeSetTime(int time)\n{\n    if (time == 0) {\n        time = 1;\n    }\n\n    fallout_game_time = time;\n}\n\n// 0x4A34CC\nvoid inc_game_time(int ticks)\n{\n    fallout_game_time += ticks;\n\n    int v1 = 0;\n\n    unsigned int year = fallout_game_time / GAME_TIME_TICKS_PER_YEAR;\n    if (year >= 13) {\n        endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_TIMEOUT);\n        game_user_wants_to_quit = 2;\n    }\n\n    // FIXME: This condition will never be true.\n    if (v1) {\n        gtime_q_process(NULL, NULL);\n    }\n}\n\n// 0x4A3518\nvoid inc_game_time_in_seconds(int seconds)\n{\n    // NOTE: Uninline.\n    inc_game_time(seconds * 10);\n}\n\n// 0x4A3570\nint gtime_q_add()\n{\n    int v1 = 10 * (60 * (60 - (fallout_game_time / 600) % 60 - 1) + 3600 * (24 - (fallout_game_time / 600) / 60 % 24 - 1) + 60);\n    if (queue_add(v1, NULL, NULL, EVENT_TYPE_GAME_TIME) == -1) {\n        return -1;\n    }\n\n    if (map_data.name[0] != '\\0') {\n        if (queue_add(600, NULL, NULL, EVENT_TYPE_MAP_UPDATE_EVENT) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4A3620\nint gtime_q_process(Object* obj, void* data)\n{\n    int movie_index;\n    int v4;\n\n    movie_index = -1;\n\n    debug_printf(\"\\nQUEUE PROCESS: Midnight!\");\n\n    if (gmovieIsPlaying()) {\n        return 0;\n    }\n\n    obj_unjam_all_locks();\n\n    if (!gdialogActive()) {\n        scriptsCheckGameEvents(&movie_index, -1);\n    }\n\n    v4 = critter_check_rads(obj_dude);\n\n    queue_clear_type(4, 0);\n\n    gtime_q_add();\n\n    if (movie_index != -1) {\n        v4 = 1;\n    }\n\n    return v4;\n}\n\n// 0x4A3690\nint scriptsCheckGameEvents(int* moviePtr, int window)\n{\n    int movie = -1;\n    int movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC;\n    bool endgame = false;\n    bool adjustRep = false;\n\n    int day = fallout_game_time / GAME_TIME_TICKS_PER_DAY;\n\n    if (game_get_global_var(GVAR_ENEMY_ARROYO)) {\n        movie = MOVIE_AFAILED;\n        movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_STOP_MUSIC;\n        endgame = true;\n    } else {\n        if (day >= 360 || game_get_global_var(GVAR_FALLOUT_2) >= 3) {\n            movie = MOVIE_ARTIMER4;\n            if (!gmovie_has_been_played(MOVIE_ARTIMER4)) {\n                adjustRep = true;\n                wmAreaSetVisibleState(CITY_ARROYO, 0, 1);\n                wmAreaSetVisibleState(CITY_DESTROYED_ARROYO, 1, 1);\n                wmAreaMarkVisitedState(CITY_DESTROYED_ARROYO, 2);\n            }\n        } else if (day >= 270 && game_get_global_var(GVAR_FALLOUT_2) != 3) {\n            adjustRep = true;\n            movie = MOVIE_ARTIMER3;\n        } else if (day >= 180 && game_get_global_var(GVAR_FALLOUT_2) != 3) {\n            adjustRep = true;\n            movie = MOVIE_ARTIMER2;\n        } else if (day >= 90 && game_get_global_var(GVAR_FALLOUT_2) != 3) {\n            adjustRep = true;\n            movie = MOVIE_ARTIMER1;\n        }\n    }\n\n    if (movie != -1) {\n        if (gmovie_has_been_played(movie)) {\n            movie = -1;\n        } else {\n            if (window != -1) {\n                win_hide(window);\n            }\n\n            gmovie_play(movie, movieFlags);\n\n            if (window != -1) {\n                win_show(window);\n            }\n\n            if (adjustRep) {\n                int rep = game_get_global_var(GVAR_TOWN_REP_ARROYO);\n                game_set_global_var(GVAR_TOWN_REP_ARROYO, rep - 15);\n            }\n        }\n    }\n\n    if (endgame) {\n        game_user_wants_to_quit = 2;\n    } else {\n        tile_refresh_display();\n    }\n\n    if (moviePtr != NULL) {\n        *moviePtr = movie;\n    }\n\n    return 0;\n}\n\n// 0x4A382C\nint scr_map_q_process(Object* obj, void* data)\n{\n    scrExecMapProcScripts(SCRIPT_PROC_MAP_UPDATE);\n\n    queue_clear_type(EVENT_TYPE_MAP_UPDATE_EVENT, NULL);\n\n    if (map_data.name[0] == '\\0') {\n        return 0;\n    }\n\n    if (queue_add(600, NULL, NULL, EVENT_TYPE_MAP_UPDATE_EVENT) != -1) {\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x4A386C\nint new_obj_id()\n{\n    // 0x51C7D4\n    static int cur_id = 4;\n\n    Object* ptr;\n\n    do {\n        cur_id++;\n        ptr = obj_find_first();\n\n        while (ptr) {\n            if (cur_id == ptr->id) {\n                break;\n            }\n\n            ptr = obj_find_next();\n        }\n    } while (ptr);\n\n    if (cur_id >= 18000) {\n        debug_printf(\"\\n    ERROR: new_obj_id() !!!! Picked PLAYER ID!!!!\");\n    }\n\n    cur_id++;\n\n    return cur_id;\n}\n\n// 0x4A390C\nint scr_find_sid_from_program(Program* program)\n{\n    for (int type = 0; type < SCRIPT_TYPE_COUNT; type++) {\n        ScriptListExtent* extent = scriptlists[type].head;\n        while (extent != NULL) {\n            for (int index = 0; index < extent->length; index++) {\n                Script* script = &(extent->scripts[index]);\n                if (script->program == program) {\n                    return script->sid;\n                }\n            }\n            extent = extent->next;\n        }\n    }\n\n    return -1;\n}\n\n// 0x4A39AC\nObject* scr_find_obj_from_program(Program* program)\n{\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        return NULL;\n    }\n\n    if (script->owner != NULL) {\n        return script->owner;\n    }\n\n    if (SID_TYPE(sid) != SCRIPT_TYPE_SPATIAL) {\n        return NULL;\n    }\n\n    Object* object;\n    int fid = art_id(OBJ_TYPE_INTERFACE, 3, 0, 0, 0);\n    obj_new(&object, fid, -1);\n    obj_turn_off(object, NULL);\n    obj_toggle_flat(object, NULL);\n    object->sid = sid;\n\n    // NOTE: Redundant, we've already obtained script earlier. Probably\n    // inlining.\n    Script* v1;\n    if (scr_ptr(sid, &v1) == -1) {\n        // FIXME: this is clearly an error, but I guess it's never reached since\n        // we've already obtained script for given sid earlier.\n        return (Object*)-1;\n    }\n\n    object->id = new_obj_id();\n    v1->field_1C = object->id;\n    v1->owner = object;\n\n    for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) {\n        Script* spatialScript = scr_find_first_at(elevation);\n        while (spatialScript != NULL) {\n            if (spatialScript == script) {\n                obj_move_to_tile(object, builtTileGetTile(script->sp.built_tile), elevation, NULL);\n                return object;\n            }\n            spatialScript = scr_find_next_at();\n        }\n    }\n\n    return object;\n}\n\n// 0x4A3B0C\nint scr_set_objs(int sid, Object* source, Object* target)\n{\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        return -1;\n    }\n\n    script->source = source;\n    script->target = target;\n\n    return 0;\n}\n\n// 0x4A3B34\nvoid scr_set_ext_param(int sid, int value)\n{\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        script->fixedParam = value;\n    }\n}\n\n// 0x4A3B54\nint scr_set_action_num(int sid, int value)\n{\n    Script* scr;\n\n    if (scr_ptr(sid, &scr) == -1) {\n        return -1;\n    }\n\n    scr->actionBeingUsed = value;\n\n    return 0;\n}\n\n// 0x4A3B74\nProgram* loadProgram(const char* name)\n{\n    char path[MAX_PATH];\n\n    strcpy(path, cd_path_base);\n    strcat(path, script_path_base);\n    strcat(path, name);\n    strcat(path, \".int\");\n\n    return allocateProgram(path);\n}\n\n// 0x4A3C2C\nstatic void doBkProcesses()\n{\n    // 0x66774C\n    static bool set;\n\n    // 0x667748\n    static int lasttime;\n\n    if (!set) {\n        lasttime = get_bk_time();\n        set = 1;\n    }\n\n    int v0 = get_bk_time();\n    if (script_engine_running) {\n        lasttime = v0;\n\n        // NOTE: There is a loop at 0x4A3C64, consisting of one iteration, going\n        // downwards from 1.\n        for (int index = 0; index < 1; index++) {\n            updatePrograms();\n        }\n    }\n\n    updateWindows();\n\n    if (script_engine_running && script_engine_run_critters) {\n        if (!gdialogActive()) {\n            script_chk_critters();\n            script_chk_timed_events();\n        }\n    }\n}\n\n// 0x4A3CA0\nstatic void script_chk_critters()\n{\n    // 0x51C7DC\n    static int count = 0;\n\n    if (!gdialogActive() && !isInCombat()) {\n        ScriptList* scriptList;\n        ScriptListExtent* scriptListExtent;\n\n        int scriptsCount = 0;\n\n        scriptList = &(scriptlists[SCRIPT_TYPE_CRITTER]);\n        scriptListExtent = scriptList->head;\n        while (scriptListExtent != NULL) {\n            scriptsCount += scriptListExtent->length;\n            scriptListExtent = scriptListExtent->next;\n        }\n\n        count += 1;\n        if (count >= scriptsCount) {\n            count = 0;\n        }\n\n        if (count < scriptsCount) {\n            int proc = isInCombat() ? SCRIPT_PROC_COMBAT : SCRIPT_PROC_CRITTER;\n            int extentIndex = count / SCRIPT_LIST_EXTENT_SIZE;\n            int scriptIndex = count % SCRIPT_LIST_EXTENT_SIZE;\n\n            scriptList = &(scriptlists[SCRIPT_TYPE_CRITTER]);\n            scriptListExtent = scriptList->head;\n            while (scriptListExtent != NULL && extentIndex != 0) {\n                extentIndex -= 1;\n                scriptListExtent = scriptListExtent->next;\n            }\n\n            if (scriptListExtent != NULL) {\n                Script* script = &(scriptListExtent->scripts[scriptIndex]);\n                exec_script_proc(script->sid, proc);\n            }\n        }\n    }\n}\n\n// TODO: Check.\n//\n// 0x4A3D84\nstatic void script_chk_timed_events()\n{\n    // 0x51C7E0\n    static int last_time = 0;\n\n    // 0x51C7E4\n    static int last_light_time = 0;\n\n    int v0 = get_bk_time();\n\n    int v1 = false;\n    if (!isInCombat()) {\n        v1 = true;\n    }\n\n    if (game_state() != GAME_STATE_4) {\n        if (elapsed_tocks(v0, last_light_time) >= 30000) {\n            last_light_time = v0;\n            scrExecMapProcScripts(SCRIPT_PROC_MAP_UPDATE);\n        }\n    } else {\n        v1 = false;\n    }\n\n    if (elapsed_tocks(v0, last_time) >= 100) {\n        last_time = v0;\n        if (!isInCombat()) {\n            fallout_game_time += 1;\n        }\n        v1 = true;\n    }\n\n    if (v1) {\n        while (!queue_is_empty()) {\n            int time = game_time();\n            int v2 = queue_next_time();\n            if (time < v2) {\n                break;\n            }\n\n            queue_process();\n        }\n    }\n}\n\n// 0x4A3E30\nvoid scrSetQueueTestVals(Object* a1, int a2)\n{\n    scrQueueTestObj = a1;\n    scrQueueTestValue = a2;\n}\n\n// 0x4A3E3C\nint scrQueueRemoveFixed(Object* obj, void* data)\n{\n    ScriptEvent* scriptEvent = (ScriptEvent*)data;\n    return obj == scrQueueTestObj && scriptEvent->fixedParam == scrQueueTestValue;\n}\n\n// 0x4A3E60\nint script_q_add(int sid, int delay, int param)\n{\n    ScriptEvent* scriptEvent = (ScriptEvent*)mem_malloc(sizeof(*scriptEvent));\n    if (scriptEvent == NULL) {\n        return -1;\n    }\n\n    scriptEvent->sid = sid;\n    scriptEvent->fixedParam = param;\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        mem_free(scriptEvent);\n        return -1;\n    }\n\n    if (queue_add(delay, script->owner, scriptEvent, EVENT_TYPE_SCRIPT) == -1) {\n        mem_free(scriptEvent);\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4A3EDC\nint script_q_save(File* stream, void* data)\n{\n    ScriptEvent* scriptEvent = (ScriptEvent*)data;\n\n    if (db_fwriteInt(stream, scriptEvent->sid) == -1) return -1;\n    if (db_fwriteInt(stream, scriptEvent->fixedParam) == -1) return -1;\n\n    return 0;\n}\n\n// 0x4A3F04\nint script_q_load(File* stream, void** dataPtr)\n{\n    ScriptEvent* scriptEvent = (ScriptEvent*)mem_malloc(sizeof(*scriptEvent));\n    if (scriptEvent == NULL) {\n        return -1;\n    }\n\n    if (db_freadInt(stream, &(scriptEvent->sid)) == -1) goto err;\n    if (db_freadInt(stream, &(scriptEvent->fixedParam)) == -1) goto err;\n\n    *dataPtr = scriptEvent;\n\n    return 0;\n\nerr:\n\n    // there is a memory leak in original code, free is not called\n    mem_free(scriptEvent);\n\n    return -1;\n}\n\n// 0x4A3F4C\nint script_q_process(Object* obj, void* data)\n{\n    ScriptEvent* scriptEvent = (ScriptEvent*)data;\n\n    Script* script;\n    if (scr_ptr(scriptEvent->sid, &script) == -1) {\n        return 0;\n    }\n\n    script->fixedParam = scriptEvent->fixedParam;\n\n    exec_script_proc(scriptEvent->sid, SCRIPT_PROC_TIMED);\n\n    return 0;\n}\n\n// 0x4A3F80\nint scripts_clear_state()\n{\n    scriptState.requests = 0;\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4A3F90\nint scripts_clear_combat_requests(Script* script)\n{\n    if ((scriptState.requests & SCRIPT_REQUEST_COMBAT) != 0 && scriptState.combatState1.attacker == script->owner) {\n        scriptState.requests &= ~(SCRIPT_REQUEST_LOCKED | SCRIPT_REQUEST_COMBAT);\n    }\n    return 0;\n}\n\n// 0x4A3FB4\nint scripts_check_state()\n{\n    if (scriptState.requests == 0) {\n        return 0;\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_COMBAT) != 0) {\n        if (!action_explode_running()) {\n            // entering combat\n            scriptState.requests &= ~(SCRIPT_REQUEST_LOCKED | SCRIPT_REQUEST_COMBAT);\n            memcpy(&scriptState.combatState2, &scriptState.combatState1, sizeof(scriptState.combatState2));\n\n            if ((scriptState.requests & SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE) != 0) {\n                scriptState.requests &= ~SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE;\n                combat(NULL);\n            } else {\n                combat(&scriptState.combatState2);\n                memset(&scriptState.combatState2, 0, sizeof(scriptState.combatState2));\n            }\n        }\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_TOWN_MAP) != 0) {\n        scriptState.requests &= ~SCRIPT_REQUEST_TOWN_MAP;\n        wmTownMap();\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_WORLD_MAP) != 0) {\n        scriptState.requests &= ~SCRIPT_REQUEST_WORLD_MAP;\n        wmWorldMap();\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_ELEVATOR) != 0) {\n        int map = map_data.field_34;\n        int elevation = scriptState.elevatorLevel;\n        int tile = -1;\n\n        scriptState.requests &= ~SCRIPT_REQUEST_ELEVATOR;\n\n        if (elevator_select(scriptState.elevatorType, &map, &elevation, &tile) != -1) {\n            automap_pip_save();\n\n            if (map == map_data.field_34) {\n                if (elevation == map_elevation) {\n                    register_clear(obj_dude);\n                    obj_set_rotation(obj_dude, ROTATION_SE, 0);\n                    obj_attempt_placement(obj_dude, tile, elevation, 0);\n                } else {\n                    Object* elevatorDoors = obj_find_first_at(obj_dude->elevation);\n                    while (elevatorDoors != NULL) {\n                        int pid = elevatorDoors->pid;\n                        if (PID_TYPE(pid) == OBJ_TYPE_SCENERY\n                            && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6)\n                            && tile_dist(elevatorDoors->tile, obj_dude->tile) <= 4) {\n                            break;\n                        }\n                        elevatorDoors = obj_find_next_at();\n                    }\n\n                    register_clear(obj_dude);\n                    obj_set_rotation(obj_dude, ROTATION_SE, 0);\n                    obj_attempt_placement(obj_dude, tile, elevation, 0);\n\n                    if (elevatorDoors != NULL) {\n                        obj_set_frame(elevatorDoors, 0, NULL);\n                        obj_move_to_tile(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL);\n                        elevatorDoors->flags &= ~OBJECT_OPEN_DOOR;\n                        elevatorDoors->data.scenery.door.openFlags &= ~0x01;\n                        obj_rebuild_all_light();\n                    } else {\n                        debug_printf(\"\\nWarning: Elevator: Couldn't find old elevator doors!\");\n                    }\n                }\n            } else {\n                Object* elevatorDoors = obj_find_first_at(obj_dude->elevation);\n                while (elevatorDoors != NULL) {\n                    int pid = elevatorDoors->pid;\n                    if (PID_TYPE(pid) == OBJ_TYPE_SCENERY\n                        && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6)\n                        && tile_dist(elevatorDoors->tile, obj_dude->tile) <= 4) {\n                        break;\n                    }\n                    elevatorDoors = obj_find_next_at();\n                }\n\n                if (elevatorDoors != NULL) {\n                    obj_set_frame(elevatorDoors, 0, NULL);\n                    obj_move_to_tile(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL);\n                    elevatorDoors->flags &= ~OBJECT_OPEN_DOOR;\n                    elevatorDoors->data.scenery.door.openFlags &= ~0x01;\n                    obj_rebuild_all_light();\n                } else {\n                    debug_printf(\"\\nWarning: Elevator: Couldn't find old elevator doors!\");\n                }\n\n                MapTransition transition;\n                memset(&transition, 0, sizeof(transition));\n\n                transition.map = map;\n                transition.elevation = elevation;\n                transition.tile = tile;\n                transition.rotation = ROTATION_SE;\n\n                map_leave_map(&transition);\n            }\n        }\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_EXPLOSION) != 0) {\n        scriptState.requests &= ~SCRIPT_REQUEST_EXPLOSION;\n        action_explode(scriptState.explosionTile, scriptState.explosionElevation, scriptState.explosionMinDamage, scriptState.explosionMaxDamage, NULL, 1);\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_DIALOG) != 0) {\n        scriptState.requests &= ~SCRIPT_REQUEST_DIALOG;\n        gdialogEnter(scriptState.dialogTarget, 0);\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_ENDGAME) != 0) {\n        scriptState.requests &= ~SCRIPT_REQUEST_ENDGAME;\n        endgame_slideshow();\n        endgame_movie();\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_LOOTING) != 0) {\n        scriptState.requests &= ~SCRIPT_REQUEST_LOOTING;\n        loot_container(scriptState.lootingBy, scriptState.lootingFrom);\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_STEALING) != 0) {\n        scriptState.requests &= ~SCRIPT_REQUEST_STEALING;\n        inven_steal_container(scriptState.stealingBy, scriptState.stealingFrom);\n    }\n\n    return 0;\n}\n\n// 0x4A43A0\nint scripts_check_state_in_combat()\n{\n    if ((scriptState.requests & SCRIPT_REQUEST_ELEVATOR) != 0) {\n        int map = map_data.field_34;\n        int elevation = scriptState.elevatorLevel;\n        int tile = -1;\n\n        if (elevator_select(scriptState.elevatorType, &map, &elevation, &tile) != -1) {\n            automap_pip_save();\n\n            if (map == map_data.field_34) {\n                if (elevation == map_elevation) {\n                    register_clear(obj_dude);\n                    obj_set_rotation(obj_dude, ROTATION_SE, 0);\n                    obj_attempt_placement(obj_dude, tile, elevation, 0);\n                } else {\n                    Object* elevatorDoors = obj_find_first_at(obj_dude->elevation);\n                    while (elevatorDoors != NULL) {\n                        int pid = elevatorDoors->pid;\n                        if (PID_TYPE(pid) == OBJ_TYPE_SCENERY\n                            && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6)\n                            && tile_dist(elevatorDoors->tile, obj_dude->tile) <= 4) {\n                            break;\n                        }\n                        elevatorDoors = obj_find_next_at();\n                    }\n\n                    register_clear(obj_dude);\n                    obj_set_rotation(obj_dude, ROTATION_SE, 0);\n                    obj_attempt_placement(obj_dude, tile, elevation, 0);\n\n                    if (elevatorDoors != NULL) {\n                        obj_set_frame(elevatorDoors, 0, NULL);\n                        obj_move_to_tile(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL);\n                        elevatorDoors->flags &= ~OBJECT_OPEN_DOOR;\n                        elevatorDoors->data.scenery.door.openFlags &= ~0x01;\n                        obj_rebuild_all_light();\n                    } else {\n                        debug_printf(\"\\nWarning: Elevator: Couldn't find old elevator doors!\");\n                    }\n                }\n            } else {\n                MapTransition transition;\n                memset(&transition, 0, sizeof(transition));\n\n                transition.map = map;\n                transition.elevation = elevation;\n                transition.tile = tile;\n                transition.rotation = ROTATION_SE;\n\n                map_leave_map(&transition);\n            }\n        }\n    }\n\n    if ((scriptState.requests & SCRIPT_REQUEST_LOOTING) != 0) {\n        loot_container(scriptState.lootingBy, scriptState.lootingFrom);\n    }\n\n    // NOTE: Uninline.\n    scripts_clear_state();\n\n    return 0;\n}\n\n// 0x4A457C\nint scripts_request_combat(STRUCT_664980* a1)\n{\n    if ((scriptState.requests & SCRIPT_REQUEST_LOCKED) != 0) {\n        return -1;\n    }\n\n    if (a1) {\n        static_assert(sizeof(scriptState.combatState1) == sizeof(*a1), \"wrong size\");\n        memcpy(&scriptState.combatState1, a1, sizeof(scriptState.combatState1));\n    } else {\n        scriptState.requests |= SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE;\n    }\n\n    scriptState.requests |= SCRIPT_REQUEST_COMBAT;\n\n    return 0;\n}\n\n// 0x4A45D4\nvoid scripts_request_combat_locked(STRUCT_664980* a1)\n{\n    if (a1 != NULL) {\n        memcpy(&scriptState.combatState1, a1, sizeof(scriptState.combatState1));\n    } else {\n        scriptState.requests |= SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE;\n    }\n\n    scriptState.requests |= (SCRIPT_REQUEST_LOCKED | SCRIPT_REQUEST_COMBAT);\n}\n\n// 0x4A4644\nvoid scripts_request_worldmap()\n{\n    if (isInCombat()) {\n        game_user_wants_to_quit = 1;\n    }\n\n    scriptState.requests |= SCRIPT_REQUEST_WORLD_MAP;\n}\n\n// scripts_request_elevator\n// 0x4A466C\nint scripts_request_elevator(Object* a1, int a2)\n{\n    int elevatorType = a2;\n    int elevatorLevel = map_elevation;\n\n    int tile = a1->tile;\n    if (tile == -1) {\n        debug_printf(\"\\nError: scripts_request_elevator! Bad tile num\");\n        return -1;\n    }\n\n    // In the following code we are looking for an elevator. 5 tiles in each direction\n    tile = tile - (HEX_GRID_WIDTH * 5) - 5; // left upper corner\n\n    Object* obj;\n    for (int y = -5; y < 5; y++) {\n        for (int x = -5; x < 5; x++) {\n            obj = obj_find_first_at(a1->elevation);\n            while (obj != NULL) {\n                if (tile == obj->tile && obj->pid == PROTO_ID_0x200050D) {\n                    break;\n                }\n\n                obj = obj_find_next_at();\n            }\n\n            if (obj != NULL) {\n                break;\n            }\n\n            tile += 1;\n        }\n\n        if (obj != NULL) {\n            break;\n        }\n\n        tile += HEX_GRID_WIDTH - 10;\n    }\n\n    if (obj != NULL) {\n        elevatorType = obj->data.scenery.elevator.type;\n        elevatorLevel = obj->data.scenery.elevator.level;\n    }\n\n    if (elevatorType == -1) {\n        return -1;\n    }\n\n    scriptState.requests |= SCRIPT_REQUEST_ELEVATOR;\n    scriptState.elevatorType = elevatorType;\n    scriptState.elevatorLevel = elevatorLevel;\n\n    return 0;\n}\n\n// 0x4A4730\nint scripts_request_explosion(int tile, int elevation, int minDamage, int maxDamage)\n{\n    scriptState.requests |= SCRIPT_REQUEST_EXPLOSION;\n    scriptState.explosionTile = tile;\n    scriptState.explosionElevation = elevation;\n    scriptState.explosionMinDamage = minDamage;\n    scriptState.explosionMaxDamage = maxDamage;\n    return 0;\n}\n\n// 0x4A4754\nvoid scripts_request_dialog(Object* obj)\n{\n    scriptState.dialogTarget = obj;\n    scriptState.requests |= SCRIPT_REQUEST_DIALOG;\n}\n\n// 0x4A4770\nvoid scripts_request_endgame_slideshow()\n{\n    scriptState.requests |= SCRIPT_REQUEST_ENDGAME;\n}\n\n// 0x4A477C\nint scripts_request_loot_container(Object* a1, Object* a2)\n{\n    scriptState.lootingBy = a1;\n    scriptState.lootingFrom = a2;\n    scriptState.requests |= SCRIPT_REQUEST_LOOTING;\n    return 0;\n}\n\n// 0x4A479C\nint scripts_request_steal_container(Object* a1, Object* a2)\n{\n    scriptState.stealingBy = a1;\n    scriptState.stealingFrom = a2;\n    scriptState.requests |= SCRIPT_REQUEST_STEALING;\n    return 0;\n}\n\n// NOTE: Inlined.\nvoid script_make_path(char* path)\n{\n    strcpy(path, cd_path_base);\n    strcat(path, script_path_base);\n}\n\n// exec_script_proc\n// 0x4A4810\nint exec_script_proc(int sid, int proc)\n{\n    if (!script_engine_running) {\n        return -1;\n    }\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        return -1;\n    }\n\n    script->scriptOverrides = 0;\n\n    bool programLoaded = false;\n    if ((script->flags & SCRIPT_FLAG_0x01) == 0) {\n        clock();\n\n        char name[16];\n        if (scr_index_to_name(script->field_14 & 0xFFFFFF, name) == -1) {\n            return -1;\n        }\n\n        char* pch = strchr(name, '.');\n        if (pch != NULL) {\n            *pch = '\\0';\n        }\n\n        script->program = loadProgram(name);\n        if (script->program == NULL) {\n            debug_printf(\"\\nError: exec_script_proc: script load failed!\");\n            return -1;\n        }\n\n        programLoaded = true;\n        script->flags |= SCRIPT_FLAG_0x01;\n    }\n\n    Program* program = script->program;\n    if (program == NULL) {\n        return -1;\n    }\n\n    if ((program->flags & 0x0124) != 0) {\n        return 0;\n    }\n\n    int v9 = script->procs[proc];\n    if (v9 == 0) {\n        v9 = 1;\n    }\n\n    if (v9 == -1) {\n        return -1;\n    }\n\n    if (script->target == NULL) {\n        script->target = script->owner;\n    }\n\n    script->flags |= SCRIPT_FLAG_0x04;\n\n    if (programLoaded) {\n        scr_build_lookup_table(script);\n\n        v9 = script->procs[proc];\n        if (v9 == 0) {\n            v9 = 1;\n        }\n\n        script->action = 0;\n        // NOTE: Uninline.\n        runProgram(program);\n        interpret(program, -1);\n    }\n\n    script->action = proc;\n\n    executeProcedure(program, v9);\n\n    script->source = NULL;\n\n    return 0;\n}\n\n// Locate built-in procs for given script.\n//\n// 0x4A49D0\nstatic int scr_build_lookup_table(Script* script)\n{\n    for (int proc = 0; proc < SCRIPT_PROC_COUNT; proc++) {\n        int index = interpretFindProcedure(script->program, procTableStrs[proc]);\n        if (index == -1) {\n            index = SCRIPT_PROC_NO_PROC;\n        }\n        script->procs[proc] = index;\n    }\n\n    return 0;\n}\n\n// 0x4A4A08\nbool scriptHasProc(int sid, int proc)\n{\n    Script* scr;\n\n    if (scr_ptr(sid, &scr) == -1) {\n        return 0;\n    }\n\n    return scr->procs[proc] != SCRIPT_PROC_NO_PROC;\n}\n\n// 0x4A4D50\nstatic int scrInitListInfo()\n{\n    char path[MAX_PATH];\n    script_make_path(path);\n    strcat(path, \"scripts.lst\");\n\n    File* stream = db_fopen(path, \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    char string[260];\n    while (db_fgets(string, 260, stream)) {\n        maxScriptNum++;\n\n        ScriptsListEntry* entries = (ScriptsListEntry*)mem_realloc(scriptListInfo, sizeof(*entries) * maxScriptNum);\n        if (entries == NULL) {\n            return -1;\n        }\n\n        scriptListInfo = entries;\n\n        ScriptsListEntry* entry = &(entries[maxScriptNum - 1]);\n        entry->local_vars_num = 0;\n\n        char* substr = strstr(string, \".int\");\n        if (substr != NULL) {\n            int length = substr - string;\n            if (length > 13) {\n                return -1;\n            }\n\n            strncpy(entry->name, string, 13);\n            entry->name[length] = '\\0';\n        }\n\n        if (strstr(string, \"#\") != NULL) {\n            substr = strstr(string, \"local_vars=\");\n            if (substr != NULL) {\n                entry->local_vars_num = atoi(substr + 11);\n            }\n        }\n    }\n\n    db_fclose(stream);\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4A4EFC\nstatic int scrExitListInfo()\n{\n    if (scriptListInfo != NULL) {\n        mem_free(scriptListInfo);\n        scriptListInfo = NULL;\n    }\n\n    maxScriptNum = 0;\n\n    return 0;\n}\n\n// 0x4A4F28\nint scr_find_str_run_info(int scriptIndex, int* a2, int sid)\n{\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        return -1;\n    }\n\n    script->localVarsCount = scriptListInfo[scriptIndex].local_vars_num;\n\n    return 0;\n}\n\n// 0x4A4F68\nstatic int scr_index_to_name(int scriptIndex, char* name)\n{\n    sprintf(name, \"%s.int\", scriptListInfo[scriptIndex].name);\n    return 0;\n}\n\n// scr_set_dude_script\n// 0x4A4F90\nint scr_set_dude_script()\n{\n    if (scr_clear_dude_script() == -1) {\n        return -1;\n    }\n\n    if (obj_dude == NULL) {\n        debug_printf(\"Error in scr_set_dude_script: obj_dude uninitialized!\");\n        return -1;\n    }\n\n    Proto* proto;\n    if (proto_ptr(0x1000000, &proto) == -1) {\n        debug_printf(\"Error in scr_set_dude_script: can't find obj_dude proto!\");\n        return -1;\n    }\n\n    proto->critter.sid = 0x4000000;\n\n    obj_new_sid(obj_dude, &(obj_dude->sid));\n\n    Script* script;\n    if (scr_ptr(obj_dude->sid, &script) == -1) {\n        debug_printf(\"Error in scr_set_dude_script: can't find obj_dude script!\");\n        return -1;\n    }\n\n    script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n\n    return 0;\n}\n\n// scr_clear_dude_script\n// 0x4A5044\nint scr_clear_dude_script()\n{\n    if (obj_dude == NULL) {\n        debug_printf(\"\\nError in scr_clear_dude_script: obj_dude uninitialized!\");\n        return -1;\n    }\n\n    if (obj_dude->sid != -1) {\n        Script* script;\n        if (scr_ptr(obj_dude->sid, &script) != -1) {\n            script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10);\n        }\n\n        scr_remove(obj_dude->sid);\n\n        obj_dude->sid = -1;\n    }\n\n    return 0;\n}\n\n// scr_init\n// 0x4A50A8\nint scr_init()\n{\n    if (!message_init(&script_message_file)) {\n        return -1;\n    }\n\n    for (int index = 0; index < 1450; index++) {\n        if (!message_init(&(script_dialog_msgs[index]))) {\n            return -1;\n        }\n    }\n\n    scr_remove_all();\n    interpretOutputFunc(win_debug);\n    initInterpreter();\n    scr_header_load();\n\n    // NOTE: Uninline.\n    scripts_clear_state();\n\n    partyMemberClear();\n\n    if (scrInitListInfo() == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4A5120\nint scr_reset()\n{\n    scr_remove_all();\n\n    // NOTE: Uninline.\n    scripts_clear_state();\n\n    partyMemberClear();\n\n    return 0;\n}\n\n// 0x4A5138\nint scr_game_init()\n{\n    int i;\n    char path[MAX_PATH];\n\n    if (!message_init(&script_message_file)) {\n        debug_printf(\"\\nError initing script message file!\");\n        return -1;\n    }\n\n    for (i = 0; i < 1450; i++) {\n        if (!message_init(&(script_dialog_msgs[i]))) {\n            debug_printf(\"\\nERROR IN SCRIPT_DIALOG_MSGS!\");\n            return -1;\n        }\n    }\n\n    sprintf(path, \"%s%s\", msg_path, \"script.msg\");\n    if (!message_load(&script_message_file, path)) {\n        debug_printf(\"\\nError loading script message file!\");\n        return -1;\n    }\n\n    script_engine_running = true;\n    script_engine_game_mode = 1;\n    fallout_game_time = 1;\n    gameTimeSetTime(302400);\n    add_bk_process(doBkProcesses);\n\n    if (scr_set_dude_script() == -1) {\n        return -1;\n    }\n\n    scrSpatialsEnabled = true;\n\n    // NOTE: Uninline.\n    scripts_clear_state();\n\n    return 0;\n}\n\n// 0x4A5240\nint scr_game_reset()\n{\n    debug_printf(\"\\nScripts: [Game Reset]\");\n    scr_game_exit();\n    scr_game_init();\n    partyMemberClear();\n    scr_remove_all_force();\n    return scr_set_dude_script();\n}\n\n// 0x4A5274\nint scr_exit()\n{\n    script_engine_running = false;\n    script_engine_run_critters = 0;\n    if (!message_exit(&script_message_file)) {\n        debug_printf(\"\\nError exiting script message file!\");\n        return -1;\n    }\n\n    scr_remove_all();\n    scr_remove_all_force();\n    interpretClose();\n    clearPrograms();\n\n    // NOTE: Uninline.\n    scripts_clear_state();\n\n    // NOTE: Uninline.\n    scrExitListInfo();\n\n    return 0;\n}\n\n// scr_message_free\n// 0x4A52F4\nint scr_message_free()\n{\n    for (int index = 0; index < 1450; index++) {\n        MessageList* messageList = &(script_dialog_msgs[index]);\n        if (messageList->entries_num != 0) {\n            if (!message_exit(messageList)) {\n                debug_printf(\"\\nERROR in scr_message_free!\");\n                return -1;\n            }\n\n            if (!message_init(messageList)) {\n                debug_printf(\"\\nERROR in scr_message_free!\");\n                return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4A535C\nint scr_game_exit()\n{\n    script_engine_game_mode = 0;\n    script_engine_running = false;\n    script_engine_run_critters = 0;\n    scr_message_free();\n    scr_remove_all();\n    clearPrograms();\n    remove_bk_process(doBkProcesses);\n    message_exit(&script_message_file);\n    if (scr_clear_dude_script() == -1) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    scripts_clear_state();\n\n    return 0;\n}\n\n// scr_enable\n// 0x4A53A8\nint scr_enable()\n{\n    if (!script_engine_game_mode) {\n        return -1;\n    }\n\n    script_engine_run_critters = 1;\n    script_engine_running = true;\n    return 0;\n}\n\n// scr_disable\n// 0x4A53D0\nint scr_disable()\n{\n    script_engine_running = false;\n    return 0;\n}\n\n// 0x4A53E0\nvoid scr_enable_critters()\n{\n    script_engine_run_critters = 1;\n}\n\n// 0x4A53F0\nvoid scr_disable_critters()\n{\n    script_engine_run_critters = 0;\n}\n\n// 0x4A5400\nint scr_game_save(File* stream)\n{\n    return db_fwriteIntCount(stream, game_global_vars, num_game_global_vars);\n}\n\n// 0x4A5424\nint scr_game_load(File* stream)\n{\n    return db_freadIntCount(stream, game_global_vars, num_game_global_vars);\n}\n\n// NOTE: For unknown reason save game files contains two identical sets of game\n// global variables (saved with [scr_game_save]). The first set is\n// read with [scr_game_load], the second set is simply thrown away\n// using this function.\n//\n// 0x4A5448\nint scr_game_load2(File* stream)\n{\n    int* vars = (int*)mem_malloc(sizeof(*vars) * num_game_global_vars);\n    if (vars == NULL) {\n        return -1;\n    }\n\n    if (db_freadIntCount(stream, vars, num_game_global_vars) == -1) {\n        // FIXME: Leaks vars.\n        return -1;\n    }\n\n    mem_free(vars);\n\n    return 0;\n}\n\n// 0x4A5490\nstatic int scr_header_load()\n{\n    num_script_indexes = 0;\n\n    char path[MAX_PATH];\n    script_make_path(path);\n    strcat(path, \"scripts.lst\");\n\n    File* stream = db_fopen(path, \"rt\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    while (1) {\n        int ch = db_fgetc(stream);\n        if (ch == -1) {\n            break;\n        }\n\n        if (ch == '\\n') {\n            num_script_indexes++;\n        }\n    }\n\n    num_script_indexes++;\n\n    db_fclose(stream);\n\n    for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) {\n        ScriptList* scriptList = &(scriptlists[scriptType]);\n        scriptList->head = NULL;\n        scriptList->tail = NULL;\n        scriptList->length = 0;\n        scriptList->nextScriptId = 0;\n    }\n\n    return 0;\n}\n\n// 0x4A5590\nstatic int scr_write_ScriptSubNode(Script* scr, File* stream)\n{\n    if (db_fwriteInt(stream, scr->sid) == -1) return -1;\n    if (db_fwriteInt(stream, scr->field_4) == -1) return -1;\n\n    switch (SID_TYPE(scr->sid)) {\n    case SCRIPT_TYPE_SPATIAL:\n        if (db_fwriteInt(stream, scr->sp.built_tile) == -1) return -1;\n        if (db_fwriteInt(stream, scr->sp.radius) == -1) return -1;\n        break;\n    case SCRIPT_TYPE_TIMED:\n        if (db_fwriteInt(stream, scr->tm.time) == -1) return -1;\n        break;\n    }\n\n    if (db_fwriteInt(stream, scr->flags) == -1) return -1;\n    if (db_fwriteInt(stream, scr->field_14) == -1) return -1;\n    if (db_fwriteInt(stream, (int)scr->program) == -1) return -1; // FIXME: writing pointer to file\n    if (db_fwriteInt(stream, scr->field_1C) == -1) return -1;\n    if (db_fwriteInt(stream, scr->localVarsOffset) == -1) return -1;\n    if (db_fwriteInt(stream, scr->localVarsCount) == -1) return -1;\n    if (db_fwriteInt(stream, scr->field_28) == -1) return -1;\n    if (db_fwriteInt(stream, scr->action) == -1) return -1;\n    if (db_fwriteInt(stream, scr->fixedParam) == -1) return -1;\n    if (db_fwriteInt(stream, scr->actionBeingUsed) == -1) return -1;\n    if (db_fwriteInt(stream, scr->scriptOverrides) == -1) return -1;\n    if (db_fwriteInt(stream, scr->field_48) == -1) return -1;\n    if (db_fwriteInt(stream, scr->howMuch) == -1) return -1;\n    if (db_fwriteInt(stream, scr->field_50) == -1) return -1;\n\n    return 0;\n}\n\n// 0x4A5704\nstatic int scr_write_ScriptNode(ScriptListExtent* a1, File* stream)\n{\n    for (int index = 0; index < SCRIPT_LIST_EXTENT_SIZE; index++) {\n        Script* script = &(a1->scripts[index]);\n        if (scr_write_ScriptSubNode(script, stream) != 0) {\n            return -1;\n        }\n    }\n\n    if (db_fwriteInt(stream, a1->length) != 0) {\n        return -1;\n    }\n\n    if (db_fwriteInt(stream, (int)a1->next) != 0) {\n        // FIXME: writing pointer to file\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4A5768\nint scr_save(File* stream)\n{\n    for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) {\n        ScriptList* scriptList = &(scriptlists[scriptType]);\n\n        int scriptCount = scriptList->length * SCRIPT_LIST_EXTENT_SIZE;\n        if (scriptList->tail != NULL) {\n            scriptCount += scriptList->tail->length - SCRIPT_LIST_EXTENT_SIZE;\n        }\n\n        ScriptListExtent* scriptExtent = scriptList->head;\n        ScriptListExtent* lastScriptExtent = NULL;\n        while (scriptExtent != NULL) {\n            for (int index = 0; index < scriptExtent->length; index++) {\n                Script* script = &(scriptExtent->scripts[index]);\n\n                lastScriptExtent = scriptList->tail;\n                if ((script->flags & SCRIPT_FLAG_0x08) != 0) {\n                    scriptCount--;\n\n                    int backwardsIndex = lastScriptExtent->length - 1;\n                    if (lastScriptExtent == scriptExtent && backwardsIndex <= index) {\n                        break;\n                    }\n\n                    while (lastScriptExtent != scriptExtent || backwardsIndex > index) {\n                        Script* backwardsScript = &(lastScriptExtent->scripts[backwardsIndex]);\n                        if ((backwardsScript->flags & SCRIPT_FLAG_0x08) == 0) {\n                            break;\n                        }\n\n                        backwardsIndex--;\n\n                        if (backwardsIndex < 0) {\n                            ScriptListExtent* previousScriptExtent = scriptList->head;\n                            while (previousScriptExtent->next != lastScriptExtent) {\n                                previousScriptExtent = previousScriptExtent->next;\n                            }\n\n                            lastScriptExtent = previousScriptExtent;\n                            backwardsIndex = lastScriptExtent->length - 1;\n                        }\n                    }\n\n                    if (lastScriptExtent != scriptExtent || backwardsIndex > index) {\n                        Script temp;\n                        memcpy(&temp, script, sizeof(Script));\n                        memcpy(script, &(lastScriptExtent->scripts[backwardsIndex]), sizeof(Script));\n                        memcpy(&(lastScriptExtent->scripts[backwardsIndex]), &temp, sizeof(Script));\n\n                        scriptCount++;\n                    }\n                }\n            }\n            scriptExtent = scriptExtent->next;\n        }\n\n        if (db_fwriteInt(stream, scriptCount) == -1) {\n            return -1;\n        }\n\n        if (scriptCount > 0) {\n            ScriptListExtent* scriptExtent = scriptList->head;\n            while (scriptExtent != lastScriptExtent) {\n                if (scr_write_ScriptNode(scriptExtent, stream) == -1) {\n                    return -1;\n                }\n                scriptExtent = scriptExtent->next;\n            }\n\n            if (lastScriptExtent != NULL) {\n                int index;\n                for (index = 0; index < lastScriptExtent->length; index++) {\n                    Script* script = &(lastScriptExtent->scripts[index]);\n                    if ((script->flags & SCRIPT_FLAG_0x08) != 0) {\n                        break;\n                    }\n                }\n\n                if (index > 0) {\n                    int length = lastScriptExtent->length;\n                    lastScriptExtent->length = index;\n                    if (scr_write_ScriptNode(lastScriptExtent, stream) == -1) {\n                        return -1;\n                    }\n                    lastScriptExtent->length = length;\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4A5A1C\nstatic int scr_read_ScriptSubNode(Script* scr, File* stream)\n{\n    int prg;\n\n    if (db_freadInt(stream, &(scr->sid)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->field_4)) == -1) return -1;\n\n    switch (SID_TYPE(scr->sid)) {\n    case SCRIPT_TYPE_SPATIAL:\n        if (db_freadInt(stream, &(scr->sp.built_tile)) == -1) return -1;\n        if (db_freadInt(stream, &(scr->sp.radius)) == -1) return -1;\n        break;\n    case SCRIPT_TYPE_TIMED:\n        if (db_freadInt(stream, &(scr->tm.time)) == -1) return -1;\n        break;\n    }\n\n    if (db_freadInt(stream, &(scr->flags)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->field_14)) == -1) return -1;\n    if (db_freadInt(stream, &(prg)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->field_1C)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->localVarsOffset)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->localVarsCount)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->field_28)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->action)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->fixedParam)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->actionBeingUsed)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->scriptOverrides)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->field_48)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->howMuch)) == -1) return -1;\n    if (db_freadInt(stream, &(scr->field_50)) == -1) return -1;\n\n    scr->program = NULL;\n    scr->owner = NULL;\n    scr->source = NULL;\n    scr->target = NULL;\n\n    for (int index = 0; index < SCRIPT_PROC_COUNT; index++) {\n        scr->procs[index] = 0;\n    }\n\n    if (!(map_data.flags & 1)) {\n        scr->localVarsCount = 0;\n    }\n\n    return 0;\n}\n\n// 0x4A5BE8\nstatic int scr_read_ScriptNode(ScriptListExtent* scriptExtent, File* stream)\n{\n    for (int index = 0; index < SCRIPT_LIST_EXTENT_SIZE; index++) {\n        Script* scr = &(scriptExtent->scripts[index]);\n        if (scr_read_ScriptSubNode(scr, stream) != 0) {\n            return -1;\n        }\n    }\n\n    if (db_freadInt(stream, &(scriptExtent->length)) != 0) {\n        return -1;\n    }\n\n    int next;\n    if (db_freadInt(stream, &(next)) != 0) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4A5C50\nint scr_load(File* stream)\n{\n    for (int index = 0; index < SCRIPT_TYPE_COUNT; index++) {\n        ScriptList* scriptList = &(scriptlists[index]);\n\n        int scriptsCount = 0;\n        if (db_freadInt(stream, &scriptsCount) == -1) {\n            return -1;\n        }\n\n        if (scriptsCount != 0) {\n            scriptList->length = scriptsCount / 16;\n\n            if (scriptsCount % 16 != 0) {\n                scriptList->length++;\n            }\n\n            ScriptListExtent* extent = (ScriptListExtent*)mem_malloc(sizeof(*extent));\n            scriptList->head = extent;\n            scriptList->tail = extent;\n            if (extent == NULL) {\n                return -1;\n            }\n\n            if (scr_read_ScriptNode(extent, stream) != 0) {\n                return -1;\n            }\n\n            for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) {\n                Script* script = &(extent->scripts[scriptIndex]);\n                script->owner = NULL;\n                script->source = NULL;\n                script->target = NULL;\n                script->program = NULL;\n                script->flags &= ~SCRIPT_FLAG_0x01;\n            }\n\n            extent->next = NULL;\n\n            ScriptListExtent* prevExtent = extent;\n            for (int extentIndex = 1; extentIndex < scriptList->length; extentIndex++) {\n                ScriptListExtent* extent = (ScriptListExtent*)mem_malloc(sizeof(*extent));\n                if (extent == NULL) {\n                    return -1;\n                }\n\n                if (scr_read_ScriptNode(extent, stream) != 0) {\n                    return -1;\n                }\n\n                for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) {\n                    Script* script = &(extent->scripts[scriptIndex]);\n                    script->owner = NULL;\n                    script->source = NULL;\n                    script->target = NULL;\n                    script->program = NULL;\n                    script->flags &= ~SCRIPT_FLAG_0x01;\n                }\n\n                prevExtent->next = extent;\n\n                extent->next = NULL;\n                prevExtent = extent;\n            }\n\n            scriptList->tail = prevExtent;\n        } else {\n            scriptList->head = NULL;\n            scriptList->tail = NULL;\n            scriptList->length = 0;\n        }\n    }\n\n    return 0;\n}\n\n// scr_ptr\n// 0x4A5E34\nint scr_ptr(int sid, Script** scriptPtr)\n{\n    *scriptPtr = NULL;\n\n    if (sid == -1) {\n        return -1;\n    }\n\n    if (sid == 0xCCCCCCCC) {\n        debug_printf(\"\\nERROR: scr_ptr called with UN-SET id #!!!!\");\n        return -1;\n    }\n\n    ScriptList* scriptList = &(scriptlists[SID_TYPE(sid)]);\n    ScriptListExtent* scriptListExtent = scriptList->head;\n\n    while (scriptListExtent != NULL) {\n        for (int index = 0; index < scriptListExtent->length; index++) {\n            Script* script = &(scriptListExtent->scripts[index]);\n            if (script->sid == sid) {\n                *scriptPtr = script;\n                return 0;\n            }\n        }\n        scriptListExtent = scriptListExtent->next;\n    }\n\n    return -1;\n}\n\n// 0x4A5ED8\nstatic int scr_new_id(int scriptType)\n{\n    int scriptId = scriptlists[scriptType].nextScriptId++;\n    int v1 = scriptType << 24;\n\n    while (scriptId < 32000) {\n        Script* script;\n        if (scr_ptr(v1 | scriptId, &script) == -1) {\n            break;\n        }\n        scriptId++;\n    }\n\n    return scriptId;\n}\n\n// 0x4A5F28\nint scr_new(int* sidPtr, int scriptType)\n{\n    ScriptList* scriptList = &(scriptlists[scriptType]);\n    ScriptListExtent* scriptListExtent = scriptList->tail;\n    if (scriptList->head != NULL) {\n        // There is at least one extent available, which means tail is also set.\n        if (scriptListExtent->length == SCRIPT_LIST_EXTENT_SIZE) {\n            ScriptListExtent* newExtent = scriptListExtent->next = (ScriptListExtent*)mem_malloc(sizeof(*newExtent));\n            if (newExtent == NULL) {\n                return -1;\n            }\n\n            newExtent->length = 0;\n            newExtent->next = NULL;\n\n            scriptList->tail = newExtent;\n            scriptList->length++;\n\n            scriptListExtent = newExtent;\n        }\n    } else {\n        // Script head\n        scriptListExtent = (ScriptListExtent*)mem_malloc(sizeof(ScriptListExtent));\n        if (scriptListExtent == NULL) {\n            return -1;\n        }\n\n        scriptListExtent->length = 0;\n        scriptListExtent->next = NULL;\n\n        scriptList->head = scriptListExtent;\n        scriptList->tail = scriptListExtent;\n        scriptList->length = 1;\n    }\n\n    int sid = scr_new_id(scriptType) | (scriptType << 24);\n\n    *sidPtr = sid;\n\n    Script* scr = &(scriptListExtent->scripts[scriptListExtent->length]);\n    scr->sid = sid;\n    scr->sp.built_tile = -1;\n    scr->sp.radius = -1;\n    scr->flags = 0;\n    scr->field_14 = -1;\n    scr->program = 0;\n    scr->localVarsOffset = -1;\n    scr->localVarsCount = 0;\n    scr->field_28 = 0;\n    scr->action = 0;\n    scr->fixedParam = 0;\n    scr->owner = 0;\n    scr->source = 0;\n    scr->target = 0;\n    scr->actionBeingUsed = -1;\n    scr->scriptOverrides = 0;\n    scr->field_48 = 0;\n    scr->howMuch = 0;\n    scr->field_50 = 0;\n\n    for (int index = 0; index < SCRIPT_PROC_COUNT; index++) {\n        scr->procs[index] = SCRIPT_PROC_NO_PROC;\n    }\n\n    scriptListExtent->length++;\n\n    return 0;\n}\n\n// scr_remove_local_vars\n// 0x4A60D4\nint scr_remove_local_vars(Script* script)\n{\n    if (script == NULL) {\n        return -1;\n    }\n\n    if (script->localVarsCount != 0) {\n        int oldMapLocalVarsCount = num_map_local_vars;\n        if (oldMapLocalVarsCount > 0 && script->localVarsOffset >= 0) {\n            num_map_local_vars -= script->localVarsCount;\n\n            if (oldMapLocalVarsCount - script->localVarsCount != script->localVarsOffset && script->localVarsOffset != -1) {\n                memmove(map_local_vars + script->localVarsOffset,\n                    map_local_vars + (script->localVarsOffset + script->localVarsCount),\n                    sizeof(*map_local_vars) * (oldMapLocalVarsCount - script->localVarsCount - script->localVarsOffset));\n\n                map_local_vars = (int*)mem_realloc(map_local_vars, sizeof(*map_local_vars) * num_map_local_vars);\n                if (map_local_vars == NULL) {\n                    debug_printf(\"\\nError in mem_realloc in scr_remove_local_vars!\\n\");\n                }\n\n                for (int index = 0; index < SCRIPT_TYPE_COUNT; index++) {\n                    ScriptList* scriptList = &(scriptlists[index]);\n                    ScriptListExtent* extent = scriptList->head;\n                    while (extent != NULL) {\n                        for (int index = 0; index < extent->length; index++) {\n                            Script* other = &(extent->scripts[index]);\n                            if (other->localVarsOffset > script->localVarsOffset) {\n                                other->localVarsOffset -= script->localVarsCount;\n                            }\n                        }\n                        extent = extent->next;\n                    }\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\n// scr_remove\n// 0x4A61D4\nint scr_remove(int sid)\n{\n    if (sid == -1) {\n        return -1;\n    }\n\n    ScriptList* scriptList = &(scriptlists[SID_TYPE(sid)]);\n\n    ScriptListExtent* scriptListExtent = scriptList->head;\n    int index;\n    while (scriptListExtent != NULL) {\n        for (index = 0; index < scriptListExtent->length; index++) {\n            Script* script = &(scriptListExtent->scripts[index]);\n            if (script->sid == sid) {\n                break;\n            }\n        }\n\n        if (index < scriptListExtent->length) {\n            break;\n        }\n\n        scriptListExtent = scriptListExtent->next;\n    }\n\n    if (scriptListExtent == NULL) {\n        return -1;\n    }\n\n    Script* script = &(scriptListExtent->scripts[index]);\n    if ((script->flags & SCRIPT_FLAG_0x02) != 0) {\n        if (script->program != NULL) {\n            script->program = NULL;\n        }\n    }\n\n    if ((script->flags & SCRIPT_FLAG_0x10) == 0) {\n        // NOTE: Uninline.\n        scripts_clear_combat_requests(script);\n\n        if (scr_remove_local_vars(script) == -1) {\n            debug_printf(\"\\nERROR Removing local vars on scr_remove!!\\n\");\n        }\n\n        if (queue_remove_this(script->owner, EVENT_TYPE_SCRIPT) == -1) {\n            debug_printf(\"\\nERROR Removing Timed Events on scr_remove!!\\n\");\n        }\n\n        if (scriptListExtent == scriptList->tail && index + 1 == scriptListExtent->length) {\n            // Removing last script in tail extent\n            scriptListExtent->length -= 1;\n\n            if (scriptListExtent->length == 0) {\n                scriptList->length--;\n                mem_free(scriptListExtent);\n\n                if (scriptList->length != 0) {\n                    ScriptListExtent* v13 = scriptList->head;\n                    while (scriptList->tail != v13->next) {\n                        v13 = v13->next;\n                    }\n                    v13->next = NULL;\n                    scriptList->tail = v13;\n                } else {\n                    scriptList->head = NULL;\n                    scriptList->tail = NULL;\n                }\n            }\n        } else {\n            // Relocate last script from tail extent into this script's slot.\n            memcpy(&(scriptListExtent->scripts[index]), &(scriptList->tail->scripts[scriptList->tail->length - 1]), sizeof(Script));\n\n            // Decrement number of scripts in tail extent.\n            scriptList->tail->length -= 1;\n\n            // Check to see if this extent became empty.\n            if (scriptList->tail->length == 0) {\n                scriptList->length -= 1;\n\n                // Find previous extent that is about to become a new tail for\n                // this script list.\n                ScriptListExtent* prev = scriptList->head;\n                while (prev->next != scriptList->tail) {\n                    prev = prev->next;\n                }\n                prev->next = NULL;\n\n                mem_free(scriptList->tail);\n                scriptList->tail = prev;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4A63E0\nint scr_remove_all()\n{\n    queue_clear_type(EVENT_TYPE_SCRIPT, NULL);\n    scr_message_free();\n\n    for (int scrType = 0; scrType < SCRIPT_TYPE_COUNT; scrType++) {\n        ScriptList* scriptList = &(scriptlists[scrType]);\n\n        // TODO: Super odd way to remove scripts. The problem is that [scrRemove]\n        // does relocate scripts between extents, so current extent may become\n        // empty. In addition there is a 0x10 flag on the script that is not\n        // removed. Find a way to refactor this.\n        ScriptListExtent* scriptListExtent = scriptList->head;\n        while (scriptListExtent != NULL) {\n            ScriptListExtent* next = NULL;\n            for (int scriptIndex = 0; scriptIndex < scriptListExtent->length;) {\n                Script* script = &(scriptListExtent->scripts[scriptIndex]);\n\n                if ((script->flags & SCRIPT_FLAG_0x10) != 0) {\n                    scriptIndex++;\n                } else {\n                    if (scriptIndex != 0 || scriptListExtent->length != 1) {\n                        scr_remove(script->sid);\n                    } else {\n                        next = scriptListExtent->next;\n                        scr_remove(script->sid);\n                    }\n                }\n            }\n\n            scriptListExtent = next;\n        }\n    }\n\n    scr_find_first_idx = 0;\n    scr_find_first_ptr = NULL;\n    scr_find_first_elev = 0;\n    map_script_id = -1;\n\n    clearPrograms();\n    exportClearAllVariables();\n\n    return 0;\n}\n\n// 0x4A64A8\nint scr_remove_all_force()\n{\n    queue_clear_type(EVENT_TYPE_SCRIPT, NULL);\n    scr_message_free();\n\n    for (int type = 0; type < SCRIPT_TYPE_COUNT; type++) {\n        ScriptList* scriptList = &(scriptlists[type]);\n        ScriptListExtent* extent = scriptList->head;\n        while (extent != NULL) {\n            ScriptListExtent* next = extent->next;\n            mem_free(extent);\n            extent = next;\n        }\n\n        scriptList->head = NULL;\n        scriptList->tail = NULL;\n        scriptList->length = 0;\n    }\n\n    scr_find_first_idx = 0;\n    scr_find_first_ptr = 0;\n    scr_find_first_elev = 0;\n    map_script_id = -1;\n    clearPrograms();\n    exportClearAllVariables();\n\n    return 0;\n}\n\n// 0x4A6524\nScript* scr_find_first_at(int elevation)\n{\n    scr_find_first_elev = elevation;\n    scr_find_first_idx = 0;\n    scr_find_first_ptr = scriptlists[SCRIPT_TYPE_SPATIAL].head;\n\n    if (scr_find_first_ptr == NULL) {\n        return NULL;\n    }\n\n    Script* script = &(scr_find_first_ptr->scripts[0]);\n    if ((script->flags & SCRIPT_FLAG_0x02) != 0 || builtTileGetElevation(script->sp.built_tile) != elevation) {\n        script = scr_find_next_at();\n    }\n\n    return script;\n}\n\n// 0x4A6564\nScript* scr_find_next_at()\n{\n    ScriptListExtent* scriptListExtent = scr_find_first_ptr;\n    int scriptIndex = scr_find_first_idx;\n\n    if (scriptListExtent == NULL) {\n        return NULL;\n    }\n\n    for (;;) {\n        scriptIndex++;\n\n        if (scriptIndex == SCRIPT_LIST_EXTENT_SIZE) {\n            scriptListExtent = scriptListExtent->next;\n            scriptIndex = 0;\n        } else if (scriptIndex >= scriptListExtent->length) {\n            scriptListExtent = NULL;\n        }\n\n        if (scriptListExtent == NULL) {\n            break;\n        }\n\n        Script* script = &(scriptListExtent->scripts[scriptIndex]);\n        if ((script->flags & SCRIPT_FLAG_0x02) == 0 && builtTileGetElevation(script->sp.built_tile) == scr_find_first_elev) {\n            break;\n        }\n    }\n\n    Script* script;\n    if (scriptListExtent != NULL) {\n        script = &(scriptListExtent->scripts[scriptIndex]);\n    } else {\n        script = NULL;\n    }\n\n    scr_find_first_idx = scriptIndex;\n    scr_find_first_ptr = scriptListExtent;\n\n    return script;\n}\n\n// 0x4A65F0\nvoid scr_spatials_enable()\n{\n    scrSpatialsEnabled = true;\n}\n\n// 0x4A6600\nvoid scr_spatials_disable()\n{\n    scrSpatialsEnabled = false;\n}\n\n// 0x4A6610\nbool scr_chk_spatials_in(Object* object, int tile, int elevation)\n{\n    if (object == obj_mouse) {\n        return false;\n    }\n\n    if (object == obj_mouse_flat) {\n        return false;\n    }\n\n    if ((object->flags & OBJECT_HIDDEN) != 0 || (object->flags & OBJECT_FLAT) != 0) {\n        return false;\n    }\n\n    if (tile < 10) {\n        return false;\n    }\n\n    if (!scrSpatialsEnabled) {\n        return false;\n    }\n\n    scrSpatialsEnabled = false;\n\n    int builtTile = builtTileCreate(tile, elevation);\n\n    for (Script* script = scr_find_first_at(elevation); script != NULL; script = scr_find_next_at()) {\n        if (builtTile == script->sp.built_tile) {\n            // NOTE: Uninline.\n            scr_set_objs(script->sid, object, NULL);\n        } else {\n            if (script->sp.radius == 0) {\n                continue;\n            }\n\n            int distance = tile_dist(builtTileGetTile(script->sp.built_tile), tile);\n            if (distance > script->sp.radius) {\n                continue;\n            }\n\n            // NOTE: Uninline.\n            scr_set_objs(script->sid, object, NULL);\n        }\n\n        exec_script_proc(script->sid, SCRIPT_PROC_SPATIAL);\n    }\n\n    scrSpatialsEnabled = true;\n\n    return true;\n}\n\n// 0x4A677C\nint scr_load_all_scripts()\n{\n    for (int scriptListIndex = 0; scriptListIndex < SCRIPT_TYPE_COUNT; scriptListIndex++) {\n        ScriptList* scriptList = &(scriptlists[scriptListIndex]);\n        ScriptListExtent* extent = scriptList->head;\n        while (extent != NULL) {\n            for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) {\n                Script* script = &(extent->scripts[scriptIndex]);\n                exec_script_proc(script->sid, SCRIPT_PROC_START);\n            }\n            extent = extent->next;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4A67DC\nvoid scr_exec_map_enter_scripts()\n{\n    scrExecMapProcScripts(SCRIPT_PROC_MAP_ENTER);\n}\n\n// 0x4A67E4\nvoid scr_exec_map_update_scripts()\n{\n    scrExecMapProcScripts(SCRIPT_PROC_MAP_UPDATE);\n}\n\n// 0x4A67EC\nstatic void scrExecMapProcScripts(int proc)\n{\n    scrSpatialsEnabled = false;\n\n    int fixedParam = 0;\n    if (proc == SCRIPT_PROC_MAP_ENTER) {\n        fixedParam = (map_data.flags & 1) == 0;\n    } else {\n        exec_script_proc(map_script_id, proc);\n    }\n\n    int sidListCapacity = 0;\n    for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) {\n        ScriptList* scriptList = &(scriptlists[scriptType]);\n        ScriptListExtent* scriptListExtent = scriptList->head;\n        while (scriptListExtent != NULL) {\n            sidListCapacity += scriptListExtent->length;\n            scriptListExtent = scriptListExtent->next;\n        }\n    }\n\n    if (sidListCapacity == 0) {\n        return;\n    }\n\n    int* sidList = (int*)mem_malloc(sizeof(*sidList) * sidListCapacity);\n    if (sidList == NULL) {\n        debug_printf(\"\\nError: scr_exec_map_update_scripts: Out of memory for sidList!\");\n        return;\n    }\n\n    int sidListLength = 0;\n    for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) {\n        ScriptList* scriptList = &(scriptlists[scriptType]);\n        ScriptListExtent* scriptListExtent = scriptList->head;\n        while (scriptListExtent != NULL) {\n            for (int scriptIndex = 0; scriptIndex < scriptListExtent->length; scriptIndex++) {\n                Script* script = &(scriptListExtent->scripts[scriptIndex]);\n                if (script->sid != map_script_id && script->procs[proc] > 0) {\n                    sidList[sidListLength++] = script->sid;\n                }\n            }\n            scriptListExtent = scriptListExtent->next;\n        }\n    }\n\n    if (proc == SCRIPT_PROC_MAP_ENTER) {\n        for (int index = 0; index < sidListLength; index++) {\n            Script* script;\n            if (scr_ptr(sidList[index], &script) != -1) {\n                script->fixedParam = fixedParam;\n            }\n\n            exec_script_proc(sidList[index], proc);\n        }\n    } else {\n        for (int index = 0; index < sidListLength; index++) {\n            exec_script_proc(sidList[index], proc);\n        }\n    }\n\n    mem_free(sidList);\n\n    scrSpatialsEnabled = true;\n}\n\n// 0x4A69A0\nvoid scr_exec_map_exit_scripts()\n{\n    scrExecMapProcScripts(SCRIPT_PROC_MAP_EXIT);\n}\n\n// 0x4A6B64\nint scr_get_dialog_msg_file(int a1, MessageList** messageListPtr)\n{\n    if (a1 == -1) {\n        return -1;\n    }\n\n    int messageListIndex = a1 - 1;\n    MessageList* messageList = &(script_dialog_msgs[messageListIndex]);\n    if (messageList->entries_num == 0) {\n        char scriptName[20];\n        scriptName[0] = '\\0';\n        scr_index_to_name(messageListIndex & 0xFFFFFF, scriptName);\n\n        char* pch = strrchr(scriptName, '.');\n        if (pch != NULL) {\n            *pch = '\\0';\n        }\n\n        char path[MAX_PATH];\n        sprintf(path, \"dialog\\\\%s.msg\", scriptName);\n\n        if (!message_load(messageList, path)) {\n            debug_printf(\"\\nError loading script dialog message file!\");\n            return -1;\n        }\n\n        if (!message_filter(messageList)) {\n            debug_printf(\"\\nError filtering script dialog message file!\");\n            return -1;\n        }\n    }\n\n    *messageListPtr = messageList;\n\n    return 0;\n}\n\n// 0x4A6C50\nchar* scr_get_msg_str(int messageListId, int messageId)\n{\n    return scr_get_msg_str_speech(messageListId, messageId, 0);\n}\n\n// 0x4A6C5C\nchar* scr_get_msg_str_speech(int messageListId, int messageId, int a3)\n{\n    // 0x51C7F0\n    static char err_str[] = \"Error\";\n\n    // 0x51C7F4\n    static char blank_str[] = \"\";\n\n    if (messageListId == 0 && messageId == 0) {\n        return blank_str;\n    }\n\n    if (messageListId == -1 && messageId == -1) {\n        return blank_str;\n    }\n\n    if (messageListId == -2 && messageId == -2) {\n        MessageListItem messageListItem;\n        return getmsg(&proto_main_msg_file, &messageListItem, 650);\n    }\n\n    MessageList* messageList;\n    if (scr_get_dialog_msg_file(messageListId, &messageList) == -1) {\n        debug_printf(\"\\nERROR: message_str: can't find message file: List: %d!\", messageListId);\n        return NULL;\n    }\n\n    if (FID_TYPE(dialogue_head) != OBJ_TYPE_HEAD) {\n        a3 = 0;\n    }\n\n    MessageListItem messageListItem;\n    messageListItem.num = messageId;\n    if (!message_search(messageList, &messageListItem)) {\n        debug_printf(\"\\nError: can't find message: List: %d, Num: %d!\", messageListId, messageId);\n        return err_str;\n    }\n\n    if (a3) {\n        if (gdialogActive()) {\n            if (messageListItem.audio != NULL && messageListItem.audio[0] != '\\0') {\n                if (messageListItem.flags & 0x01) {\n                    gdialogSetupSpeech(NULL);\n                } else {\n                    gdialogSetupSpeech(messageListItem.audio);\n                }\n            } else {\n                debug_printf(\"Missing speech name: %d\\n\", messageListItem.num);\n            }\n        }\n    }\n\n    return messageListItem.text;\n}\n\n// 0x4A6D64\nint scr_get_local_var(int sid, int variable, int* value)\n{\n    // 0x667750\n    static char tempStr[20];\n\n    if (SID_TYPE(sid) == SCRIPT_TYPE_SYSTEM) {\n        debug_printf(\"\\nError! System scripts/Map scripts not allowed local_vars! \");\n\n        tempStr[0] = '\\0';\n        scr_index_to_name(sid & 0xFFFFFF, tempStr);\n\n        debug_printf(\":%s\\n\", tempStr);\n\n        *value = -1;\n        return -1;\n    }\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        *value = -1;\n        return -1;\n    }\n\n    if (script->localVarsCount == 0) {\n        // NOTE: Uninline.\n        scr_find_str_run_info(script->field_14, &(script->field_50), sid);\n    }\n\n    if (script->localVarsCount > 0) {\n        if (script->localVarsOffset == -1) {\n            script->localVarsOffset = map_malloc_local_var(script->localVarsCount);\n        }\n\n        *value = map_get_local_var(script->localVarsOffset + variable);\n    }\n\n    return 0;\n}\n\n// 0x4A6E58\nint scr_set_local_var(int sid, int variable, int value)\n{\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        return -1;\n    }\n\n    if (script->localVarsCount == 0) {\n        // NOTE: Uninline.\n        scr_find_str_run_info(script->field_14, &(script->field_50), sid);\n    }\n\n    if (script->localVarsCount <= 0) {\n        return -1;\n    }\n\n    if (script->localVarsOffset == -1) {\n        script->localVarsOffset = map_malloc_local_var(script->localVarsCount);\n    }\n\n    map_set_local_var(script->localVarsOffset + variable, value);\n\n    return 0;\n}\n\n// Performs combat script and returns true if default action has been overriden\n// by script.\n//\n// 0x4A6EFC\nbool scr_end_combat()\n{\n    if (map_script_id == 0 || map_script_id == -1) {\n        return false;\n    }\n\n    int team = combat_player_knocked_out_by();\n    if (team == -1) {\n        return false;\n    }\n\n    Script* before;\n    if (scr_ptr(map_script_id, &before) != -1) {\n        before->fixedParam = team;\n    }\n\n    exec_script_proc(map_script_id, SCRIPT_PROC_COMBAT);\n\n    bool success = false;\n\n    Script* after;\n    if (scr_ptr(map_script_id, &after) != -1) {\n        if (after->scriptOverrides != 0) {\n            success = true;\n        }\n    }\n\n    return success;\n}\n\n// 0x4A6F70\nint scr_explode_scenery(Object* a1, int tile, int radius, int elevation)\n{\n    int scriptExtentsCount = scriptlists[SCRIPT_TYPE_SPATIAL].length + scriptlists[SCRIPT_TYPE_ITEM].length;\n    if (scriptExtentsCount == 0) {\n        return 0;\n    }\n\n    int* scriptIds = (int*)mem_malloc(sizeof(*scriptIds) * scriptExtentsCount * SCRIPT_LIST_EXTENT_SIZE);\n    if (scriptIds == NULL) {\n        return -1;\n    }\n\n    ScriptListExtent* extent;\n    int scriptsCount = 0;\n\n    scrSpatialsEnabled = false;\n\n    extent = scriptlists[SCRIPT_TYPE_ITEM].head;\n    while (extent != NULL) {\n        for (int index = 0; index < extent->length; index++) {\n            Script* script = &(extent->scripts[index]);\n            if (script->procs[SCRIPT_PROC_DAMAGE] <= 0 && script->program == NULL) {\n                exec_script_proc(script->sid, SCRIPT_PROC_START);\n            }\n\n            if (script->procs[SCRIPT_PROC_DAMAGE] > 0) {\n                Object* self = script->owner;\n                if (self != NULL) {\n                    if (self->elevation == elevation && tile_dist(self->tile, tile) <= radius) {\n                        scriptIds[scriptsCount] = script->sid;\n                        scriptsCount += 1;\n                    }\n                }\n            }\n        }\n        extent = extent->next;\n    }\n\n    extent = scriptlists[SCRIPT_TYPE_SPATIAL].head;\n    while (extent != NULL) {\n        for (int index = 0; index < extent->length; index++) {\n            Script* script = &(extent->scripts[index]);\n            if (script->procs[SCRIPT_PROC_DAMAGE] <= 0 && script->program == NULL) {\n                exec_script_proc(script->sid, SCRIPT_PROC_START);\n            }\n\n            if (script->procs[SCRIPT_PROC_DAMAGE] > 0\n                && builtTileGetElevation(script->sp.built_tile) == elevation\n                && tile_dist(builtTileGetTile(script->sp.built_tile), tile) <= radius) {\n                scriptIds[scriptsCount] = script->sid;\n                scriptsCount += 1;\n            }\n        }\n        extent = extent->next;\n    }\n\n    for (int index = 0; index < scriptsCount; index++) {\n        Script* script;\n        int sid = scriptIds[index];\n\n        if (scr_ptr(sid, &script) != -1) {\n            script->fixedParam = 20;\n        }\n\n        // TODO: Obtaining script twice, probably some inlining.\n        if (scr_ptr(sid, &script) != -1) {\n            script->source = NULL;\n            script->target = a1;\n        }\n\n        exec_script_proc(sid, SCRIPT_PROC_DAMAGE);\n    }\n\n    // TODO: Redundant, we already know `scriptIds` is not NULL.\n    if (scriptIds != NULL) {\n        mem_free(scriptIds);\n    }\n\n    scrSpatialsEnabled = true;\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/scripts.h",
    "content": "#ifndef FALLOUT_GAME_SCRIPTS_H_\n#define FALLOUT_GAME_SCRIPTS_H_\n\n#include <stdbool.h>\n\n#include \"game/combat_defs.h\"\n#include \"plib/db/db.h\"\n#include \"int/intrpret.h\"\n#include \"game/message.h\"\n#include \"game/object_types.h\"\n\n#define SCRIPT_FLAG_0x01 0x01\n#define SCRIPT_FLAG_0x02 0x02\n#define SCRIPT_FLAG_0x04 0x04\n#define SCRIPT_FLAG_0x08 0x08\n#define SCRIPT_FLAG_0x10 0x10\n\n// 60 * 60 * 10\n#define GAME_TIME_TICKS_PER_HOUR 36000\n\n// 24 * 60 * 60 * 10\n#define GAME_TIME_TICKS_PER_DAY 864000\n\n// 365 * 24 * 60 * 60 * 10\n#define GAME_TIME_TICKS_PER_YEAR 315360000\n\ntypedef enum ScriptRequests {\n    SCRIPT_REQUEST_COMBAT = 0x01,\n    SCRIPT_REQUEST_TOWN_MAP = 0x02,\n    SCRIPT_REQUEST_WORLD_MAP = 0x04,\n    SCRIPT_REQUEST_ELEVATOR = 0x08,\n    SCRIPT_REQUEST_EXPLOSION = 0x10,\n    SCRIPT_REQUEST_DIALOG = 0x20,\n    SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE = 0x40,\n    SCRIPT_REQUEST_ENDGAME = 0x80,\n    SCRIPT_REQUEST_LOOTING = 0x100,\n    SCRIPT_REQUEST_STEALING = 0x200,\n    SCRIPT_REQUEST_LOCKED = 0x400,\n} ScriptRequests;\n\ntypedef enum ScriptType {\n    SCRIPT_TYPE_SYSTEM, // s_system\n    SCRIPT_TYPE_SPATIAL, // s_spatial\n    SCRIPT_TYPE_TIMED, // s_time\n    SCRIPT_TYPE_ITEM, // s_item\n    SCRIPT_TYPE_CRITTER, // s_critter\n    SCRIPT_TYPE_COUNT,\n} ScriptType;\n\ntypedef enum ScriptProc {\n    SCRIPT_PROC_NO_PROC = 0,\n    SCRIPT_PROC_START = 1,\n    SCRIPT_PROC_SPATIAL = 2,\n    SCRIPT_PROC_DESCRIPTION = 3,\n    SCRIPT_PROC_PICKUP = 4,\n    SCRIPT_PROC_DROP = 5,\n    SCRIPT_PROC_USE = 6,\n    SCRIPT_PROC_USE_OBJ_ON = 7,\n    SCRIPT_PROC_USE_SKILL_ON = 8,\n    SCRIPT_PROC_9 = 9, // use_ad_on_proc\n    SCRIPT_PROC_10 = 10, // use_disad_on_proc\n    SCRIPT_PROC_TALK = 11,\n    SCRIPT_PROC_CRITTER = 12,\n    SCRIPT_PROC_COMBAT = 13,\n    SCRIPT_PROC_DAMAGE = 14,\n    SCRIPT_PROC_MAP_ENTER = 15,\n    SCRIPT_PROC_MAP_EXIT = 16,\n    SCRIPT_PROC_CREATE = 17,\n    SCRIPT_PROC_DESTROY = 18,\n    SCRIPT_PROC_19 = 19, // barter_init_proc\n    SCRIPT_PROC_20 = 20, // barter_proc\n    SCRIPT_PROC_LOOK_AT = 21,\n    SCRIPT_PROC_TIMED = 22,\n    SCRIPT_PROC_MAP_UPDATE = 23,\n    SCRIPT_PROC_PUSH = 24,\n    SCRIPT_PROC_IS_DROPPING = 25,\n    SCRIPT_PROC_COMBAT_IS_STARTING = 26,\n    SCRIPT_PROC_COMBAT_IS_OVER = 27,\n    SCRIPT_PROC_COUNT,\n} ScriptProc;\n\nstatic_assert(SCRIPT_PROC_COUNT == 28, \"wrong count\");\n\ntypedef struct ScriptsListEntry {\n    char name[16];\n    int local_vars_num;\n} ScriptsListEntry;\n\ntypedef struct Script {\n    // scr_id\n    int sid;\n\n    // scr_next\n    int field_4;\n\n    union {\n        struct {\n            // scr_udata.sp.built_tile\n            int built_tile;\n            // scr_udata.sp.radius\n            int radius;\n        } sp;\n        struct {\n            // scr_udata.tm.time\n            int time;\n        } tm;\n    };\n\n    // scr_flags\n    int flags;\n\n    // scr_script_idx\n    int field_14;\n\n    Program* program;\n\n    // scr_oid\n    int field_1C;\n\n    // scr_local_var_offset\n    int localVarsOffset;\n\n    // scr_num_local_vars\n    int localVarsCount;\n\n    // return value?\n    int field_28;\n\n    // Currently executed action.\n    //\n    // See [op_script_action].\n    int action;\n    int fixedParam;\n    Object* owner;\n\n    // source_obj\n    Object* source;\n\n    // target_obj\n    Object* target;\n    int actionBeingUsed;\n    int scriptOverrides;\n    int field_48;\n    int howMuch;\n    int field_50;\n    int procs[SCRIPT_PROC_COUNT];\n    int field_C4;\n    int field_C8;\n    int field_CC;\n    int field_D0;\n    int field_D4;\n    int field_D8;\n    int field_DC;\n} Script;\n\nstatic_assert(sizeof(Script) == 0xE0, \"wrong size\");\n\nextern int num_script_indexes;\nextern Object* scrQueueTestObj;\nextern int scrQueueTestValue;\n\nextern MessageList script_dialog_msgs[1450];\nextern MessageList script_message_file;\n\nint game_time();\nvoid game_time_date(int* monthPtr, int* dayPtr, int* yearPtr);\nint game_time_hour();\nchar* game_time_hour_str();\nvoid inc_game_time(int a1);\nvoid inc_game_time_in_seconds(int a1);\nvoid gameTimeSetTime(int time);\nint gtime_q_add();\nint gtime_q_process(Object* obj, void* data);\nint scriptsCheckGameEvents(int* moviePtr, int window);\nint scr_map_q_process(Object* obj, void* data);\nint new_obj_id();\nint scr_find_sid_from_program(Program* program);\nObject* scr_find_obj_from_program(Program* program);\nint scr_set_objs(int sid, Object* source, Object* target);\nvoid scr_set_ext_param(int a1, int a2);\nint scr_set_action_num(int sid, int a2);\nProgram* loadProgram(const char* name);\nvoid scrSetQueueTestVals(Object* a1, int a2);\nint scrQueueRemoveFixed(Object* obj, void* data);\nint script_q_add(int sid, int delay, int param);\nint script_q_save(File* stream, void* data);\nint script_q_load(File* stream, void** dataPtr);\nint script_q_process(Object* obj, void* data);\nint scripts_clear_state();\nint scripts_clear_combat_requests(Script* script);\nint scripts_check_state();\nint scripts_check_state_in_combat();\nint scripts_request_combat(STRUCT_664980* a1);\nvoid scripts_request_combat_locked(STRUCT_664980* ptr);\nvoid scripts_request_worldmap();\nint scripts_request_elevator(Object* a1, int a2);\nint scripts_request_explosion(int tile, int elevation, int minDamage, int maxDamage);\nvoid scripts_request_dialog(Object* a1);\nvoid scripts_request_endgame_slideshow();\nint scripts_request_loot_container(Object* a1, Object* a2);\nint scripts_request_steal_container(Object* a1, Object* a2);\nvoid script_make_path(char* path);\nint exec_script_proc(int sid, int proc);\nbool scriptHasProc(int sid, int proc);\nint scr_find_str_run_info(int a1, int* a2, int sid);\nint scr_set_dude_script();\nint scr_clear_dude_script();\nint scr_init();\nint scr_reset();\nint scr_game_init();\nint scr_game_reset();\nint scr_exit();\nint scr_message_free();\nint scr_game_exit();\nint scr_enable();\nint scr_disable();\nvoid scr_enable_critters();\nvoid scr_disable_critters();\nint scr_game_save(File* stream);\nint scr_game_load(File* stream);\nint scr_game_load2(File* stream);\nint scr_save(File* stream);\nint scr_load(File* stream);\nint scr_ptr(int sid, Script** script);\nint scr_new(int* sidPtr, int scriptType);\nint scr_remove_local_vars(Script* script);\nint scr_remove(int index);\nint scr_remove_all();\nint scr_remove_all_force();\nScript* scr_find_first_at(int elevation);\nScript* scr_find_next_at();\nvoid scr_spatials_enable();\nvoid scr_spatials_disable();\nbool scr_chk_spatials_in(Object* obj, int tile, int elevation);\nint scr_load_all_scripts();\nvoid scr_exec_map_enter_scripts();\nvoid scr_exec_map_update_scripts();\nvoid scr_exec_map_exit_scripts();\nint scr_get_dialog_msg_file(int a1, MessageList** out_message_list);\nchar* scr_get_msg_str(int messageListId, int messageId);\nchar* scr_get_msg_str_speech(int messageListId, int messageId, int a3);\nint scr_get_local_var(int a1, int a2, int* a3);\nint scr_set_local_var(int a1, int a2, int a3);\nbool scr_end_combat();\nint scr_explode_scenery(Object* a1, int tile, int radius, int elevation);\n\n#endif /* FALLOUT_GAME_SCRIPTS_H_ */\n"
  },
  {
    "path": "src/game/select.c",
    "content": "#include \"game/select.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"game/editor.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/options.h\"\n#include \"game/palette.h\"\n#include \"game/proto.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/trait.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define CS_WINDOW_WIDTH 640\n#define CS_WINDOW_HEIGHT 480\n\n#define CS_WINDOW_BACKGROUND_X 40\n#define CS_WINDOW_BACKGROUND_Y 30\n#define CS_WINDOW_BACKGROUND_WIDTH 560\n#define CS_WINDOW_BACKGROUND_HEIGHT 300\n\n#define CS_WINDOW_PREVIOUS_BUTTON_X 292\n#define CS_WINDOW_PREVIOUS_BUTTON_Y 320\n\n#define CS_WINDOW_NEXT_BUTTON_X 318\n#define CS_WINDOW_NEXT_BUTTON_Y 320\n\n#define CS_WINDOW_TAKE_BUTTON_X 81\n#define CS_WINDOW_TAKE_BUTTON_Y 323\n\n#define CS_WINDOW_MODIFY_BUTTON_X 435\n#define CS_WINDOW_MODIFY_BUTTON_Y 320\n\n#define CS_WINDOW_CREATE_BUTTON_X 80\n#define CS_WINDOW_CREATE_BUTTON_Y 425\n\n#define CS_WINDOW_BACK_BUTTON_X 461\n#define CS_WINDOW_BACK_BUTTON_Y 425\n\n#define CS_WINDOW_NAME_MID_X 318\n#define CS_WINDOW_PRIMARY_STAT_MID_X 362\n#define CS_WINDOW_SECONDARY_STAT_MID_X 379\n#define CS_WINDOW_BIO_X 438\n\ntypedef enum PremadeCharacter {\n    PREMADE_CHARACTER_NARG,\n    PREMADE_CHARACTER_CHITSA,\n    PREMADE_CHARACTER_MINGUN,\n    PREMADE_CHARACTER_COUNT,\n} PremadeCharacter;\n\ntypedef struct PremadeCharacterDescription {\n    char fileName[20];\n    int face;\n    char field_18[20];\n} PremadeCharacterDescription;\n\nstatic void select_exit();\nstatic bool select_update_display();\nstatic bool select_display_portrait();\nstatic bool select_display_stats();\nstatic bool select_display_bio();\nstatic bool select_fatal_error(bool rc);\n\n// 0x51C84C\nstatic int premade_index = PREMADE_CHARACTER_NARG;\n\n// 0x51C850\nstatic PremadeCharacterDescription premade_characters[PREMADE_CHARACTER_COUNT] = {\n    { \"premade\\\\combat\", 201, \"VID 208-197-88-125\" },\n    { \"premade\\\\stealth\", 202, \"VID 208-206-49-229\" },\n    { \"premade\\\\diplomat\", 203, \"VID 208-206-49-227\" },\n};\n\n// 0x51C8D4\nstatic int premade_total = PREMADE_CHARACTER_COUNT;\n\n// 0x51C7F8\nint select_window_id = -1;\n\n// 0x51C7FC\nstatic unsigned char* select_window_buffer = NULL;\n\n// 0x51C800\nstatic unsigned char* monitor = NULL;\n\n// 0x51C804\nstatic int previous_button = -1;\n\n// 0x51C808\nstatic CacheEntry* previous_button_up_key = NULL;\n\n// 0x51C80C\nstatic CacheEntry* previous_button_down_key = NULL;\n\n// 0x51C810\nstatic int next_button = -1;\n\n// 0x51C814\nstatic CacheEntry* next_button_up_key = NULL;\n\n// 0x51C818\nstatic CacheEntry* next_button_down_key = NULL;\n\n// 0x51C81C\nstatic int take_button = -1;\n\n// 0x51C820\nstatic CacheEntry* take_button_up_key = NULL;\n\n// 0x51C824\nstatic CacheEntry* take_button_down_key = NULL;\n\n// 0x51C828\nstatic int modify_button = -1;\n\n// 0x51C82C\nstatic CacheEntry* modify_button_up_key = NULL;\n\n// 0x51C830\nstatic CacheEntry* modify_button_down_key = NULL;\n\n// 0x51C834\nstatic int create_button = -1;\n\n// 0x51C838\nstatic CacheEntry* create_button_up_key = NULL;\n\n// 0x51C83C\nstatic CacheEntry* create_button_down_key = NULL;\n\n// 0x51C840\nstatic int back_button = -1;\n\n// 0x51C844\nstatic CacheEntry* back_button_up_key = NULL;\n\n// 0x51C848\nstatic CacheEntry* back_button_down_key = NULL;\n\n// 0x667764\nstatic unsigned char* take_button_up;\n\n// 0x667768\nstatic unsigned char* modify_button_down;\n\n// 0x66776C\nstatic unsigned char* back_button_up;\n\n// 0x667770\nstatic unsigned char* create_button_up;\n\n// 0x667774\nstatic unsigned char* modify_button_up;\n\n// 0x667778\nstatic unsigned char* back_button_down;\n\n// 0x66777C\nstatic unsigned char* create_button_down;\n\n// 0x667780\nstatic unsigned char* take_button_down;\n\n// 0x667784\nstatic unsigned char* next_button_down;\n\n// 0x667788\nstatic unsigned char* next_button_up;\n\n// 0x66778C\nstatic unsigned char* previous_button_up;\n\n// 0x667790\nstatic unsigned char* previous_button_down;\n\n// 0x4A71D0\nint select_character()\n{\n    if (!select_init()) {\n        return 0;\n    }\n\n    bool cursorWasHidden = mouse_hidden();\n    if (cursorWasHidden) {\n        mouse_show();\n    }\n\n    loadColorTable(\"color.pal\");\n    palette_fade_to(cmap);\n\n    int rc = 0;\n    bool done = false;\n    while (!done) {\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        int keyCode = get_input();\n\n        switch (keyCode) {\n        case KEY_MINUS:\n        case KEY_UNDERSCORE:\n            DecGamma();\n            break;\n        case KEY_EQUAL:\n        case KEY_PLUS:\n            IncGamma();\n            break;\n        case KEY_UPPERCASE_B:\n        case KEY_LOWERCASE_B:\n        case KEY_ESCAPE:\n            rc = 3;\n            done = true;\n            break;\n        case KEY_UPPERCASE_C:\n        case KEY_LOWERCASE_C:\n            ResetPlayer();\n            if (editor_design(1) == 0) {\n                rc = 2;\n                done = true;\n            } else {\n                select_update_display();\n            }\n\n            break;\n        case KEY_UPPERCASE_M:\n        case KEY_LOWERCASE_M:\n            if (!editor_design(1)) {\n                rc = 2;\n                done = true;\n            } else {\n                select_update_display();\n            }\n\n            break;\n        case KEY_UPPERCASE_T:\n        case KEY_LOWERCASE_T:\n            rc = 2;\n            done = true;\n\n            break;\n        case KEY_F10:\n            game_quit_with_confirm();\n            break;\n        case KEY_ARROW_LEFT:\n            gsound_play_sfx_file(\"ib2p1xx1\");\n            // FALLTHROUGH\n        case 500:\n            premade_index -= 1;\n            if (premade_index < 0) {\n                premade_index = premade_total - 1;\n            }\n\n            select_update_display();\n            break;\n        case KEY_ARROW_RIGHT:\n            gsound_play_sfx_file(\"ib2p1xx1\");\n            // FALLTHROUGH\n        case 501:\n            premade_index += 1;\n            if (premade_index >= premade_total) {\n                premade_index = 0;\n            }\n\n            select_update_display();\n            break;\n        }\n    }\n\n    palette_fade_to(black_palette);\n    select_exit();\n\n    if (cursorWasHidden) {\n        mouse_hide();\n    }\n\n    return rc;\n}\n\n// 0x4A7468\nbool select_init()\n{\n    int backgroundFid;\n    unsigned char* backgroundFrmData;\n\n    if (select_window_id != -1) {\n        return false;\n    }\n\n    int characterSelectorWindowX = 0;\n    int characterSelectorWindowY = 0;\n    select_window_id = win_add(characterSelectorWindowX, characterSelectorWindowY, CS_WINDOW_WIDTH, CS_WINDOW_HEIGHT, colorTable[0], 0);\n    if (select_window_id == -1) {\n        return select_fatal_error(false);\n    }\n\n    select_window_buffer = win_get_buf(select_window_id);\n    if (select_window_buffer == NULL) {\n        return select_fatal_error(false);\n    }\n\n    CacheEntry* backgroundFrmHandle;\n    backgroundFid = art_id(OBJ_TYPE_INTERFACE, 174, 0, 0, 0);\n    backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle);\n    if (backgroundFrmData == NULL) {\n        return select_fatal_error(false);\n    }\n\n    buf_to_buf(backgroundFrmData,\n        CS_WINDOW_WIDTH,\n        CS_WINDOW_HEIGHT,\n        CS_WINDOW_WIDTH,\n        select_window_buffer,\n        CS_WINDOW_WIDTH);\n\n    monitor = (unsigned char*)mem_malloc(CS_WINDOW_BACKGROUND_WIDTH * CS_WINDOW_BACKGROUND_HEIGHT);\n    if (monitor == NULL)\n        return select_fatal_error(false);\n\n    buf_to_buf(backgroundFrmData + CS_WINDOW_WIDTH * CS_WINDOW_BACKGROUND_Y + CS_WINDOW_BACKGROUND_X,\n        CS_WINDOW_BACKGROUND_WIDTH,\n        CS_WINDOW_BACKGROUND_HEIGHT,\n        CS_WINDOW_WIDTH,\n        monitor,\n        CS_WINDOW_BACKGROUND_WIDTH);\n\n    art_ptr_unlock(backgroundFrmHandle);\n\n    int fid;\n\n    // Setup \"Previous\" button.\n    fid = art_id(OBJ_TYPE_INTERFACE, 122, 0, 0, 0);\n    previous_button_up = art_ptr_lock_data(fid, 0, 0, &previous_button_up_key);\n    if (previous_button_up == NULL) {\n        return select_fatal_error(false);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 123, 0, 0, 0);\n    previous_button_down = art_ptr_lock_data(fid, 0, 0, &previous_button_down_key);\n    if (previous_button_down == NULL) {\n        return select_fatal_error(false);\n    }\n\n    previous_button = win_register_button(select_window_id,\n        CS_WINDOW_PREVIOUS_BUTTON_X,\n        CS_WINDOW_PREVIOUS_BUTTON_Y,\n        20,\n        18,\n        -1,\n        -1,\n        -1,\n        500,\n        previous_button_up,\n        previous_button_down,\n        NULL,\n        0);\n    if (previous_button == -1) {\n        return select_fatal_error(false);\n    }\n\n    win_register_button_sound_func(previous_button, gsound_med_butt_press, gsound_med_butt_release);\n\n    // Setup \"Next\" button.\n    fid = art_id(OBJ_TYPE_INTERFACE, 124, 0, 0, 0);\n    next_button_up = art_ptr_lock_data(fid, 0, 0, &next_button_up_key);\n    if (next_button_up == NULL) {\n        return select_fatal_error(false);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 125, 0, 0, 0);\n    next_button_down = art_ptr_lock_data(fid, 0, 0, &next_button_down_key);\n    if (next_button_down == NULL) {\n        return select_fatal_error(false);\n    }\n\n    next_button = win_register_button(select_window_id,\n        CS_WINDOW_NEXT_BUTTON_X,\n        CS_WINDOW_NEXT_BUTTON_Y,\n        20,\n        18,\n        -1,\n        -1,\n        -1,\n        501,\n        next_button_up,\n        next_button_down,\n        NULL,\n        0);\n    if (next_button == -1) {\n        return select_fatal_error(false);\n    }\n\n    win_register_button_sound_func(next_button, gsound_med_butt_press, gsound_med_butt_release);\n\n    // Setup \"Take\" button.\n    fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n    take_button_up = art_ptr_lock_data(fid, 0, 0, &take_button_up_key);\n    if (take_button_up == NULL) {\n        return select_fatal_error(false);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n    take_button_down = art_ptr_lock_data(fid, 0, 0, &take_button_down_key);\n    if (take_button_down == NULL) {\n        return select_fatal_error(false);\n    }\n\n    take_button = win_register_button(select_window_id,\n        CS_WINDOW_TAKE_BUTTON_X,\n        CS_WINDOW_TAKE_BUTTON_Y,\n        15,\n        16,\n        -1,\n        -1,\n        -1,\n        KEY_LOWERCASE_T,\n        take_button_up,\n        take_button_down,\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (take_button == -1) {\n        return select_fatal_error(false);\n    }\n\n    win_register_button_sound_func(take_button, gsound_red_butt_press, gsound_red_butt_release);\n\n    // Setup \"Modify\" button.\n    fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n    modify_button_up = art_ptr_lock_data(fid, 0, 0, &modify_button_up_key);\n    if (modify_button_up == NULL)\n        return select_fatal_error(false);\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n    modify_button_down = art_ptr_lock_data(fid, 0, 0, &modify_button_down_key);\n    if (modify_button_down == NULL) {\n        return select_fatal_error(false);\n    }\n\n    modify_button = win_register_button(select_window_id,\n        CS_WINDOW_MODIFY_BUTTON_X,\n        CS_WINDOW_MODIFY_BUTTON_Y,\n        15,\n        16,\n        -1,\n        -1,\n        -1,\n        KEY_LOWERCASE_M,\n        modify_button_up,\n        modify_button_down,\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (modify_button == -1) {\n        return select_fatal_error(false);\n    }\n\n    win_register_button_sound_func(modify_button, gsound_red_butt_press, gsound_red_butt_release);\n\n    // Setup \"Create\" button.\n    fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n    create_button_up = art_ptr_lock_data(fid, 0, 0, &create_button_up_key);\n    if (create_button_up == NULL) {\n        return select_fatal_error(false);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n    create_button_down = art_ptr_lock_data(fid, 0, 0, &create_button_down_key);\n    if (create_button_down == NULL) {\n        return select_fatal_error(false);\n    }\n\n    create_button = win_register_button(select_window_id,\n        CS_WINDOW_CREATE_BUTTON_X,\n        CS_WINDOW_CREATE_BUTTON_Y,\n        15,\n        16,\n        -1,\n        -1,\n        -1,\n        KEY_LOWERCASE_C,\n        create_button_up,\n        create_button_down,\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (create_button == -1) {\n        return select_fatal_error(false);\n    }\n\n    win_register_button_sound_func(create_button, gsound_red_butt_press, gsound_red_butt_release);\n\n    // Setup \"Back\" button.\n    fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n    back_button_up = art_ptr_lock_data(fid, 0, 0, &back_button_up_key);\n    if (back_button_up == NULL) {\n        return select_fatal_error(false);\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n    back_button_down = art_ptr_lock_data(fid, 0, 0, &back_button_down_key);\n    if (back_button_down == NULL) {\n        return select_fatal_error(false);\n    }\n\n    back_button = win_register_button(select_window_id,\n        CS_WINDOW_BACK_BUTTON_X,\n        CS_WINDOW_BACK_BUTTON_Y,\n        15,\n        16,\n        -1,\n        -1,\n        -1,\n        KEY_ESCAPE,\n        back_button_up,\n        back_button_down,\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (back_button == -1) {\n        return select_fatal_error(false);\n    }\n\n    win_register_button_sound_func(back_button, gsound_red_butt_press, gsound_red_butt_release);\n\n    premade_index = PREMADE_CHARACTER_NARG;\n\n    win_draw(select_window_id);\n\n    if (!select_update_display()) {\n        return select_fatal_error(false);\n    }\n\n    return true;\n}\n\n// 0x4A7AD4\nstatic void select_exit()\n{\n    if (select_window_id == -1) {\n        return;\n    }\n\n    if (previous_button != -1) {\n        win_delete_button(previous_button);\n        previous_button = -1;\n    }\n\n    if (previous_button_down != NULL) {\n        art_ptr_unlock(previous_button_down_key);\n        previous_button_down_key = NULL;\n        previous_button_down = NULL;\n    }\n\n    if (previous_button_up != NULL) {\n        art_ptr_unlock(previous_button_up_key);\n        previous_button_up_key = NULL;\n        previous_button_up = NULL;\n    }\n\n    if (next_button != -1) {\n        win_delete_button(next_button);\n        next_button = -1;\n    }\n\n    if (next_button_down != NULL) {\n        art_ptr_unlock(next_button_down_key);\n        next_button_down_key = NULL;\n        next_button_down = NULL;\n    }\n\n    if (next_button_up != NULL) {\n        art_ptr_unlock(next_button_up_key);\n        next_button_up_key = NULL;\n        next_button_up = NULL;\n    }\n\n    if (take_button != -1) {\n        win_delete_button(take_button);\n        take_button = -1;\n    }\n\n    if (take_button_down != NULL) {\n        art_ptr_unlock(take_button_down_key);\n        take_button_down_key = NULL;\n        take_button_down = NULL;\n    }\n\n    if (take_button_up != NULL) {\n        art_ptr_unlock(take_button_up_key);\n        take_button_up_key = NULL;\n        take_button_up = NULL;\n    }\n\n    if (modify_button != -1) {\n        win_delete_button(modify_button);\n        modify_button = -1;\n    }\n\n    if (modify_button_down != NULL) {\n        art_ptr_unlock(modify_button_down_key);\n        modify_button_down_key = NULL;\n        modify_button_down = NULL;\n    }\n\n    if (modify_button_up != NULL) {\n        art_ptr_unlock(modify_button_up_key);\n        modify_button_up_key = NULL;\n        modify_button_up = NULL;\n    }\n\n    if (create_button != -1) {\n        win_delete_button(create_button);\n        create_button = -1;\n    }\n\n    if (create_button_down != NULL) {\n        art_ptr_unlock(create_button_down_key);\n        create_button_down_key = NULL;\n        create_button_down = NULL;\n    }\n\n    if (create_button_up != NULL) {\n        art_ptr_unlock(create_button_up_key);\n        create_button_up_key = NULL;\n        create_button_up = NULL;\n    }\n\n    if (back_button != -1) {\n        win_delete_button(back_button);\n        back_button = -1;\n    }\n\n    if (back_button_down != NULL) {\n        art_ptr_unlock(back_button_down_key);\n        back_button_down_key = NULL;\n        back_button_down = NULL;\n    }\n\n    if (back_button_up != NULL) {\n        art_ptr_unlock(back_button_up_key);\n        back_button_up_key = NULL;\n        back_button_up = NULL;\n    }\n\n    if (monitor != NULL) {\n        mem_free(monitor);\n        monitor = NULL;\n    }\n\n    win_delete(select_window_id);\n    select_window_id = -1;\n}\n\n// 0x4A7D58\nstatic bool select_update_display()\n{\n    char path[FILENAME_MAX];\n    sprintf(path, \"%s.gcd\", premade_characters[premade_index].fileName);\n    if (proto_dude_init(path) == -1) {\n        debug_printf(\"\\n ** Error in dude init! **\\n\");\n        return false;\n    }\n\n    buf_to_buf(monitor,\n        CS_WINDOW_BACKGROUND_WIDTH,\n        CS_WINDOW_BACKGROUND_HEIGHT,\n        CS_WINDOW_BACKGROUND_WIDTH,\n        select_window_buffer + CS_WINDOW_WIDTH * CS_WINDOW_BACKGROUND_Y + CS_WINDOW_BACKGROUND_X,\n        CS_WINDOW_WIDTH);\n\n    bool success = false;\n    if (select_display_portrait()) {\n        if (select_display_stats()) {\n            success = select_display_bio();\n        }\n    }\n\n    win_draw(select_window_id);\n\n    return success;\n}\n\n// 0x4A7E08\nstatic bool select_display_portrait()\n{\n    bool success = false;\n\n    CacheEntry* faceFrmHandle;\n    int faceFid = art_id(OBJ_TYPE_INTERFACE, premade_characters[premade_index].face, 0, 0, 0);\n    Art* frm = art_ptr_lock(faceFid, &faceFrmHandle);\n    if (frm != NULL) {\n        unsigned char* data = art_frame_data(frm, 0, 0);\n        if (data != NULL) {\n            int width = art_frame_width(frm, 0, 0);\n            int height = art_frame_length(frm, 0, 0);\n            trans_buf_to_buf(data, width, height, width, (select_window_buffer + CS_WINDOW_WIDTH * 23 + 27), CS_WINDOW_WIDTH);\n            success = true;\n        }\n        art_ptr_unlock(faceFrmHandle);\n    }\n\n    return success;\n}\n\n// 0x4A7EA8\nstatic bool select_display_stats()\n{\n    char* str;\n    char text[260];\n    int length;\n    int value;\n    MessageListItem messageListItem;\n\n    int oldFont = text_curr();\n    text_font(101);\n\n    text_char_width(0x20);\n\n    int vh = text_height();\n    int y = 40;\n\n    // NAME\n    str = object_name(obj_dude);\n    strcpy(text, str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_NAME_MID_X - (length / 2), text, 160, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // STRENGTH\n    y += vh + vh + vh;\n\n    value = critterGetStat(obj_dude, STAT_STRENGTH);\n    str = stat_name(STAT_STRENGTH);\n\n    sprintf(text, \"%s %02d\", str, value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    str = stat_level_description(value);\n    sprintf(text, \"  %s\", str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // PERCEPTION\n    y += vh;\n\n    value = critterGetStat(obj_dude, STAT_PERCEPTION);\n    str = stat_name(STAT_PERCEPTION);\n\n    sprintf(text, \"%s %02d\", str, value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    str = stat_level_description(value);\n    sprintf(text, \"  %s\", str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // ENDURANCE\n    y += vh;\n\n    value = critterGetStat(obj_dude, STAT_ENDURANCE);\n    str = stat_name(STAT_PERCEPTION);\n\n    sprintf(text, \"%s %02d\", str, value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    str = stat_level_description(value);\n    sprintf(text, \"  %s\", str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // CHARISMA\n    y += vh;\n\n    value = critterGetStat(obj_dude, STAT_CHARISMA);\n    str = stat_name(STAT_CHARISMA);\n\n    sprintf(text, \"%s %02d\", str, value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    str = stat_level_description(value);\n    sprintf(text, \"  %s\", str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // INTELLIGENCE\n    y += vh;\n\n    value = critterGetStat(obj_dude, STAT_INTELLIGENCE);\n    str = stat_name(STAT_INTELLIGENCE);\n\n    sprintf(text, \"%s %02d\", str, value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    str = stat_level_description(value);\n    sprintf(text, \"  %s\", str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // AGILITY\n    y += vh;\n\n    value = critterGetStat(obj_dude, STAT_AGILITY);\n    str = stat_name(STAT_AGILITY);\n\n    sprintf(text, \"%s %02d\", str, value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    str = stat_level_description(value);\n    sprintf(text, \"  %s\", str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // LUCK\n    y += vh;\n\n    value = critterGetStat(obj_dude, STAT_LUCK);\n    str = stat_name(STAT_LUCK);\n\n    sprintf(text, \"%s %02d\", str, value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    str = stat_level_description(value);\n    sprintf(text, \"  %s\", str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    y += vh; // blank line\n\n    // HIT POINTS\n    y += vh;\n\n    messageListItem.num = 16;\n    text[0] = '\\0';\n    if (message_search(&misc_message_file, &messageListItem)) {\n        strcpy(text, messageListItem.text);\n    }\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    value = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n    sprintf(text, \" %d/%d\", critter_get_hits(obj_dude), value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // ARMOR CLASS\n    y += vh;\n\n    str = stat_name(STAT_ARMOR_CLASS);\n    strcpy(text, str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    value = critterGetStat(obj_dude, STAT_ARMOR_CLASS);\n    sprintf(text, \" %d\", value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // ACTION POINTS\n    y += vh;\n\n    messageListItem.num = 15;\n    text[0] = '\\0';\n    if (message_search(&misc_message_file, &messageListItem)) {\n        strcpy(text, messageListItem.text);\n    }\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    value = critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS);\n    sprintf(text, \" %d\", value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    // MELEE DAMAGE\n    y += vh;\n\n    str = stat_name(STAT_ARMOR_CLASS);\n    strcpy(text, str);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    value = critterGetStat(obj_dude, STAT_ARMOR_CLASS);\n    sprintf(text, \" %d\", value);\n\n    length = text_width(text);\n    text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n    y += vh; // blank line\n\n    // SKILLS\n    int skills[DEFAULT_TAGGED_SKILLS];\n    skill_get_tags(skills, DEFAULT_TAGGED_SKILLS);\n\n    for (int index = 0; index < DEFAULT_TAGGED_SKILLS; index++) {\n        y += vh;\n\n        str = skill_name(skills[index]);\n        strcpy(text, str);\n\n        length = text_width(text);\n        text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n\n        value = skill_level(obj_dude, skills[index]);\n        sprintf(text, \" %d%%\", value);\n\n        length = text_width(text);\n        text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n    }\n\n    // TRAITS\n    int traits[TRAITS_MAX_SELECTED_COUNT];\n    trait_get(&(traits[0]), &(traits[1]));\n\n    for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) {\n        y += vh;\n\n        str = trait_name(traits[index]);\n        strcpy(text, str);\n\n        length = text_width(text);\n        text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]);\n    }\n\n    text_font(oldFont);\n\n    return true;\n}\n\n// 0x4A8AE4\nstatic bool select_display_bio()\n{\n    int oldFont = text_curr();\n    text_font(101);\n\n    char path[FILENAME_MAX];\n    sprintf(path, \"%s.bio\", premade_characters[premade_index].fileName);\n\n    File* stream = db_fopen(path, \"rt\");\n    if (stream != NULL) {\n        int y = 40;\n        int lineHeight = text_height();\n\n        char string[256];\n        while (db_fgets(string, 256, stream) && y < 260) {\n            text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_BIO_X, string, CS_WINDOW_WIDTH - CS_WINDOW_BIO_X, CS_WINDOW_WIDTH, colorTable[992]);\n            y += lineHeight;\n        }\n\n        db_fclose(stream);\n    }\n\n    text_font(oldFont);\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x4A8BD0\nstatic bool select_fatal_error(bool rc)\n{\n    select_exit();\n    return rc;\n}\n"
  },
  {
    "path": "src/game/select.h",
    "content": "#ifndef FALLOUT_GAME_SELECT_H_\n#define FALLOUT_GAME_SELECT_H_\n\n#include <stdbool.h>\n\nextern int select_window_id;\n\nint select_character();\nbool select_init();\n\n#endif /* FALLOUT_GAME_SELECT_H_ */\n"
  },
  {
    "path": "src/game/selfrun.c",
    "content": "#include \"game/selfrun.h\"\n\n#include <direct.h>\n#include <stdlib.h>\n\n#include \"plib/gnw/input.h\"\n#include \"plib/db/db.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"plib/gnw/vcr.h\"\n\ntypedef enum SelfrunState {\n    SELFRUN_STATE_TURNED_OFF,\n    SELFRUN_STATE_PLAYING,\n    SELFRUN_STATE_RECORDING,\n} SelfrunState;\n\nstatic void selfrun_playback_callback(int reason);\nstatic int selfrun_load_data(const char* path, SelfrunData* selfrunData);\nstatic int selfrun_save_data(const char* path, SelfrunData* selfrunData);\n\n// 0x51C8D8\nstatic int selfrun_state = SELFRUN_STATE_TURNED_OFF;\n\n// 0x4A8BE0\nint selfrun_get_list(char*** fileListPtr, int* fileListLengthPtr)\n{\n    if (fileListPtr == NULL) {\n        return -1;\n    }\n\n    if (fileListLengthPtr == NULL) {\n        return -1;\n    }\n\n    *fileListLengthPtr = db_get_file_list(\"selfrun\\\\*.sdf\", fileListPtr, 0, 0);\n\n    return 0;\n}\n\n// 0x4A8C10\nint selfrun_free_list(char*** fileListPtr)\n{\n    if (fileListPtr == NULL) {\n        return -1;\n    }\n\n    db_free_file_list(fileListPtr, 0);\n\n    return 0;\n}\n\n// 0x4A8C28\nint selfrun_prep_playback(const char* fileName, SelfrunData* selfrunData)\n{\n    if (fileName == NULL) {\n        return -1;\n    }\n\n    if (selfrunData == NULL) {\n        return -1;\n    }\n\n    if (vcr_status() != VCR_STATE_TURNED_OFF) {\n        return -1;\n    }\n\n    if (selfrun_state != SELFRUN_STATE_TURNED_OFF) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", \"selfrun\\\\\", fileName);\n\n    if (selfrun_load_data(path, selfrunData) != 0) {\n        return -1;\n    }\n\n    selfrun_state = SELFRUN_STATE_PLAYING;\n\n    return 0;\n}\n\n// 0x4A8C88\nvoid selfrun_playback_loop(SelfrunData* selfrunData)\n{\n    if (selfrun_state == SELFRUN_STATE_PLAYING) {\n        char path[MAX_PATH];\n        sprintf(path, \"%s%s\", \"selfrun\\\\\", selfrunData->recordingFileName);\n\n        if (vcr_play(path, VCR_TERMINATE_ON_KEY_PRESS | VCR_TERMINATE_ON_MOUSE_PRESS, selfrun_playback_callback)) {\n            bool cursorWasHidden = mouse_hidden();\n            if (cursorWasHidden) {\n                mouse_show();\n            }\n\n            while (selfrun_state == SELFRUN_STATE_PLAYING) {\n                int keyCode = get_input();\n                if (keyCode != selfrunData->stopKeyCode) {\n                    game_handle_input(keyCode, false);\n                }\n            }\n\n            while (mouse_get_buttons() != 0) {\n                get_input();\n            }\n\n            if (cursorWasHidden) {\n                mouse_hide();\n            }\n        }\n    }\n}\n\n// 0x4A8D28\nint selfrun_prep_recording(const char* recordingName, const char* mapFileName, SelfrunData* selfrunData)\n{\n    if (recordingName == NULL) {\n        return -1;\n    }\n\n    if (mapFileName == NULL) {\n        return -1;\n    }\n\n    if (vcr_status() != VCR_STATE_TURNED_OFF) {\n        return -1;\n    }\n\n    if (selfrun_state != SELFRUN_STATE_TURNED_OFF) {\n        return -1;\n    }\n\n    sprintf(selfrunData->recordingFileName, \"%s%s\", recordingName, \".vcr\");\n    strcpy(selfrunData->mapFileName, mapFileName);\n\n    selfrunData->stopKeyCode = KEY_CTRL_R;\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s%s\", \"selfrun\\\\\", recordingName, \".sdf\");\n\n    if (selfrun_save_data(path, selfrunData) != 0) {\n        return -1;\n    }\n\n    selfrun_state = SELFRUN_STATE_RECORDING;\n\n    return 0;\n}\n\n// 0x4A8DDC\nvoid selfrun_recording_loop(SelfrunData* selfrunData)\n{\n    if (selfrun_state == SELFRUN_STATE_RECORDING) {\n        char path[MAX_PATH];\n        sprintf(path, \"%s%s\", \"selfrun\\\\\", selfrunData->recordingFileName);\n        if (vcr_record(path)) {\n            if (!mouse_hidden()) {\n                mouse_show();\n            }\n\n            bool done = false;\n            while (!done) {\n                int keyCode = get_input();\n                if (keyCode == selfrunData->stopKeyCode) {\n                    vcr_stop();\n                    game_user_wants_to_quit = 2;\n                    done = true;\n                } else {\n                    game_handle_input(keyCode, false);\n                }\n            }\n        }\n        selfrun_state = SELFRUN_STATE_TURNED_OFF;\n    }\n}\n\n// 0x4A8E74\nstatic void selfrun_playback_callback(int reason)\n{\n    game_user_wants_to_quit = 2;\n    selfrun_state = SELFRUN_STATE_TURNED_OFF;\n}\n\n// 0x4A8E8C\nstatic int selfrun_load_data(const char* path, SelfrunData* selfrunData)\n{\n    if (path == NULL) {\n        return -1;\n    }\n\n    if (selfrunData == NULL) {\n        return -1;\n    }\n\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int rc = -1;\n    if (db_freadByteCount(stream, selfrunData->recordingFileName, SELFRUN_RECORDING_FILE_NAME_LENGTH) == 0\n        && db_freadByteCount(stream, selfrunData->mapFileName, SELFRUN_MAP_FILE_NAME_LENGTH) == 0\n        && db_freadInt(stream, &(selfrunData->stopKeyCode)) == 0) {\n        rc = 0;\n    }\n\n    db_fclose(stream);\n\n    return rc;\n}\n\n// 0x4A8EF4\nstatic int selfrun_save_data(const char* path, SelfrunData* selfrunData)\n{\n    if (path == NULL) {\n        return -1;\n    }\n\n    if (selfrunData == NULL) {\n        return -1;\n    }\n\n    char* masterPatches;\n    config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatches);\n\n    char selfrunDirectoryPath[MAX_PATH];\n    sprintf(selfrunDirectoryPath, \"%s\\\\%s\", masterPatches, \"selfrun\\\\\");\n\n    mkdir(selfrunDirectoryPath);\n\n    File* stream = db_fopen(path, \"wb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int rc = -1;\n    if (db_fwriteByteCount(stream, selfrunData->recordingFileName, SELFRUN_RECORDING_FILE_NAME_LENGTH) == 0\n        && db_fwriteByteCount(stream, selfrunData->mapFileName, SELFRUN_MAP_FILE_NAME_LENGTH) == 0\n        && db_fwriteInt(stream, selfrunData->stopKeyCode) == 0) {\n        rc = 0;\n    }\n\n    db_fclose(stream);\n\n    return rc;\n}\n"
  },
  {
    "path": "src/game/selfrun.h",
    "content": "#ifndef FALLOUT_GAME_SELFRUN_H_\n#define FALLOUT_GAME_SELFRUN_H_\n\n#define SELFRUN_RECORDING_FILE_NAME_LENGTH 13\n#define SELFRUN_MAP_FILE_NAME_LENGTH 13\n\ntypedef struct SelfrunData {\n    char recordingFileName[SELFRUN_RECORDING_FILE_NAME_LENGTH];\n    char mapFileName[SELFRUN_MAP_FILE_NAME_LENGTH];\n    int stopKeyCode;\n} SelfrunData;\n\nstatic_assert(sizeof(SelfrunData) == 32, \"wrong size\");\n\nint selfrun_get_list(char*** fileListPtr, int* fileListLengthPtr);\nint selfrun_free_list(char*** fileListPtr);\nint selfrun_prep_playback(const char* fileName, SelfrunData* selfrunData);\nvoid selfrun_playback_loop(SelfrunData* selfrunData);\nint selfrun_prep_recording(const char* recordingName, const char* mapFileName, SelfrunData* selfrunData);\nvoid selfrun_recording_loop(SelfrunData* selfrunData);\n\n#endif /* FALLOUT_GAME_SELFRUN_H_ */\n"
  },
  {
    "path": "src/game/sfxcache.c",
    "content": "#include \"game/sfxcache.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/db/db.h\"\n#include \"game/cache.h\"\n#include \"game/gconfig.h\"\n#include \"plib/gnw/memory.h\"\n#include \"sound_decoder.h\"\n#include \"game/sfxlist.h\"\n\n#define SOUND_EFFECTS_CACHE_MIN_SIZE 0x40000\n\ntypedef struct SoundEffect {\n    // NOTE: This field is only 1 byte, likely unsigned char. It always uses\n    // cmp for checking implying it's not bitwise flags. Therefore it's better\n    // to express it as boolean.\n    bool used;\n    CacheEntry* cacheHandle;\n    int tag;\n    int dataSize;\n    int fileSize;\n    // TODO: Make size_t.\n    int position;\n    int dataPosition;\n    unsigned char* data;\n} SoundEffect;\n\nstatic_assert(sizeof(SoundEffect) == 32, \"wrong size\");\n\nstatic int sfxc_effect_size(int tag, int* sizePtr);\nstatic int sfxc_effect_load(int tag, int* sizePtr, unsigned char* data);\nstatic void sfxc_effect_free(void* ptr);\nstatic int sfxc_handle_list_create();\nstatic void sfxc_handle_list_destroy();\nstatic int sfxc_handle_create(int* handlePtr, int id, void* data, CacheEntry* cacheHandle);\nstatic void sfxc_handle_destroy(int handle);\nstatic bool sfxc_handle_is_legal(int a1);\nstatic bool sfxc_mode_is_legal(int mode);\nstatic int sfxc_decode(int handle, void* buf, unsigned int size);\nstatic int sfxc_ad_reader(int handle, void* buf, unsigned int size);\n\n// 0x51C8F0\nstatic bool sfxc_initialized = false;\n\n// 0x51C8F4\nstatic int sfxc_cmpr = 1;\n\n// 0x51C8EC\nstatic Cache* sfxc_pcache = NULL;\n\n// 0x51C8DC\nstatic int sfxc_dlevel = INT_MAX;\n\n// 0x51C8E0\nstatic char* sfxc_effect_path = NULL;\n\n// 0x51C8E4\nstatic SoundEffect* sfxc_handle_list = NULL;\n\n// 0x51C8E8\nstatic int sfxc_files_open = 0;\n\n// 0x4A8FC0\nint sfxc_init(int cacheSize, const char* effectsPath)\n{\n    if (!config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEBUG_SFXC_KEY, &sfxc_dlevel)) {\n        sfxc_dlevel = 1;\n    }\n\n    if (cacheSize <= SOUND_EFFECTS_CACHE_MIN_SIZE) {\n        return -1;\n    }\n\n    if (effectsPath == NULL) {\n        effectsPath = \"\";\n    }\n\n    sfxc_effect_path = mem_strdup(effectsPath);\n    if (sfxc_effect_path == NULL) {\n        return -1;\n    }\n\n    if (sfxl_init(sfxc_effect_path, sfxc_cmpr, sfxc_dlevel) != SFXL_OK) {\n        mem_free(sfxc_effect_path);\n        return -1;\n    }\n\n    if (sfxc_handle_list_create() != 0) {\n        sfxl_exit();\n        mem_free(sfxc_effect_path);\n        return -1;\n    }\n\n    sfxc_pcache = (Cache*)mem_malloc(sizeof(*sfxc_pcache));\n    if (sfxc_pcache == NULL) {\n        sfxc_handle_list_destroy();\n        sfxl_exit();\n        mem_free(sfxc_effect_path);\n        return -1;\n    }\n\n    if (!cache_init(sfxc_pcache, sfxc_effect_size, sfxc_effect_load, sfxc_effect_free, cacheSize)) {\n        mem_free(sfxc_pcache);\n        sfxc_handle_list_destroy();\n        sfxl_exit();\n        mem_free(sfxc_effect_path);\n        return -1;\n    }\n\n    sfxc_initialized = true;\n\n    return 0;\n}\n\n// 0x4A90FC\nvoid sfxc_exit()\n{\n    if (sfxc_initialized) {\n        cache_exit(sfxc_pcache);\n        mem_free(sfxc_pcache);\n        sfxc_pcache = NULL;\n\n        sfxc_handle_list_destroy();\n\n        sfxl_exit();\n\n        mem_free(sfxc_effect_path);\n\n        sfxc_initialized = false;\n    }\n}\n\n// 0x4A9140\nint sfxc_is_initialized()\n{\n    return sfxc_initialized;\n}\n\n// 0x4A9148\nvoid sfxc_flush()\n{\n    if (sfxc_initialized) {\n        cache_flush(sfxc_pcache);\n    }\n}\n\n// 0x4A915C\nint sfxc_cached_open(const char* fname, int mode, ...)\n{\n    if (sfxc_files_open >= SOUND_EFFECTS_MAX_COUNT) {\n        return -1;\n    }\n\n    char* copy = mem_strdup(fname);\n    if (copy == NULL) {\n        return -1;\n    }\n\n    int tag;\n    int err = sfxl_name_to_tag(copy, &tag);\n\n    mem_free(copy);\n\n    if (err != SFXL_OK) {\n        return -1;\n    }\n\n    void* data;\n    CacheEntry* cacheHandle;\n    if (!cache_lock(sfxc_pcache, tag, &data, &cacheHandle)) {\n        return -1;\n    }\n\n    int handle;\n    if (sfxc_handle_create(&handle, tag, data, cacheHandle) != 0) {\n        cache_unlock(sfxc_pcache, cacheHandle);\n        return -1;\n    }\n\n    return handle;\n}\n\n// 0x4A9220\nint sfxc_cached_close(int handle)\n{\n    if (!sfxc_handle_is_legal(handle)) {\n        return -1;\n    }\n\n    SoundEffect* soundEffect = &(sfxc_handle_list[handle]);\n    if (!cache_unlock(sfxc_pcache, soundEffect->cacheHandle)) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    sfxc_handle_destroy(handle);\n\n    return 0;\n}\n\n// 0x4A9274\nint sfxc_cached_read(int handle, void* buf, unsigned int size)\n{\n    if (!sfxc_handle_is_legal(handle)) {\n        return -1;\n    }\n\n    if (size == 0) {\n        return 0;\n    }\n\n    SoundEffect* soundEffect = &(sfxc_handle_list[handle]);\n    if (soundEffect->dataSize - soundEffect->position <= 0) {\n        return 0;\n    }\n\n    size_t bytesToRead;\n    // NOTE: Original code uses signed comparison.\n    if ((int)size < (soundEffect->dataSize - soundEffect->position)) {\n        bytesToRead = size;\n    } else {\n        bytesToRead = soundEffect->dataSize - soundEffect->position;\n    }\n\n    switch (sfxc_cmpr) {\n    case 0:\n        memcpy(buf, soundEffect->data + soundEffect->position, bytesToRead);\n        break;\n    case 1:\n        if (sfxc_decode(handle, buf, bytesToRead) != 0) {\n            return -1;\n        }\n        break;\n    default:\n        return -1;\n    }\n\n    soundEffect->position += bytesToRead;\n\n    return bytesToRead;\n}\n\n// 0x4A9350\nint sfxc_cached_write(int handle, const void* buf, unsigned int size)\n{\n    return -1;\n}\n\n// 0x4A9358\nlong sfxc_cached_seek(int handle, long offset, int origin)\n{\n    if (!sfxc_handle_is_legal(handle)) {\n        return -1;\n    }\n\n    SoundEffect* soundEffect = &(sfxc_handle_list[handle]);\n\n    int position;\n    switch (origin) {\n    case SEEK_SET:\n        position = 0;\n        break;\n    case SEEK_CUR:\n        position = soundEffect->position;\n        break;\n    case SEEK_END:\n        position = soundEffect->dataSize;\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    long normalizedOffset = abs(offset);\n\n    if (offset >= 0) {\n        long remainingSize = soundEffect->dataSize - soundEffect->position;\n        if (normalizedOffset > remainingSize) {\n            normalizedOffset = remainingSize;\n        }\n        offset = position + normalizedOffset;\n    } else {\n        if (normalizedOffset > position) {\n            return -1;\n        }\n\n        offset = position - normalizedOffset;\n    }\n\n    soundEffect->position = offset;\n\n    return offset;\n}\n\n// 0x4A93F4\nlong sfxc_cached_tell(int handle)\n{\n    if (!sfxc_handle_is_legal(handle)) {\n        return -1;\n    }\n\n    SoundEffect* soundEffect = &(sfxc_handle_list[handle]);\n    return soundEffect->position;\n}\n\n// 0x4A9418\nlong sfxc_cached_file_size(int handle)\n{\n    if (!sfxc_handle_is_legal(handle)) {\n        return 0;\n    }\n\n    SoundEffect* soundEffect = &(sfxc_handle_list[handle]);\n    return soundEffect->dataSize;\n}\n\n// 0x4A9434\nstatic int sfxc_effect_size(int tag, int* sizePtr)\n{\n    int size;\n    if (sfxl_size_cached(tag, &size) == -1) {\n        return -1;\n    }\n\n    *sizePtr = size;\n\n    return 0;\n}\n\n// 0x4A945C\nstatic int sfxc_effect_load(int tag, int* sizePtr, unsigned char* data)\n{\n    if (!sfxl_tag_is_legal(tag)) {\n        return -1;\n    }\n\n    int size;\n    sfxl_size_cached(tag, &size);\n\n    char* name;\n    sfxl_name(tag, &name);\n\n    if (db_read_to_buf(name, data)) {\n        mem_free(name);\n        return -1;\n    }\n\n    mem_free(name);\n\n    *sizePtr = size;\n\n    return 0;\n}\n\n// 0x4A94CC\nstatic void sfxc_effect_free(void* ptr)\n{\n    mem_free(ptr);\n}\n\n// 0x4A94D4\nstatic int sfxc_handle_list_create()\n{\n    sfxc_handle_list = (SoundEffect*)mem_malloc(sizeof(*sfxc_handle_list) * SOUND_EFFECTS_MAX_COUNT);\n    if (sfxc_handle_list == NULL) {\n        return -1;\n    }\n\n    for (int index = 0; index < SOUND_EFFECTS_MAX_COUNT; index++) {\n        SoundEffect* soundEffect = &(sfxc_handle_list[index]);\n        soundEffect->used = false;\n    }\n\n    sfxc_files_open = 0;\n\n    return 0;\n}\n\n// 0x4A9518\nstatic void sfxc_handle_list_destroy()\n{\n    if (sfxc_files_open) {\n        for (int index = 0; index < SOUND_EFFECTS_MAX_COUNT; index++) {\n            SoundEffect* soundEffect = &(sfxc_handle_list[index]);\n            if (!soundEffect->used) {\n                sfxc_cached_close(index);\n            }\n        }\n    }\n\n    mem_free(sfxc_handle_list);\n}\n\n// 0x4A9550\nstatic int sfxc_handle_create(int* handlePtr, int tag, void* data, CacheEntry* cacheHandle)\n{\n    if (sfxc_files_open >= SOUND_EFFECTS_MAX_COUNT) {\n        return -1;\n    }\n\n    SoundEffect* soundEffect;\n    int index;\n    for (index = 0; index < SOUND_EFFECTS_MAX_COUNT; index++) {\n        soundEffect = &(sfxc_handle_list[index]);\n        if (!soundEffect->used) {\n            break;\n        }\n    }\n\n    if (index == SOUND_EFFECTS_MAX_COUNT) {\n        return -1;\n    }\n\n    soundEffect->used = true;\n    soundEffect->cacheHandle = cacheHandle;\n    soundEffect->tag = tag;\n\n    sfxl_size_full(tag, &(soundEffect->dataSize));\n    sfxl_size_cached(tag, &(soundEffect->fileSize));\n\n    soundEffect->position = 0;\n    soundEffect->dataPosition = 0;\n\n    soundEffect->data = (unsigned char*)data;\n\n    *handlePtr = index;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4A9604\nstatic void sfxc_handle_destroy(int handle)\n{\n    // NOTE: There is an overflow when handle == SOUND_EFFECTS_MAX_COUNT, but\n    // thanks to [sfxc_handle_is_legal] handle will always be less than\n    // [SOUND_EFFECTS_MAX_COUNT].\n    if (handle <= SOUND_EFFECTS_MAX_COUNT) {\n        sfxc_handle_list[handle].used = false;\n    }\n}\n\n// 0x4A961C\nstatic bool sfxc_handle_is_legal(int handle)\n{\n    if (handle >= SOUND_EFFECTS_MAX_COUNT) {\n        return false;\n    }\n\n    SoundEffect* soundEffect = &sfxc_handle_list[handle];\n\n    if (!soundEffect->used) {\n        return false;\n    }\n\n    if (soundEffect->dataSize < soundEffect->position) {\n        return false;\n    }\n\n    return sfxl_tag_is_legal(soundEffect->tag);\n}\n\n// NOTE: Unused.\n//\n// 0x4A9660\nstatic bool sfxc_mode_is_legal(int mode)\n{\n    if ((mode & 0x01) != 0) return false;\n    if ((mode & 0x02) != 0) return false;\n    if ((mode & 0x10) != 0) return false;\n\n    return true;\n}\n\n// 0x4A967C\nstatic int sfxc_decode(int handle, void* buf, unsigned int size)\n{\n    if (!sfxc_handle_is_legal(handle)) {\n        return -1;\n    }\n\n    SoundEffect* soundEffect = &(sfxc_handle_list[handle]);\n    soundEffect->dataPosition = 0;\n\n    int v1;\n    int v2;\n    int v3;\n    SoundDecoder* soundDecoder = soundDecoderInit(sfxc_ad_reader, handle, &v1, &v2, &v3);\n\n    if (soundEffect->position != 0) {\n        void* temp = mem_malloc(soundEffect->position);\n        if (temp == NULL) {\n            soundDecoderFree(soundDecoder);\n            return -1;\n        }\n\n        size_t bytesRead = soundDecoderDecode(soundDecoder, temp, soundEffect->position);\n        mem_free(temp);\n\n        if (bytesRead != soundEffect->position) {\n            soundDecoderFree(soundDecoder);\n            return -1;\n        }\n    }\n\n    size_t bytesRead = soundDecoderDecode(soundDecoder, buf, size);\n    soundDecoderFree(soundDecoder);\n\n    if (bytesRead != size) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4A9774\nstatic int sfxc_ad_reader(int handle, void* buf, unsigned int size)\n{\n    if (size == 0) {\n        return 0;\n    }\n\n    SoundEffect* soundEffect = &(sfxc_handle_list[handle]);\n\n    unsigned int bytesToRead = soundEffect->fileSize - soundEffect->dataPosition;\n    if (size <= bytesToRead) {\n        bytesToRead = size;\n    }\n\n    memcpy(buf, soundEffect->data + soundEffect->dataPosition, bytesToRead);\n\n    soundEffect->dataPosition += bytesToRead;\n\n    return bytesToRead;\n}\n"
  },
  {
    "path": "src/game/sfxcache.h",
    "content": "#ifndef FALLOUT_GAME_SFXCACHE_H_\n#define FALLOUT_GAME_SFXCACHE_H_\n\n// The maximum number of sound effects that can be loaded and played\n// simultaneously.\n#define SOUND_EFFECTS_MAX_COUNT 4\n\nint sfxc_init(int cache_size, const char* effectsPath);\nvoid sfxc_exit();\nint sfxc_is_initialized();\nvoid sfxc_flush();\nint sfxc_cached_open(const char* fname, int mode, ...);\nint sfxc_cached_close(int handle);\nint sfxc_cached_read(int handle, void* buf, unsigned int size);\nint sfxc_cached_write(int handle, const void* buf, unsigned int size);\nlong sfxc_cached_seek(int handle, long offset, int origin);\nlong sfxc_cached_tell(int handle);\nlong sfxc_cached_file_size(int handle);\n\n#endif /* FALLOUT_GAME_SFXCACHE_H_ */\n"
  },
  {
    "path": "src/game/sfxlist.c",
    "content": "#include \"game/sfxlist.h\"\n\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/memory.h\"\n#include \"sound_decoder.h\"\n\ntypedef struct SoundEffectsListEntry {\n    char* name;\n    int dataSize;\n    int fileSize;\n    int tag;\n} SoundEffectsListEntry;\n\nstatic int sfxl_index_to_tag(int tag, int* indexPtr);\nstatic void sfxl_destroy();\nstatic int sfxl_get_names();\nstatic int sfxl_copy_names(char** fileNameList);\nstatic int sfxl_get_sizes();\nstatic int sfxl_sort_by_name();\nstatic int sfxl_compare_by_name(const void* a1, const void* a2);\nstatic int sfxl_ad_reader(int fileHandle, void* buf, unsigned int size);\n\n// 0x51C8F8\nstatic bool sfxl_initialized = false;\n\n// 0x51C8FC\nstatic int sfxl_dlevel = INT_MAX;\n\n// 0x51C900\nstatic char* sfxl_effect_path = NULL;\n\n// 0x51C904\nstatic int sfxl_effect_path_len = 0;\n\n// sndlist.lst\n//\n// 0x51C908\nstatic SoundEffectsListEntry* sfxl_list = NULL;\n\n// The length of [sfxl_list] array.\n//\n// 0x51C90C\nstatic int sfxl_files_total = 0;\n\n// 0x667F94\nstatic int sfxl_compression;\n\n// 0x4A98E0\nbool sfxl_tag_is_legal(int a1)\n{\n    return sfxl_index_to_tag(a1, NULL) == SFXL_OK;\n}\n\n// 0x4A98F4\nint sfxl_init(const char* soundEffectsPath, int a2, int debugLevel)\n{\n    char path[FILENAME_MAX];\n\n    // TODO: What for?\n    // memcpy(path, byte_4A97E0, 0xFF);\n\n    sfxl_dlevel = debugLevel;\n    sfxl_compression = a2;\n    sfxl_files_total = 0;\n\n    sfxl_effect_path = mem_strdup(soundEffectsPath);\n    if (sfxl_effect_path == NULL) {\n        return SFXL_ERR;\n    }\n\n    sfxl_effect_path_len = strlen(sfxl_effect_path);\n\n    if (sfxl_effect_path_len == 0 || soundEffectsPath[sfxl_effect_path_len - 1] == '\\\\') {\n        sprintf(path, \"%sSNDLIST.LST\", soundEffectsPath);\n    } else {\n        sprintf(path, \"%s\\\\SNDLIST.LST\", soundEffectsPath);\n    }\n\n    File* stream = db_fopen(path, \"rt\");\n    if (stream != NULL) {\n        db_fgets(path, 255, stream);\n        sfxl_files_total = atoi(path);\n\n        sfxl_list = (SoundEffectsListEntry*)mem_malloc(sizeof(*sfxl_list) * sfxl_files_total);\n        for (int index = 0; index < sfxl_files_total; index++) {\n            SoundEffectsListEntry* entry = &(sfxl_list[index]);\n\n            db_fgets(path, 255, stream);\n\n            // Remove trailing newline.\n            *(path + strlen(path) - 1) = '\\0';\n            entry->name = mem_strdup(path);\n\n            db_fgets(path, 255, stream);\n            entry->dataSize = atoi(path);\n\n            db_fgets(path, 255, stream);\n            entry->fileSize = atoi(path);\n\n            db_fgets(path, 255, stream);\n            entry->tag = atoi(path);\n        }\n\n        db_fclose(stream);\n\n        debug_printf(\"Reading SNDLIST.LST Sound FX Count: %d\", sfxl_files_total);\n    } else {\n        int err;\n\n        err = sfxl_get_names();\n        if (err != SFXL_OK) {\n            mem_free(sfxl_effect_path);\n            return err;\n        }\n\n        err = sfxl_get_sizes();\n        if (err != SFXL_OK) {\n            sfxl_destroy();\n            mem_free(sfxl_effect_path);\n            return err;\n        }\n\n        // NOTE: For unknown reason tag generation functionality is missing.\n        // You won't be able to produce the same SNDLIST.LST as the game have.\n        // All tags will be 0 (see [sfxl_get_names]).\n        //\n        // On the other hand, tags read from the SNDLIST.LST are not used in\n        // the game. Instead tag is automatically determined from entry's\n        // index (see [sfxl_name_to_tag]).\n\n        // NOTE: Uninline.\n        sfxl_sort_by_name();\n\n        File* stream = db_fopen(path, \"wt\");\n        if (stream != NULL) {\n            db_fprintf(stream, \"%d\\n\", sfxl_files_total);\n\n            for (int index = 0; index < sfxl_files_total; index++) {\n                SoundEffectsListEntry* entry = &(sfxl_list[index]);\n\n                db_fprintf(stream, \"%s\\n\", entry->name);\n                db_fprintf(stream, \"%d\\n\", entry->dataSize);\n                db_fprintf(stream, \"%d\\n\", entry->fileSize);\n                db_fprintf(stream, \"%d\\n\", entry->tag);\n            }\n\n            db_fclose(stream);\n        } else {\n            debug_printf(\"SFXLIST: Can't open file for write %s\\n\", path);\n        }\n    }\n\n    sfxl_initialized = true;\n\n    return SFXL_OK;\n}\n\n// 0x4A9C04\nvoid sfxl_exit()\n{\n    if (sfxl_initialized) {\n        sfxl_destroy();\n        mem_free(sfxl_effect_path);\n        sfxl_initialized = false;\n    }\n}\n\n// 0x4A9C28\nint sfxl_name_to_tag(char* name, int* tagPtr)\n{\n    if (strnicmp(sfxl_effect_path, name, sfxl_effect_path_len) != 0) {\n        return SFXL_ERR;\n    }\n\n    SoundEffectsListEntry dummy;\n    dummy.name = name + sfxl_effect_path_len;\n\n    SoundEffectsListEntry* entry = (SoundEffectsListEntry*)bsearch(&dummy, sfxl_list, sfxl_files_total, sizeof(*sfxl_list), sfxl_compare_by_name);\n    if (entry == NULL) {\n        return SFXL_ERR;\n    }\n\n    int index = entry - sfxl_list;\n    if (index < 0 || index >= sfxl_files_total) {\n        return SFXL_ERR;\n    }\n\n    *tagPtr = 2 * index + 2;\n\n    return SFXL_OK;\n}\n\n// 0x4A9CD8\nint sfxl_name(int tag, char** pathPtr)\n{\n    int index;\n    int err = sfxl_index_to_tag(tag, &index);\n    if (err != SFXL_OK) {\n        return err;\n    }\n\n    char* name = sfxl_list[index].name;\n\n    char* path = (char*)mem_malloc(strlen(sfxl_effect_path) + strlen(name) + 1);\n    if (path == NULL) {\n        return SFXL_ERR;\n    }\n\n    strcpy(path, sfxl_effect_path);\n    strcat(path, name);\n\n    *pathPtr = path;\n\n    return SFXL_OK;\n}\n\n// 0x4A9D90\nint sfxl_size_full(int tag, int* sizePtr)\n{\n    int index;\n    int rc = sfxl_index_to_tag(tag, &index);\n    if (rc != SFXL_OK) {\n        return rc;\n    }\n\n    SoundEffectsListEntry* entry = &(sfxl_list[index]);\n    *sizePtr = entry->dataSize;\n\n    return SFXL_OK;\n}\n\n// 0x4A9DBC\nint sfxl_size_cached(int tag, int* sizePtr)\n{\n    int index;\n    int err = sfxl_index_to_tag(tag, &index);\n    if (err != SFXL_OK) {\n        return err;\n    }\n\n    SoundEffectsListEntry* entry = &(sfxl_list[index]);\n    *sizePtr = entry->fileSize;\n\n    return SFXL_OK;\n}\n\n// 0x4A9DE8\nstatic int sfxl_index_to_tag(int tag, int* indexPtr)\n{\n    if (tag <= 0) {\n        return SFXL_ERR_TAG_INVALID;\n    }\n\n    if ((tag & 1) != 0) {\n        return SFXL_ERR_TAG_INVALID;\n    }\n\n    int index = (tag / 2) - 1;\n    if (index >= sfxl_files_total) {\n        return SFXL_ERR_TAG_INVALID;\n    }\n\n    if (indexPtr != NULL) {\n        *indexPtr = index;\n    }\n\n    return SFXL_OK;\n}\n\n// 0x4A9E44\nstatic void sfxl_destroy()\n{\n    if (sfxl_files_total < 0) {\n        return;\n    }\n\n    if (sfxl_list == NULL) {\n        return;\n    }\n\n    for (int index = 0; index < sfxl_files_total; index++) {\n        SoundEffectsListEntry* entry = &(sfxl_list[index]);\n        if (entry->name != NULL) {\n            mem_free(entry->name);\n        }\n    }\n\n    mem_free(sfxl_list);\n    sfxl_list = NULL;\n\n    sfxl_files_total = 0;\n}\n\n// 0x4A9EA0\nstatic int sfxl_get_names()\n{\n    const char* extension;\n    switch (sfxl_compression) {\n    case 0:\n        extension = \"*.SND\";\n        break;\n    case 1:\n        extension = \"*.ACM\";\n        break;\n    default:\n        return SFXL_ERR;\n    }\n\n    char* pattern = (char*)mem_malloc(strlen(sfxl_effect_path) + strlen(extension) + 1);\n    if (pattern == NULL) {\n        return SFXL_ERR;\n    }\n\n    strcpy(pattern, sfxl_effect_path);\n    strcat(pattern, extension);\n\n    char** fileNameList;\n    sfxl_files_total = db_get_file_list(pattern, &fileNameList, 0, 0);\n    mem_free(pattern);\n\n    if (sfxl_files_total > 10000) {\n        db_free_file_list(&fileNameList, 0);\n        return SFXL_ERR;\n    }\n\n    if (sfxl_files_total <= 0) {\n        return SFXL_ERR;\n    }\n\n    sfxl_list = (SoundEffectsListEntry*)mem_malloc(sizeof(*sfxl_list) * sfxl_files_total);\n    if (sfxl_list == NULL) {\n        db_free_file_list(&fileNameList, 0);\n        return SFXL_ERR;\n    }\n\n    memset(sfxl_list, 0, sizeof(*sfxl_list) * sfxl_files_total);\n\n    int err = sfxl_copy_names(fileNameList);\n\n    db_free_file_list(&fileNameList, 0);\n\n    if (err != SFXL_OK) {\n        sfxl_destroy();\n        return err;\n    }\n\n    return SFXL_OK;\n}\n\n// 0x4AA000\nstatic int sfxl_copy_names(char** fileNameList)\n{\n    for (int index = 0; index < sfxl_files_total; index++) {\n        SoundEffectsListEntry* entry = &(sfxl_list[index]);\n        entry->name = mem_strdup(*fileNameList++);\n        if (entry->name == NULL) {\n            sfxl_destroy();\n            return SFXL_ERR;\n        }\n    }\n\n    return SFXL_OK;\n}\n\n// 0x4AA050\nstatic int sfxl_get_sizes()\n{\n\n    char* path = (char*)mem_malloc(sfxl_effect_path_len + 13);\n    if (path == NULL) {\n        return SFXL_ERR;\n    }\n\n    strcpy(path, sfxl_effect_path);\n\n    char* fileName = path + sfxl_effect_path_len;\n\n    for (int index = 0; index < sfxl_files_total; index++) {\n        SoundEffectsListEntry* entry = &(sfxl_list[index]);\n        strcpy(fileName, entry->name);\n\n        int fileSize;\n        if (db_dir_entry(path, &fileSize) != 0) {\n            mem_free(path);\n            return SFXL_ERR;\n        }\n\n        if (fileSize <= 0) {\n            mem_free(path);\n            return SFXL_ERR;\n        }\n\n        entry->fileSize = fileSize;\n\n        switch (sfxl_compression) {\n        case 0:\n            entry->dataSize = fileSize;\n            break;\n        case 1:\n            if (1) {\n                File* stream = db_fopen(path, \"rb\");\n                if (stream == NULL) {\n                    mem_free(path);\n                    return 1;\n                }\n\n                int v1;\n                int v2;\n                int v3;\n                SoundDecoder* soundDecoder = soundDecoderInit(sfxl_ad_reader, (int)stream, &v1, &v2, &v3);\n                entry->dataSize = 2 * v3;\n                soundDecoderFree(soundDecoder);\n                db_fclose(stream);\n            }\n            break;\n        default:\n            mem_free(path);\n            return SFXL_ERR;\n        }\n    }\n\n    mem_free(path);\n\n    return SFXL_OK;\n}\n\n// NOTE: Inlined.\n//\n// 0x4AA200\nstatic int sfxl_sort_by_name()\n{\n    if (sfxl_files_total != 1) {\n        qsort(sfxl_list, sfxl_files_total, sizeof(*sfxl_list), sfxl_compare_by_name);\n    }\n    return 0;\n}\n\n// 0x4AA228\nstatic int sfxl_compare_by_name(const void* a1, const void* a2)\n{\n    SoundEffectsListEntry* v1 = (SoundEffectsListEntry*)a1;\n    SoundEffectsListEntry* v2 = (SoundEffectsListEntry*)a2;\n\n    return stricmp(v1->name, v2->name);\n}\n\n// 0x4AA234\nstatic int sfxl_ad_reader(int fileHandle, void* buf, unsigned int size)\n{\n    return db_fread(buf, 1, size, (File*)fileHandle);\n}\n"
  },
  {
    "path": "src/game/sfxlist.h",
    "content": "#ifndef FALLOUT_GAME_SFXLIST_H_\n#define FALLOUT_GAME_SFXLIST_H_\n\n#include <stdbool.h>\n\n#define SFXL_OK 0\n#define SFXL_ERR 1\n#define SFXL_ERR_TAG_INVALID 2\n\nbool sfxl_tag_is_legal(int tag);\nint sfxl_init(const char* soundEffectsPath, int a2, int debugLevel);\nvoid sfxl_exit();\nint sfxl_name_to_tag(char* name, int* tagPtr);\nint sfxl_name(int tag, char** pathPtr);\nint sfxl_size_full(int tag, int* sizePtr);\nint sfxl_size_cached(int tag, int* sizePtr);\n\n#endif /* FALLOUT_GAME_SFXLIST_H_ */\n"
  },
  {
    "path": "src/game/skill.c",
    "content": "#include \"game/skill.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/actions.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/palette.h\"\n#include \"game/party.h\"\n#include \"game/perk.h\"\n#include \"game/pipboy.h\"\n#include \"game/proto.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/stat.h\"\n#include \"game/trait.h\"\n\n#define SKILLS_MAX_USES_PER_DAY 3\n\n#define REPAIRABLE_DAMAGE_FLAGS_LENGTH 5\n#define HEALABLE_DAMAGE_FLAGS_LENGTH 5\n\nstatic int skillLevelCost(int a1);\nstatic void show_skill_use_messages(Object* obj, int skill, Object* a3, int a4, int a5);\nstatic int skill_game_difficulty(int skill);\nstatic int skill_use_slot_available(int skill);\nstatic int skill_use_slot_add(int skill);\nstatic int skill_use_slot_clear();\n\ntypedef struct SkillDescription {\n    char* name;\n    char* description;\n    char* attributes;\n    int frmId;\n    int defaultValue;\n    int statModifier;\n    int stat1;\n    int stat2;\n    int field_20;\n    int experience;\n    int field_28;\n} SkillDescription;\n\n// 0x51D118\nstatic SkillDescription skill_data[SKILL_COUNT] = {\n    { NULL, NULL, NULL, 28, 5, 4, STAT_AGILITY, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 29, 0, 2, STAT_AGILITY, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 30, 0, 2, STAT_AGILITY, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 31, 30, 2, STAT_AGILITY, STAT_STRENGTH, 1, 0, 0 },\n    { NULL, NULL, NULL, 32, 20, 2, STAT_AGILITY, STAT_STRENGTH, 1, 0, 0 },\n    { NULL, NULL, NULL, 33, 0, 4, STAT_AGILITY, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 34, 0, 2, STAT_PERCEPTION, STAT_INTELLIGENCE, 1, 25, 0 },\n    { NULL, NULL, NULL, 35, 5, 1, STAT_PERCEPTION, STAT_INTELLIGENCE, 1, 50, 0 },\n    { NULL, NULL, NULL, 36, 5, 3, STAT_AGILITY, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 37, 10, 1, STAT_PERCEPTION, STAT_AGILITY, 1, 25, 1 },\n    { NULL, NULL, NULL, 38, 0, 3, STAT_AGILITY, STAT_INVALID, 1, 25, 1 },\n    { NULL, NULL, NULL, 39, 10, 1, STAT_PERCEPTION, STAT_AGILITY, 1, 25, 1 },\n    { NULL, NULL, NULL, 40, 0, 4, STAT_INTELLIGENCE, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 41, 0, 3, STAT_INTELLIGENCE, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 42, 0, 5, STAT_CHARISMA, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 43, 0, 4, STAT_CHARISMA, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 44, 0, 5, STAT_LUCK, STAT_INVALID, 1, 0, 0 },\n    { NULL, NULL, NULL, 45, 0, 2, STAT_ENDURANCE, STAT_INTELLIGENCE, 1, 100, 0 },\n};\n\n// 0x51D430\nint gIsSteal = 0;\n\n// 0x51D434\nint gStealCount = 0;\n\n// 0x51D438\nint gStealSize = 0;\n\n// 0x667F98\nstatic int timesSkillUsed[SKILL_COUNT][SKILLS_MAX_USES_PER_DAY];\n\n// 0x668070\nstatic int tag_skill[NUM_TAGGED_SKILLS];\n\n// skill.msg\n//\n// 0x668080\nstatic MessageList skill_message_file;\n\n// 0x4AA318\nint skill_init()\n{\n    if (!message_init(&skill_message_file)) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"skill.msg\");\n\n    if (!message_load(&skill_message_file, path)) {\n        return -1;\n    }\n\n    for (int skill = 0; skill < SKILL_COUNT; skill++) {\n        MessageListItem messageListItem;\n\n        messageListItem.num = 100 + skill;\n        if (message_search(&skill_message_file, &messageListItem)) {\n            skill_data[skill].name = messageListItem.text;\n        }\n\n        messageListItem.num = 200 + skill;\n        if (message_search(&skill_message_file, &messageListItem)) {\n            skill_data[skill].description = messageListItem.text;\n        }\n\n        messageListItem.num = 300 + skill;\n        if (message_search(&skill_message_file, &messageListItem)) {\n            skill_data[skill].attributes = messageListItem.text;\n        }\n    }\n\n    for (int index = 0; index < NUM_TAGGED_SKILLS; index++) {\n        tag_skill[index] = -1;\n    }\n\n    // NOTE: Uninline.\n    skill_use_slot_clear();\n\n    return 0;\n}\n\n// 0x4AA448\nvoid skill_reset()\n{\n    for (int index = 0; index < NUM_TAGGED_SKILLS; index++) {\n        tag_skill[index] = -1;\n    }\n\n    // NOTE: Uninline.\n    skill_use_slot_clear();\n}\n\n// 0x4AA478\nvoid skill_exit()\n{\n    message_exit(&skill_message_file);\n}\n\n// 0x4AA488\nint skill_load(File* stream)\n{\n    return db_freadIntCount(stream, tag_skill, NUM_TAGGED_SKILLS);\n}\n\n// 0x4AA4A8\nint skill_save(File* stream)\n{\n    return db_fwriteIntCount(stream, tag_skill, NUM_TAGGED_SKILLS);\n}\n\n// 0x4AA4C8\nvoid skill_set_defaults(CritterProtoData* data)\n{\n    for (int skill = 0; skill < SKILL_COUNT; skill++) {\n        data->skills[skill] = 0;\n    }\n}\n\n// 0x4AA4E4\nvoid skill_set_tags(int* skills, int count)\n{\n    for (int index = 0; index < count; index++) {\n        tag_skill[index] = skills[index];\n    }\n}\n\n// 0x4AA508\nvoid skill_get_tags(int* skills, int count)\n{\n    for (int index = 0; index < count; index++) {\n        skills[index] = tag_skill[index];\n    }\n}\n\n// 0x4AA52C\nbool skill_is_tagged(int skill)\n{\n    return skill == tag_skill[0]\n        || skill == tag_skill[1]\n        || skill == tag_skill[2]\n        || skill == tag_skill[3];\n}\n\n// 0x4AA558\nint skill_level(Object* critter, int skill)\n{\n    if (!skillIsValid(skill)) {\n        return -5;\n    }\n\n    int baseValue = skill_points(critter, skill);\n    if (baseValue < 0) {\n        return baseValue;\n    }\n\n    SkillDescription* skillDescription = &(skill_data[skill]);\n\n    int v7 = critterGetStat(critter, skillDescription->stat1);\n    if (skillDescription->stat2 != -1) {\n        v7 += critterGetStat(critter, skillDescription->stat2);\n    }\n\n    int value = skillDescription->defaultValue + skillDescription->statModifier * v7 + baseValue * skillDescription->field_20;\n\n    if (critter == obj_dude) {\n        if (skill_is_tagged(skill)) {\n            value += baseValue * skillDescription->field_20;\n\n            if (!perk_level(critter, PERK_TAG) || skill != tag_skill[3]) {\n                value += 20;\n            }\n        }\n\n        value += trait_adjust_skill(skill);\n        value += perk_adjust_skill(critter, skill);\n        value += skill_game_difficulty(skill);\n    }\n\n    if (value > 300) {\n        value = 300;\n    }\n\n    return value;\n}\n\n// 0x4AA654\nint skill_base(int skill)\n{\n    return skillIsValid(skill) ? skill_data[skill].defaultValue : -5;\n}\n\n// 0x4AA680\nint skill_points(Object* obj, int skill)\n{\n    if (!skillIsValid(skill)) {\n        return 0;\n    }\n\n    Proto* proto;\n    proto_ptr(obj->pid, &proto);\n\n    return proto->critter.data.skills[skill];\n}\n\n// 0x4AA6BC\nint skill_inc_point(Object* obj, int skill)\n{\n    if (obj != obj_dude) {\n        return -5;\n    }\n\n    if (!skillIsValid(skill)) {\n        return -5;\n    }\n\n    Proto* proto;\n    proto_ptr(obj->pid, &proto);\n\n    int unspentSp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS);\n    if (unspentSp <= 0) {\n        return -4;\n    }\n\n    int skillValue = skill_level(obj, skill);\n    if (skillValue >= 300) {\n        return -3;\n    }\n\n    // NOTE: Uninline.\n    int requiredSp = skillLevelCost(skillValue);\n\n    if (unspentSp < requiredSp) {\n        return -4;\n    }\n\n    int rc = stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, unspentSp - requiredSp);\n    if (rc == 0) {\n        proto->critter.data.skills[skill] += 1;\n    }\n\n    return rc;\n}\n\n// 0x4AA7F8\nint skill_inc_point_force(Object* obj, int skill)\n{\n    if (obj != obj_dude) {\n        return -5;\n    }\n\n    if (!skillIsValid(skill)) {\n        return -5;\n    }\n\n    Proto* proto;\n    proto_ptr(obj->pid, &proto);\n\n    if (skill_level(obj, skill) >= 300) {\n        return -3;\n    }\n\n    proto->critter.data.skills[skill] += 1;\n\n    return 0;\n}\n\n// Returns the cost of raising skill value in skill points.\n//\n// 0x4AA87C\nstatic int skillLevelCost(int skillValue)\n{\n    if (skillValue >= 201) {\n        return 6;\n    } else if (skillValue >= 176) {\n        return 5;\n    } else if (skillValue >= 151) {\n        return 4;\n    } else if (skillValue >= 126) {\n        return 3;\n    } else if (skillValue >= 101) {\n        return 2;\n    } else {\n        return 1;\n    }\n}\n\n// Decrements specified skill value by one, returning appropriate amount as\n// unspent skill points.\n//\n// 0x4AA8C4\nint skill_dec_point(Object* critter, int skill)\n{\n    if (critter != obj_dude) {\n        return -5;\n    }\n\n    if (!skillIsValid(skill)) {\n        return -5;\n    }\n\n    Proto* proto;\n    proto_ptr(critter->pid, &proto);\n\n    if (proto->critter.data.skills[skill] <= 0) {\n        return -2;\n    }\n\n    int unspentSp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS);\n    int skillValue = skill_level(critter, skill) - 1;\n\n    // NOTE: Uninline.\n    int requiredSp = skillLevelCost(skillValue);\n\n    int newUnspentSp = unspentSp + requiredSp;\n    int rc = stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, newUnspentSp);\n    if (rc != 0) {\n        return rc;\n    }\n\n    proto->critter.data.skills[skill] -= 1;\n\n    if (skill_is_tagged(skill)) {\n        int oldSkillCost = skillLevelCost(skillValue);\n        int newSkillCost = skillLevelCost(skill_level(critter, skill));\n        if (oldSkillCost != newSkillCost) {\n            rc = stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, newUnspentSp - 1);\n            if (rc != 0) {\n                return rc;\n            }\n        }\n    }\n\n    if (proto->critter.data.skills[skill] < 0) {\n        proto->critter.data.skills[skill] = 0;\n    }\n\n    return 0;\n}\n\n// Decrements specified skill value by one.\n//\n// 0x4AAA34\nint skill_dec_point_force(Object* obj, int skill)\n{\n    Proto* proto;\n\n    if (obj != obj_dude) {\n        return -5;\n    }\n\n    if (!skillIsValid(skill)) {\n        return -5;\n    }\n\n    proto_ptr(obj->pid, &proto);\n\n    if (proto->critter.data.skills[skill] <= 0) {\n        return -2;\n    }\n\n    proto->critter.data.skills[skill] -= 1;\n\n    return 0;\n}\n\n// 0x4AAAA4\nint skill_result(Object* critter, int skill, int modifier, int* howMuch)\n{\n    if (!skillIsValid(skill)) {\n        return ROLL_FAILURE;\n    }\n\n    if (critter == obj_dude && skill != SKILL_STEAL) {\n        Object* partyMember = partyMemberWithHighestSkill(skill);\n        if (partyMember != NULL) {\n            if (partyMemberSkill(partyMember) == skill) {\n                critter = partyMember;\n            }\n        }\n    }\n\n    int skillValue = skill_level(critter, skill);\n\n    if (critter == obj_dude && skill == SKILL_STEAL) {\n        if (is_pc_flag(DUDE_STATE_SNEAKING)) {\n            if (is_pc_sneak_working()) {\n                skillValue += 30;\n            }\n        }\n    }\n\n    int criticalChance = critterGetStat(critter, STAT_CRITICAL_CHANCE);\n    return roll_check(skillValue + modifier, criticalChance, howMuch);\n}\n\n// NOTE: Unused.\n//\n// 0x4AAB34\nint skill_contest(Object* attacker, Object* defender, int skill, int attackerModifier, int defenderModifier, int* howMuch)\n{\n    int attackerRoll;\n    int attackerHowMuch;\n    int defenderRoll;\n    int defenderHowMuch;\n\n    attackerRoll = skill_result(attacker, skill, attackerModifier, &attackerHowMuch);\n    if (attackerRoll > ROLL_FAILURE) {\n        defenderRoll = skill_result(defender, skill, defenderModifier, &defenderHowMuch);\n        if (defenderRoll > ROLL_FAILURE) {\n            attackerHowMuch -= defenderHowMuch;\n        }\n\n        attackerRoll = roll_check_critical(attackerHowMuch, 0);\n    }\n\n    if (howMuch != NULL) {\n        *howMuch = attackerHowMuch;\n    }\n\n    return attackerRoll;\n}\n\n// 0x4AAB9C\nchar* skill_name(int skill)\n{\n    return skillIsValid(skill) ? skill_data[skill].name : NULL;\n}\n\n// 0x4AABC0\nchar* skill_description(int skill)\n{\n    return skillIsValid(skill) ? skill_data[skill].description : NULL;\n}\n\n// 0x4AABE4\nchar* skill_attribute(int skill)\n{\n    return skillIsValid(skill) ? skill_data[skill].attributes : NULL;\n}\n\n// 0x4AAC08\nint skill_pic(int skill)\n{\n    return skillIsValid(skill) ? skill_data[skill].frmId : 0;\n}\n\n// 0x4AAC2C\nstatic void show_skill_use_messages(Object* obj, int skill, Object* a3, int a4, int criticalChanceModifier)\n{\n    if (obj != obj_dude) {\n        return;\n    }\n\n    if (a4 <= 0) {\n        return;\n    }\n\n    SkillDescription* skillDescription = &(skill_data[skill]);\n\n    int baseExperience = skillDescription->experience;\n    if (baseExperience == 0) {\n        return;\n    }\n\n    if (skillDescription->field_28 && criticalChanceModifier < 0) {\n        baseExperience += abs(criticalChanceModifier);\n    }\n\n    int xpToAdd = a4 * baseExperience;\n\n    int before = stat_pc_get(PC_STAT_EXPERIENCE);\n\n    if (stat_pc_add_experience(xpToAdd) == 0 && a4 > 0) {\n        MessageListItem messageListItem;\n        messageListItem.num = 505; // You earn %d XP for honing your skills\n        if (message_search(&skill_message_file, &messageListItem)) {\n            int after = stat_pc_get(PC_STAT_EXPERIENCE);\n\n            char text[60];\n            sprintf(text, messageListItem.text, after - before);\n            display_print(text);\n        }\n    }\n}\n\n// skill_use\n// 0x4AAD08\nint skill_use(Object* obj, Object* a2, int skill, int criticalChanceModifier)\n{\n    MessageListItem messageListItem;\n    char text[60];\n\n    bool giveExp = true;\n    int currentHp = critterGetStat(a2, STAT_CURRENT_HIT_POINTS);\n    int maximumHp = critterGetStat(a2, STAT_MAXIMUM_HIT_POINTS);\n\n    int hpToHeal = 0;\n    int maximumHpToHeal = 0;\n    int minimumHpToHeal = 0;\n\n    if (obj == obj_dude) {\n        if (skill == SKILL_FIRST_AID || skill == SKILL_DOCTOR) {\n            int healerRank = perk_level(obj, PERK_HEALER);\n            minimumHpToHeal = 4 * healerRank;\n            maximumHpToHeal = 10 * healerRank;\n        }\n    }\n\n    int criticalChance = critterGetStat(obj, STAT_CRITICAL_CHANCE) + criticalChanceModifier;\n\n    int damageHealingAttempts = 1;\n    int v1 = 0;\n    int v2 = 0;\n\n    switch (skill) {\n    case SKILL_FIRST_AID:\n        if (skill_use_slot_available(SKILL_FIRST_AID) == -1) {\n            // 590: You've taxed your ability with that skill. Wait a while.\n            // 591: You're too tired.\n            // 592: The strain might kill you.\n            messageListItem.num = 590 + roll_random(0, 2);\n            if (message_search(&skill_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n\n            return -1;\n        }\n\n        if (critter_is_dead(a2)) {\n            // 512: You can't heal the dead.\n            // 513: Let the dead rest in peace.\n            // 514: It's dead, get over it.\n            messageListItem.num = 512 + roll_random(0, 2);\n            if (message_search(&skill_message_file, &messageListItem)) {\n                debug_printf(messageListItem.text);\n            }\n\n            break;\n        }\n\n        if (currentHp < maximumHp) {\n            palette_fade_to(black_palette);\n\n            int roll;\n            if (critter_body_type(a2) == BODY_TYPE_ROBOTIC) {\n                roll = ROLL_FAILURE;\n            } else {\n                roll = skill_result(obj, skill, criticalChance, &hpToHeal);\n            }\n\n            if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) {\n                hpToHeal = roll_random(minimumHpToHeal + 1, maximumHpToHeal + 5);\n                critter_adjust_hits(a2, hpToHeal);\n\n                if (obj == obj_dude) {\n                    // You heal %d hit points.\n                    messageListItem.num = 500;\n                    if (!message_search(&skill_message_file, &messageListItem)) {\n                        return -1;\n                    }\n\n                    if (maximumHp - currentHp < hpToHeal) {\n                        hpToHeal = maximumHp - currentHp;\n                    }\n\n                    sprintf(text, messageListItem.text, hpToHeal);\n                    display_print(text);\n                }\n\n                a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n\n                skill_use_slot_add(SKILL_FIRST_AID);\n\n                v1 = 1;\n\n                if (a2 == obj_dude) {\n                    intface_update_hit_points(true);\n                }\n            } else {\n                // You fail to do any healing.\n                messageListItem.num = 503;\n                if (!message_search(&skill_message_file, &messageListItem)) {\n                    return -1;\n                }\n\n                sprintf(text, messageListItem.text, hpToHeal);\n                display_print(text);\n            }\n\n            scr_exec_map_update_scripts();\n            palette_fade_to(cmap);\n        } else {\n            if (obj == obj_dude) {\n                // 501: You look healty already\n                // 502: %s looks healthy already\n                messageListItem.num = (a2 == obj_dude ? 501 : 502);\n                if (!message_search(&skill_message_file, &messageListItem)) {\n                    return -1;\n                }\n\n                if (a2 == obj_dude) {\n                    strcpy(text, messageListItem.text);\n                } else {\n                    sprintf(text, messageListItem.text, object_name(a2));\n                }\n\n                display_print(text);\n                giveExp = false;\n            }\n        }\n\n        if (obj == obj_dude) {\n            inc_game_time_in_seconds(1800);\n        }\n\n        break;\n    case SKILL_DOCTOR:\n        if (skill_use_slot_available(SKILL_DOCTOR) == -1) {\n            // 590: You've taxed your ability with that skill. Wait a while.\n            // 591: You're too tired.\n            // 592: The strain might kill you.\n            messageListItem.num = 590 + roll_random(0, 2);\n            if (message_search(&skill_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n\n            return -1;\n        }\n\n        if (critter_is_dead(a2)) {\n            // 512: You can't heal the dead.\n            // 513: Let the dead rest in peace.\n            // 514: It's dead, get over it.\n            messageListItem.num = 512 + roll_random(0, 2);\n            if (message_search(&skill_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n            break;\n        }\n\n        if (currentHp < maximumHp || critter_is_crippled(a2)) {\n            palette_fade_to(black_palette);\n\n            if (critter_body_type(a2) != BODY_TYPE_ROBOTIC && critter_is_crippled(a2)) {\n                // Damage flags which can be healed using \"Doctor\" skill.\n                //\n                // 0x4AA304\n                static const int flags[HEALABLE_DAMAGE_FLAGS_LENGTH] = {\n                    DAM_BLIND,\n                    DAM_CRIP_ARM_LEFT,\n                    DAM_CRIP_ARM_RIGHT,\n                    DAM_CRIP_LEG_RIGHT,\n                    DAM_CRIP_LEG_LEFT,\n                };\n\n                for (int index = 0; index < HEALABLE_DAMAGE_FLAGS_LENGTH; index++) {\n                    if ((a2->data.critter.combat.results & flags[index]) != 0) {\n                        damageHealingAttempts++;\n\n                        int roll = skill_result(obj, skill, criticalChance, &hpToHeal);\n\n                        // 530: damaged eye\n                        // 531: crippled left arm\n                        // 532: crippled right arm\n                        // 533: crippled right leg\n                        // 534: crippled left leg\n                        messageListItem.num = 530 + index;\n                        if (!message_search(&skill_message_file, &messageListItem)) {\n                            return -1;\n                        }\n\n                        MessageListItem prefix;\n\n                        if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) {\n                            a2->data.critter.combat.results &= ~flags[index];\n                            a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n\n                            // 520: You heal your %s.\n                            // 521: You heal the %s.\n                            prefix.num = (a2 == obj_dude ? 520 : 521);\n\n                            skill_use_slot_add(SKILL_DOCTOR);\n\n                            v1 = 1;\n                            v2 = 1;\n                        } else {\n                            // 525: You fail to heal your %s.\n                            // 526: You fail to heal the %s.\n                            prefix.num = (a2 == obj_dude ? 525 : 526);\n                        }\n\n                        if (!message_search(&skill_message_file, &prefix)) {\n                            return -1;\n                        }\n\n                        sprintf(text, prefix.text, messageListItem.text);\n                        display_print(text);\n                        show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier);\n\n                        giveExp = false;\n                    }\n                }\n            }\n\n            int roll;\n            if (critter_body_type(a2) == BODY_TYPE_ROBOTIC) {\n                roll = ROLL_FAILURE;\n            } else {\n                int skillValue = skill_level(obj, skill);\n                roll = roll_check(skillValue, criticalChance, &hpToHeal);\n            }\n\n            if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) {\n                hpToHeal = roll_random(minimumHpToHeal + 4, maximumHpToHeal + 10);\n                critter_adjust_hits(a2, hpToHeal);\n\n                if (obj == obj_dude) {\n                    // You heal %d hit points.\n                    messageListItem.num = 500;\n                    if (!message_search(&skill_message_file, &messageListItem)) {\n                        return -1;\n                    }\n\n                    if (maximumHp - currentHp < hpToHeal) {\n                        hpToHeal = maximumHp - currentHp;\n                    }\n                    sprintf(text, messageListItem.text, hpToHeal);\n                    display_print(text);\n                }\n\n                if (!v2) {\n                    skill_use_slot_add(SKILL_DOCTOR);\n                }\n\n                a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n\n                if (a2 == obj_dude) {\n                    intface_update_hit_points(true);\n                }\n\n                v1 = 1;\n                show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier);\n                scr_exec_map_update_scripts();\n                palette_fade_to(cmap);\n\n                giveExp = false;\n            } else {\n                // You fail to do any healing.\n                messageListItem.num = 503;\n                if (!message_search(&skill_message_file, &messageListItem)) {\n                    return -1;\n                }\n\n                sprintf(text, messageListItem.text, hpToHeal);\n                display_print(text);\n\n                scr_exec_map_update_scripts();\n                palette_fade_to(cmap);\n            }\n        } else {\n            if (obj == obj_dude) {\n                // 501: You look healty already\n                // 502: %s looks healthy already\n                messageListItem.num = (a2 == obj_dude ? 501 : 502);\n                if (!message_search(&skill_message_file, &messageListItem)) {\n                    return -1;\n                }\n\n                if (a2 == obj_dude) {\n                    strcpy(text, messageListItem.text);\n                } else {\n                    sprintf(text, messageListItem.text, object_name(a2));\n                }\n\n                display_print(text);\n\n                giveExp = false;\n            }\n        }\n\n        if (obj == obj_dude) {\n            inc_game_time_in_seconds(3600 * damageHealingAttempts);\n        }\n\n        break;\n    case SKILL_SNEAK:\n    case SKILL_LOCKPICK:\n        break;\n    case SKILL_STEAL:\n        scripts_request_steal_container(obj, a2);\n        break;\n    case SKILL_TRAPS:\n        messageListItem.num = 551; // You fail to find any traps.\n        if (message_search(&skill_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n\n        return -1;\n    case SKILL_SCIENCE:\n        messageListItem.num = 552; // You fail to learn anything.\n        if (message_search(&skill_message_file, &messageListItem)) {\n            display_print(messageListItem.text);\n        }\n\n        return -1;\n    case SKILL_REPAIR:\n        if (critter_body_type(a2) != BODY_TYPE_ROBOTIC) {\n            // You cannot repair that.\n            messageListItem.num = 553;\n            if (message_search(&skill_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n            return -1;\n        }\n\n        if (skill_use_slot_available(SKILL_REPAIR) == -1) {\n            // 590: You've taxed your ability with that skill. Wait a while.\n            // 591: You're too tired.\n            // 592: The strain might kill you.\n            messageListItem.num = 590 + roll_random(0, 2);\n            if (message_search(&skill_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n            return -1;\n        }\n\n        if (critter_is_dead(a2)) {\n            // You got it?\n            messageListItem.num = 1101;\n            if (message_search(&skill_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n            break;\n        }\n\n        if (currentHp < maximumHp || critter_is_crippled(a2)) {\n            // Damage flags which can be repaired using \"Repair\" skill.\n            //\n            // 0x4AA2F0\n            static const int flags[REPAIRABLE_DAMAGE_FLAGS_LENGTH] = {\n                DAM_BLIND,\n                DAM_CRIP_ARM_LEFT,\n                DAM_CRIP_ARM_RIGHT,\n                DAM_CRIP_LEG_RIGHT,\n                DAM_CRIP_LEG_LEFT,\n            };\n\n            palette_fade_to(black_palette);\n\n            for (int index = 0; index < REPAIRABLE_DAMAGE_FLAGS_LENGTH; index++) {\n                if ((a2->data.critter.combat.results & flags[index]) != 0) {\n                    damageHealingAttempts++;\n\n                    int roll = skill_result(obj, skill, criticalChance, &hpToHeal);\n\n                    // 530: damaged eye\n                    // 531: crippled left arm\n                    // 532: crippled right arm\n                    // 533: crippled right leg\n                    // 534: crippled left leg\n                    messageListItem.num = 530 + index;\n                    if (!message_search(&skill_message_file, &messageListItem)) {\n                        return -1;\n                    }\n\n                    MessageListItem prefix;\n\n                    if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) {\n                        a2->data.critter.combat.results &= ~flags[index];\n                        a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n\n                        // 520: You heal your %s.\n                        // 521: You heal the %s.\n                        prefix.num = (a2 == obj_dude ? 520 : 521);\n                        skill_use_slot_add(SKILL_REPAIR);\n\n                        v1 = 1;\n                        v2 = 1;\n                    } else {\n                        // 525: You fail to heal your %s.\n                        // 526: You fail to heal the %s.\n                        prefix.num = (a2 == obj_dude ? 525 : 526);\n                    }\n\n                    if (!message_search(&skill_message_file, &prefix)) {\n                        return -1;\n                    }\n\n                    sprintf(text, prefix.text, messageListItem.text);\n                    display_print(text);\n\n                    show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier);\n                    giveExp = false;\n                }\n            }\n\n            int skillValue = skill_level(obj, skill);\n            int roll = roll_check(skillValue, criticalChance, &hpToHeal);\n\n            if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) {\n                hpToHeal = roll_random(minimumHpToHeal + 4, maximumHpToHeal + 10);\n                critter_adjust_hits(a2, hpToHeal);\n\n                if (obj == obj_dude) {\n                    // You heal %d hit points.\n                    messageListItem.num = 500;\n                    if (!message_search(&skill_message_file, &messageListItem)) {\n                        return -1;\n                    }\n\n                    if (maximumHp - currentHp < hpToHeal) {\n                        hpToHeal = maximumHp - currentHp;\n                    }\n                    sprintf(text, messageListItem.text, hpToHeal);\n                    display_print(text);\n                }\n\n                if (!v2) {\n                    skill_use_slot_add(SKILL_REPAIR);\n                }\n\n                a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n\n                if (a2 == obj_dude) {\n                    intface_update_hit_points(true);\n                }\n\n                v1 = 1;\n                show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier);\n                scr_exec_map_update_scripts();\n                palette_fade_to(cmap);\n\n                giveExp = false;\n            } else {\n                // You fail to do any healing.\n                messageListItem.num = 503;\n                if (!message_search(&skill_message_file, &messageListItem)) {\n                    return -1;\n                }\n\n                sprintf(text, messageListItem.text, hpToHeal);\n                display_print(text);\n\n                scr_exec_map_update_scripts();\n                palette_fade_to(cmap);\n            }\n        } else {\n            if (obj == obj_dude) {\n                // 501: You look healty already\n                // 502: %s looks healthy already\n                messageListItem.num = (a2 == obj_dude ? 501 : 502);\n                if (!message_search(&skill_message_file, &messageListItem)) {\n                    return -1;\n                }\n\n                sprintf(text, messageListItem.text, object_name(a2));\n                display_print(text);\n\n                giveExp = false;\n            }\n        }\n\n        if (obj == obj_dude) {\n            inc_game_time_in_seconds(1800 * damageHealingAttempts);\n        }\n\n        break;\n    default:\n        messageListItem.num = 510; // skill_use: invalid skill used.\n        if (message_search(&skill_message_file, &messageListItem)) {\n            debug_printf(messageListItem.text);\n        }\n\n        return -1;\n    }\n\n    if (giveExp) {\n        show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier);\n    }\n\n    if (skill == SKILL_FIRST_AID || skill == SKILL_DOCTOR) {\n        scr_exec_map_update_scripts();\n    }\n\n    return 0;\n}\n\n// 0x4ABBE4\nint skill_check_stealing(Object* a1, Object* a2, Object* item, bool isPlanting)\n{\n    int howMuch;\n\n    int stealModifier = gStealCount;\n    stealModifier--;\n    stealModifier = -stealModifier;\n\n    if (a1 != obj_dude || !perkHasRank(a1, PERK_PICKPOCKET)) {\n        // -4% per item size\n        stealModifier -= 4 * item_size(item);\n\n        if (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER) {\n            // check facing: -25% if face to face\n            if (is_hit_from_front(a1, a2)) {\n                stealModifier -= 25;\n            }\n        }\n    }\n\n    if ((a2->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) {\n        stealModifier += 20;\n    }\n\n    int stealChance = stealModifier + skill_level(a1, SKILL_STEAL);\n    if (stealChance > 95) {\n        stealChance = 95;\n    }\n\n    int stealRoll;\n    if (a1 == obj_dude && isPartyMember(a2)) {\n        stealRoll = ROLL_CRITICAL_SUCCESS;\n    } else {\n        int criticalChance = critterGetStat(a1, STAT_CRITICAL_CHANCE);\n        stealRoll = roll_check(stealChance, criticalChance, &howMuch);\n    }\n\n    int catchRoll;\n    if (stealRoll == ROLL_CRITICAL_SUCCESS) {\n        catchRoll = ROLL_CRITICAL_FAILURE;\n    } else if (stealRoll == ROLL_CRITICAL_FAILURE) {\n        catchRoll = ROLL_SUCCESS;\n    } else {\n        int catchChance;\n        if (PID_TYPE(a2->pid) == OBJ_TYPE_CRITTER) {\n            catchChance = skill_level(a2, SKILL_STEAL) - stealModifier;\n        } else {\n            catchChance = 30 - stealModifier;\n        }\n\n        catchRoll = roll_check(catchChance, 0, &howMuch);\n    }\n\n    MessageListItem messageListItem;\n    char text[60];\n\n    if (catchRoll != ROLL_SUCCESS && catchRoll != ROLL_CRITICAL_SUCCESS) {\n        // 571: You steal the %s.\n        // 573: You plant the %s.\n        messageListItem.num = isPlanting ? 573 : 571;\n        if (!message_search(&skill_message_file, &messageListItem)) {\n            return -1;\n        }\n\n        sprintf(text, messageListItem.text, object_name(item));\n        display_print(text);\n\n        return 1;\n    } else {\n        // 570: You're caught stealing the %s.\n        // 572: You're caught planting the %s.\n        messageListItem.num = isPlanting ? 572 : 570;\n        if (!message_search(&skill_message_file, &messageListItem)) {\n            return -1;\n        }\n\n        sprintf(text, messageListItem.text, object_name(item));\n        display_print(text);\n\n        return 0;\n    }\n}\n\n// 0x4ABDEC\nstatic int skill_game_difficulty(int skill)\n{\n    switch (skill) {\n    case SKILL_FIRST_AID:\n    case SKILL_DOCTOR:\n    case SKILL_SNEAK:\n    case SKILL_LOCKPICK:\n    case SKILL_STEAL:\n    case SKILL_TRAPS:\n    case SKILL_SCIENCE:\n    case SKILL_REPAIR:\n    case SKILL_SPEECH:\n    case SKILL_BARTER:\n    case SKILL_GAMBLING:\n    case SKILL_OUTDOORSMAN:\n        if (1) {\n            int gameDifficulty = GAME_DIFFICULTY_NORMAL;\n            config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty);\n\n            if (gameDifficulty == GAME_DIFFICULTY_HARD) {\n                return -10;\n            } else if (gameDifficulty == GAME_DIFFICULTY_EASY) {\n                return 20;\n            }\n        }\n        break;\n    }\n\n    return 0;\n}\n\n// 0x4ABE44\nstatic int skill_use_slot_available(int skill)\n{\n    for (int slot = 0; slot < SKILLS_MAX_USES_PER_DAY; slot++) {\n        if (timesSkillUsed[skill][slot] == 0) {\n            return slot;\n        }\n    }\n\n    int time = game_time();\n    int hoursSinceLastUsage = (time - timesSkillUsed[skill][0]) / GAME_TIME_TICKS_PER_HOUR;\n    if (hoursSinceLastUsage <= 24) {\n        return -1;\n    }\n\n    return SKILLS_MAX_USES_PER_DAY - 1;\n}\n\n// 0x4ABEB8\nstatic int skill_use_slot_add(int skill)\n{\n    int slot = skill_use_slot_available(skill);\n    if (slot == -1) {\n        return -1;\n    }\n\n    if (timesSkillUsed[skill][slot] != 0) {\n        for (int i = 0; i < slot; i++) {\n            timesSkillUsed[skill][i] = timesSkillUsed[skill][i + 1];\n        }\n    }\n\n    timesSkillUsed[skill][slot] = game_time();\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4ABF24\nstatic int skill_use_slot_clear()\n{\n    memset(timesSkillUsed, 0, sizeof(timesSkillUsed));\n    return 0;\n}\n\n// 0x4ABF3C\nint skill_use_slot_save(File* stream)\n{\n    return db_fwriteIntCount(stream, (int*)timesSkillUsed, SKILL_COUNT * SKILLS_MAX_USES_PER_DAY);\n}\n\n// 0x4ABF5C\nint skill_use_slot_load(File* stream)\n{\n    return db_freadIntCount(stream, (int*)timesSkillUsed, SKILL_COUNT * SKILLS_MAX_USES_PER_DAY);\n}\n\n// 0x4ABF7C\nchar* skillGetPartyMemberString(Object* critter, bool isDude)\n{\n    int baseMessageId;\n    int count;\n\n    if (isDude) {\n        baseMessageId = 1100;\n        count = 4;\n    } else {\n        baseMessageId = 1000;\n        count = 5;\n    }\n\n    int messageId = roll_random(0, count);\n\n    MessageListItem messageListItem;\n    char* msg = getmsg(&skill_message_file, &messageListItem, baseMessageId + messageId);\n    return msg;\n}\n"
  },
  {
    "path": "src/game/skill.h",
    "content": "#ifndef FALLOUT_GAME_SKILL_H_\n#define FALLOUT_GAME_SKILL_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/object_types.h\"\n#include \"game/proto_types.h\"\n#include \"game/skill_defs.h\"\n\nextern int gIsSteal;\nextern int gStealCount;\nextern int gStealSize;\n\nint skill_init();\nvoid skill_reset();\nvoid skill_exit();\nint skill_load(File* stream);\nint skill_save(File* stream);\nvoid skill_set_defaults(CritterProtoData* data);\nvoid skill_set_tags(int* skills, int count);\nvoid skill_get_tags(int* skills, int count);\nbool skill_is_tagged(int skill);\nint skill_level(Object* critter, int skill);\nint skill_base(int skill);\nint skill_points(Object* critter, int skill);\nint skill_inc_point(Object* critter, int skill);\nint skill_inc_point_force(Object* critter, int skill);\nint skill_dec_point(Object* critter, int skill);\nint skill_dec_point_force(Object* critter, int skill);\nint skill_result(Object* critter, int skill, int a3, int* a4);\nint skill_contest(Object* attacker, Object* defender, int skill, int attackerModifier, int defenderModifier, int* howMuch);\nchar* skill_name(int skill);\nchar* skill_description(int skill);\nchar* skill_attribute(int skill);\nint skill_pic(int skill);\nint skill_use(Object* obj, Object* a2, int skill, int a4);\nint skill_check_stealing(Object* a1, Object* a2, Object* item, bool isPlanting);\nint skill_use_slot_save(File* stream);\nint skill_use_slot_load(File* stream);\nchar* skillGetPartyMemberString(Object* critter, bool isDude);\n\n// Returns true if skill is valid.\nstatic inline bool skillIsValid(int skill)\n{\n    return skill >= 0 && skill < SKILL_COUNT;\n}\n\n#endif /* FALLOUT_GAME_SKILL_H_ */\n"
  },
  {
    "path": "src/game/skill_defs.h",
    "content": "#ifndef SKILL_DEFS_H\n#define SKILL_DEFS_H\n\n// max number of tagged skills\n#define NUM_TAGGED_SKILLS 4\n\n#define DEFAULT_TAGGED_SKILLS 3\n\n// Available skills.\ntypedef enum Skill {\n    SKILL_SMALL_GUNS,\n    SKILL_BIG_GUNS,\n    SKILL_ENERGY_WEAPONS,\n    SKILL_UNARMED,\n    SKILL_MELEE_WEAPONS,\n    SKILL_THROWING,\n    SKILL_FIRST_AID,\n    SKILL_DOCTOR,\n    SKILL_SNEAK,\n    SKILL_LOCKPICK,\n    SKILL_STEAL,\n    SKILL_TRAPS,\n    SKILL_SCIENCE,\n    SKILL_REPAIR,\n    SKILL_SPEECH,\n    SKILL_BARTER,\n    SKILL_GAMBLING,\n    SKILL_OUTDOORSMAN,\n    SKILL_COUNT,\n} Skill;\n\n#endif /* SKILL_DEFS_H */\n"
  },
  {
    "path": "src/game/skilldex.c",
    "content": "#include \"game/skilldex.h\"\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/cycle.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gmouse.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/map.h\"\n#include \"game/message.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/skill.h\"\n#include \"plib/gnw/rect.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define SKILLDEX_WINDOW_RIGHT_MARGIN 4\n#define SKILLDEX_WINDOW_BOTTOM_MARGIN 6\n\n#define SKILLDEX_SKILL_BUTTON_BUFFER_COUNT (SKILLDEX_SKILL_COUNT * 2)\n\ntypedef enum SkilldexFrm {\n    SKILLDEX_FRM_BACKGROUND,\n    SKILLDEX_FRM_BUTTON_ON,\n    SKILLDEX_FRM_BUTTON_OFF,\n    SKILLDEX_FRM_LITTLE_RED_BUTTON_UP,\n    SKILLDEX_FRM_LITTLE_RED_BUTTON_DOWN,\n    SKILLDEX_FRM_BIG_NUMBERS,\n    SKILLDEX_FRM_COUNT,\n} SkilldexFrm;\n\ntypedef enum SkilldexSkill {\n    SKILLDEX_SKILL_SNEAK,\n    SKILLDEX_SKILL_LOCKPICK,\n    SKILLDEX_SKILL_STEAL,\n    SKILLDEX_SKILL_TRAPS,\n    SKILLDEX_SKILL_FIRST_AID,\n    SKILLDEX_SKILL_DOCTOR,\n    SKILLDEX_SKILL_SCIENCE,\n    SKILLDEX_SKILL_REPAIR,\n    SKILLDEX_SKILL_COUNT,\n} SkilldexSkill;\n\nstatic int skilldex_start();\nstatic void skilldex_end();\n\n// 0x51D43C\nstatic bool bk_enable = false;\n\n// 0x51D440\nstatic int grphfid[SKILLDEX_FRM_COUNT] = {\n    121,\n    119,\n    120,\n    8,\n    9,\n    170,\n};\n\n// Maps Skilldex options into skills.\n//\n// 0x51D458\nstatic int sklxref[SKILLDEX_SKILL_COUNT] = {\n    SKILL_SNEAK,\n    SKILL_LOCKPICK,\n    SKILL_STEAL,\n    SKILL_TRAPS,\n    SKILL_FIRST_AID,\n    SKILL_DOCTOR,\n    SKILL_SCIENCE,\n    SKILL_REPAIR,\n};\n\n// 0x668088\nstatic Size ginfo[SKILLDEX_FRM_COUNT];\n\n// 0x6680B8\nstatic unsigned char* skldxbtn[SKILLDEX_SKILL_BUTTON_BUFFER_COUNT];\n\n// skilldex.msg\n//\n// 0x6680F8\nstatic MessageList skldxmsg;\n\n// 0x668100\nstatic MessageListItem mesg;\n\n// 0x668110\nstatic unsigned char* skldxbmp[SKILLDEX_FRM_COUNT];\n\n// 0x668128\nstatic CacheEntry* grphkey[SKILLDEX_FRM_COUNT];\n\n// 0x668140\nstatic int skldxwin;\n\n// 0x668144\nstatic unsigned char* winbuf;\n\n// 0x668148\nstatic int fontsave;\n\n// 0x4ABFD0\nint skilldex_select()\n{\n    if (skilldex_start() == -1) {\n        debug_printf(\"\\n ** Error loading skilldex dialog data! **\\n\");\n        return -1;\n    }\n\n    int rc = -1;\n    while (rc == -1) {\n        int keyCode = get_input();\n\n        if (keyCode == KEY_ESCAPE || keyCode == 500 || game_user_wants_to_quit != 0) {\n            rc = 0;\n        } else if (keyCode == KEY_RETURN) {\n            gsound_play_sfx_file(\"ib1p1xx1\");\n            rc = 0;\n        } else if (keyCode >= 501 && keyCode <= 509) {\n            rc = keyCode - 500;\n        }\n    }\n\n    if (rc != 0) {\n        block_for_tocks(1000 / 9);\n    }\n\n    skilldex_end();\n\n    return rc;\n}\n\n// 0x4AC054\nstatic int skilldex_start()\n{\n    fontsave = text_curr();\n    bk_enable = false;\n\n    gmouse_3d_off();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    if (!message_init(&skldxmsg)) {\n        return -1;\n    }\n\n    char path[FILENAME_MAX];\n    sprintf(path, \"%s%s\", msg_path, \"skilldex.msg\");\n\n    if (!message_load(&skldxmsg, path)) {\n        return -1;\n    }\n\n    int frmIndex;\n    for (frmIndex = 0; frmIndex < SKILLDEX_FRM_COUNT; frmIndex++) {\n        int fid = art_id(OBJ_TYPE_INTERFACE, grphfid[frmIndex], 0, 0, 0);\n        skldxbmp[frmIndex] = art_lock(fid, &(grphkey[frmIndex]), &(ginfo[frmIndex].width), &(ginfo[frmIndex].height));\n        if (skldxbmp[frmIndex] == NULL) {\n            break;\n        }\n    }\n\n    if (frmIndex < SKILLDEX_FRM_COUNT) {\n        while (--frmIndex >= 0) {\n            art_ptr_unlock(grphkey[frmIndex]);\n        }\n\n        message_exit(&skldxmsg);\n\n        return -1;\n    }\n\n    bool cycle = false;\n    int buttonDataIndex;\n    for (buttonDataIndex = 0; buttonDataIndex < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; buttonDataIndex++) {\n        skldxbtn[buttonDataIndex] = (unsigned char*)mem_malloc(ginfo[SKILLDEX_FRM_BUTTON_ON].height * ginfo[SKILLDEX_FRM_BUTTON_ON].width + 512);\n        if (skldxbtn[buttonDataIndex] == NULL) {\n            break;\n        }\n\n        // NOTE: Original code uses bitwise XOR.\n        cycle = !cycle;\n\n        unsigned char* data;\n        int size;\n        if (cycle) {\n            size = ginfo[SKILLDEX_FRM_BUTTON_OFF].width * ginfo[SKILLDEX_FRM_BUTTON_OFF].height;\n            data = skldxbmp[SKILLDEX_FRM_BUTTON_OFF];\n        } else {\n            size = ginfo[SKILLDEX_FRM_BUTTON_ON].width * ginfo[SKILLDEX_FRM_BUTTON_ON].height;\n            data = skldxbmp[SKILLDEX_FRM_BUTTON_ON];\n        }\n\n        memcpy(skldxbtn[buttonDataIndex], data, size);\n    }\n\n    if (buttonDataIndex < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT) {\n        while (--buttonDataIndex >= 0) {\n            mem_free(skldxbtn[buttonDataIndex]);\n        }\n\n        for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) {\n            art_ptr_unlock(grphkey[index]);\n        }\n\n        message_exit(&skldxmsg);\n\n        return -1;\n    }\n\n    int skilldexWindowX = 640 - ginfo[SKILLDEX_FRM_BACKGROUND].width - SKILLDEX_WINDOW_RIGHT_MARGIN;\n    int skilldexWindowY = 480 - INTERFACE_BAR_HEIGHT - 1 - ginfo[SKILLDEX_FRM_BACKGROUND].height - SKILLDEX_WINDOW_BOTTOM_MARGIN;\n    skldxwin = win_add(skilldexWindowX,\n        skilldexWindowY,\n        ginfo[SKILLDEX_FRM_BACKGROUND].width,\n        ginfo[SKILLDEX_FRM_BACKGROUND].height,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02);\n    if (skldxwin == -1) {\n        for (int index = 0; index < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; index++) {\n            mem_free(skldxbtn[index]);\n        }\n\n        for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) {\n            art_ptr_unlock(grphkey[index]);\n        }\n\n        message_exit(&skldxmsg);\n\n        return -1;\n    }\n\n    bk_enable = map_disable_bk_processes();\n\n    cycle_disable();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    winbuf = win_get_buf(skldxwin);\n    memcpy(winbuf,\n        skldxbmp[SKILLDEX_FRM_BACKGROUND],\n        ginfo[SKILLDEX_FRM_BACKGROUND].width * ginfo[SKILLDEX_FRM_BACKGROUND].height);\n\n    text_font(103);\n\n    // Render \"SKILLDEX\" title.\n    char* title = getmsg(&skldxmsg, &mesg, 100);\n    text_to_buf(winbuf + 14 * ginfo[SKILLDEX_FRM_BACKGROUND].width + 55,\n        title,\n        ginfo[SKILLDEX_FRM_BACKGROUND].width,\n        ginfo[SKILLDEX_FRM_BACKGROUND].width,\n        colorTable[18979]);\n\n    // Render skill values.\n    int valueY = 48;\n    for (int index = 0; index < SKILLDEX_SKILL_COUNT; index++) {\n        int value = skill_level(obj_dude, sklxref[index]);\n        if (value == -1) {\n            value = 0;\n        }\n\n        int hundreds = value / 100;\n        buf_to_buf(skldxbmp[SKILLDEX_FRM_BIG_NUMBERS] + 14 * hundreds,\n            14,\n            24,\n            336,\n            winbuf + ginfo[SKILLDEX_FRM_BACKGROUND].width * valueY + 110,\n            ginfo[SKILLDEX_FRM_BACKGROUND].width);\n\n        int tens = (value % 100) / 10;\n        buf_to_buf(skldxbmp[SKILLDEX_FRM_BIG_NUMBERS] + 14 * tens,\n            14,\n            24,\n            336,\n            winbuf + ginfo[SKILLDEX_FRM_BACKGROUND].width * valueY + 124,\n            ginfo[SKILLDEX_FRM_BACKGROUND].width);\n\n        int ones = (value % 100) % 10;\n        buf_to_buf(skldxbmp[SKILLDEX_FRM_BIG_NUMBERS] + 14 * ones,\n            14,\n            24,\n            336,\n            winbuf + ginfo[SKILLDEX_FRM_BACKGROUND].width * valueY + 138,\n            ginfo[SKILLDEX_FRM_BACKGROUND].width);\n\n        valueY += 36;\n    }\n\n    // Render skill buttons.\n    int lineHeight = text_height();\n\n    int buttonY = 45;\n    int nameY = ((ginfo[SKILLDEX_FRM_BUTTON_OFF].height - lineHeight) / 2) + 1;\n    for (int index = 0; index < SKILLDEX_SKILL_COUNT; index++) {\n        char name[MESSAGE_LIST_ITEM_FIELD_MAX_SIZE];\n        strcpy(name, getmsg(&skldxmsg, &mesg, 102 + index));\n\n        int nameX = ((ginfo[SKILLDEX_FRM_BUTTON_OFF].width - text_width(name)) / 2) + 1;\n        if (nameX < 0) {\n            nameX = 0;\n        }\n\n        text_to_buf(skldxbtn[index * 2] + ginfo[SKILLDEX_FRM_BUTTON_ON].width * nameY + nameX,\n            name,\n            ginfo[SKILLDEX_FRM_BUTTON_ON].width,\n            ginfo[SKILLDEX_FRM_BUTTON_ON].width,\n            colorTable[18979]);\n\n        text_to_buf(skldxbtn[index * 2 + 1] + ginfo[SKILLDEX_FRM_BUTTON_OFF].width * nameY + nameX,\n            name,\n            ginfo[SKILLDEX_FRM_BUTTON_OFF].width,\n            ginfo[SKILLDEX_FRM_BUTTON_OFF].width,\n            colorTable[14723]);\n\n        int btn = win_register_button(skldxwin,\n            15,\n            buttonY,\n            ginfo[SKILLDEX_FRM_BUTTON_OFF].width,\n            ginfo[SKILLDEX_FRM_BUTTON_OFF].height,\n            -1,\n            -1,\n            -1,\n            501 + index,\n            skldxbtn[index * 2],\n            skldxbtn[index * 2 + 1],\n            NULL,\n            BUTTON_FLAG_TRANSPARENT);\n        if (btn != -1) {\n            win_register_button_sound_func(btn, gsound_lrg_butt_press, gsound_lrg_butt_release);\n        }\n\n        buttonY += 36;\n    }\n\n    // Render \"CANCEL\" button.\n    char* cancel = getmsg(&skldxmsg, &mesg, 101);\n    text_to_buf(winbuf + ginfo[SKILLDEX_FRM_BACKGROUND].width * 337 + 72,\n        cancel,\n        ginfo[SKILLDEX_FRM_BACKGROUND].width,\n        ginfo[SKILLDEX_FRM_BACKGROUND].width,\n        colorTable[18979]);\n\n    int cancelBtn = win_register_button(skldxwin,\n        48,\n        338,\n        ginfo[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].width,\n        ginfo[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].height,\n        -1,\n        -1,\n        -1,\n        500,\n        skldxbmp[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP],\n        skldxbmp[SKILLDEX_FRM_LITTLE_RED_BUTTON_DOWN],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n    if (cancelBtn != -1) {\n        win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release);\n    }\n\n    win_draw(skldxwin);\n\n    return 0;\n}\n\n// 0x4AC67C\nstatic void skilldex_end()\n{\n    win_delete(skldxwin);\n\n    for (int index = 0; index < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; index++) {\n        mem_free(skldxbtn[index]);\n    }\n\n    for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) {\n        art_ptr_unlock(grphkey[index]);\n    }\n\n    message_exit(&skldxmsg);\n\n    text_font(fontsave);\n\n    if (bk_enable) {\n        map_enable_bk_processes();\n    }\n\n    cycle_enable();\n\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n}\n"
  },
  {
    "path": "src/game/skilldex.h",
    "content": "#ifndef FALLOUT_GAME_SKILLDEX_H_\n#define FALLOUT_GAME_SKILLDEX_H_\n\ntypedef enum SkilldexRC {\n    SKILLDEX_RC_ERROR = -1,\n    SKILLDEX_RC_CANCELED,\n    SKILLDEX_RC_SNEAK,\n    SKILLDEX_RC_LOCKPICK,\n    SKILLDEX_RC_STEAL,\n    SKILLDEX_RC_TRAPS,\n    SKILLDEX_RC_FIRST_AID,\n    SKILLDEX_RC_DOCTOR,\n    SKILLDEX_RC_SCIENCE,\n    SKILLDEX_RC_REPAIR,\n} SkilldexRC;\n\nint skilldex_select();\n\n#endif /* FALLOUT_GAME_SKILLDEX_H_ */\n"
  },
  {
    "path": "src/game/stat.c",
    "content": "#include \"game/stat.h\"\n\n#include <stdio.h>\n\n#include \"game/combat.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/display.h\"\n#include \"game/game.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/message.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n\n// Provides metadata about stats.\ntypedef struct StatDescription {\n    char* name;\n    char* description;\n    int frmId;\n    int minimumValue;\n    int maximumValue;\n    int defaultValue;\n} StatDescription;\n\n// 0x51D53C\nstatic StatDescription stat_data[STAT_COUNT] = {\n    { NULL, NULL, 0, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 },\n    { NULL, NULL, 1, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 },\n    { NULL, NULL, 2, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 },\n    { NULL, NULL, 3, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 },\n    { NULL, NULL, 4, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 },\n    { NULL, NULL, 5, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 },\n    { NULL, NULL, 6, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 },\n    { NULL, NULL, 10, 0, 999, 0 },\n    { NULL, NULL, 75, 1, 99, 0 },\n    { NULL, NULL, 18, 0, 999, 0 },\n    { NULL, NULL, 31, 0, INT_MAX, 0 },\n    { NULL, NULL, 32, 0, 500, 0 },\n    { NULL, NULL, 20, 0, 999, 0 },\n    { NULL, NULL, 24, 0, 60, 0 },\n    { NULL, NULL, 25, 0, 30, 0 },\n    { NULL, NULL, 26, 0, 100, 0 },\n    { NULL, NULL, 94, -60, 100, 0 },\n    { NULL, NULL, 0, 0, 100, 0 },\n    { NULL, NULL, 0, 0, 100, 0 },\n    { NULL, NULL, 0, 0, 100, 0 },\n    { NULL, NULL, 0, 0, 100, 0 },\n    { NULL, NULL, 0, 0, 100, 0 },\n    { NULL, NULL, 0, 0, 100, 0 },\n    { NULL, NULL, 0, 0, 100, 0 },\n    { NULL, NULL, 22, 0, 90, 0 },\n    { NULL, NULL, 0, 0, 90, 0 },\n    { NULL, NULL, 0, 0, 90, 0 },\n    { NULL, NULL, 0, 0, 90, 0 },\n    { NULL, NULL, 0, 0, 90, 0 },\n    { NULL, NULL, 0, 0, 100, 0 },\n    { NULL, NULL, 0, 0, 90, 0 },\n    { NULL, NULL, 83, 0, 95, 0 },\n    { NULL, NULL, 23, 0, 95, 0 },\n    { NULL, NULL, 0, 16, 101, 25 },\n    { NULL, NULL, 0, 0, 1, 0 },\n    { NULL, NULL, 10, 0, 2000, 0 },\n    { NULL, NULL, 11, 0, 2000, 0 },\n    { NULL, NULL, 12, 0, 2000, 0 },\n};\n\n// 0x51D8CC\nstatic StatDescription pc_stat_data[PC_STAT_COUNT] = {\n    { NULL, NULL, 0, 0, INT_MAX, 0 },\n    { NULL, NULL, 0, 1, PC_LEVEL_MAX, 1 },\n    { NULL, NULL, 0, 0, INT_MAX, 0 },\n    { NULL, NULL, 0, -20, 20, 0 },\n    { NULL, NULL, 0, 0, INT_MAX, 0 },\n};\n\n// 0x66817C\nstatic MessageList stat_message_file;\n\n// 0x668184\nstatic char* level_description[PRIMARY_STAT_RANGE];\n\n// 0x6681AC\nstatic int curr_pc_stat[PC_STAT_COUNT];\n\n// 0x4AED70\nint stat_init()\n{\n    MessageListItem messageListItem;\n\n    // NOTE: Uninline.\n    stat_pc_set_defaults();\n\n    if (!message_init(&stat_message_file)) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"%s%s\", msg_path, \"stat.msg\");\n\n    if (!message_load(&stat_message_file, path)) {\n        return -1;\n    }\n\n    for (int stat = 0; stat < STAT_COUNT; stat++) {\n        stat_data[stat].name = getmsg(&stat_message_file, &messageListItem, 100 + stat);\n        stat_data[stat].description = getmsg(&stat_message_file, &messageListItem, 200 + stat);\n    }\n\n    for (int pcStat = 0; pcStat < PC_STAT_COUNT; pcStat++) {\n        pc_stat_data[pcStat].name = getmsg(&stat_message_file, &messageListItem, 400 + pcStat);\n        pc_stat_data[pcStat].description = getmsg(&stat_message_file, &messageListItem, 500 + pcStat);\n    }\n\n    for (int index = 0; index < PRIMARY_STAT_RANGE; index++) {\n        level_description[index] = getmsg(&stat_message_file, &messageListItem, 301 + index);\n    }\n\n    return 0;\n}\n\n// 0x4AEEC0\nint stat_reset()\n{\n    // NOTE: Uninline.\n    stat_pc_set_defaults();\n\n    return 0;\n}\n\n// 0x4AEEE4\nint stat_exit()\n{\n    message_exit(&stat_message_file);\n\n    return 0;\n}\n\n// 0x4AEEF4\nint stat_load(File* stream)\n{\n    for (int index = 0; index < PC_STAT_COUNT; index++) {\n        if (db_freadInt(stream, &(curr_pc_stat[index])) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4AEF20\nint stat_save(File* stream)\n{\n    for (int index = 0; index < PC_STAT_COUNT; index++) {\n        if (db_fwriteInt(stream, curr_pc_stat[index]) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4AEF48\nint critterGetStat(Object* critter, int stat)\n{\n    int value;\n    if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) {\n        value = stat_get_base(critter, stat);\n        value += stat_get_bonus(critter, stat);\n\n        switch (stat) {\n        case STAT_PERCEPTION:\n            if ((critter->data.critter.combat.results & DAM_BLIND) != 0) {\n                value -= 5;\n            }\n            break;\n        case STAT_MAXIMUM_ACTION_POINTS:\n            if (1) {\n                int remainingCarryWeight = critterGetStat(critter, STAT_CARRY_WEIGHT) - item_total_weight(critter);\n                if (remainingCarryWeight < 0) {\n                    value -= -remainingCarryWeight / 40 + 1;\n                }\n            }\n            break;\n        case STAT_ARMOR_CLASS:\n            if (isInCombat()) {\n                if (combat_whose_turn() != critter) {\n                    int actionPointsMultiplier = 1;\n                    int hthEvadeBonus = 0;\n\n                    if (critter == obj_dude) {\n                        if (perkHasRank(obj_dude, PERK_HTH_EVADE)) {\n                            bool hasWeapon = false;\n\n                            Object* item2 = inven_right_hand(obj_dude);\n                            if (item2 != NULL) {\n                                if (item_get_type(item2) == ITEM_TYPE_WEAPON) {\n                                    if (item_w_anim_code(item2) != WEAPON_ANIMATION_NONE) {\n                                        hasWeapon = true;\n                                    }\n                                }\n                            }\n\n                            if (!hasWeapon) {\n                                Object* item1 = inven_left_hand(obj_dude);\n                                if (item1 != NULL) {\n                                    if (item_get_type(item1) == ITEM_TYPE_WEAPON) {\n                                        if (item_w_anim_code(item1) != WEAPON_ANIMATION_NONE) {\n                                            hasWeapon = true;\n                                        }\n                                    }\n                                }\n                            }\n\n                            if (!hasWeapon) {\n                                actionPointsMultiplier = 2;\n                                hthEvadeBonus = skill_level(obj_dude, SKILL_UNARMED) / 12;\n                            }\n                        }\n                    }\n                    value += hthEvadeBonus;\n                    value += critter->data.critter.combat.ap * actionPointsMultiplier;\n                }\n            }\n            break;\n        case STAT_AGE:\n            value += game_time() / GAME_TIME_TICKS_PER_YEAR;\n            break;\n        }\n\n        if (critter == obj_dude) {\n            switch (stat) {\n            case STAT_STRENGTH:\n                if (perk_level(critter, PERK_GAIN_STRENGTH)) {\n                    value++;\n                }\n\n                if (perk_level(critter, PERK_ADRENALINE_RUSH)) {\n                    if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) < (critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS) / 2)) {\n                        value++;\n                    }\n                }\n                break;\n            case STAT_PERCEPTION:\n                if (perk_level(critter, PERK_GAIN_PERCEPTION)) {\n                    value++;\n                }\n                break;\n            case STAT_ENDURANCE:\n                if (perk_level(critter, PERK_GAIN_ENDURANCE)) {\n                    value++;\n                }\n                break;\n            case STAT_CHARISMA:\n                if (1) {\n                    if (perk_level(critter, PERK_GAIN_CHARISMA)) {\n                        value++;\n                    }\n\n                    bool hasMirrorShades = false;\n\n                    Object* item2 = inven_right_hand(critter);\n                    if (item2 != NULL && item2->pid == PROTO_ID_MIRRORED_SHADES) {\n                        hasMirrorShades = true;\n                    }\n\n                    Object* item1 = inven_left_hand(critter);\n                    if (item1 != NULL && item1->pid == PROTO_ID_MIRRORED_SHADES) {\n                        hasMirrorShades = true;\n                    }\n\n                    if (hasMirrorShades) {\n                        value++;\n                    }\n                }\n                break;\n            case STAT_INTELLIGENCE:\n                if (perk_level(critter, PERK_GAIN_INTELLIGENCE)) {\n                    value++;\n                }\n                break;\n            case STAT_AGILITY:\n                if (perk_level(critter, PERK_GAIN_AGILITY)) {\n                    value++;\n                }\n                break;\n            case STAT_LUCK:\n                if (perk_level(critter, PERK_GAIN_LUCK)) {\n                    value++;\n                }\n                break;\n            case STAT_MAXIMUM_HIT_POINTS:\n                if (perk_level(critter, PERK_ALCOHOL_RAISED_HIT_POINTS)) {\n                    value += 2;\n                }\n\n                if (perk_level(critter, PERK_ALCOHOL_RAISED_HIT_POINTS_II)) {\n                    value += 4;\n                }\n\n                if (perk_level(critter, PERK_ALCOHOL_LOWERED_HIT_POINTS)) {\n                    value -= 2;\n                }\n\n                if (perk_level(critter, PERK_ALCOHOL_LOWERED_HIT_POINTS_II)) {\n                    value -= 4;\n                }\n\n                if (perk_level(critter, PERK_AUTODOC_RAISED_HIT_POINTS)) {\n                    value += 2;\n                }\n\n                if (perk_level(critter, PERK_AUTODOC_RAISED_HIT_POINTS_II)) {\n                    value += 4;\n                }\n\n                if (perk_level(critter, PERK_AUTODOC_LOWERED_HIT_POINTS)) {\n                    value -= 2;\n                }\n\n                if (perk_level(critter, PERK_AUTODOC_LOWERED_HIT_POINTS_II)) {\n                    value -= 4;\n                }\n                break;\n            case STAT_DAMAGE_RESISTANCE:\n            case STAT_DAMAGE_RESISTANCE_EXPLOSION:\n                if (perk_level(critter, PERK_DERMAL_IMPACT_ARMOR)) {\n                    value += 5;\n                } else if (perk_level(critter, PERK_DERMAL_IMPACT_ASSAULT_ENHANCEMENT)) {\n                    value += 10;\n                }\n                break;\n            case STAT_DAMAGE_RESISTANCE_LASER:\n            case STAT_DAMAGE_RESISTANCE_FIRE:\n            case STAT_DAMAGE_RESISTANCE_PLASMA:\n                if (perk_level(critter, PERK_PHOENIX_ARMOR_IMPLANTS)) {\n                    value += 5;\n                } else if (perk_level(critter, PERK_PHOENIX_ASSAULT_ENHANCEMENT)) {\n                    value += 10;\n                }\n                break;\n            case STAT_RADIATION_RESISTANCE:\n            case STAT_POISON_RESISTANCE:\n                if (perk_level(critter, PERK_VAULT_CITY_INOCULATIONS)) {\n                    value += 10;\n                }\n                break;\n            }\n        }\n\n        value = min(max(value, stat_data[stat].minimumValue), stat_data[stat].maximumValue);\n    } else {\n        switch (stat) {\n        case STAT_CURRENT_HIT_POINTS:\n            value = critter_get_hits(critter);\n            break;\n        case STAT_CURRENT_POISON_LEVEL:\n            value = critter_get_poison(critter);\n            break;\n        case STAT_CURRENT_RADIATION_LEVEL:\n            value = critter_get_rads(critter);\n            break;\n        default:\n            value = 0;\n            break;\n        }\n    }\n\n    return value;\n}\n\n// Returns base stat value (accounting for traits if critter is dude).\n//\n// 0x4AF3E0\nint stat_get_base(Object* critter, int stat)\n{\n    int value = stat_get_base_direct(critter, stat);\n\n    if (critter == obj_dude) {\n        value += trait_adjust_stat(stat);\n    }\n\n    return value;\n}\n\n// 0x4AF408\nint stat_get_base_direct(Object* critter, int stat)\n{\n    Proto* proto;\n\n    if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) {\n        proto_ptr(critter->pid, &proto);\n        return proto->critter.data.baseStats[stat];\n    } else {\n        switch (stat) {\n        case STAT_CURRENT_HIT_POINTS:\n            return critter_get_hits(critter);\n        case STAT_CURRENT_POISON_LEVEL:\n            return critter_get_poison(critter);\n        case STAT_CURRENT_RADIATION_LEVEL:\n            return critter_get_rads(critter);\n        }\n    }\n\n    return 0;\n}\n\n// 0x4AF474\nint stat_get_bonus(Object* critter, int stat)\n{\n    if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) {\n        Proto* proto;\n        proto_ptr(critter->pid, &proto);\n        return proto->critter.data.bonusStats[stat];\n    }\n\n    return 0;\n}\n\n// 0x4AF4BC\nint stat_set_base(Object* critter, int stat, int value)\n{\n    Proto* proto;\n\n    if (!statIsValid(stat)) {\n        return -5;\n    }\n\n    if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) {\n        if (stat > STAT_LUCK && stat <= STAT_POISON_RESISTANCE) {\n            // Cannot change base value of derived stats.\n            return -1;\n        }\n\n        if (critter == obj_dude) {\n            value -= trait_adjust_stat(stat);\n        }\n\n        if (value < stat_data[stat].minimumValue) {\n            return -2;\n        }\n\n        if (value > stat_data[stat].maximumValue) {\n            return -3;\n        }\n\n        proto_ptr(critter->pid, &proto);\n        proto->critter.data.baseStats[stat] = value;\n\n        if (stat >= STAT_STRENGTH && stat <= STAT_LUCK) {\n            stat_recalc_derived(critter);\n        }\n\n        return 0;\n    }\n\n    switch (stat) {\n    case STAT_CURRENT_HIT_POINTS:\n        return critter_adjust_hits(critter, value - critter_get_hits(critter));\n    case STAT_CURRENT_POISON_LEVEL:\n        return critter_adjust_poison(critter, value - critter_get_poison(critter));\n    case STAT_CURRENT_RADIATION_LEVEL:\n        return critter_adjust_rads(critter, value - critter_get_rads(critter));\n    }\n\n    // Should be unreachable\n    return 0;\n}\n\n// 0x4AF5D4\nint inc_stat(Object* critter, int stat)\n{\n    int value = stat_get_base_direct(critter, stat);\n\n    if (critter == obj_dude) {\n        value += trait_adjust_stat(stat);\n    }\n\n    return stat_set_base(critter, stat, value + 1);\n}\n\n// 0x4AF608\nint dec_stat(Object* critter, int stat)\n{\n    int value = stat_get_base_direct(critter, stat);\n\n    if (critter == obj_dude) {\n        value += trait_adjust_stat(stat);\n    }\n\n    return stat_set_base(critter, stat, value - 1);\n}\n\n// 0x4AF63C\nint stat_set_bonus(Object* critter, int stat, int value)\n{\n    if (!statIsValid(stat)) {\n        return -5;\n    }\n\n    if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) {\n        Proto* proto;\n        proto_ptr(critter->pid, &proto);\n        proto->critter.data.bonusStats[stat] = value;\n\n        if (stat >= STAT_STRENGTH && stat <= STAT_LUCK) {\n            stat_recalc_derived(critter);\n        }\n\n        return 0;\n    } else {\n        switch (stat) {\n        case STAT_CURRENT_HIT_POINTS:\n            return critter_adjust_hits(critter, value);\n        case STAT_CURRENT_POISON_LEVEL:\n            return critter_adjust_poison(critter, value);\n        case STAT_CURRENT_RADIATION_LEVEL:\n            return critter_adjust_rads(critter, value);\n        }\n    }\n\n    // Should be unreachable\n    return -1;\n}\n\n// 0x4AF6CC\nvoid stat_set_defaults(CritterProtoData* data)\n{\n    for (int stat = 0; stat < SAVEABLE_STAT_COUNT; stat++) {\n        data->baseStats[stat] = stat_data[stat].defaultValue;\n        data->bonusStats[stat] = 0;\n    }\n}\n\n// 0x4AF6FC\nvoid stat_recalc_derived(Object* critter)\n{\n    int strength = critterGetStat(critter, STAT_STRENGTH);\n    int perception = critterGetStat(critter, STAT_PERCEPTION);\n    int endurance = critterGetStat(critter, STAT_ENDURANCE);\n    int intelligence = critterGetStat(critter, STAT_INTELLIGENCE);\n    int agility = critterGetStat(critter, STAT_AGILITY);\n    int luck = critterGetStat(critter, STAT_LUCK);\n\n    Proto* proto;\n    proto_ptr(critter->pid, &proto);\n    CritterProtoData* data = &(proto->critter.data);\n\n    data->baseStats[STAT_MAXIMUM_HIT_POINTS] = stat_get_base(critter, STAT_STRENGTH) + stat_get_base(critter, STAT_ENDURANCE) * 2 + 15;\n    data->baseStats[STAT_MAXIMUM_ACTION_POINTS] = agility / 2 + 5;\n    data->baseStats[STAT_ARMOR_CLASS] = agility;\n    data->baseStats[STAT_MELEE_DAMAGE] = max(strength - 5, 1);\n    data->baseStats[STAT_CARRY_WEIGHT] = 25 * strength + 25;\n    data->baseStats[STAT_SEQUENCE] = 2 * perception;\n    data->baseStats[STAT_HEALING_RATE] = max(endurance / 3, 1);\n    data->baseStats[STAT_CRITICAL_CHANCE] = luck;\n    data->baseStats[STAT_BETTER_CRITICALS] = 0;\n    data->baseStats[STAT_RADIATION_RESISTANCE] = 2 * endurance;\n    data->baseStats[STAT_POISON_RESISTANCE] = 5 * endurance;\n}\n\n// 0x4AF854\nchar* stat_name(int stat)\n{\n    return statIsValid(stat) ? stat_data[stat].name : NULL;\n}\n\n// 0x4AF898\nchar* stat_description(int stat)\n{\n    return statIsValid(stat) ? stat_data[stat].description : NULL;\n}\n\n// 0x4AF8DC\nchar* stat_level_description(int value)\n{\n    if (value < PRIMARY_STAT_MIN) {\n        value = PRIMARY_STAT_MIN;\n    } else if (value > PRIMARY_STAT_MAX) {\n        value = PRIMARY_STAT_MAX;\n    }\n\n    return level_description[value - PRIMARY_STAT_MIN];\n}\n\n// 0x4AF8FC\nint stat_pc_get(int pcStat)\n{\n    return pcStatIsValid(pcStat) ? curr_pc_stat[pcStat] : 0;\n}\n\n// 0x4AF910\nint stat_pc_set(int pcStat, int value)\n{\n    int result;\n\n    if (!pcStatIsValid(pcStat)) {\n        return -5;\n    }\n\n    if (value < pc_stat_data[pcStat].minimumValue) {\n        return -2;\n    }\n\n    if (value > pc_stat_data[pcStat].maximumValue) {\n        return -3;\n    }\n\n    if (pcStat != PC_STAT_EXPERIENCE || value >= curr_pc_stat[PC_STAT_EXPERIENCE]) {\n        curr_pc_stat[pcStat] = value;\n        if (pcStat == PC_STAT_EXPERIENCE) {\n            result = statPCAddExperienceCheckPMs(0, true);\n        } else {\n            result = 0;\n        }\n    } else {\n        result = statPcResetExperience(value);\n    }\n\n    return result;\n}\n\n// Reset stats.\n//\n// 0x4AF980\nvoid stat_pc_set_defaults()\n{\n    for (int pcStat = 0; pcStat < PC_STAT_COUNT; pcStat++) {\n        curr_pc_stat[pcStat] = pc_stat_data[pcStat].defaultValue;\n    }\n}\n\n// Returns experience to reach next level.\n//\n// 0x4AF9A0\nint stat_pc_min_exp()\n{\n    return statPcMinExpForLevel(curr_pc_stat[PC_STAT_LEVEL] + 1);\n}\n\n// Returns exp to reach given level.\n//\n// 0x4AF9A8\nint statPcMinExpForLevel(int level)\n{\n    if (level >= PC_LEVEL_MAX) {\n        return -1;\n    }\n\n    int v1 = level / 2;\n    if ((level & 1) != 0) {\n        return 1000 * v1 * level;\n    } else {\n        return 1000 * v1 * (level - 1);\n    }\n}\n\n// 0x4AF9F4\nchar* stat_pc_name(int pcStat)\n{\n    return pcStat >= 0 && pcStat < PC_STAT_COUNT ? pc_stat_data[pcStat].name : NULL;\n}\n\n// 0x4AFA14\nchar* stat_pc_description(int pcStat)\n{\n    return pcStat >= 0 && pcStat < PC_STAT_COUNT ? pc_stat_data[pcStat].description : NULL;\n}\n\n// 0x4AFA34\nint stat_picture(int stat)\n{\n    return statIsValid(stat) ? stat_data[stat].frmId : 0;\n}\n\n// Roll D10 against specified stat.\n//\n// This function is intended to be used with one of SPECIAL stats (which are\n// capped at 10, hence d10), not with artitrary stat, but does not enforce it.\n//\n// An optional [modifier] can be supplied as a bonus (or penalty) to the stat's\n// value.\n//\n// Upon return [howMuch] will be set to difference between stat's value\n// (accounting for given [modifier]) and d10 roll, which can be positive (or\n// zero) when roll succeeds, or negative when roll fails. Set [howMuch] to\n// `NULL` if you're not interested in this value.\n//\n// 0x4AFA78\nint stat_result(Object* critter, int stat, int modifier, int* howMuch)\n{\n    int value = critterGetStat(critter, stat) + modifier;\n    int chance = roll_random(PRIMARY_STAT_MIN, PRIMARY_STAT_MAX);\n\n    if (howMuch != NULL) {\n        *howMuch = value - chance;\n    }\n\n    if (chance <= value) {\n        return ROLL_SUCCESS;\n    }\n\n    return ROLL_FAILURE;\n}\n\n// 0x4AFAA8\nint stat_pc_add_experience(int xp)\n{\n    return statPCAddExperienceCheckPMs(xp, true);\n}\n\n// 0x4AFAB8\nint statPCAddExperienceCheckPMs(int xp, bool a2)\n{\n    int newXp = curr_pc_stat[PC_STAT_EXPERIENCE];\n    newXp += xp;\n    newXp += perk_level(obj_dude, PERK_SWIFT_LEARNER) * 5 * xp / 100;\n\n    if (newXp < pc_stat_data[PC_STAT_EXPERIENCE].minimumValue) {\n        newXp = pc_stat_data[PC_STAT_EXPERIENCE].minimumValue;\n    }\n\n    if (newXp > pc_stat_data[PC_STAT_EXPERIENCE].maximumValue) {\n        newXp = pc_stat_data[PC_STAT_EXPERIENCE].maximumValue;\n    }\n\n    curr_pc_stat[PC_STAT_EXPERIENCE] = newXp;\n\n    while (curr_pc_stat[PC_STAT_LEVEL] < PC_LEVEL_MAX) {\n        if (newXp < stat_pc_min_exp()) {\n            break;\n        }\n\n        if (stat_pc_set(PC_STAT_LEVEL, curr_pc_stat[PC_STAT_LEVEL] + 1) == 0) {\n            int maxHpBefore = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n\n            // You have gone up a level.\n            MessageListItem messageListItem;\n            messageListItem.num = 600;\n            if (message_search(&stat_message_file, &messageListItem)) {\n                display_print(messageListItem.text);\n            }\n\n            pc_flag_on(DUDE_STATE_LEVEL_UP_AVAILABLE);\n\n            gsound_play_sfx_file(\"levelup\");\n\n            // NOTE: Uninline.\n            int endurance = stat_get_base(obj_dude, STAT_ENDURANCE);\n\n            int hpPerLevel = endurance / 2 + 2;\n            hpPerLevel += perk_level(obj_dude, PERK_LIFEGIVER) * 4;\n\n            int bonusHp = stat_get_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n            stat_set_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS, bonusHp + hpPerLevel);\n\n            int maxHpAfter = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n            critter_adjust_hits(obj_dude, maxHpAfter - maxHpBefore);\n\n            intface_update_hit_points(false);\n\n            if (a2) {\n                partyMemberIncLevels();\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4AFC38\nint statPcResetExperience(int xp)\n{\n    int oldLevel = curr_pc_stat[PC_STAT_LEVEL];\n    curr_pc_stat[PC_STAT_EXPERIENCE] = xp;\n\n    int level = 1;\n    do {\n        level += 1;\n    } while (xp >= statPcMinExpForLevel(level) && level < PC_LEVEL_MAX);\n\n    int newLevel = level - 1;\n\n    stat_pc_set(PC_STAT_LEVEL, newLevel);\n    pc_flag_off(DUDE_STATE_LEVEL_UP_AVAILABLE);\n\n    // NOTE: Uninline.\n    int endurance = stat_get_base(obj_dude, STAT_ENDURANCE);\n\n    int hpPerLevel = endurance / 2 + 2;\n    hpPerLevel += perk_level(obj_dude, PERK_LIFEGIVER) * 4;\n\n    int deltaHp = (oldLevel - newLevel) * hpPerLevel;\n    critter_adjust_hits(obj_dude, -deltaHp);\n\n    int bonusHp = stat_get_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS);\n\n    stat_set_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS, bonusHp - deltaHp);\n\n    intface_update_hit_points(false);\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/stat.h",
    "content": "#ifndef FALLOUT_GAME_STAT_H_\n#define FALLOUT_GAME_STAT_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/object_types.h\"\n#include \"game/proto_types.h\"\n#include \"game/stat_defs.h\"\n\n#define STAT_ERR_INVALID_STAT -5\n\nint stat_init();\nint stat_reset();\nint stat_exit();\nint stat_load(File* stream);\nint stat_save(File* stream);\nint critterGetStat(Object* critter, int stat);\nint stat_get_base(Object* critter, int stat);\nint stat_get_base_direct(Object* critter, int stat);\nint stat_get_bonus(Object* critter, int stat);\nint stat_set_base(Object* critter, int stat, int value);\nint inc_stat(Object* critter, int stat);\nint dec_stat(Object* critter, int stat);\nint stat_set_bonus(Object* critter, int stat, int value);\nvoid stat_set_defaults(CritterProtoData* data);\nvoid stat_recalc_derived(Object* critter);\nchar* stat_name(int stat);\nchar* stat_description(int stat);\nchar* stat_level_description(int value);\nint stat_pc_get(int pcStat);\nint stat_pc_set(int pcStat, int value);\nvoid stat_pc_set_defaults();\nint stat_pc_min_exp();\nint statPcMinExpForLevel(int level);\nchar* stat_pc_name(int pcStat);\nchar* stat_pc_description(int pcStat);\nint stat_picture(int stat);\nint stat_result(Object* critter, int stat, int modifier, int* howMuch);\nint stat_pc_add_experience(int xp);\nint statPCAddExperienceCheckPMs(int xp, bool a2);\nint statPcResetExperience(int a1);\n\nstatic inline bool statIsValid(int stat)\n{\n    return stat >= 0 && stat < STAT_COUNT;\n}\n\nstatic inline bool pcStatIsValid(int pcStat)\n{\n    return pcStat >= 0 && pcStat < PC_STAT_COUNT;\n}\n\n#endif /* FALLOUT_GAME_STAT_H_ */\n"
  },
  {
    "path": "src/game/stat_defs.h",
    "content": "#ifndef STAT_DEFS\n#define STAT_DEFS\n\n// The minimum value of SPECIAL stat.\n#define PRIMARY_STAT_MIN (1)\n\n// The maximum value of SPECIAL stat.\n#define PRIMARY_STAT_MAX (10)\n\n// The number of values of SPECIAL stat.\n//\n// Every stat value has it's own human readable description. This value is used\n// as number of these descriptions.\n#define PRIMARY_STAT_RANGE ((PRIMARY_STAT_MAX) - (PRIMARY_STAT_MIN) + 1)\n\n// The maximum number of PC level.\n#define PC_LEVEL_MAX 99\n\n// Available stats.\ntypedef enum Stat {\n    STAT_STRENGTH,\n    STAT_PERCEPTION,\n    STAT_ENDURANCE,\n    STAT_CHARISMA,\n    STAT_INTELLIGENCE,\n    STAT_AGILITY,\n    STAT_LUCK,\n    STAT_MAXIMUM_HIT_POINTS,\n    STAT_MAXIMUM_ACTION_POINTS,\n    STAT_ARMOR_CLASS,\n    STAT_UNARMED_DAMAGE,\n    STAT_MELEE_DAMAGE,\n    STAT_CARRY_WEIGHT,\n    STAT_SEQUENCE,\n    STAT_HEALING_RATE,\n    STAT_CRITICAL_CHANCE,\n    STAT_BETTER_CRITICALS,\n    STAT_DAMAGE_THRESHOLD,\n    STAT_DAMAGE_THRESHOLD_LASER,\n    STAT_DAMAGE_THRESHOLD_FIRE,\n    STAT_DAMAGE_THRESHOLD_PLASMA,\n    STAT_DAMAGE_THRESHOLD_ELECTRICAL,\n    STAT_DAMAGE_THRESHOLD_EMP,\n    STAT_DAMAGE_THRESHOLD_EXPLOSION,\n    STAT_DAMAGE_RESISTANCE,\n    STAT_DAMAGE_RESISTANCE_LASER,\n    STAT_DAMAGE_RESISTANCE_FIRE,\n    STAT_DAMAGE_RESISTANCE_PLASMA,\n    STAT_DAMAGE_RESISTANCE_ELECTRICAL,\n    STAT_DAMAGE_RESISTANCE_EMP,\n    STAT_DAMAGE_RESISTANCE_EXPLOSION,\n    STAT_RADIATION_RESISTANCE,\n    STAT_POISON_RESISTANCE,\n    STAT_AGE,\n    STAT_GENDER,\n    STAT_CURRENT_HIT_POINTS,\n    STAT_CURRENT_POISON_LEVEL,\n    STAT_CURRENT_RADIATION_LEVEL,\n    STAT_COUNT,\n\n    // Number of primary stats.\n    PRIMARY_STAT_COUNT = 7,\n\n    // Number of SPECIAL stats (primary + secondary).\n    SPECIAL_STAT_COUNT = 33,\n\n    // Number of saveable stats (i.e. excluding CURRENT pseudostats).\n    SAVEABLE_STAT_COUNT = 35,\n} Stat;\n\n#define STAT_INVALID -1\n\n// Special stats that are only relevant to player character.\ntypedef enum PcStat {\n    PC_STAT_UNSPENT_SKILL_POINTS,\n    PC_STAT_LEVEL,\n    PC_STAT_EXPERIENCE,\n    PC_STAT_REPUTATION,\n    PC_STAT_KARMA,\n    PC_STAT_COUNT,\n} PcStat;\n\n#endif /* STAT_DEFS */\n"
  },
  {
    "path": "src/game/strparse.c",
    "content": "#include \"game/strparse.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/debug.h\"\n\n// strParseValue\n// 0x4AFD10\nint strParseValue(char** stringPtr, int* valuePtr)\n{\n    char *str, *remaining_str;\n    int v1, v2, v3;\n    char tmp;\n\n    if (*stringPtr == NULL) {\n        return 0;\n    }\n\n    str = *stringPtr;\n\n    strlwr(str);\n\n    v1 = strspn(str, \" \");\n    str += v1;\n\n    v2 = strcspn(str, \",\");\n    v3 = v1 + v2;\n\n    remaining_str = *stringPtr + v3;\n    *stringPtr = remaining_str;\n\n    if (*remaining_str != '\\0') {\n        *stringPtr = remaining_str + 1;\n    }\n\n    if (v2 != 0) {\n        tmp = *(str + v2);\n        *(str + v2) = '\\0';\n    }\n\n    *valuePtr = atoi(str);\n\n    if (v2 != 0) {\n        *(str + v2) = tmp;\n    }\n\n    return 0;\n}\n\n// strParseStrFromList\n// 0x4AFE08\nint strParseStrFromList(char** stringPtr, int* valuePtr, const char** stringList, int stringListLength)\n{\n    int i;\n    char *str, *remaining_str;\n    int v1, v2, v3;\n    char tmp;\n\n    if (*stringPtr == NULL) {\n        return 0;\n    }\n\n    str = *stringPtr;\n\n    strlwr(str);\n\n    v1 = strspn(str, \" \");\n    str += v1;\n\n    v2 = strcspn(str, \",\");\n    v3 = v1 + v2;\n\n    remaining_str = *stringPtr + v3;\n    *stringPtr = remaining_str;\n\n    if (*remaining_str != '\\0') {\n        *stringPtr = remaining_str + 1;\n    }\n\n    if (v2 != 0) {\n        tmp = *(str + v2);\n        *(str + v2) = '\\0';\n    }\n\n    for (i = 0; i < stringListLength; i++) {\n        if (stricmp(str, stringList[i]) == 0) {\n            break;\n        }\n    }\n\n    if (v2 != 0) {\n        *(str + v2) = tmp;\n    }\n\n    if (i == stringListLength) {\n        debug_printf(\"\\nstrParseStrFromList Error: Couldn't find match for string: %s!\", str);\n        *valuePtr = -1;\n        return -1;\n    }\n\n    *valuePtr = i;\n\n    return 0;\n}\n\n// strParseStrFromFunc\n// 0x4AFEDC\nint strParseStrFromFunc(char** stringPtr, int* valuePtr, StringParserCallback* callback)\n{\n    char *str, *remaining_str;\n    int v1, v2, v3;\n    char tmp;\n    int result;\n\n    if (*stringPtr == NULL) {\n        return 0;\n    }\n\n    str = *stringPtr;\n\n    strlwr(str);\n\n    v1 = strspn(str, \" \");\n    str += v1;\n\n    v2 = strcspn(str, \",\");\n    v3 = v1 + v2;\n\n    remaining_str = *stringPtr + v3;\n    *stringPtr = remaining_str;\n\n    if (*remaining_str != '\\0') {\n        *stringPtr = remaining_str + 1;\n    }\n\n    if (v2 != 0) {\n        tmp = *(str + v2);\n        *(str + v2) = '\\0';\n    }\n\n    result = callback(str, valuePtr);\n\n    if (v2 != 0) {\n        *(str + v2) = tmp;\n    }\n\n    if (result != 0) {\n        debug_printf(\"\\nstrParseStrFromFunc Error: Couldn't find match for string: %s!\", str);\n        *valuePtr = -1;\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4AFF7C\nint strParseStrSepVal(char** stringPtr, const char* key, int* valuePtr, const char* delimeter)\n{\n    char* str;\n    int v1, v2, v3, v4, v5;\n    char tmp1, tmp2;\n    int result;\n\n    result = -1;\n\n    if (*stringPtr == NULL) {\n        return 0;\n    }\n\n    str = *stringPtr;\n\n    if (*str == '\\0') {\n        return -1;\n    }\n\n    strlwr(str);\n\n    if (*str == ',') {\n        str++;\n        *stringPtr = *stringPtr + 1;\n    }\n\n    v1 = strspn(str, \" \");\n    str += v1;\n\n    v2 = strcspn(str, \",\");\n    v3 = v1 + v2;\n\n    tmp1 = *(str + v2);\n    *(str + v2) = '\\0';\n\n    v4 = strcspn(str, delimeter);\n    v5 = v1 + v4;\n\n    tmp2 = *(str + v4);\n    *(str + v4) = '\\0';\n\n    if (strcmp(str, key) == 0) {\n        *stringPtr = *stringPtr + v3;\n        *valuePtr = atoi(str + v4 + 1);\n        result = 0;\n    }\n\n    *(str + v4) = tmp2;\n    *(str + v2) = tmp1;\n\n    return result;\n}\n\n// 0x4B005C\nint strParseStrAndSepVal(char** stringPtr, char* key, int* valuePtr, const char* delimiter)\n{\n    char* str;\n    int v1, v2, v3, v4, v5;\n    char tmp1, tmp2;\n\n    if (*stringPtr == NULL) {\n        return 0;\n    }\n\n    str = *stringPtr;\n\n    if (*str == '\\0') {\n        return -1;\n    }\n\n    strlwr(str);\n\n    if (*str == ',') {\n        str++;\n        *stringPtr = *stringPtr + 1;\n    }\n\n    v1 = strspn(str, \" \");\n    str += v1;\n\n    v2 = strcspn(str, \",\");\n    v3 = v1 + v2;\n\n    tmp1 = *(str + v2);\n    *(str + v2) = '\\0';\n\n    v4 = strcspn(str, delimiter);\n    v5 = v1 + v4;\n\n    tmp2 = *(str + v4);\n    *(str + v4) = '\\0';\n\n    strcpy(key, str);\n\n    *stringPtr = *stringPtr + v3;\n    *valuePtr = atoi(str + v4 + 1);\n\n    *(str + v4) = tmp2;\n    *(str + v2) = tmp1;\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/strparse.h",
    "content": "#ifndef FALLOUT_GAME_STRPARSE_H_\n#define FALLOUT_GAME_STRPARSE_H_\n\ntypedef int(StringParserCallback)(char* string, int* valuePtr);\n\nint strParseValue(char** stringPtr, int* valuePtr);\nint strParseStrFromList(char** stringPtr, int* valuePtr, const char** list, int count);\nint strParseStrFromFunc(char** stringPtr, int* valuePtr, StringParserCallback* callback);\nint strParseStrSepVal(char** stringPtr, const char* key, int* valuePtr, const char* delimeter);\nint strParseStrAndSepVal(char** stringPtr, char* key, int* valuePtr, const char* delimeter);\n\n#endif /* FALLOUT_GAME_STRPARSE_H_ */\n"
  },
  {
    "path": "src/game/textobj.c",
    "content": "#include \"game/textobj.h\"\n\n#include <string.h>\n\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/gconfig.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/object.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/tile.h\"\n#include \"game/wordwrap.h\"\n\n// The maximum number of text objects that can exist at the same time.\n#define TEXT_OBJECTS_MAX_COUNT 20\n\ntypedef enum TextObjectFlags {\n    TEXT_OBJECT_MARKED_FOR_REMOVAL = 0x01,\n    TEXT_OBJECT_UNBOUNDED = 0x02,\n} TextObjectFlags;\n\ntypedef struct TextObject {\n    int flags;\n    Object* owner;\n    unsigned int time;\n    int linesCount;\n    int sx;\n    int sy;\n    int tile;\n    int x;\n    int y;\n    int width;\n    int height;\n    unsigned char* data;\n} TextObject;\n\nstatic_assert(sizeof(TextObject) == 48, \"wrong size\");\n\nstatic void text_object_bk();\nstatic void text_object_get_offset(TextObject* textObject);\n\n// 0x51D944\nstatic int text_object_index = 0;\n\n// 0x51D948\nstatic unsigned int text_object_base_delay = 3500;\n\n// 0x51D94C\nstatic unsigned int text_object_line_delay = 1399;\n\n// 0x6681C0\nstatic TextObject* text_object_list[TEXT_OBJECTS_MAX_COUNT];\n\n// 0x668210\nstatic int display_width;\n\n// 0x668214\nstatic int display_height;\n\n// 0x668218\nstatic unsigned char* display_buffer;\n\n// 0x66821C\nstatic bool text_object_enabled;\n\n// 0x668220\nstatic bool text_object_initialized;\n\n// 0x4B0130\nint text_object_init(unsigned char* windowBuffer, int width, int height)\n{\n    if (text_object_initialized) {\n        return -1;\n    }\n\n    display_buffer = windowBuffer;\n    display_width = width;\n    display_height = height;\n    text_object_index = 0;\n\n    add_bk_process(text_object_bk);\n\n    double textBaseDelay;\n    if (!config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, &textBaseDelay)) {\n        textBaseDelay = 3.5;\n    }\n\n    double textLineDelay;\n    if (!config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, &textLineDelay)) {\n        textLineDelay = 1.399993896484375;\n    }\n\n    text_object_base_delay = (unsigned int)(textBaseDelay * 1000.0);\n    text_object_line_delay = (unsigned int)(textLineDelay * 1000.0);\n\n    text_object_enabled = true;\n    text_object_initialized = true;\n\n    return 0;\n}\n\n// 0x4B021C\nint text_object_reset()\n{\n    if (!text_object_initialized) {\n        return -1;\n    }\n\n    for (int index = 0; index < text_object_index; index++) {\n        mem_free(text_object_list[index]->data);\n        mem_free(text_object_list[index]);\n    }\n\n    text_object_index = 0;\n    add_bk_process(text_object_bk);\n\n    return 0;\n}\n\n// 0x4B0280\nvoid text_object_exit()\n{\n    if (text_object_initialized) {\n        text_object_reset();\n        remove_bk_process(text_object_bk);\n        text_object_initialized = false;\n    }\n}\n\n// 0x4B02A4\nvoid text_object_disable()\n{\n    text_object_enabled = false;\n}\n\n// 0x4B02B0\nvoid text_object_enable()\n{\n    text_object_enabled = true;\n}\n\n// NOTE: Unused.\n//\n// 0x4B02BC\nint text_object_is_enabled()\n{\n    return text_object_enabled;\n}\n\n// 0x4B02C4\nvoid text_object_set_base_delay(double value)\n{\n    if (value < 1.0) {\n        value = 1.0;\n    }\n\n    text_object_base_delay = (int)(value * 1000.0);\n}\n\n// NOTE: Unused.\n//\n// 0x4B0308\nunsigned int text_object_get_base_delay()\n{\n    return text_object_base_delay / 1000;\n}\n\n// 0x4B031C\nvoid text_object_set_line_delay(double value)\n{\n    if (value < 0.0) {\n        value = 0.0;\n    }\n\n    text_object_line_delay = (int)(value * 1000.0);\n}\n\n// NOTE: Unused.\n//\n// 0x4B0358\nunsigned int text_object_get_line_delay()\n{\n    return text_object_line_delay / 1000;\n}\n\n// text_object_create\n// 0x4B036C\nint text_object_create(Object* object, char* string, int font, int color, int a5, Rect* rect)\n{\n    if (!text_object_initialized) {\n        return -1;\n    }\n\n    if (text_object_index >= TEXT_OBJECTS_MAX_COUNT - 1) {\n        return -1;\n    }\n\n    if (string == NULL) {\n        return -1;\n    }\n\n    if (*string == '\\0') {\n        return -1;\n    }\n\n    TextObject* textObject = (TextObject*)mem_malloc(sizeof(*textObject));\n    if (textObject == NULL) {\n        return -1;\n    }\n\n    memset(textObject, 0, sizeof(*textObject));\n\n    int oldFont = text_curr();\n    text_font(font);\n\n    short beginnings[WORD_WRAP_MAX_COUNT];\n    short count;\n    if (word_wrap(string, 200, beginnings, &count) != 0) {\n        text_font(oldFont);\n        return -1;\n    }\n\n    textObject->linesCount = count - 1;\n    if (textObject->linesCount < 1) {\n        debug_printf(\"**Error in text_object_create()\\n\");\n    }\n\n    textObject->width = 0;\n\n    for (int index = 0; index < textObject->linesCount; index++) {\n        char* ending = string + beginnings[index + 1];\n        char* beginning = string + beginnings[index];\n        if (ending[-1] == ' ') {\n            --ending;\n        }\n\n        char c = *ending;\n        *ending = '\\0';\n\n        // NOTE: Calls [text_width] twice, probably result of using min/max macro\n        int width = text_width(beginning);\n        if (width >= textObject->width) {\n            textObject->width = width;\n        }\n\n        *ending = c;\n    }\n\n    textObject->height = (text_height() + 1) * textObject->linesCount;\n\n    if (a5 != -1) {\n        textObject->width += 2;\n        textObject->height += 2;\n    }\n\n    int size = textObject->width * textObject->height;\n    textObject->data = (unsigned char*)mem_malloc(size);\n    if (textObject->data == NULL) {\n        text_font(oldFont);\n        return -1;\n    }\n\n    memset(textObject->data, 0, size);\n\n    unsigned char* dest = textObject->data;\n    int skip = textObject->width * (text_height() + 1);\n\n    if (a5 != -1) {\n        dest += textObject->width;\n    }\n\n    for (int index = 0; index < textObject->linesCount; index++) {\n        char* beginning = string + beginnings[index];\n        char* ending = string + beginnings[index + 1];\n        if (ending[-1] == ' ') {\n            --ending;\n        }\n\n        char c = *ending;\n        *ending = '\\0';\n\n        int width = text_width(beginning);\n        text_to_buf(dest + (textObject->width - width) / 2, beginning, textObject->width, textObject->width, color);\n\n        *ending = c;\n\n        dest += skip;\n    }\n\n    if (a5 != -1) {\n        buf_outline(textObject->data, textObject->width, textObject->height, textObject->width, a5);\n    }\n\n    if (object != NULL) {\n        textObject->tile = object->tile;\n    } else {\n        textObject->flags |= TEXT_OBJECT_UNBOUNDED;\n        textObject->tile = tile_center_tile;\n    }\n\n    text_object_get_offset(textObject);\n\n    if (rect != NULL) {\n        rect->ulx = textObject->x;\n        rect->uly = textObject->y;\n        rect->lrx = textObject->x + textObject->width - 1;\n        rect->lry = textObject->y + textObject->height - 1;\n    }\n\n    text_object_remove(object);\n\n    textObject->owner = object;\n    textObject->time = get_bk_time();\n\n    text_object_list[text_object_index] = textObject;\n    text_object_index++;\n\n    text_font(oldFont);\n\n    return 0;\n}\n\n// 0x4B06E8\nvoid text_object_render(Rect* rect)\n{\n    if (!text_object_initialized) {\n        return;\n    }\n\n    for (int index = 0; index < text_object_index; index++) {\n        TextObject* textObject = text_object_list[index];\n        tile_coord(textObject->tile, &(textObject->x), &(textObject->y), map_elevation);\n        textObject->x += textObject->sx;\n        textObject->y += textObject->sy;\n\n        Rect textObjectRect;\n        textObjectRect.ulx = textObject->x;\n        textObjectRect.uly = textObject->y;\n        textObjectRect.lrx = textObject->width + textObject->x - 1;\n        textObjectRect.lry = textObject->height + textObject->y - 1;\n        if (rect_inside_bound(&textObjectRect, rect, &textObjectRect) == 0) {\n            trans_buf_to_buf(textObject->data + textObject->width * (textObjectRect.uly - textObject->y) + (textObjectRect.ulx - textObject->x),\n                textObjectRect.lrx - textObjectRect.ulx + 1,\n                textObjectRect.lry - textObjectRect.uly + 1,\n                textObject->width,\n                display_buffer + display_width * textObjectRect.uly + textObjectRect.ulx,\n                display_width);\n        }\n    }\n}\n\n// 0x4B07F0\nint text_object_count()\n{\n    return text_object_index;\n}\n\n// 0x4B07F8\nstatic void text_object_bk()\n{\n    if (!text_object_enabled) {\n        return;\n    }\n\n    bool textObjectsRemoved = false;\n    Rect dirtyRect;\n\n    for (int index = 0; index < text_object_index; index++) {\n        TextObject* textObject = text_object_list[index];\n\n        unsigned int delay = text_object_line_delay * textObject->linesCount + text_object_base_delay;\n        if ((textObject->flags & TEXT_OBJECT_MARKED_FOR_REMOVAL) != 0 || (elapsed_tocks(get_bk_time(), textObject->time) > delay)) {\n            tile_coord(textObject->tile, &(textObject->x), &(textObject->y), map_elevation);\n            textObject->x += textObject->sx;\n            textObject->y += textObject->sy;\n\n            Rect textObjectRect;\n            textObjectRect.ulx = textObject->x;\n            textObjectRect.uly = textObject->y;\n            textObjectRect.lrx = textObject->width + textObject->x - 1;\n            textObjectRect.lry = textObject->height + textObject->y - 1;\n\n            if (textObjectsRemoved) {\n                rect_min_bound(&dirtyRect, &textObjectRect, &dirtyRect);\n            } else {\n                rectCopy(&dirtyRect, &textObjectRect);\n                textObjectsRemoved = true;\n            }\n\n            mem_free(textObject->data);\n            mem_free(textObject);\n\n            memmove(&(text_object_list[index]), &(text_object_list[index + 1]), sizeof(*text_object_list) * (text_object_index - index - 1));\n\n            text_object_index--;\n            index--;\n        }\n    }\n\n    if (textObjectsRemoved) {\n        tile_refresh_rect(&dirtyRect, map_elevation);\n    }\n}\n\n// Finds best position for placing text object.\n//\n// 0x4B0954\nstatic void text_object_get_offset(TextObject* textObject)\n{\n    int tileScreenX;\n    int tileScreenY;\n    tile_coord(textObject->tile, &tileScreenX, &tileScreenY, map_elevation);\n    textObject->x = tileScreenX + 16 - textObject->width / 2;\n    textObject->y = tileScreenY;\n\n    if ((textObject->flags & TEXT_OBJECT_UNBOUNDED) == 0) {\n        textObject->y -= textObject->height + 60;\n    }\n\n    if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width)\n        && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) {\n        textObject->sx = textObject->x - tileScreenX;\n        textObject->sy = textObject->y - tileScreenY;\n        return;\n    }\n\n    textObject->x -= textObject->width / 2;\n    if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width)\n        && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) {\n        textObject->sx = textObject->x - tileScreenX;\n        textObject->sy = textObject->y - tileScreenY;\n        return;\n    }\n\n    textObject->x += textObject->width;\n    if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width)\n        && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) {\n        textObject->sx = textObject->x - tileScreenX;\n        textObject->sy = textObject->y - tileScreenY;\n        return;\n    }\n\n    textObject->x = tileScreenX - 16 - textObject->width;\n    textObject->y = tileScreenY - 16 - textObject->height;\n    if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width)\n        && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) {\n        textObject->sx = textObject->x - tileScreenX;\n        textObject->sy = textObject->y - tileScreenY;\n        return;\n    }\n\n    textObject->x += textObject->width + 64;\n    if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width)\n        && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) {\n        textObject->sx = textObject->x - tileScreenX;\n        textObject->sy = textObject->y - tileScreenY;\n        return;\n    }\n\n    textObject->x = tileScreenX + 16 - textObject->width / 2;\n    textObject->y = tileScreenY;\n    if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width)\n        && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) {\n        textObject->sx = textObject->x - tileScreenX;\n        textObject->sy = textObject->y - tileScreenY;\n        return;\n    }\n\n    textObject->x -= textObject->width / 2;\n    if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width)\n        && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) {\n        textObject->sx = textObject->x - tileScreenX;\n        textObject->sy = textObject->y - tileScreenY;\n        return;\n    }\n\n    textObject->x += textObject->width;\n    if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width)\n        && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) {\n        textObject->sx = textObject->x - tileScreenX;\n        textObject->sy = textObject->y - tileScreenY;\n        return;\n    }\n\n    textObject->x = tileScreenX + 16 - textObject->width / 2;\n    textObject->y = tileScreenY - (textObject->height + 60);\n    textObject->sx = textObject->x - tileScreenX;\n    textObject->sy = textObject->y - tileScreenY;\n}\n\n// Marks text objects attached to [object] for removal.\n//\n// 0x4B0C00\nvoid text_object_remove(Object* object)\n{\n    for (int index = 0; index < text_object_index; index++) {\n        if (text_object_list[index]->owner == object) {\n            text_object_list[index]->flags |= TEXT_OBJECT_MARKED_FOR_REMOVAL;\n        }\n    }\n}\n"
  },
  {
    "path": "src/game/textobj.h",
    "content": "#ifndef FALLOUT_GAME_TEXTOBJ_H_\n#define FALLOUT_GAME_TEXTOBJ_H_\n\n#include <stdbool.h>\n\n#include \"plib/gnw/rect.h\"\n#include \"game/object_types.h\"\n\nint text_object_init(unsigned char* windowBuffer, int width, int height);\nint text_object_reset();\nvoid text_object_exit();\nvoid text_object_disable();\nvoid text_object_enable();\nint text_object_is_enabled();\nvoid text_object_set_base_delay(double value);\nunsigned int text_object_get_base_delay();\nvoid text_object_set_line_delay(double value);\nunsigned int text_object_get_line_delay();\nint text_object_create(Object* object, char* string, int font, int color, int a5, Rect* rect);\nvoid text_object_render(Rect* rect);\nint text_object_count();\nvoid text_object_remove(Object* object);\n\n#endif /* FALLOUT_GAME_TEXTOBJ_H_ */\n"
  },
  {
    "path": "src/game/tile.c",
    "content": "#include \"game/tile.h\"\n\n#include <assert.h>\n#include <string.h>\n\n#define _USE_MATH_DEFINES\n#include <math.h>\n\n#include \"plib/color/color.h\"\n#include \"game/config.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/light.h\"\n#include \"game/map.h\"\n#include \"game/object.h\"\n\n#define TILE_IS_VALID(tile) ((tile) >= 0 && (tile) < grid_size)\n\ntypedef struct STRUCT_51D99C {\n    int field_0;\n    int field_4;\n} STRUCT_51D99C;\n\ntypedef struct STRUCT_51DA04 {\n    int field_0;\n    int field_4;\n} STRUCT_51DA04;\n\ntypedef struct STRUCT_51DA6C {\n    int field_0;\n    int field_4;\n    int field_8;\n    int field_C; // something with light level?\n} STRUCT_51DA6C;\n\ntypedef struct STRUCT_51DB0C {\n    int field_0;\n    int field_4;\n    int field_8;\n} STRUCT_51DB0C;\n\ntypedef struct STRUCT_51DB48 {\n    int field_0;\n    int field_4;\n    int field_8;\n} STRUCT_51DB48;\n\nstatic void refresh_mapper(Rect* rect, int elevation);\nstatic void refresh_game(Rect* rect, int elevation);\nstatic bool tile_on_edge(int tile);\nstatic void roof_fill_on(int x, int y, int elevation);\nstatic void roof_fill_off(int x, int y, int elevation);\nstatic void roof_draw(int fid, int x, int y, Rect* rect, int light);\n\n// 0x51D950\nstatic bool borderInitialized = false;\n\n// 0x51D954\nstatic bool scroll_blocking_on = true;\n\n// 0x51D958\nstatic bool scroll_limiting_on = true;\n\n// 0x51D95C\nstatic bool show_roof = true;\n\n// 0x51D960\nstatic bool show_grid = false;\n\n// 0x51D964\nstatic TileWindowRefreshElevationProc* tile_refresh = refresh_game;\n\n// 0x51D968\nstatic bool refresh_enabled = true;\n\n// 0x51D96C\nint off_tile[2][6] = {\n    {\n        16,\n        32,\n        16,\n        -16,\n        -32,\n        -16,\n    },\n    {\n        -12,\n        0,\n        12,\n        12,\n        0,\n        -12,\n    }\n};\n\n// 0x51D99C\nstatic STRUCT_51D99C rightside_up_table[13] = {\n    { -1, 2 },\n    { 78, 2 },\n    { 76, 6 },\n    { 73, 8 },\n    { 71, 10 },\n    { 68, 14 },\n    { 65, 16 },\n    { 63, 18 },\n    { 61, 20 },\n    { 58, 24 },\n    { 55, 26 },\n    { 53, 28 },\n    { 50, 32 },\n};\n\n// 0x51DA04\nstatic STRUCT_51DA04 upside_down_table[13] = {\n    { 0, 32 },\n    { 48, 32 },\n    { 49, 30 },\n    { 52, 26 },\n    { 55, 24 },\n    { 57, 22 },\n    { 60, 18 },\n    { 63, 16 },\n    { 65, 14 },\n    { 67, 12 },\n    { 70, 8 },\n    { 73, 6 },\n    { 75, 4 },\n};\n\n// 0x51DA6C\nstatic STRUCT_51DA6C verticies[10] = {\n    { 16, -1, -201, 0 },\n    { 48, -2, -2, 0 },\n    { 960, 0, 0, 0 },\n    { 992, 199, -1, 0 },\n    { 1024, 198, 198, 0 },\n    { 1936, 200, 200, 0 },\n    { 1968, 399, 199, 0 },\n    { 2000, 398, 398, 0 },\n    { 2912, 400, 400, 0 },\n    { 2944, 599, 399, 0 },\n};\n\n// 0x51DB0C\nstatic STRUCT_51DB0C rightside_up_triangles[5] = {\n    { 2, 3, 0 },\n    { 3, 4, 1 },\n    { 5, 6, 3 },\n    { 6, 7, 4 },\n    { 8, 9, 6 },\n};\n\n// 0x51DB48\nstatic STRUCT_51DB48 upside_down_triangles[5] = {\n    { 0, 3, 1 },\n    { 2, 5, 3 },\n    { 3, 6, 4 },\n    { 5, 8, 6 },\n    { 6, 9, 7 },\n};\n\n// 0x668224\nstatic int intensity_map[3280];\n\n// 0x66B564\nstatic int dir_tile2[2][6];\n\n// Deltas to perform tile calculations in given direction.\n//\n// 0x66B594\nstatic int dir_tile[2][6];\n\n// 0x66B5C4\nstatic unsigned char tile_grid_blocked[512];\n\n// 0x66B7C4\nstatic unsigned char tile_grid_occupied[512];\n\n// 0x66B9C4\nstatic unsigned char tile_mask[512];\n\n// 0x66BBC4\nstatic Rect tile_border;\n\n// 0x66BBD4\nstatic Rect buf_rect;\n\n// 0x66BBE4\nstatic unsigned char tile_grid[32 * 16];\n\n// 0x66BDE4\nstatic int square_y;\n\n// 0x66BDE8\nstatic int square_x;\n\n// 0x66BDEC\nstatic int square_offx;\n\n// 0x66BDF0\nstatic int square_offy;\n\n// 0x66BDF4\nstatic TileWindowRefreshProc* blit;\n\n// 0x66BDF8\nstatic int tile_offy;\n\n// 0x66BDFC\nstatic int tile_offx;\n\n// 0x66BE00\nstatic int square_size;\n\n// Number of tiles horizontally.\n//\n// Currently this value is always 200.\n//\n// 0x66BE04\nstatic int grid_width;\n\n// 0x66BE08\nstatic TileData** squares;\n\n// 0x66BE0C\nstatic unsigned char* buf;\n\n// Number of tiles vertically.\n//\n// Currently this value is always 200.\n//\n// 0x66BE10\nstatic int grid_length;\n\n// 0x66BE14\nstatic int buf_length;\n\n// 0x66BE18\nstatic int tile_x;\n\n// 0x66BE1C\nstatic int tile_y;\n\n// The number of tiles in the hex grid.\n//\n// 0x66BE20\nstatic int grid_size;\n\n// 0x66BE24\nstatic int square_length;\n\n// 0x66BE28\nstatic int buf_full;\n\n// 0x66BE2C\nstatic int square_width;\n\n// 0x66BE30\nstatic int buf_width;\n\n// 0x66BE34\nint tile_center_tile;\n\n// 0x4B0C40\nint tile_init(TileData** a1, int squareGridWidth, int squareGridHeight, int hexGridWidth, int hexGridHeight, unsigned char* buffer, int windowWidth, int windowHeight, int windowPitch, TileWindowRefreshProc* windowRefreshProc)\n{\n    int v11;\n    int v12;\n    int v13;\n\n    int v20;\n    int v21;\n    int v22;\n    int v23;\n    int v24;\n    int v25;\n\n    square_width = squareGridWidth;\n    squares = a1;\n    grid_length = hexGridHeight;\n    square_length = squareGridHeight;\n    grid_width = hexGridWidth;\n    dir_tile[0][0] = -1;\n    dir_tile[0][4] = 1;\n    dir_tile[1][1] = -1;\n    grid_size = hexGridWidth * hexGridHeight;\n    dir_tile[1][3] = 1;\n    buf = buffer;\n    dir_tile2[0][0] = -1;\n    buf_width = windowWidth;\n    dir_tile2[0][3] = -1;\n    buf_length = windowHeight;\n    dir_tile2[1][1] = 1;\n    buf_full = windowPitch;\n    dir_tile2[1][2] = 1;\n    buf_rect.lrx = windowWidth - 1;\n    square_size = squareGridHeight * squareGridWidth;\n    buf_rect.lry = windowHeight - 1;\n    buf_rect.ulx = 0;\n    blit = windowRefreshProc;\n    buf_rect.uly = 0;\n    dir_tile[0][1] = hexGridWidth - 1;\n    dir_tile[0][2] = hexGridWidth;\n    show_grid = 0;\n    dir_tile[0][3] = hexGridWidth + 1;\n    dir_tile[1][2] = hexGridWidth;\n    dir_tile2[0][4] = hexGridWidth;\n    dir_tile2[0][5] = hexGridWidth;\n    dir_tile[0][5] = -hexGridWidth;\n    dir_tile[1][0] = -hexGridWidth - 1;\n    dir_tile[1][4] = 1 - hexGridWidth;\n    dir_tile[1][5] = -hexGridWidth;\n    dir_tile2[0][1] = -hexGridWidth - 1;\n    dir_tile2[1][4] = -hexGridWidth;\n    dir_tile2[0][2] = hexGridWidth - 1;\n    dir_tile2[1][5] = -hexGridWidth;\n    dir_tile2[1][0] = hexGridWidth + 1;\n    dir_tile2[1][3] = 1 - hexGridWidth;\n\n    v11 = 0;\n    v12 = 0;\n    do {\n        v13 = 64;\n        do {\n            tile_mask[v12++] = v13 > v11;\n            v13 -= 4;\n        } while (v13);\n\n        do {\n            tile_mask[v12++] = v13 > v11 ? 2 : 0;\n            v13 += 4;\n        } while (v13 != 64);\n\n        v11 += 16;\n    } while (v11 != 64);\n\n    v11 = 0;\n    do {\n        v13 = 0;\n        do {\n            tile_mask[v12++] = 0;\n            v13++;\n        } while (v13 < 32);\n        v11++;\n    } while (v11 < 8);\n\n    v11 = 0;\n    do {\n        v13 = 0;\n        do {\n            tile_mask[v12++] = v13 > v11 ? 0 : 3;\n            v13 += 4;\n        } while (v13 != 64);\n\n        v13 = 64;\n        do {\n            tile_mask[v12++] = v13 > v11 ? 0 : 4;\n            v13 -= 4;\n        } while (v13);\n\n        v11 += 16;\n    } while (v11 != 64);\n\n    buf_fill(tile_grid, 32, 16, 32, 0);\n    draw_line(tile_grid, 32, 16, 0, 31, 4, colorTable[4228]);\n    draw_line(tile_grid, 32, 31, 4, 31, 12, colorTable[4228]);\n    draw_line(tile_grid, 32, 31, 12, 16, 15, colorTable[4228]);\n    draw_line(tile_grid, 32, 0, 12, 16, 15, colorTable[4228]);\n    draw_line(tile_grid, 32, 0, 4, 0, 12, colorTable[4228]);\n    draw_line(tile_grid, 32, 16, 0, 0, 4, colorTable[4228]);\n\n    buf_fill(tile_grid_occupied, 32, 16, 32, 0);\n    draw_line(tile_grid_occupied, 32, 16, 0, 31, 4, colorTable[31]);\n    draw_line(tile_grid_occupied, 32, 31, 4, 31, 12, colorTable[31]);\n    draw_line(tile_grid_occupied, 32, 31, 12, 16, 15, colorTable[31]);\n    draw_line(tile_grid_occupied, 32, 0, 12, 16, 15, colorTable[31]);\n    draw_line(tile_grid_occupied, 32, 0, 4, 0, 12, colorTable[31]);\n    draw_line(tile_grid_occupied, 32, 16, 0, 0, 4, colorTable[31]);\n\n    buf_fill(tile_grid_blocked, 32, 16, 32, 0);\n    draw_line(tile_grid_blocked, 32, 16, 0, 31, 4, colorTable[31744]);\n    draw_line(tile_grid_blocked, 32, 31, 4, 31, 12, colorTable[31744]);\n    draw_line(tile_grid_blocked, 32, 31, 12, 16, 15, colorTable[31744]);\n    draw_line(tile_grid_blocked, 32, 0, 12, 16, 15, colorTable[31744]);\n    draw_line(tile_grid_blocked, 32, 0, 4, 0, 12, colorTable[31744]);\n    draw_line(tile_grid_blocked, 32, 16, 0, 0, 4, colorTable[31744]);\n\n    for (v20 = 0; v20 < 16; v20++) {\n        v21 = v20 * 32;\n        v22 = 31;\n        v23 = v21 + 31;\n\n        if (tile_grid_blocked[v23] == 0) {\n            do {\n                --v22;\n                --v23;\n            } while (v22 > 0 && tile_grid_blocked[v23] == 0);\n        }\n\n        v24 = v21;\n        v25 = 0;\n        if (tile_grid_blocked[v21] == 0) {\n            do {\n                ++v25;\n                ++v24;\n            } while (v25 < 32 && tile_grid_blocked[v24] == 0);\n        }\n\n        draw_line(tile_grid_blocked, 32, v25, v20, v22, v20, colorTable[31744]);\n    }\n\n    tile_set_center(hexGridWidth * (hexGridHeight / 2) + hexGridWidth / 2, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS);\n    tile_set_border(windowWidth, windowHeight, hexGridWidth, hexGridHeight);\n\n    char* executable;\n    config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, &executable);\n    if (stricmp(executable, \"mapper\") == 0) {\n        tile_refresh = refresh_mapper;\n    }\n\n    return 0;\n}\n\n// 0x4B11E4\nvoid tile_set_border(int windowWidth, int windowHeight, int hexGridWidth, int hexGridHeight)\n{\n    int v1 = tile_num(-320, -240, 0);\n    int v2 = tile_num(-320, windowHeight + 240, 0);\n\n    tile_border.ulx = abs(hexGridWidth - 1 - v2 % hexGridWidth - tile_x) + 6;\n    tile_border.uly = abs(tile_y - v1 / hexGridWidth) + 7;\n    tile_border.lrx = hexGridWidth - tile_border.ulx - 1;\n    tile_border.lry = hexGridHeight - tile_border.uly - 1;\n\n    if ((tile_border.ulx & 1) == 0) {\n        tile_border.ulx++;\n    }\n\n    if ((tile_border.lrx & 1) == 0) {\n        tile_border.ulx--;\n    }\n\n    borderInitialized = true;\n}\n\n// NOTE: Uncollapsed 0x4B129C.\nvoid tile_reset()\n{\n}\n\n// NOTE: Uncollapsed 0x4B129C.\nvoid tile_exit()\n{\n}\n\n// 0x4B12A8\nvoid tile_disable_refresh()\n{\n    refresh_enabled = false;\n}\n\n// 0x4B12B4\nvoid tile_enable_refresh()\n{\n    refresh_enabled = true;\n}\n\n// 0x4B12C0\nvoid tile_refresh_rect(Rect* rect, int elevation)\n{\n    if (refresh_enabled) {\n        if (elevation == map_elevation) {\n            tile_refresh(rect, elevation);\n        }\n    }\n}\n\n// 0x4B12D8\nvoid tile_refresh_display()\n{\n    if (refresh_enabled) {\n        tile_refresh(&buf_rect, map_elevation);\n    }\n}\n\n// 0x4B12F8\nint tile_set_center(int tile, int flags)\n{\n    if (!TILE_IS_VALID(tile)) {\n        return -1;\n    }\n\n    if ((flags & TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) == 0) {\n        if (scroll_limiting_on) {\n            int tileScreenX;\n            int tileScreenY;\n            tile_coord(tile, &tileScreenX, &tileScreenY, map_elevation);\n\n            int dudeScreenX;\n            int dudeScreenY;\n            tile_coord(obj_dude->tile, &dudeScreenX, &dudeScreenY, map_elevation);\n\n            int dx = abs(dudeScreenX - tileScreenX);\n            int dy = abs(dudeScreenY - tileScreenY);\n\n            if (dx > abs(dudeScreenX - tile_offx)\n                || dy > abs(dudeScreenY - tile_offy)) {\n                if (dx >= 480 || dy >= 400) {\n                    return -1;\n                }\n            }\n        }\n\n        if (scroll_blocking_on) {\n            if (obj_scroll_blocking_at(tile, map_elevation) == 0) {\n                return -1;\n            }\n        }\n    }\n\n    int tile_x = grid_width - 1 - tile % grid_width;\n    int tile_y = tile / grid_width;\n\n    if (borderInitialized) {\n        if (tile_x <= tile_border.ulx || tile_x >= tile_border.lrx || tile_y <= tile_border.uly || tile_y >= tile_border.lry) {\n            return -1;\n        }\n    }\n\n    tile_y = tile_y;\n    tile_offx = (buf_width - 32) / 2;\n    tile_x = tile_x;\n    tile_offy = (buf_length - 16) / 2;\n\n    if (tile_x & 1) {\n        tile_x -= 1;\n        tile_offx -= 32;\n    }\n\n    square_x = tile_x / 2;\n    square_y = tile_y / 2;\n    square_offx = tile_offx - 16;\n    square_offy = tile_offy - 2;\n\n    if (tile_y & 1) {\n        square_offy -= 12;\n        square_offx -= 16;\n    }\n\n    tile_center_tile = tile;\n\n    if ((flags & TILE_SET_CENTER_REFRESH_WINDOW) != 0) {\n        // NOTE: Uninline.\n        tile_refresh_display();\n    }\n\n    return 0;\n}\n\n// 0x4B1554\nstatic void refresh_mapper(Rect* rect, int elevation)\n{\n    Rect rectToUpdate;\n\n    if (rect_inside_bound(rect, &buf_rect, &rectToUpdate) == -1) {\n        return;\n    }\n\n    buf_fill(buf + buf_full * rectToUpdate.uly + rectToUpdate.ulx,\n        rectToUpdate.lrx - rectToUpdate.ulx + 1,\n        rectToUpdate.lry - rectToUpdate.uly + 1,\n        buf_full,\n        0);\n\n    square_render_floor(&rectToUpdate, elevation);\n    grid_render(&rectToUpdate, elevation);\n    obj_render_pre_roof(&rectToUpdate, elevation);\n    square_render_roof(&rectToUpdate, elevation);\n    obj_render_post_roof(&rectToUpdate, elevation);\n    blit(&rectToUpdate);\n}\n\n// 0x4B15E8\nstatic void refresh_game(Rect* rect, int elevation)\n{\n    Rect rectToUpdate;\n\n    if (rect_inside_bound(rect, &buf_rect, &rectToUpdate) == -1) {\n        return;\n    }\n\n    square_render_floor(&rectToUpdate, elevation);\n    obj_render_pre_roof(&rectToUpdate, elevation);\n    square_render_roof(&rectToUpdate, elevation);\n    obj_render_post_roof(&rectToUpdate, elevation);\n    blit(&rectToUpdate);\n}\n\n// 0x4B1634\nvoid tile_toggle_roof(int a1)\n{\n    show_roof = 1 - show_roof;\n\n    if (a1) {\n        // NOTE: Uninline.\n        tile_refresh_display();\n    }\n}\n\n// 0x4B166C\nint tile_roof_visible()\n{\n    return show_roof;\n}\n\n// 0x4B1674\nint tile_coord(int tile, int* screenX, int* screenY, int elevation)\n{\n    int v3;\n    int v4;\n    int v5;\n    int v6;\n\n    if (!TILE_IS_VALID(tile)) {\n        return -1;\n    }\n\n    v3 = grid_width - 1 - tile % grid_width;\n    v4 = tile / grid_width;\n\n    *screenX = tile_offx;\n    *screenY = tile_offy;\n\n    v5 = (v3 - tile_x) / -2;\n    *screenX += 48 * ((v3 - tile_x) / 2);\n    *screenY += 12 * v5;\n\n    if (v3 & 1) {\n        if (v3 <= tile_x) {\n            *screenX -= 16;\n            *screenY += 12;\n        } else {\n            *screenX += 32;\n        }\n    }\n\n    v6 = v4 - tile_y;\n    *screenX += 16 * v6;\n    *screenY += 12 * v6;\n\n    return 0;\n}\n\n// 0x4B1754\nint tile_num(int screenX, int screenY, int elevation)\n{\n    int v2;\n    int v3;\n    int v4;\n    int v5;\n    int v6;\n    int v7;\n    int v8;\n    int v9;\n    int v10;\n    int v11;\n    int v12;\n\n    v2 = screenY - tile_offy;\n    if (v2 >= 0) {\n        v3 = v2 / 12;\n    } else {\n        v3 = (v2 + 1) / 12 - 1;\n    }\n\n    v4 = screenX - tile_offx - 16 * v3;\n    v5 = v2 - 12 * v3;\n\n    if (v4 >= 0) {\n        v6 = v4 / 64;\n    } else {\n        v6 = (v4 + 1) / 64 - 1;\n    }\n\n    v7 = v6 + v3;\n    v8 = v4 - (v6 * 64);\n    v9 = 2 * v6;\n\n    if (v8 >= 32) {\n        v8 -= 32;\n        v9++;\n    }\n\n    v10 = tile_y + v7;\n    v11 = tile_x + v9;\n\n    switch (tile_mask[32 * v5 + v8]) {\n    case 2:\n        v11++;\n        if (v11 & 1) {\n            v10--;\n        }\n        break;\n    case 1:\n        v10--;\n        break;\n    case 3:\n        v11--;\n        if (!(v11 & 1)) {\n            v10++;\n        }\n        break;\n    case 4:\n        v10++;\n        break;\n    default:\n        break;\n    }\n\n    v12 = grid_width - 1 - v11;\n    if (v12 >= 0 && v12 < grid_width && v10 >= 0 && v10 < grid_length) {\n        return grid_width * v10 + v12;\n    }\n\n    return -1;\n}\n\n// tile_distance\n// 0x4B185C\nint tile_dist(int tile1, int tile2)\n{\n    int i;\n    int v9;\n    int v8;\n    int v2;\n\n    if (tile1 == -1) {\n        return 9999;\n    }\n\n    if (tile2 == -1) {\n        return 9999;\n    }\n\n    int x1;\n    int y1;\n    tile_coord(tile2, &x1, &y1, 0);\n\n    v2 = tile1;\n    for (i = 0; v2 != tile2; i++) {\n        // TODO: Looks like inlined rotation_to_tile.\n        int x2;\n        int y2;\n        tile_coord(v2, &x2, &y2, 0);\n\n        int dx = x1 - x2;\n        int dy = y1 - y2;\n\n        if (x1 == x2) {\n            if (dy < 0) {\n                v9 = 0;\n            } else {\n                v9 = 2;\n            }\n        } else {\n            v8 = (int)trunc(atan2((double)-dy, (double)dx) * 180.0 * 0.3183098862851122);\n\n            v9 = 360 - (v8 + 180) - 90;\n            if (v9 < 0) {\n                v9 += 360;\n            }\n\n            v9 /= 60;\n\n            if (v9 >= 6) {\n                v9 = 5;\n            }\n        }\n\n        v2 += dir_tile[v2 % grid_width & 1][v9];\n    }\n\n    return i;\n}\n\n// 0x4B1994\nbool tile_in_front_of(int tile1, int tile2)\n{\n    int x1;\n    int y1;\n    tile_coord(tile1, &x1, &y1, 0);\n\n    int x2;\n    int y2;\n    tile_coord(tile2, &x2, &y2, 0);\n\n    int dx = x2 - x1;\n    int dy = y2 - y1;\n\n    return (double)dx <= (double)dy * -4.0;\n}\n\n// 0x4B1A00\nbool tile_to_right_of(int tile1, int tile2)\n{\n    int x1;\n    int y1;\n    tile_coord(tile1, &x1, &y1, 0);\n\n    int x2;\n    int y2;\n    tile_coord(tile2, &x2, &y2, 0);\n\n    int dx = x2 - x1;\n    int dy = y2 - y1;\n\n    // NOTE: the value below looks like 4/3, which is 0x3FF55555555555, but it's\n    // binary value is slightly different: 0x3FF55555555556. This difference plays\n    // important role as seen right in the beginning of the game, comparing tiles\n    // 17488 (0x4450) and 15288 (0x3BB8).\n    return (double)dx <= (double)dy * 1.3333333333333335;\n}\n\n// tile_num_in_direction\n// 0x4B1A6C\nint tile_num_in_direction(int tile, int rotation, int distance)\n{\n    int newTile = tile;\n    for (int index = 0; index < distance; index++) {\n        if (tile_on_edge(newTile)) {\n            break;\n        }\n\n        int parity = (newTile % grid_width) & 1;\n        newTile += dir_tile[parity][rotation];\n    }\n\n    return newTile;\n}\n\n// rotation_to_tile\n// 0x4B1ABC\nint tile_dir(int tile1, int tile2)\n{\n    int x1;\n    int y1;\n    tile_coord(tile1, &x1, &y1, 0);\n\n    int x2;\n    int y2;\n    tile_coord(tile2, &x2, &y2, 0);\n\n    int dy = y2 - y1;\n    x2 -= x1;\n    y2 -= y1;\n\n    if (x2 != 0) {\n        // TODO: Check.\n        int v6 = (int)trunc(atan2((double)-dy, (double)x2) * 180.0 * 0.3183098862851122);\n        int v7 = 360 - (v6 + 180) - 90;\n        if (v7 < 0) {\n            v7 += 360;\n        }\n\n        v7 /= 60;\n\n        if (v7 >= ROTATION_COUNT) {\n            v7 = ROTATION_NW;\n        }\n        return v7;\n    }\n\n    return dy < 0 ? ROTATION_NE : ROTATION_SE;\n}\n\n// 0x4B1B84\nint tile_num_beyond(int from, int to, int distance)\n{\n    if (distance <= 0 || from == to) {\n        return from;\n    }\n\n    int fromX;\n    int fromY;\n    tile_coord(from, &fromX, &fromY, 0);\n    fromX += 16;\n    fromY += 8;\n\n    int toX;\n    int toY;\n    tile_coord(to, &toX, &toY, 0);\n    toX += 16;\n    toY += 8;\n\n    int deltaX = toX - fromX;\n    int deltaY = toY - fromY;\n\n    int v27 = 2 * abs(deltaX);\n\n    int stepX = 0;\n    if (deltaX > 0)\n        stepX = 1;\n    else if (deltaX < 0)\n        stepX = -1;\n\n    int v26 = 2 * abs(deltaY);\n\n    int stepY = 0;\n    if (deltaY > 0)\n        stepY = 1;\n    else if (deltaY < 0)\n        stepY = -1;\n\n    int v28 = from;\n    int tileX = fromX;\n    int tileY = fromY;\n\n    int v6 = 0;\n\n    if (v27 > v26) {\n        int middle = v26 - v27 / 2;\n        while (true) {\n            int tile = tile_num(tileX, tileY, 0);\n            if (tile != v28) {\n                v6 += 1;\n                if (v6 == distance || tile_on_edge(tile)) {\n                    return tile;\n                }\n\n                v28 = tile;\n            }\n\n            if (middle >= 0) {\n                middle -= v27;\n                tileY += stepY;\n            }\n\n            middle += v26;\n            tileX += stepX;\n        }\n    } else {\n        int middle = v27 - v26 / 2;\n        while (true) {\n            int tile = tile_num(tileX, tileY, 0);\n            if (tile != v28) {\n                v6 += 1;\n                if (v6 == distance || tile_on_edge(tile)) {\n                    return tile;\n                }\n\n                v28 = tile;\n            }\n\n            if (middle >= 0) {\n                middle -= v26;\n                tileX += stepX;\n            }\n\n            middle += v27;\n            tileY += stepY;\n        }\n    }\n\n    assert(false && \"Should be unreachable\");\n}\n\n// 0x4B1D20\nstatic bool tile_on_edge(int tile)\n{\n    if (!TILE_IS_VALID(tile)) {\n        return false;\n    }\n\n    if (tile < grid_width) {\n        return true;\n    }\n\n    if (tile >= grid_size - grid_width) {\n        return true;\n    }\n\n    if (tile % grid_width == 0) {\n        return true;\n    }\n\n    if (tile % grid_width == grid_width - 1) {\n        return true;\n    }\n\n    return false;\n}\n\n// 0x4B1D80\nvoid tile_enable_scroll_blocking()\n{\n    scroll_blocking_on = true;\n}\n\n// 0x4B1D8C\nvoid tile_disable_scroll_blocking()\n{\n    scroll_blocking_on = false;\n}\n\n// 0x4B1D98\nbool tile_get_scroll_blocking()\n{\n    return scroll_blocking_on;\n}\n\n// 0x4B1DA0\nvoid tile_enable_scroll_limiting()\n{\n    scroll_limiting_on = true;\n}\n\n// 0x4B1DAC\nvoid tile_disable_scroll_limiting()\n{\n    scroll_limiting_on = false;\n}\n\n// 0x4B1DB8\nbool tile_get_scroll_limiting()\n{\n    return scroll_limiting_on;\n}\n\n// 0x4B1DC0\nint square_coord(int squareTile, int* coordX, int* coordY, int elevation)\n{\n    int v5;\n    int v6;\n    int v7;\n    int v8;\n    int v9;\n\n    if (squareTile < 0 || squareTile >= square_size) {\n        return -1;\n    }\n\n    v5 = square_width - 1 - squareTile % square_width;\n    v6 = squareTile / square_width;\n    v7 = square_x;\n\n    *coordX = square_offx;\n    *coordY = square_offy;\n\n    v8 = v5 - v7;\n    *coordX += 48 * v8;\n    *coordY -= 12 * v8;\n\n    v9 = v6 - square_y;\n    *coordX += 32 * v9;\n    *coordY += 24 * v9;\n\n    return 0;\n}\n\n// 0x4B1E60\nint square_coord_roof(int squareTile, int* screenX, int* screenY, int elevation)\n{\n    int v5;\n    int v6;\n    int v7;\n    int v8;\n    int v9;\n    int v10;\n\n    if (squareTile < 0 || squareTile >= square_size) {\n        return -1;\n    }\n\n    v5 = square_width - 1 - squareTile % square_width;\n    v6 = squareTile / square_width;\n    v7 = square_x;\n    *screenX = square_offx;\n    *screenY = square_offy;\n\n    v8 = v5 - v7;\n    *screenX += 48 * v8;\n    *screenY -= 12 * v8;\n\n    v9 = v6 - square_y;\n    *screenX += 32 * v9;\n    v10 = 24 * v9 + *screenY;\n    *screenY = v10;\n    *screenY = v10 - 96;\n\n    return 0;\n}\n\n// 0x4B1F04\nint square_num(int screenX, int screenY, int elevation)\n{\n    int coordY;\n    int coordX;\n\n    square_xy(screenX, screenY, elevation, &coordX, &coordY);\n\n    if (coordX >= 0 && coordX < square_width && coordY >= 0 && coordY < square_length) {\n        return coordX + square_width * coordY;\n    }\n\n    return -1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B1F4C\nint square_num_roof(int screenX, int screenY, int elevation)\n{\n    int x;\n    int y;\n\n    square_xy_roof(screenX, screenY, elevation, &x, &y);\n\n    if (x >= 0 && x < square_width && y >= 0 && y < square_length) {\n        return x + square_width * y;\n    }\n\n    return -1;\n}\n\n// 0x4B1F94\nvoid square_xy(int screenX, int screenY, int elevation, int* coordX, int* coordY)\n{\n    int v4;\n    int v5;\n    int v6;\n    int v8;\n\n    v4 = screenX - square_offx;\n    v5 = screenY - square_offy - 12;\n    v6 = 3 * v4 - 4 * v5;\n    *coordX = v6 >= 0 ? (v6 / 192) : ((v6 + 1) / 192 - 1);\n\n    v8 = 4 * v5 + v4;\n    *coordY = v8 >= 0 ? (v8 / 128) : ((v8 + 1) / 128 - 1);\n\n    *coordX += square_x;\n    *coordY += square_y;\n\n    *coordX = square_width - 1 - *coordX;\n}\n\n// 0x4B203C\nvoid square_xy_roof(int screenX, int screenY, int elevation, int* coordX, int* coordY)\n{\n    int v4;\n    int v5;\n    int v6;\n    int v8;\n\n    v4 = screenX - square_offx;\n    v5 = screenY + 96 - square_offy - 12;\n    v6 = 3 * v4 - 4 * v5;\n\n    *coordX = (v6 >= 0) ? (v6 / 192) : ((v6 + 1) / 192 - 1);\n\n    v8 = 4 * v5 + v4;\n    *coordY = v8 >= 0 ? (v8 / 128) : ((v8 + 1) / 128 - 1);\n\n    *coordX += square_x;\n    *coordY += square_y;\n\n    *coordX = square_width - 1 - *coordX;\n}\n\n// 0x4B20E8\nvoid square_render_roof(Rect* rect, int elevation)\n{\n    if (!show_roof) {\n        return;\n    }\n\n    int temp;\n    int minY;\n    int minX;\n    int maxX;\n    int maxY;\n\n    square_xy_roof(rect->ulx, rect->uly, elevation, &temp, &minY);\n    square_xy_roof(rect->lrx, rect->uly, elevation, &minX, &temp);\n    square_xy_roof(rect->ulx, rect->lry, elevation, &maxX, &temp);\n    square_xy_roof(rect->lrx, rect->lry, elevation, &temp, &maxY);\n\n    if (minX < 0) {\n        minX = 0;\n    }\n\n    if (minX >= square_width) {\n        minX = square_width - 1;\n    }\n\n    if (minY < 0) {\n        minY = 0;\n    }\n\n    // FIXME: Probably a bug - testing X, then changing Y.\n    if (minX >= square_length) {\n        minY = square_length - 1;\n    }\n\n    int light = light_get_ambient();\n\n    int baseSquareTile = square_width * minY;\n\n    for (int y = minY; y <= maxY; y++) {\n        for (int x = minX; x <= maxX; x++) {\n            int squareTile = baseSquareTile + x;\n            int frmId = squares[elevation]->field_0[squareTile];\n            frmId >>= 16;\n            if ((((frmId & 0xF000) >> 12) & 0x01) == 0) {\n                int fid = art_id(OBJ_TYPE_TILE, frmId & 0xFFF, 0, 0, 0);\n                if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) {\n                    int screenX;\n                    int screenY;\n                    square_coord_roof(squareTile, &screenX, &screenY, elevation);\n                    roof_draw(fid, screenX, screenY, rect, light);\n                }\n            }\n        }\n        baseSquareTile += square_width;\n    }\n}\n\n// 0x4B22D0\nstatic void roof_fill_on(int a1, int a2, int elevation)\n{\n    while ((a1 >= 0 && a1 < square_width) && (a2 >= 0 && a2 < square_length)) {\n        int squareTile = square_width * a2 + a1;\n        int value = squares[elevation]->field_0[squareTile];\n        int upper = (value >> 16) & 0xFFFF;\n\n        int id = upper & 0xFFF;\n        if (art_id(OBJ_TYPE_TILE, id, 0, 0, 0) == art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) {\n            break;\n        }\n\n        int flag = (upper & 0xF000) >> 12;\n        if ((flag & 0x01) == 0) {\n            break;\n        }\n\n        flag &= ~0x01;\n\n        squares[elevation]->field_0[squareTile] = (value & 0xFFFF) | (((flag << 12) | id) << 16);\n\n        roof_fill_on(a1 - 1, a2, elevation);\n        roof_fill_on(a1 + 1, a2, elevation);\n        roof_fill_on(a1, a2 - 1, elevation);\n\n        a2++;\n    }\n}\n\n// 0x4B23D4\nvoid tile_fill_roof(int a1, int a2, int elevation, int a4)\n{\n    if (a4) {\n        roof_fill_on(a1, a2, elevation);\n    } else {\n        roof_fill_off(a1, a2, elevation);\n    }\n}\n\n// 0x4B23DC\nstatic void roof_fill_off(int a1, int a2, int elevation)\n{\n    while ((a1 >= 0 && a1 < square_width) && (a2 >= 0 && a2 < square_length)) {\n        int squareTile = square_width * a2 + a1;\n        int value = squares[elevation]->field_0[squareTile];\n        int upper = (value >> 16) & 0xFFFF;\n\n        int id = upper & 0xFFF;\n        if (art_id(OBJ_TYPE_TILE, id, 0, 0, 0) == art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) {\n            break;\n        }\n\n        int flag = (upper & 0xF000) >> 12;\n        if ((flag & 0x03) != 0) {\n            break;\n        }\n\n        flag |= 0x01;\n\n        squares[elevation]->field_0[squareTile] = (value & 0xFFFF) | (((flag << 12) | id) << 16);\n\n        roof_fill_off(a1 - 1, a2, elevation);\n        roof_fill_off(a1 + 1, a2, elevation);\n        roof_fill_off(a1, a2 - 1, elevation);\n\n        a2++;\n    }\n}\n\n// 0x4B24E0\nstatic void roof_draw(int fid, int x, int y, Rect* rect, int light)\n{\n    CacheEntry* tileFrmHandle;\n    Art* tileFrm = art_ptr_lock(fid, &tileFrmHandle);\n    if (tileFrm == NULL) {\n        return;\n    }\n\n    int tileWidth = art_frame_width(tileFrm, 0, 0);\n    int tileHeight = art_frame_length(tileFrm, 0, 0);\n\n    Rect tileRect;\n    tileRect.ulx = x;\n    tileRect.uly = y;\n    tileRect.lrx = x + tileWidth - 1;\n    tileRect.lry = y + tileHeight - 1;\n\n    if (rect_inside_bound(&tileRect, rect, &tileRect) == 0) {\n        unsigned char* tileFrmBuffer = art_frame_data(tileFrm, 0, 0);\n        tileFrmBuffer += tileWidth * (tileRect.uly - y) + (tileRect.ulx - x);\n\n        CacheEntry* eggFrmHandle;\n        Art* eggFrm = art_ptr_lock(obj_egg->fid, &eggFrmHandle);\n        if (eggFrm != NULL) {\n            int eggWidth = art_frame_width(eggFrm, 0, 0);\n            int eggHeight = art_frame_length(eggFrm, 0, 0);\n\n            int eggScreenX;\n            int eggScreenY;\n            tile_coord(obj_egg->tile, &eggScreenX, &eggScreenY, obj_egg->elevation);\n\n            eggScreenX += 16;\n            eggScreenY += 8;\n\n            eggScreenX += eggFrm->xOffsets[0];\n            eggScreenY += eggFrm->yOffsets[0];\n\n            eggScreenX += obj_egg->x;\n            eggScreenY += obj_egg->y;\n\n            Rect eggRect;\n            eggRect.ulx = eggScreenX - eggWidth / 2;\n            eggRect.uly = eggScreenY - eggHeight + 1;\n            eggRect.lrx = eggRect.ulx + eggWidth - 1;\n            eggRect.lry = eggScreenY;\n\n            obj_egg->sx = eggRect.ulx;\n            obj_egg->sy = eggRect.uly;\n\n            Rect intersectedRect;\n            if (rect_inside_bound(&eggRect, &tileRect, &intersectedRect) == 0) {\n                Rect rects[4];\n\n                rects[0].ulx = tileRect.ulx;\n                rects[0].uly = tileRect.uly;\n                rects[0].lrx = tileRect.lrx;\n                rects[0].lry = intersectedRect.uly - 1;\n\n                rects[1].ulx = tileRect.ulx;\n                rects[1].uly = intersectedRect.uly;\n                rects[1].lrx = intersectedRect.ulx - 1;\n                rects[1].lry = intersectedRect.lry;\n\n                rects[2].ulx = intersectedRect.lrx + 1;\n                rects[2].uly = intersectedRect.uly;\n                rects[2].lrx = tileRect.lrx;\n                rects[2].lry = intersectedRect.lry;\n\n                rects[3].ulx = tileRect.ulx;\n                rects[3].uly = intersectedRect.lry + 1;\n                rects[3].lrx = tileRect.lrx;\n                rects[3].lry = tileRect.lry;\n\n                for (int i = 0; i < 4; i++) {\n                    Rect* cr = &(rects[i]);\n                    if (cr->ulx <= cr->lrx && cr->uly <= cr->lry) {\n                        dark_trans_buf_to_buf(tileFrmBuffer + tileWidth * (cr->uly - tileRect.uly) + (cr->ulx - tileRect.ulx),\n                            cr->lrx - cr->ulx + 1,\n                            cr->lry - cr->uly + 1,\n                            tileWidth,\n                            buf,\n                            cr->ulx,\n                            cr->uly,\n                            buf_full,\n                            light);\n                    }\n                }\n\n                unsigned char* eggBuf = art_frame_data(eggFrm, 0, 0);\n                intensity_mask_buf_to_buf(tileFrmBuffer + tileWidth * (intersectedRect.uly - tileRect.uly) + (intersectedRect.ulx - tileRect.ulx),\n                    intersectedRect.lrx - intersectedRect.ulx + 1,\n                    intersectedRect.lry - intersectedRect.uly + 1,\n                    tileWidth,\n                    buf + buf_full * intersectedRect.uly + intersectedRect.ulx,\n                    buf_full,\n                    eggBuf + eggWidth * (intersectedRect.uly - eggRect.uly) + (intersectedRect.ulx - eggRect.ulx),\n                    eggWidth,\n                    light);\n            } else {\n                dark_trans_buf_to_buf(tileFrmBuffer, tileRect.lrx - tileRect.ulx + 1, tileRect.lry - tileRect.uly + 1, tileWidth, buf, tileRect.ulx, tileRect.uly, buf_full, light);\n            }\n\n            art_ptr_unlock(eggFrmHandle);\n        }\n    }\n\n    art_ptr_unlock(tileFrmHandle);\n}\n\n// 0x4B2944\nvoid square_render_floor(Rect* rect, int elevation)\n{\n    int minY;\n    int maxX;\n    int maxY;\n    int minX;\n    int temp;\n\n    square_xy(rect->ulx, rect->uly, elevation, &temp, &minY);\n    square_xy(rect->lrx, rect->uly, elevation, &minX, &temp);\n    square_xy(rect->ulx, rect->lry, elevation, &maxX, &temp);\n    square_xy(rect->lrx, rect->lry, elevation, &temp, &maxY);\n\n    if (minX < 0) {\n        minX = 0;\n    }\n\n    if (minX >= square_width) {\n        minX = square_width - 1;\n    }\n\n    if (minY < 0) {\n        minY = 0;\n    }\n\n    if (minX >= square_length) {\n        minY = square_length - 1;\n    }\n\n    light_get_ambient();\n\n    temp = square_width * minY;\n    for (int v15 = minY; v15 <= maxY; v15++) {\n        for (int i = minX; i <= maxX; i++) {\n            int v3 = temp + i;\n            int frmId = squares[elevation]->field_0[v3];\n            if ((((frmId & 0xF000) >> 12) & 0x01) == 0) {\n                int v12;\n                int v13;\n                square_coord(v3, &v12, &v13, elevation);\n                int fid = art_id(OBJ_TYPE_TILE, frmId & 0xFFF, 0, 0, 0);\n                floor_draw(fid, v12, v13, rect);\n            }\n        }\n        temp += square_width;\n    }\n}\n\n// 0x4B2B10\nbool square_roof_intersect(int x, int y, int elevation)\n{\n    if (!show_roof) {\n        return false;\n    }\n\n    bool result = false;\n\n    int tileX;\n    int tileY;\n    square_xy_roof(x, y, elevation, &tileX, &tileY);\n\n    TileData* ptr = squares[elevation];\n    int idx = square_width * tileY + tileX;\n    int upper = ptr->field_0[square_width * tileY + tileX] >> 16;\n    int fid = art_id(OBJ_TYPE_TILE, upper & 0xFFF, 0, 0, 0);\n    if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) {\n        if ((((upper & 0xF000) >> 12) & 1) == 0) {\n            int fid = art_id(OBJ_TYPE_TILE, upper & 0xFFF, 0, 0, 0);\n            CacheEntry* handle;\n            Art* art = art_ptr_lock(fid, &handle);\n            if (art != NULL) {\n                unsigned char* data = art_frame_data(art, 0, 0);\n                if (data != NULL) {\n                    int v18;\n                    int v17;\n                    square_coord_roof(idx, &v18, &v17, elevation);\n\n                    int width = art_frame_width(art, 0, 0);\n                    if (data[width * (y - v17) + x - v18] != 0) {\n                        result = true;\n                    }\n                }\n                art_ptr_unlock(handle);\n            }\n        }\n    }\n\n    return result;\n}\n\n// NOTE: Unused.\n//\n// 0x4B2E60\nvoid grid_toggle()\n{\n    show_grid = 1 - show_grid;\n}\n\n// NOTE: Unused.\n//\n// 0x4B2E78\nvoid grid_on()\n{\n    show_grid = 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B2E84\nvoid grid_off()\n{\n    show_grid = 0;\n}\n\n// 0x4B2E90\nint get_grid_flag()\n{\n    return show_grid;\n}\n\n// 0x4B2E98\nvoid grid_render(Rect* rect, int elevation)\n{\n    if (!show_grid) {\n        return;\n    }\n\n    for (int y = rect->uly - 12; y < rect->lry + 12; y += 6) {\n        for (int x = rect->ulx - 32; x < rect->lrx + 32; x += 16) {\n            int tile = tile_num(x, y, elevation);\n            draw_grid(tile, elevation, rect);\n        }\n    }\n}\n\n// 0x4B2EF0\nvoid grid_draw(int tile, int elevation)\n{\n    Rect rect;\n\n    tile_coord(tile, &(rect.ulx), &(rect.uly), elevation);\n\n    rect.lrx = rect.ulx + 32 - 1;\n    rect.lry = rect.uly + 16 - 1;\n    if (rect_inside_bound(&rect, &buf_rect, &rect) != -1) {\n        draw_grid(tile, elevation, &rect);\n        blit(&rect);\n    }\n}\n\n// 0x4B2F4C\nvoid draw_grid(int tile, int elevation, Rect* rect)\n{\n    if (tile == -1) {\n        return;\n    }\n\n    int x;\n    int y;\n    tile_coord(tile, &x, &y, elevation);\n\n    Rect r;\n    r.ulx = x;\n    r.uly = y;\n    r.lrx = x + 32 - 1;\n    r.lry = y + 16 - 1;\n\n    if (rect_inside_bound(&r, rect, &r) == -1) {\n        return;\n    }\n\n    if (obj_blocking_at(NULL, tile, elevation) != NULL) {\n        trans_buf_to_buf(tile_grid_blocked + 32 * (r.uly - y) + (r.ulx - x),\n            r.lrx - r.ulx + 1,\n            r.lry - r.uly + 1,\n            32,\n            buf + buf_full * r.uly + r.ulx,\n            buf_full);\n        return;\n    }\n\n    if (obj_occupied(tile, elevation)) {\n        trans_buf_to_buf(tile_grid_occupied + 32 * (r.uly - y) + (r.ulx - x),\n            r.lrx - r.ulx + 1,\n            r.lry - r.uly + 1,\n            32,\n            buf + buf_full * r.uly + r.ulx,\n            buf_full);\n        return;\n    }\n\n    translucent_trans_buf_to_buf(tile_grid_occupied + 32 * (r.uly - y) + (r.ulx - x),\n        r.lrx - r.ulx + 1,\n        r.lry - r.uly + 1,\n        32,\n        buf + buf_full * r.uly + r.ulx,\n        0,\n        0,\n        buf_full,\n        wallBlendTable,\n        commonGrayTable);\n}\n\n// 0x4B30C4\nvoid floor_draw(int fid, int x, int y, Rect* rect)\n{\n    if (art_get_disable(FID_TYPE(fid)) != 0) {\n        return;\n    }\n\n    CacheEntry* cacheEntry;\n    Art* art = art_ptr_lock(fid, &cacheEntry);\n    if (art == NULL) {\n        return;\n    }\n\n    int elev = map_elevation;\n    int left = rect->ulx;\n    int top = rect->uly;\n    int width = rect->lrx - rect->ulx + 1;\n    int height = rect->lry - rect->uly + 1;\n    int frameWidth;\n    int frameHeight;\n    int v15;\n    int v76;\n    int v77;\n    int v78;\n    int v79;\n\n    int savedX = x;\n    int savedY = y;\n\n    if (left < 0) {\n        left = 0;\n    }\n\n    if (top < 0) {\n        top = 0;\n    }\n\n    if (left + width > buf_width) {\n        width = buf_width - left;\n    }\n\n    if (top + height > buf_length) {\n        height = buf_length - top;\n    }\n\n    if (x >= buf_width || x > rect->lrx || y >= buf_length || y > rect->lry) goto out;\n\n    frameWidth = art_frame_width(art, 0, 0);\n    frameHeight = art_frame_length(art, 0, 0);\n\n    if (left < x) {\n        v79 = 0;\n        int v12 = left + width;\n        v77 = frameWidth + x <= v12 ? frameWidth : v12 - x;\n    } else {\n        v79 = left - x;\n        x = left;\n        v77 = frameWidth - v79;\n        if (v77 > width) {\n            v77 = width;\n        }\n    }\n\n    if (top < y) {\n        int v14 = height + top;\n        v78 = 0;\n        v76 = frameHeight + y <= v14 ? frameHeight : v14 - y;\n    } else {\n        v78 = top - y;\n        y = top;\n        v76 = frameHeight - v78;\n        if (v76 > height) {\n            v76 = height;\n        }\n    }\n\n    if (v77 <= 0 || v76 <= 0) goto out;\n\n    v15 = tile_num(savedX, savedY + 13, map_elevation);\n    if (v15 != -1) {\n        int v17 = light_get_ambient();\n        for (int i = v15 & 1; i < 10; i++) {\n            // NOTE: calling light_get_tile two times, probably a result of using __min kind macro\n            int v21 = light_get_tile(elev, v15 + verticies[i].field_4);\n            if (v21 <= v17) {\n                v21 = v17;\n            }\n\n            verticies[i].field_C = v21;\n        }\n\n        int v23 = 0;\n        for (int i = 0; i < 9; i++) {\n            if (verticies[i + 1].field_C != verticies[i].field_C) {\n                break;\n            }\n\n            v23++;\n        }\n\n        if (v23 == 9) {\n            unsigned char* frame_data = art_frame_data(art, 0, 0);\n            dark_trans_buf_to_buf(frame_data + frameWidth * v78 + v79, v77, v76, frameWidth, buf, x, y, buf_full, verticies[0].field_C);\n            goto out;\n        }\n\n        for (int i = 0; i < 5; i++) {\n            STRUCT_51DB0C* ptr_51DB0C = &(rightside_up_triangles[i]);\n            int v32 = verticies[ptr_51DB0C->field_8].field_C;\n            int v33 = verticies[ptr_51DB0C->field_8].field_0;\n            int v34 = verticies[ptr_51DB0C->field_4].field_C - verticies[ptr_51DB0C->field_0].field_C;\n            // TODO: Probably wrong.\n            int v35 = v34 / 32;\n            int v36 = (verticies[ptr_51DB0C->field_0].field_C - v32) / 13;\n            int* v37 = &(intensity_map[v33]);\n            if (v35 != 0) {\n                if (v36 != 0) {\n                    for (int i = 0; i < 13; i++) {\n                        int v41 = v32;\n                        int v42 = rightside_up_table[i].field_4;\n                        v37 += rightside_up_table[i].field_0;\n                        for (int j = 0; j < v42; j++) {\n                            *v37++ = v41;\n                            v41 += v35;\n                        }\n                        v32 += v36;\n                    }\n                } else {\n                    for (int i = 0; i < 13; i++) {\n                        int v38 = v32;\n                        int v39 = rightside_up_table[i].field_4;\n                        v37 += rightside_up_table[i].field_0;\n                        for (int j = 0; j < v39; j++) {\n                            *v37++ = v38;\n                            v38 += v35;\n                        }\n                    }\n                }\n            } else {\n                if (v36 != 0) {\n                    for (int i = 0; i < 13; i++) {\n                        int v46 = rightside_up_table[i].field_4;\n                        v37 += rightside_up_table[i].field_0;\n                        for (int j = 0; j < v46; j++) {\n                            *v37++ = v32;\n                        }\n                        v32 += v36;\n                    }\n                } else {\n                    for (int i = 0; i < 13; i++) {\n                        int v44 = rightside_up_table[i].field_4;\n                        v37 += rightside_up_table[i].field_0;\n                        for (int j = 0; j < v44; j++) {\n                            *v37++ = v32;\n                        }\n                    }\n                }\n            }\n        }\n\n        for (int i = 0; i < 5; i++) {\n            STRUCT_51DB48* ptr_51DB48 = &(upside_down_triangles[i]);\n            int v50 = verticies[ptr_51DB48->field_0].field_C;\n            int v51 = verticies[ptr_51DB48->field_0].field_0;\n            int v52 = verticies[ptr_51DB48->field_8].field_C - v50;\n            // TODO: Probably wrong.\n            int v53 = v52 / 32;\n            int v54 = (verticies[ptr_51DB48->field_4].field_C - v50) / 13;\n            int* v55 = &(intensity_map[v51]);\n            if (v53 != 0) {\n                if (v54 != 0) {\n                    for (int i = 0; i < 13; i++) {\n                        int v59 = v50;\n                        int v60 = upside_down_table[i].field_4;\n                        v55 += upside_down_table[i].field_0;\n                        for (int j = 0; j < v60; j++) {\n                            *v55++ = v59;\n                            v59 += v53;\n                        }\n                        v50 += v54;\n                    }\n                } else {\n                    for (int i = 0; i < 13; i++) {\n                        int v56 = v50;\n                        int v57 = upside_down_table[i].field_4;\n                        v55 += upside_down_table[i].field_0;\n                        for (int j = 0; j < v57; j++) {\n                            *v55++ = v56;\n                            v56 += v53;\n                        }\n                    }\n                }\n            } else {\n                if (v54 != 0) {\n                    for (int i = 0; i < 13; i++) {\n                        int v64 = upside_down_table[i].field_4;\n                        v55 += upside_down_table[i].field_0;\n                        for (int j = 0; j < v64; j++) {\n                            *v55++ = v50;\n                        }\n                        v50 += v54;\n                    }\n                } else {\n                    for (int i = 0; i < 13; i++) {\n                        int v62 = upside_down_table[i].field_4;\n                        v55 += upside_down_table[i].field_0;\n                        for (int j = 0; j < v62; j++) {\n                            *v55++ = v50;\n                        }\n                    }\n                }\n            }\n        }\n\n        unsigned char* v66 = buf + buf_full * y + x;\n        unsigned char* v67 = art_frame_data(art, 0, 0) + frameWidth * v78 + v79;\n        int* v68 = &(intensity_map[160 + 80 * v78]) + v79;\n        int v86 = frameWidth - v77;\n        int v85 = buf_full - v77;\n        int v87 = 80 - v77;\n\n        while (--v76 != -1) {\n            for (int kk = 0; kk < v77; kk++) {\n                if (*v67 != 0) {\n                    *v66 = intensityColorTable[*v67][*v68 >> 9];\n                }\n                v67++;\n                v68++;\n                v66++;\n            }\n            v66 += v85;\n            v68 += v87;\n            v67 += v86;\n        }\n    }\n\nout:\n\n    art_ptr_unlock(cacheEntry);\n}\n\n// 0x4B372C\nint tile_make_line(int from, int to, int* tiles, int tilesCapacity)\n{\n    if (tilesCapacity <= 1) {\n        return 0;\n    }\n\n    int count = 0;\n\n    int fromX;\n    int fromY;\n    tile_coord(from, &fromX, &fromY, map_elevation);\n    fromX += 16;\n    fromY += 8;\n\n    int toX;\n    int toY;\n    tile_coord(to, &toX, &toY, map_elevation);\n    toX += 16;\n    toY += 8;\n\n    tiles[count++] = from;\n\n    int stepX;\n    int deltaX = toX - fromX;\n    if (deltaX > 0)\n        stepX = 1;\n    else if (deltaX < 0)\n        stepX = -1;\n    else\n        stepX = 0;\n\n    int stepY;\n    int deltaY = toY - fromY;\n    if (deltaY > 0)\n        stepY = 1;\n    else if (deltaY < 0)\n        stepY = -1;\n    else\n        stepY = 0;\n\n    int v28 = 2 * abs(toX - fromX);\n    int v27 = 2 * abs(toY - fromY);\n\n    int tileX = fromX;\n    int tileY = fromY;\n\n    if (v28 <= v27) {\n        int middleX = v28 - v27 / 2;\n        while (true) {\n            int tile = tile_num(tileX, tileY, map_elevation);\n            tiles[count] = tile;\n\n            if (tile == to) {\n                count++;\n                break;\n            }\n\n            if (tile != tiles[count - 1] && (count == 1 || tile != tiles[count - 2])) {\n                count++;\n                if (count == tilesCapacity) {\n                    break;\n                }\n            }\n\n            if (tileY == toY) {\n                break;\n            }\n\n            if (middleX >= 0) {\n                tileX += stepX;\n                middleX -= v27;\n            }\n\n            middleX += v28;\n            tileY += stepY;\n        }\n    } else {\n        int middleY = v27 - v28 / 2;\n        while (true) {\n            int tile = tile_num(tileX, tileY, map_elevation);\n            tiles[count] = tile;\n\n            if (tile == to) {\n                count++;\n                break;\n            }\n\n            if (tile != tiles[count - 1] && (count == 1 || tile != tiles[count - 2])) {\n                count++;\n                if (count == tilesCapacity) {\n                    break;\n                }\n            }\n\n            if (tileX == toX) {\n                return count;\n            }\n\n            if (middleY >= 0) {\n                tileY += stepY;\n                middleY -= v28;\n            }\n\n            middleY += v27;\n            tileX += stepX;\n        }\n    }\n\n    return count;\n}\n\n// 0x4B3924\nint tile_scroll_to(int tile, int flags)\n{\n    if (tile == tile_center_tile) {\n        return -1;\n    }\n\n    int oldCenterTile = tile_center_tile;\n\n    int v9[200];\n    int count = tile_make_line(tile_center_tile, tile, v9, 200);\n    if (count == 0) {\n        return -1;\n    }\n\n    int index = 1;\n    for (; index < count; index++) {\n        if (tile_set_center(v9[index], 0) == -1) {\n            break;\n        }\n    }\n\n    int rc = 0;\n    if ((flags & 0x01) != 0) {\n        if (index != count) {\n            tile_set_center(oldCenterTile, 0);\n            rc = -1;\n        }\n    }\n\n    if ((flags & 0x02) != 0) {\n        // NOTE: Uninline.\n        tile_refresh_display();\n    }\n\n    return rc;\n}\n"
  },
  {
    "path": "src/game/tile.h",
    "content": "#ifndef FALLOUT_GAME_TILE_H_\n#define FALLOUT_GAME_TILE_H_\n\n#include <stdbool.h>\n\n#include \"plib/gnw/rect.h\"\n#include \"game/map.h\"\n#include \"game/object_types.h\"\n\n#define TILE_SET_CENTER_REFRESH_WINDOW 0x01\n#define TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS 0x02\n\ntypedef void(TileWindowRefreshProc)(Rect* rect);\ntypedef void(TileWindowRefreshElevationProc)(Rect* rect, int elevation);\n\nextern int off_tile[2][6];\n\nextern int tile_center_tile;\n\nint tile_init(TileData** a1, int squareGridWidth, int squareGridHeight, int hexGridWidth, int hexGridHeight, unsigned char* buf, int windowWidth, int windowHeight, int windowPitch, TileWindowRefreshProc* windowRefreshProc);\nvoid tile_set_border(int windowWidth, int windowHeight, int hexGridWidth, int hexGridHeight);\nvoid tile_reset();\nvoid tile_exit();\nvoid tile_disable_refresh();\nvoid tile_enable_refresh();\nvoid tile_refresh_rect(Rect* rect, int elevation);\nvoid tile_refresh_display();\nint tile_set_center(int tile, int flags);\nvoid tile_toggle_roof(int a1);\nint tile_roof_visible();\nint tile_coord(int tile, int* x, int* y, int elevation);\nint tile_num(int x, int y, int elevation);\nint tile_dist(int a1, int a2);\nbool tile_in_front_of(int tile1, int tile2);\nbool tile_to_right_of(int tile1, int tile2);\nint tile_num_in_direction(int tile, int rotation, int distance);\nint tile_dir(int a1, int a2);\nint tile_num_beyond(int from, int to, int distance);\nvoid tile_enable_scroll_blocking();\nvoid tile_disable_scroll_blocking();\nbool tile_get_scroll_blocking();\nvoid tile_enable_scroll_limiting();\nvoid tile_disable_scroll_limiting();\nbool tile_get_scroll_limiting();\nint square_coord(int squareTile, int* coordX, int* coordY, int elevation);\nint square_coord_roof(int squareTile, int* screenX, int* screenY, int elevation);\nint square_num(int screenX, int screenY, int elevation);\nint square_num_roof(int screenX, int screenY, int elevation);\nvoid square_xy(int screenX, int screenY, int elevation, int* coordX, int* coordY);\nvoid square_xy_roof(int screenX, int screenY, int elevation, int* coordX, int* coordY);\nvoid square_render_roof(Rect* rect, int elevation);\nvoid tile_fill_roof(int x, int y, int elevation, int a4);\nvoid square_render_floor(Rect* rect, int elevation);\nbool square_roof_intersect(int x, int y, int elevation);\nvoid grid_toggle();\nvoid grid_on();\nvoid grid_off();\nint get_grid_flag();\nvoid grid_render(Rect* rect, int elevation);\nvoid grid_draw(int tile, int elevation);\nvoid draw_grid(int tile, int elevation, Rect* rect);\nvoid floor_draw(int fid, int x, int y, Rect* rect);\nint tile_make_line(int currentCenterTile, int newCenterTile, int* tiles, int tilesCapacity);\nint tile_scroll_to(int tile, int flags);\n\n#endif /* FALLOUT_GAME_TILE_H_ */\n"
  },
  {
    "path": "src/game/trait.c",
    "content": "#include \"game/trait.h\"\n\n#include <stdio.h>\n\n#include \"game/game.h\"\n#include \"game/message.h\"\n#include \"game/object.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n\n// Provides metadata about traits.\ntypedef struct TraitDescription {\n    // The name of trait.\n    char* name;\n\n    // The description of trait.\n    //\n    // The description is only used in character editor to inform player about\n    // effects of this trait.\n    char* description;\n\n    // Identifier of art in [intrface.lst].\n    int frmId;\n} TraitDescription;\n\n// 0x66BE38\nstatic MessageList trait_message_file;\n\n// List of selected traits.\n//\n// 0x66BE40\nstatic int pc_trait[TRAITS_MAX_SELECTED_COUNT];\n\n// 0x51DB84\nstatic TraitDescription trait_data[TRAIT_COUNT] = {\n    { NULL, NULL, 55 },\n    { NULL, NULL, 56 },\n    { NULL, NULL, 57 },\n    { NULL, NULL, 58 },\n    { NULL, NULL, 59 },\n    { NULL, NULL, 60 },\n    { NULL, NULL, 61 },\n    { NULL, NULL, 62 },\n    { NULL, NULL, 63 },\n    { NULL, NULL, 64 },\n    { NULL, NULL, 65 },\n    { NULL, NULL, 66 },\n    { NULL, NULL, 67 },\n    { NULL, NULL, 94 },\n    { NULL, NULL, 69 },\n    { NULL, NULL, 70 },\n};\n\n// 0x4B39F0\nint trait_init()\n{\n    if (!message_init(&trait_message_file)) {\n        return -1;\n    }\n\n    char path[FILENAME_MAX];\n    sprintf(path, \"%s%s\", msg_path, \"trait.msg\");\n\n    if (!message_load(&trait_message_file, path)) {\n        return -1;\n    }\n\n    for (int trait = 0; trait < TRAIT_COUNT; trait++) {\n        MessageListItem messageListItem;\n\n        messageListItem.num = 100 + trait;\n        if (message_search(&trait_message_file, &messageListItem)) {\n            trait_data[trait].name = messageListItem.text;\n        }\n\n        messageListItem.num = 200 + trait;\n        if (message_search(&trait_message_file, &messageListItem)) {\n            trait_data[trait].description = messageListItem.text;\n        }\n    }\n\n    // NOTE: Uninline.\n    trait_reset();\n\n    return true;\n}\n\n// 0x4B3ADC\nvoid trait_reset()\n{\n    for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) {\n        pc_trait[index] = -1;\n    }\n}\n\n// 0x4B3AF8\nvoid trait_exit()\n{\n    message_exit(&trait_message_file);\n}\n\n// Loads trait system state from save game.\n//\n// 0x4B3B08\nint trait_load(File* stream)\n{\n    return db_freadIntCount(stream, pc_trait, TRAITS_MAX_SELECTED_COUNT);\n}\n\n// Saves trait system state to save game.\n//\n// 0x4B3B28\nint trait_save(File* stream)\n{\n    return db_fwriteIntCount(stream, pc_trait, TRAITS_MAX_SELECTED_COUNT);\n}\n\n// Sets selected traits.\n//\n// 0x4B3B48\nvoid trait_set(int trait1, int trait2)\n{\n    pc_trait[0] = trait1;\n    pc_trait[1] = trait2;\n}\n\n// Returns selected traits.\n//\n// 0x4B3B54\nvoid trait_get(int* trait1, int* trait2)\n{\n    *trait1 = pc_trait[0];\n    *trait2 = pc_trait[1];\n}\n\n// Returns a name of the specified trait, or `NULL` if the specified trait is\n// out of range.\n//\n// 0x4B3B68\nchar* trait_name(int trait)\n{\n    return trait >= 0 && trait < TRAIT_COUNT ? trait_data[trait].name : NULL;\n}\n\n// Returns a description of the specified trait, or `NULL` if the specified\n// trait is out of range.\n//\n// 0x4B3B88\nchar* trait_description(int trait)\n{\n    return trait >= 0 && trait < TRAIT_COUNT ? trait_data[trait].description : NULL;\n}\n\n// Return an art ID of the specified trait, or `0` if the specified trait is\n// out of range.\n//\n// 0x4B3BA8\nint trait_pic(int trait)\n{\n    return trait >= 0 && trait < TRAIT_COUNT ? trait_data[trait].frmId : 0;\n}\n\n// Returns `true` if the specified trait is selected.\n//\n// 0x4B3BC8\nbool trait_level(int trait)\n{\n    return pc_trait[0] == trait || pc_trait[1] == trait;\n}\n\n// Returns stat modifier depending on selected traits.\n//\n// 0x4B3C7C\nint trait_adjust_stat(int stat)\n{\n    int modifier = 0;\n\n    switch (stat) {\n    case STAT_STRENGTH:\n        if (trait_level(TRAIT_GIFTED)) {\n            modifier += 1;\n        }\n        if (trait_level(TRAIT_BRUISER)) {\n            modifier += 2;\n        }\n        break;\n    case STAT_PERCEPTION:\n        if (trait_level(TRAIT_GIFTED)) {\n            modifier += 1;\n        }\n        break;\n    case STAT_ENDURANCE:\n        if (trait_level(TRAIT_GIFTED)) {\n            modifier += 1;\n        }\n        break;\n    case STAT_CHARISMA:\n        if (trait_level(TRAIT_GIFTED)) {\n            modifier += 1;\n        }\n        break;\n    case STAT_INTELLIGENCE:\n        if (trait_level(TRAIT_GIFTED)) {\n            modifier += 1;\n        }\n        break;\n    case STAT_AGILITY:\n        if (trait_level(TRAIT_GIFTED)) {\n            modifier += 1;\n        }\n        if (trait_level(TRAIT_SMALL_FRAME)) {\n            modifier += 1;\n        }\n        break;\n    case STAT_LUCK:\n        if (trait_level(TRAIT_GIFTED)) {\n            modifier += 1;\n        }\n        break;\n    case STAT_MAXIMUM_ACTION_POINTS:\n        if (trait_level(TRAIT_BRUISER)) {\n            modifier -= 2;\n        }\n        break;\n    case STAT_ARMOR_CLASS:\n        if (trait_level(TRAIT_KAMIKAZE)) {\n            modifier -= stat_get_base_direct(obj_dude, STAT_ARMOR_CLASS);\n        }\n        break;\n    case STAT_MELEE_DAMAGE:\n        if (trait_level(TRAIT_HEAVY_HANDED)) {\n            modifier += 4;\n        }\n        break;\n    case STAT_CARRY_WEIGHT:\n        if (trait_level(TRAIT_SMALL_FRAME)) {\n            modifier -= 10 * stat_get_base_direct(obj_dude, STAT_STRENGTH);\n        }\n        break;\n    case STAT_SEQUENCE:\n        if (trait_level(TRAIT_KAMIKAZE)) {\n            modifier += 5;\n        }\n        break;\n    case STAT_HEALING_RATE:\n        if (trait_level(TRAIT_FAST_METABOLISM)) {\n            modifier += 2;\n        }\n        break;\n    case STAT_CRITICAL_CHANCE:\n        if (trait_level(TRAIT_FINESSE)) {\n            modifier += 10;\n        }\n        break;\n    case STAT_BETTER_CRITICALS:\n        if (trait_level(TRAIT_HEAVY_HANDED)) {\n            modifier -= 30;\n        }\n        break;\n    case STAT_RADIATION_RESISTANCE:\n        if (trait_level(TRAIT_FAST_METABOLISM)) {\n            modifier -= -stat_get_base_direct(obj_dude, STAT_RADIATION_RESISTANCE);\n        }\n        break;\n    case STAT_POISON_RESISTANCE:\n        if (trait_level(TRAIT_FAST_METABOLISM)) {\n            modifier -= -stat_get_base_direct(obj_dude, STAT_POISON_RESISTANCE);\n        }\n        break;\n    }\n\n    return modifier;\n}\n\n// Returns skill modifier depending on selected traits.\n//\n// 0x4B40FC\nint trait_adjust_skill(int skill)\n{\n    int modifier = 0;\n\n    if (trait_level(TRAIT_GIFTED)) {\n        modifier -= 10;\n    }\n\n    if (trait_level(TRAIT_GOOD_NATURED)) {\n        switch (skill) {\n        case SKILL_SMALL_GUNS:\n        case SKILL_BIG_GUNS:\n        case SKILL_ENERGY_WEAPONS:\n        case SKILL_UNARMED:\n        case SKILL_MELEE_WEAPONS:\n        case SKILL_THROWING:\n            modifier -= 10;\n            break;\n        case SKILL_FIRST_AID:\n        case SKILL_DOCTOR:\n        case SKILL_SPEECH:\n        case SKILL_BARTER:\n            modifier += 15;\n            break;\n        }\n    }\n\n    return modifier;\n}\n"
  },
  {
    "path": "src/game/trait.h",
    "content": "#ifndef FALLOUT_GAME_TRAIT_H_\n#define FALLOUT_GAME_TRAIT_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n#include \"game/trait_defs.h\"\n\nint trait_init();\nvoid trait_reset();\nvoid trait_exit();\nint trait_load(File* stream);\nint trait_save(File* stream);\nvoid trait_set(int trait1, int trait2);\nvoid trait_get(int* trait1, int* trait2);\nchar* trait_name(int trait);\nchar* trait_description(int trait);\nint trait_pic(int trait);\nbool trait_level(int trait);\nint trait_adjust_stat(int stat);\nint trait_adjust_skill(int skill);\n\n#endif /* FALLOUT_GAME_TRAIT_H_ */\n"
  },
  {
    "path": "src/game/trait_defs.h",
    "content": "#ifndef TRAIT_DEFS\n#define TRAIT_DEFS\n\n// The maximum number of traits a player is allowed to select.\n#define TRAITS_MAX_SELECTED_COUNT 2\n\n// Available traits.\ntypedef enum Trait {\n    TRAIT_FAST_METABOLISM,\n    TRAIT_BRUISER,\n    TRAIT_SMALL_FRAME,\n    TRAIT_ONE_HANDER,\n    TRAIT_FINESSE,\n    TRAIT_KAMIKAZE,\n    TRAIT_HEAVY_HANDED,\n    TRAIT_FAST_SHOT,\n    TRAIT_BLOODY_MESS,\n    TRAIT_JINXED,\n    TRAIT_GOOD_NATURED,\n    TRAIT_CHEM_RELIANT,\n    TRAIT_CHEM_RESISTANT,\n    TRAIT_SEX_APPEAL,\n    TRAIT_SKILLED,\n    TRAIT_GIFTED,\n    TRAIT_COUNT,\n} Trait;\n\n#endif /* TRAIT_DEFS */\n"
  },
  {
    "path": "src/game/trap.c",
    "content": "#include \"game/trap.h\"\n\n#include <stdlib.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/gnw.h\"\n\ntypedef struct TrapEntry {\n    int address;\n    int name;\n    int field_8;\n    int field_C;\n} TrapEntry;\n\ntypedef struct DuplicateEntry {\n    int address;\n    int field_4;\n    int size;\n    int name;\n    int field_10;\n} DuplicateEntry;\n\nstatic void duplicate_report(int trap, int offset, const char* file, int line);\nstatic void heap_report(int trap, int address, const char* file, int line);\n\n// NOTE: Probably |trap_list_data|.\n//\n// 0x51DC44\nstatic TrapEntry* off_51DC44 = NULL;\n\n// NOTE: Probably |trap_list_duplicate|.\n//\n// 0x66BE48\nstatic DuplicateEntry stru_66BE48[512];\n\n// 0x4B4190\nvoid trap_exit()\n{\n}\n\n// 0x4B4190\nvoid trap_init()\n{\n}\n\n// 0x4B43A4\nstatic void trap_report(int trap, int address, const char* file, int line)\n{\n    TrapEntry* entry;\n\n    entry = &(off_51DC44[trap]);\n    debug_printf(\"TRAPPED A STOMP ERROR:\\n\");\n    debug_printf(\"Stomp caught by check on line %d\", line);\n    debug_printf(\" of module %s.\\n\", file);\n    debug_printf(\"Data stomped was in trap %s\", entry->name);\n    debug_printf(\" at address %p.\\n\", entry->address);\n    debug_printf(\"See comment in trap.c for suggestions on better\");\n    debug_printf(\" isolating the stomp bug.\\n\");\n    GNWSystemError(\"STOMPED!\");\n    exit(1);\n}\n\n// 0x4B4430\nstatic void duplicate_report(int trap, int offset, const char* file, int line)\n{\n    DuplicateEntry* entry;\n\n    entry = &(stru_66BE48[trap]);\n    debug_printf(\"TRAPPED A STOMP ERROR:\\n\");\n    debug_printf(\"Stomp caught by check on line %d\", line);\n    debug_printf(\" of module %s.\\n\", file);\n    debug_printf(\"Data stomped was in trap %s\", entry->name);\n    debug_printf(\" at address %p.\\n\", entry->address + offset);\n    debug_printf(\"This is duplicate trap number %d\", trap);\n    debug_printf(\" at an internal offset of %d.\\n\", offset);\n    debug_printf(\"Trap size is %d.\\n\", entry->size);\n    debug_printf(\"See comment in trap.c for suggestions on better\");\n    debug_printf(\" isolating the stomp bug.\\n\");\n    GNWSystemError(\"STOMPED!\");\n    exit(1);\n}\n\n// 0x4B44F0\nstatic void heap_report(int trap, int address, const char* file, int line)\n{\n    debug_printf(\"TRAPPED A STOMP ERROR:\\n\");\n    debug_printf(\"Stomp caught by check on line %d\", line);\n    debug_printf(\" of module %s.\\n\", file);\n    debug_printf(\"Data stomped was in heap trap number %d\", trap);\n    debug_printf(\" at address %p.\\n\", address);\n    debug_printf(\"See comment in trap.c for suggestions on better\");\n    debug_printf(\" isolating the stomp bug.\\n\");\n    GNWSystemError(\"STOMPED!\");\n    exit(1);\n}\n"
  },
  {
    "path": "src/game/trap.h",
    "content": "#ifndef FALLOUT_GAME_TRAP_H_\n#define FALLOUT_GAME_TRAP_H_\n\nvoid trap_exit();\nvoid trap_init();\n\n#endif /* FALLOUT_GAME_TRAP_H_ */\n"
  },
  {
    "path": "src/game/version.c",
    "content": "#include \"game/version.h\"\n\n#include <stdio.h>\n\n// 0x4B4580\nvoid getverstr(char* dest)\n{\n    sprintf(dest, \"FALLOUT II %d.%02d\", VERSION_MAJOR, VERSION_MINOR);\n}\n"
  },
  {
    "path": "src/game/version.h",
    "content": "#ifndef FALLOUT_GAME_VERSION_H_\n#define FALLOUT_GAME_VERSION_H_\n\n// The size of buffer for version string.\n#define VERSION_MAX 32\n\n#define VERSION_MAJOR 1\n#define VERSION_MINOR 2\n#define VERSION_RELEASE 'R'\n#define VERSION_BUILD_TIME \"Dec 11 1998 16:54:30\"\n\nvoid getverstr(char* dest);\n\n#endif /* FALLOUT_GAME_VERSION_H_ */\n"
  },
  {
    "path": "src/game/wordwrap.c",
    "content": "#include \"game/wordwrap.h\"\n\n#include <ctype.h>\n#include <stddef.h>\n#include <string.h>\n\n#include \"plib/gnw/text.h\"\n\n// 0x4BC6F0\nint word_wrap(const char* string, int width, short* breakpoints, short* breakpointsLengthPtr)\n{\n    breakpoints[0] = 0;\n    *breakpointsLengthPtr = 1;\n\n    for (int index = 1; index < WORD_WRAP_MAX_COUNT; index++) {\n        breakpoints[index] = -1;\n    }\n\n    if (text_max() > width) {\n        return -1;\n    }\n\n    if (text_width(string) < width) {\n        breakpoints[*breakpointsLengthPtr] = (short)strlen(string);\n        *breakpointsLengthPtr += 1;\n        return 0;\n    }\n\n    int gap = text_spacing();\n\n    int accum = 0;\n    const char* prevSpaceOrHyphen = NULL;\n    const char* pch = string;\n    while (*pch != '\\0') {\n        accum += gap + text_char_width(*pch & 0xFF);\n        if (accum <= width) {\n            // NOTE: quests.txt #807 uses extended ascii.\n            if (isspace(*pch & 0xFF) || *pch == '-') {\n                prevSpaceOrHyphen = pch;\n            }\n        } else {\n            if (*breakpointsLengthPtr == WORD_WRAP_MAX_COUNT) {\n                return -1;\n            }\n\n            if (prevSpaceOrHyphen != NULL) {\n                // Word wrap.\n                breakpoints[*breakpointsLengthPtr] = prevSpaceOrHyphen - string + 1;\n                *breakpointsLengthPtr += 1;\n\n                pch = prevSpaceOrHyphen;\n            } else {\n                // Character wrap.\n                breakpoints[*breakpointsLengthPtr] = pch - string;\n                *breakpointsLengthPtr += 1;\n\n                pch--;\n            }\n\n            prevSpaceOrHyphen = NULL;\n            accum = 0;\n        }\n        pch++;\n    }\n\n    if (*breakpointsLengthPtr == WORD_WRAP_MAX_COUNT) {\n        return -1;\n    }\n\n    breakpoints[*breakpointsLengthPtr] = pch - string + 1;\n    *breakpointsLengthPtr += 1;\n\n    return 0;\n}\n"
  },
  {
    "path": "src/game/wordwrap.h",
    "content": "#ifndef FALLOUT_GAME_WORDWRAP_H_\n#define FALLOUT_GAME_WORDWRAP_H_\n\n#define WORD_WRAP_MAX_COUNT 64\n\nint word_wrap(const char* string, int width, short* breakpoints, short* breakpointsLengthPtr);\n\n#endif /* FALLOUT_GAME_WORDWRAP_H_ */\n"
  },
  {
    "path": "src/game/worldmap.c",
    "content": "#include \"game/worldmap.h\"\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/anim.h\"\n#include \"game/art.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"game/config.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"game/cycle.h\"\n#include \"plib/db/db.h\"\n#include \"game/bmpdlog.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"game/display.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gmouse.h\"\n#include \"game/gmovie.h\"\n#include \"game/gsound.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/map_defs.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/message.h\"\n#include \"game/object_types.h\"\n#include \"game/object.h\"\n#include \"game/party.h\"\n#include \"game/perk.h\"\n#include \"game/protinst.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/strparse.h\"\n#include \"plib/gnw/text.h\"\n#include \"game/tile.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define CITY_NAME_SIZE (40)\n#define TILE_WALK_MASK_NAME_SIZE (40)\n#define ENTRANCE_LIST_CAPACITY (10)\n\n#define MAP_AMBIENT_SOUND_EFFECTS_CAPACITY (6)\n#define MAP_STARTING_POINTS_CAPACITY (15)\n\n#define SUBTILE_GRID_WIDTH (7)\n#define SUBTILE_GRID_HEIGHT (6)\n\n#define ENCOUNTER_ENTRY_SPECIAL (0x01)\n\n#define ENCOUNTER_SUBINFO_DEAD (0x01)\n\n#define WM_WINDOW_DIAL_X (532)\n#define WM_WINDOW_DIAL_Y (48)\n\n#define WM_TOWN_LIST_SCROLL_UP_X (480)\n#define WM_TOWN_LIST_SCROLL_UP_Y (137)\n\n#define WM_TOWN_LIST_SCROLL_DOWN_X (WM_TOWN_LIST_SCROLL_UP_X)\n#define WM_TOWN_LIST_SCROLL_DOWN_Y (152)\n\n#define WM_WINDOW_GLOBE_OVERLAY_X (495)\n#define WM_WINDOW_GLOBE_OVERLAY_Y (330)\n\n#define WM_WINDOW_CAR_X (514)\n#define WM_WINDOW_CAR_Y (336)\n\n#define WM_WINDOW_CAR_OVERLAY_X (499)\n#define WM_WINDOW_CAR_OVERLAY_Y (330)\n\n#define WM_WINDOW_CAR_FUEL_BAR_X (500)\n#define WM_WINDOW_CAR_FUEL_BAR_Y (339)\n#define WM_WINDOW_CAR_FUEL_BAR_HEIGHT (70)\n\n#define WM_TOWN_WORLD_SWITCH_X (519)\n#define WM_TOWN_WORLD_SWITCH_Y (439)\n\n#define WM_TILE_WIDTH (350)\n#define WM_TILE_HEIGHT (300)\n\n#define WM_SUBTILE_SIZE (50)\n\n#define WM_WINDOW_WIDTH (640)\n#define WM_WINDOW_HEIGHT (480)\n\n#define WM_VIEW_X (22)\n#define WM_VIEW_Y (21)\n#define WM_VIEW_WIDTH (450)\n#define WM_VIEW_HEIGHT (443)\n\ntypedef enum EncounterFormationType {\n    ENCOUNTER_FORMATION_TYPE_SURROUNDING,\n    ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE,\n    ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE,\n    ENCOUNTER_FORMATION_TYPE_WEDGE,\n    ENCOUNTER_FORMATION_TYPE_CONE,\n    ENCOUNTER_FORMATION_TYPE_HUDDLE,\n    ENCOUNTER_FORMATION_TYPE_COUNT,\n} EncounterFormationType;\n\ntypedef enum EncounterFrequencyType {\n    ENCOUNTER_FREQUENCY_TYPE_NONE,\n    ENCOUNTER_FREQUENCY_TYPE_RARE,\n    ENCOUNTER_FREQUENCY_TYPE_UNCOMMON,\n    ENCOUNTER_FREQUENCY_TYPE_COMMON,\n    ENCOUNTER_FREQUENCY_TYPE_FREQUENT,\n    ENCOUNTER_FREQUENCY_TYPE_FORCED,\n    ENCOUNTER_FREQUENCY_TYPE_COUNT,\n} EncounterFrequencyType;\n\ntypedef enum EncounterSceneryType {\n    ENCOUNTER_SCENERY_TYPE_NONE,\n    ENCOUNTER_SCENERY_TYPE_LIGHT,\n    ENCOUNTER_SCENERY_TYPE_NORMAL,\n    ENCOUNTER_SCENERY_TYPE_HEAVY,\n    ENCOUNTER_SCENERY_TYPE_COUNT,\n} EncounterSceneryType;\n\ntypedef enum EncounterSituation {\n    ENCOUNTER_SITUATION_NOTHING,\n    ENCOUNTER_SITUATION_AMBUSH,\n    ENCOUNTER_SITUATION_FIGHTING,\n    ENCOUNTER_SITUATION_AND,\n    ENCOUNTER_SITUATION_COUNT,\n} EncounterSituation;\n\ntypedef enum EncounterLogicalOperator {\n    ENCOUNTER_LOGICAL_OPERATOR_NONE,\n    ENCOUNTER_LOGICAL_OPERATOR_AND,\n    ENCOUNTER_LOGICAL_OPERATOR_OR,\n} EncounterLogicalOperator;\n\ntypedef enum EncounterConditionType {\n    ENCOUNTER_CONDITION_TYPE_NONE = 0,\n    ENCOUNTER_CONDITION_TYPE_GLOBAL = 1,\n    ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS = 2,\n    ENCOUNTER_CONDITION_TYPE_RANDOM = 3,\n    ENCOUNTER_CONDITION_TYPE_PLAYER = 4,\n    ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED = 5,\n    ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY = 6,\n} EncounterConditionType;\n\ntypedef enum EncounterConditionalOperator {\n    ENCOUNTER_CONDITIONAL_OPERATOR_NONE,\n    ENCOUNTER_CONDITIONAL_OPERATOR_EQUAL,\n    ENCOUNTER_CONDITIONAL_OPERATOR_NOT_EQUAL,\n    ENCOUNTER_CONDITIONAL_OPERATOR_LESS_THAN,\n    ENCOUNTER_CONDITIONAL_OPERATOR_GREATER_THAN,\n    ENCOUNTER_CONDITIONAL_OPERATOR_COUNT,\n} EncounterConditionalOperator;\n\ntypedef enum Daytime {\n    DAY_PART_MORNING,\n    DAY_PART_AFTERNOON,\n    DAY_PART_NIGHT,\n    DAY_PART_COUNT,\n} Daytime;\n\ntypedef enum LockState {\n    LOCK_STATE_UNLOCKED,\n    LOCK_STATE_LOCKED,\n} LockState;\n\ntypedef enum SubtileState {\n    SUBTILE_STATE_UNKNOWN,\n    SUBTILE_STATE_KNOWN,\n    SUBTILE_STATE_VISITED,\n} SubtileState;\n\ntypedef enum WorldMapEncounterFrm {\n    WORLD_MAP_ENCOUNTER_FRM_RANDOM_BRIGHT,\n    WORLD_MAP_ENCOUNTER_FRM_RANDOM_DARK,\n    WORLD_MAP_ENCOUNTER_FRM_SPECIAL_BRIGHT,\n    WORLD_MAP_ENCOUNTER_FRM_SPECIAL_DARK,\n    WORLD_MAP_ENCOUNTER_FRM_COUNT,\n} WorldMapEncounterFrm;\n\ntypedef enum WorldmapArrowFrm {\n    WORLDMAP_ARROW_FRM_NORMAL,\n    WORLDMAP_ARROW_FRM_PRESSED,\n    WORLDMAP_ARROW_FRM_COUNT,\n} WorldmapArrowFrm;\n\ntypedef enum CitySize {\n    CITY_SIZE_SMALL,\n    CITY_SIZE_MEDIUM,\n    CITY_SIZE_LARGE,\n    CITY_SIZE_COUNT,\n} CitySize;\n\ntypedef struct EntranceInfo {\n    int state;\n    int x;\n    int y;\n    int map;\n    int elevation;\n    int tile;\n    int rotation;\n} EntranceInfo;\n\ntypedef struct CityInfo {\n    char name[CITY_NAME_SIZE];\n    int areaId;\n    int x;\n    int y;\n    int size;\n    int state;\n    int lockState;\n    int visitedState;\n    int mapFid;\n    int labelFid;\n    int entrancesLength;\n    EntranceInfo entrances[ENTRANCE_LIST_CAPACITY];\n} CityInfo;\n\ntypedef struct MapAmbientSoundEffectInfo {\n    char name[40];\n    int chance;\n} MapAmbientSoundEffectInfo;\n\ntypedef struct MapStartPointInfo {\n    int elevation;\n    int tile;\n    int field_8;\n} MapStartPointInfo;\n\ntypedef struct MapInfo {\n    char lookupName[40];\n    int field_28;\n    int field_2C;\n    char mapFileName[40];\n    char music[40];\n    int flags;\n    int ambientSoundEffectsLength;\n    MapAmbientSoundEffectInfo ambientSoundEffects[MAP_AMBIENT_SOUND_EFFECTS_CAPACITY];\n    int startPointsLength;\n    MapStartPointInfo startPoints[MAP_STARTING_POINTS_CAPACITY];\n} MapInfo;\n\ntypedef struct Terrain {\n    char lookupName[40];\n    int type;\n    int mapsLength;\n    int maps[20];\n} Terrain;\n\ntypedef struct EncounterConditionEntry {\n    int type;\n    int conditionalOperator;\n    int param;\n    int value;\n} EncounterConditionEntry;\n\ntypedef struct EncounterCondition {\n    int entriesLength;\n    EncounterConditionEntry entries[3];\n    int logicalOperators[2];\n} EncounterCondition;\n\ntypedef struct ENCOUNTER_ENTRY_ENC {\n    int minQuantity; // min\n    int maxQuantity; // max\n    int field_8;\n    int situation;\n} ENCOUNTER_ENTRY_ENC;\n\ntypedef struct EncounterEntry {\n    int flags;\n    int map;\n    int scenery;\n    int chance;\n    int counter;\n    EncounterCondition condition;\n    int field_50;\n    ENCOUNTER_ENTRY_ENC field_54[6];\n} EncounterEntry;\n\ntypedef struct EncounterTable {\n    char lookupName[40];\n    int field_28;\n    int mapsLength;\n    int maps[6];\n    int field_48;\n    int entriesLength;\n    EncounterEntry entries[41];\n} EncounterTable;\n\ntypedef struct ENC_BASE_TYPE_38_48 {\n    int pid;\n    int minimumQuantity;\n    int maximumQuantity;\n    bool isEquipped;\n} ENC_BASE_TYPE_38_48;\n\ntypedef struct ENC_BASE_TYPE_38 {\n    char field_0[40];\n    int field_28;\n    int field_2C;\n    int ratio;\n    int pid;\n    int flags;\n    int distance;\n    int tile;\n    int itemsLength;\n    ENC_BASE_TYPE_38_48 items[10];\n    int team;\n    int script;\n    EncounterCondition condition;\n} ENC_BASE_TYPE_38;\n\ntypedef struct ENC_BASE_TYPE {\n    char name[40];\n    int position;\n    int spacing;\n    int distance;\n    int field_34;\n    ENC_BASE_TYPE_38 field_38[10];\n} ENC_BASE_TYPE;\n\ntypedef struct SubtileInfo {\n    int terrain;\n    int field_4;\n    int encounterChance[DAY_PART_COUNT];\n    int encounterType;\n    int state;\n} SubtileInfo;\n\n// A worldmap tile is 7x6 area, thus consisting of 42 individual subtiles.\ntypedef struct TileInfo {\n    int fid;\n    CacheEntry* handle;\n    unsigned char* data;\n    char walkMaskName[TILE_WALK_MASK_NAME_SIZE];\n    unsigned char* walkMaskData;\n    int encounterDifficultyModifier;\n    SubtileInfo subtiles[SUBTILE_GRID_HEIGHT][SUBTILE_GRID_WIDTH];\n} TileInfo;\n\ntypedef struct CitySizeDescription {\n    int fid;\n    int width;\n    int height;\n    CacheEntry* handle;\n    unsigned char* data;\n} CitySizeDescription;\n\ntypedef struct WmGenData {\n    bool mousePressed;\n    bool didMeetFrankHorrigan;\n\n    int currentAreaId;\n    int worldPosX;\n    int worldPosY;\n    SubtileInfo* currentSubtile;\n\n    int dword_672E18;\n\n    bool isWalking;\n    int walkDestinationX;\n    int walkDestinationY;\n    int walkDistance;\n    int walkLineDelta;\n    int walkLineDeltaMainAxisStep;\n    int walkLineDeltaCrossAxisStep;\n    int walkWorldPosMainAxisStepX;\n    int walkWorldPosCrossAxisStepX;\n    int walkWorldPosMainAxisStepY;\n    int walkWorldPosCrossAxisStepY;\n\n    int encounterIconIsVisible;\n    int encounterMapId;\n    int encounterTableId;\n    int encounterEntryId;\n    int encounterCursorId;\n\n    int oldWorldPosX;\n    int oldWorldPosY;\n\n    bool isInCar;\n    int currentCarAreaId;\n    int carFuel;\n\n    CacheEntry* carImageFrmHandle;\n    Art* carImageFrm;\n    int carImageFrmWidth;\n    int carImageFrmHeight;\n    int carImageCurrentFrameIndex;\n\n    CacheEntry* hotspotNormalFrmHandle;\n    unsigned char* hotspotNormalFrmData;\n    CacheEntry* hotspotPressedFrmHandle;\n    unsigned char* hotspotPressedFrmData;\n    int hotspotFrmWidth;\n    int hotspotFrmHeight;\n\n    CacheEntry* destinationMarkerFrmHandle;\n    unsigned char* destinationMarkerFrmData;\n    int destinationMarkerFrmWidth;\n    int destinationMarkerFrmHeight;\n\n    CacheEntry* locationMarkerFrmHandle;\n    unsigned char* locationMarkerFrmData;\n    int locationMarkerFrmWidth;\n    int locationMarkerFrmHeight;\n\n    CacheEntry* encounterCursorFrmHandle[WORLD_MAP_ENCOUNTER_FRM_COUNT];\n    unsigned char* encounterCursorFrmData[WORLD_MAP_ENCOUNTER_FRM_COUNT];\n    int encounterCursorFrmWidths[WORLD_MAP_ENCOUNTER_FRM_COUNT];\n    int encounterCursorFrmHeights[WORLD_MAP_ENCOUNTER_FRM_COUNT];\n\n    int viewportMaxX;\n    int viewportMaxY;\n\n    CacheEntry* tabsBackgroundFrmHandle;\n    int tabsBackgroundFrmWidth;\n    int tabsBackgroundFrmHeight;\n    int tabsOffsetY;\n    unsigned char* tabsBackgroundFrmData;\n\n    CacheEntry* tabsBorderFrmHandle;\n    unsigned char* tabsBorderFrmData;\n\n    CacheEntry* dialFrmHandle;\n    int dialFrmWidth;\n    int dialFrmHeight;\n    int dialFrmCurrentFrameIndex;\n    Art* dialFrm;\n\n    CacheEntry* carImageOverlayFrmHandle;\n    int carImageOverlayFrmWidth;\n    int carImageOverlayFrmHeight;\n    unsigned char* carImageOverlayFrmData;\n\n    CacheEntry* globeOverlayFrmHandle;\n    int globeOverlayFrmWidth;\n    int globeOverlayFrmHeight;\n    unsigned char* globeOverlayFrmData;\n\n    int oldTabsOffsetY;\n    int tabsScrollingDelta;\n\n    CacheEntry* littleRedButtonNormalFrmHandle;\n    CacheEntry* littleRedButtonPressedFrmHandle;\n    unsigned char* littleRedButtonNormalFrmData;\n    unsigned char* littleRedButtonPressedFrmData;\n\n    CacheEntry* scrollUpButtonFrmHandle[WORLDMAP_ARROW_FRM_COUNT];\n    int scrollUpButtonFrmWidth;\n    int scrollUpButtonFrmHeight;\n    unsigned char* scrollUpButtonFrmData[WORLDMAP_ARROW_FRM_COUNT];\n\n    CacheEntry* scrollDownButtonFrmHandle[WORLDMAP_ARROW_FRM_COUNT];\n    int scrollDownButtonFrmWidth;\n    int scrollDownButtonFrmHeight;\n    unsigned char* scrollDownButtonFrmData[WORLDMAP_ARROW_FRM_COUNT];\n\n    CacheEntry* monthsFrmHandle;\n    Art* monthsFrm;\n\n    CacheEntry* numbersFrmHandle;\n    Art* numbersFrm;\n\n    int oldFont;\n} WmGenData;\n\nstatic void wmSetFlags(int* flagsPtr, int flag, int value);\nstatic int wmGenDataInit();\nstatic int wmGenDataReset();\nstatic int wmWorldMapSaveTempData();\nstatic int wmWorldMapLoadTempData();\nstatic int wmConfigInit();\nstatic int wmReadEncounterType(Config* config, char* lookupName, char* sectionKey);\nstatic int wmParseEncounterTableIndex(EncounterEntry* entry, char* string);\nstatic int wmParseEncounterSubEncStr(EncounterEntry* encounterEntry, char** stringPtr);\nstatic int wmParseFindSubEncTypeMatch(char* str, int* valuePtr);\nstatic int wmFindEncBaseTypeMatch(char* str, int* valuePtr);\nstatic int wmReadEncBaseType(char* name, int* valuePtr);\nstatic int wmParseEncBaseSubTypeStr(ENC_BASE_TYPE_38* ptr, char** stringPtr);\nstatic int wmEncBaseTypeSlotInit(ENC_BASE_TYPE* entry);\nstatic int wmEncBaseSubTypeSlotInit(ENC_BASE_TYPE_38* entry);\nstatic int wmEncounterSubEncSlotInit(ENCOUNTER_ENTRY_ENC* entry);\nstatic int wmEncounterTypeSlotInit(EncounterEntry* entry);\nstatic int wmEncounterTableSlotInit(EncounterTable* encounterTable);\nstatic int wmTileSlotInit(TileInfo* tile);\nstatic int wmTerrainTypeSlotInit(Terrain* terrain);\nstatic int wmConditionalDataInit(EncounterCondition* condition);\nstatic int wmParseTerrainTypes(Config* config, char* string);\nstatic int wmParseTerrainRndMaps(Config* config, Terrain* terrain);\nstatic int wmParseSubTileInfo(TileInfo* tile, int row, int column, char* string);\nstatic int wmParseFindEncounterTypeMatch(char* string, int* valuePtr);\nstatic int wmParseFindTerrainTypeMatch(char* string, int* valuePtr);\nstatic int wmParseEncounterItemType(char** stringPtr, ENC_BASE_TYPE_38_48* a2, int* a3, const char* delim);\nstatic int wmParseItemType(char* string, ENC_BASE_TYPE_38_48* ptr);\nstatic int wmParseConditional(char** stringPtr, const char* a2, EncounterCondition* condition);\nstatic int wmParseSubConditional(char** stringPtr, const char* a2, int* typePtr, int* operatorPtr, int* paramPtr, int* valuePtr);\nstatic int wmParseConditionalEval(char** stringPtr, int* conditionalOperatorPtr);\nstatic int wmAreaSlotInit(CityInfo* area);\nstatic int wmAreaInit();\nstatic int wmParseFindMapIdxMatch(char* string, int* valuePtr);\nstatic int wmEntranceSlotInit(EntranceInfo* entrance);\nstatic int wmMapSlotInit(MapInfo* map);\nstatic int wmMapInit();\nstatic int wmRStartSlotInit(MapStartPointInfo* rsp);\nstatic int wmMatchEntranceFromMap(int areaIdx, int mapIdx, int* entranceIdxPtr);\nstatic int wmMatchEntranceElevFromMap(int areaIdx, int mapIdx, int elevation, int* entranceIdxPtr);\nstatic int wmMatchAreaFromMap(int mapIdx, int* areaIdxPtr);\nstatic int wmWorldMapFunc(int a1);\nstatic int wmInterfaceCenterOnParty();\nstatic void wmCheckGameEvents();\nstatic int wmRndEncounterOccurred();\nstatic int wmPartyFindCurSubTile();\nstatic int wmFindCurSubTileFromPos(int x, int y, SubtileInfo** subtilePtr);\nstatic int wmFindCurTileFromPos(int x, int y, TileInfo** tilePtr);\nstatic int wmRndEncounterPick();\nstatic int wmSetupCritterObjs(int type_idx, Object** critterPtr, int critterCount);\nstatic int wmSetupRndNextTileNumInit(ENC_BASE_TYPE* a1);\nstatic int wmSetupRndNextTileNum(ENC_BASE_TYPE* a1, ENC_BASE_TYPE_38* a2, int* out_tile_num);\nstatic bool wmEvalConditional(EncounterCondition* a1, int* a2);\nstatic bool wmEvalSubConditional(int operand1, int condionalOperator, int operand2);\nstatic bool wmGameTimeIncrement(int a1);\nstatic int wmGrabTileWalkMask(int tile);\nstatic bool wmWorldPosInvalid(int x, int y);\nstatic void wmPartyInitWalking(int x, int y);\nstatic void wmPartyWalkingStep();\nstatic void wmInterfaceScrollTabsStart(int delta);\nstatic void wmInterfaceScrollTabsStop();\nstatic void wmInterfaceScrollTabsUpdate();\nstatic int wmInterfaceInit();\nstatic int wmInterfaceExit();\nstatic int wmInterfaceScroll(int dx, int dy, bool* successPtr);\nstatic int wmInterfaceScrollPixel(int stepX, int stepY, int dx, int dy, bool* success, bool shouldRefresh);\nstatic void wmMouseBkProc();\nstatic int wmMarkSubTileOffsetVisited(int tile, int subtileX, int subtileY, int offsetX, int offsetY);\nstatic int wmMarkSubTileOffsetKnown(int tile, int subtileX, int subtileY, int offsetX, int offsetY);\nstatic int wmMarkSubTileOffsetVisitedFunc(int tile, int subtileX, int subtileY, int offsetX, int offsetY, int subtileState);\nstatic void wmMarkSubTileRadiusVisited(int x, int y);\nstatic int wmTileGrabArt(int tileIdx);\nstatic int wmInterfaceRefresh();\nstatic void wmInterfaceRefreshDate(bool shouldRefreshWindow);\nstatic int wmMatchWorldPosToArea(int x, int y, int* areaIdxPtr);\nstatic int wmInterfaceDrawCircleOverlay(CityInfo* city, CitySizeDescription* citySizeDescription, unsigned char* dest, int x, int y);\nstatic void wmInterfaceDrawSubTileRectFogged(unsigned char* dest, int width, int height, int pitch);\nstatic int wmInterfaceDrawSubTileList(TileInfo* tileInfo, int column, int row, int x, int y, int a6);\nstatic int wmDrawCursorStopped();\nstatic bool wmCursorIsVisible();\nstatic int wmGetAreaName(CityInfo* city, char* name);\nstatic void wmMarkAllSubTiles(int state);\nstatic int wmTownMapFunc(int* mapIdxPtr);\nstatic int wmTownMapInit();\nstatic int wmTownMapRefresh();\nstatic int wmTownMapExit();\nstatic int wmRefreshInterfaceOverlay(bool shouldRefreshWindow);\nstatic void wmInterfaceRefreshCarFuel();\nstatic int wmRefreshTabs();\nstatic int wmMakeTabsLabelList(int** quickDestinationsPtr, int* quickDestinationsLengthPtr);\nstatic int wmTabsCompareNames(const void* a1, const void* a2);\nstatic int wmFreeTabsLabelList(int** quickDestinationsListPtr, int* quickDestinationsLengthPtr);\nstatic void wmRefreshInterfaceDial(bool shouldRefreshWindow);\nstatic void wmInterfaceDialSyncTime(bool shouldRefreshWindow);\nstatic int wmAreaFindFirstValidMap(int* mapIdxPtr);\n\nstatic bool cityIsValid(int areaIdx);\n\n// 0x4BC878\nstatic const char* gWorldmapEncDefaultMsg[2] = {\n    \"You detect something up ahead.\",\n    \"Do you wish to encounter it?\",\n};\n\n// 0x50EE44\nstatic char _aCricket[] = \"cricket\";\n\n// 0x50EE4C\nstatic char _aCricket1[] = \"cricket1\";\n\n// 0x51DD88\nstatic const char* wmStateStrs[2] = {\n    \"off\",\n    \"on\"\n};\n\n// 0x51DD90\nstatic const char* wmYesNoStrs[2] = {\n    \"no\",\n    \"yes\",\n};\n\n// 0x51DD98\nstatic const char* wmFreqStrs[ENCOUNTER_FREQUENCY_TYPE_COUNT] = {\n    \"none\",\n    \"rare\",\n    \"uncommon\",\n    \"common\",\n    \"frequent\",\n    \"forced\",\n};\n\n// 0x51DDB0\nstatic const char* wmFillStrs[9] = {\n    \"no_fill\",\n    \"fill_n\",\n    \"fill_s\",\n    \"fill_e\",\n    \"fill_w\",\n    \"fill_nw\",\n    \"fill_ne\",\n    \"fill_sw\",\n    \"fill_se\",\n};\n\n// 0x51DDD4\nstatic const char* wmSceneryStrs[ENCOUNTER_SCENERY_TYPE_COUNT] = {\n    \"none\",\n    \"light\",\n    \"normal\",\n    \"heavy\",\n};\n\n// 0x51DDE4\nstatic Terrain* wmTerrainTypeList = NULL;\n\n// 0x51DDE8\nstatic int wmMaxTerrainTypes = 0;\n\n// 0x51DDEC\nstatic TileInfo* wmTileInfoList = NULL;\n\n// 0x51DDF0\nstatic int wmMaxTileNum = 0;\n\n// The width of worldmap grid in tiles.\n//\n// There is no separate variable for grid height, instead its calculated as\n// [wmMaxTileNum] / [gWorldmapTilesGridWidth].\n//\n// num_horizontal_tiles\n// 0x51DDF4\nstatic int wmNumHorizontalTiles = 0;\n\n// 0x51DDF8\nstatic CityInfo* wmAreaInfoList = NULL;\n\n// 0x51DDFC\nstatic int wmMaxAreaNum = 0;\n\n// 0x51DE00\nstatic const char* wmAreaSizeStrs[CITY_SIZE_COUNT] = {\n    \"small\",\n    \"medium\",\n    \"large\",\n};\n\n// 0x51DE0C\nstatic MapInfo* wmMapInfoList = NULL;\n\n// 0x51DE10\nstatic int wmMaxMapNum = 0;\n\n// 0x51DE14\nstatic int wmBkWin = -1;\n\n// 0x51DE18\nstatic CacheEntry* wmBkKey = INVALID_CACHE_ENTRY;\n\n// 0x51DE1C\nstatic int wmBkWidth = 0;\n\n// 0x51DE20\nstatic int wmBkHeight = 0;\n\n// 0x51DE24\nstatic unsigned char* wmBkWinBuf = NULL;\n\n// 0x51DE28\nstatic unsigned char* wmBkArtBuf = NULL;\n\n// 0x51DE2C\nstatic int wmWorldOffsetX = 0;\n\n// 0x51DE30\nstatic int wmWorldOffsetY = 0;\n\n// 0x51DE34\nunsigned char* circleBlendTable = NULL;\n\n// 0x51DE38\nstatic int wmInterfaceWasInitialized = 0;\n\n// 0x51DE3C\nstatic const char* wmEncOpStrs[ENCOUNTER_SITUATION_COUNT] = {\n    \"nothing\",\n    \"ambush\",\n    \"fighting\",\n    \"and\",\n};\n\n// 0x51DE4C\nstatic const char* wmConditionalOpStrs[ENCOUNTER_CONDITIONAL_OPERATOR_COUNT] = {\n    \"_\",\n    \"==\",\n    \"!=\",\n    \"<\",\n    \">\",\n};\n\n// 0x51DE64\nstatic const char* wmConditionalQualifierStrs[2] = {\n    \"and\",\n    \"or\",\n};\n\n// 0x51DE6C\nstatic const char* wmFormationStrs[ENCOUNTER_FORMATION_TYPE_COUNT] = {\n    \"surrounding\",\n    \"straight_line\",\n    \"double_line\",\n    \"wedge\",\n    \"cone\",\n    \"huddle\",\n};\n\n// 0x51DE84\nstatic const int wmRndCursorFids[WORLD_MAP_ENCOUNTER_FRM_COUNT] = {\n    154,\n    155,\n    438,\n    439,\n};\n\n// 0x51DE94\nstatic int* wmLabelList = NULL;\n\n// 0x51DE98\nstatic int wmLabelCount = 0;\n\n// 0x51DE9C\nstatic int wmTownMapCurArea = -1;\n\n// 0x51DEA0\nstatic unsigned int wmLastRndTime = 0;\n\n// 0x51DEA4\nstatic int wmRndIndex = 0;\n\n// 0x51DEA8\nstatic int wmRndCallCount = 0;\n\n// 0x51DEB8\nstatic unsigned char* wmTownBuffer = NULL;\n\n// 0x51DEBC\nstatic CacheEntry* wmTownKey = INVALID_CACHE_ENTRY;\n\n// 0x51DEC0\nstatic int wmTownWidth = 0;\n\n// 0x51DEC4\nstatic int wmTownHeight = 0;\n\n// 0x51DEC8\nstatic char* wmRemapSfxList[2] = {\n    _aCricket,\n    _aCricket1,\n};\n\n// 0x672DB8\nstatic int wmRndTileDirs[2];\n\n// 0x672DC0\nstatic int wmRndCenterTiles[2];\n\n// 0x672DC8\nstatic int wmRndCenterRotations[2];\n\n// 0x672DD0\nstatic int wmRndRotOffsets[2];\n\n// Buttons for city entrances.\n//\n// 0x672DD8\nstatic int wmTownMapButtonId[ENTRANCE_LIST_CAPACITY];\n\n// NOTE: There are no symbols in |mapper2.exe| for the range between |wmGenData|\n// and |wmMsgFile| implying everything in between are fields of the large\n// struct.\n//\n// 0x672E00\nstatic WmGenData wmGenData;\n\n// worldmap.msg\n//\n// 0x672FB0\nstatic MessageList wmMsgFile;\n\n// 0x672FB8\nstatic int wmFreqValues[6];\n\n// 0x672FD0\nstatic int wmRndOriginalCenterTile;\n\n// worldmap.txt\n//\n// 0x672FD4\nstatic Config* pConfigCfg;\n\n// 0x672FD8\nstatic int wmTownMapSubButtonIds[7];\n\n// 0x672FF4\nstatic ENC_BASE_TYPE* wmEncBaseTypeList;\n\n// 0x672FF8\nstatic CitySizeDescription wmSphereData[CITY_SIZE_COUNT];\n\n// 0x673034\nstatic EncounterTable* wmEncounterTableList;\n\n// Number of enc_base_types.\n//\n// 0x673038\nstatic int _wmMaxEncBaseTypes;\n\n// 0x67303C\nstatic int wmMaxEncounterInfoTables;\n\n// 0x4BC890\nstatic void wmSetFlags(int* flagsPtr, int flag, int value)\n{\n    if (value) {\n        *flagsPtr |= flag;\n    } else {\n        *flagsPtr &= ~flag;\n    }\n}\n\n// 0x4BC89C\nint wmWorldMap_init()\n{\n    char path[MAX_PATH];\n\n    if (wmGenDataInit() == -1) {\n        return -1;\n    }\n\n    if (!message_init(&wmMsgFile)) {\n        return -1;\n    }\n\n    sprintf(path, \"%s%s\", msg_path, \"worldmap.msg\");\n\n    if (!message_load(&wmMsgFile, path)) {\n        return -1;\n    }\n\n    if (wmConfigInit() == -1) {\n        return -1;\n    }\n\n    wmGenData.viewportMaxX = WM_TILE_WIDTH * wmNumHorizontalTiles - WM_VIEW_WIDTH;\n    wmGenData.viewportMaxY = WM_TILE_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles) - WM_VIEW_HEIGHT;\n    circleBlendTable = getColorBlendTable(colorTable[992]);\n\n    wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY);\n    wmWorldMapSaveTempData();\n\n    return 0;\n}\n\n// 0x4BC984\nstatic int wmGenDataInit()\n{\n    wmGenData.didMeetFrankHorrigan = false;\n    wmGenData.currentAreaId = -1;\n    wmGenData.worldPosX = 173;\n    wmGenData.worldPosY = 122;\n    wmGenData.currentSubtile = NULL;\n    wmGenData.dword_672E18 = 0;\n    wmGenData.isWalking = false;\n    wmGenData.walkDestinationX = -1;\n    wmGenData.walkDestinationY = -1;\n    wmGenData.walkDistance = 0;\n    wmGenData.walkLineDelta = 0;\n    wmGenData.walkLineDeltaMainAxisStep = 0;\n    wmGenData.walkLineDeltaCrossAxisStep = 0;\n    wmGenData.walkWorldPosMainAxisStepX = 0;\n    wmGenData.walkWorldPosMainAxisStepY = 0;\n    wmGenData.walkWorldPosCrossAxisStepY = 0;\n    wmGenData.encounterIconIsVisible = 0;\n    wmGenData.encounterMapId = -1;\n    wmGenData.encounterTableId = -1;\n    wmGenData.encounterEntryId = -1;\n    wmGenData.encounterCursorId = -1;\n    wmGenData.oldWorldPosX = 0;\n    wmGenData.oldWorldPosY = 0;\n    wmGenData.isInCar = false;\n    wmGenData.currentCarAreaId = -1;\n    wmGenData.carFuel = CAR_FUEL_MAX;\n    wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.carImageFrmWidth = 0;\n    wmGenData.carImageFrmHeight = 0;\n    wmGenData.carImageCurrentFrameIndex = 0;\n    wmGenData.hotspotNormalFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.hotspotNormalFrmData = NULL;\n    wmGenData.hotspotPressedFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.hotspotPressedFrmData = NULL;\n    wmGenData.hotspotFrmWidth = 0;\n    wmGenData.hotspotFrmHeight = 0;\n    wmGenData.destinationMarkerFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.destinationMarkerFrmData = NULL;\n    wmGenData.destinationMarkerFrmWidth = 0;\n    wmGenData.destinationMarkerFrmHeight = 0;\n    wmGenData.locationMarkerFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.locationMarkerFrmData = NULL;\n    wmGenData.locationMarkerFrmWidth = 0;\n    wmGenData.mousePressed = false;\n    wmGenData.walkWorldPosCrossAxisStepX = 0;\n    wmGenData.locationMarkerFrmHeight = 0;\n    wmGenData.carImageFrm = NULL;\n\n    for (int index = 0; index < WORLD_MAP_ENCOUNTER_FRM_COUNT; index++) {\n        wmGenData.encounterCursorFrmHandle[index] = INVALID_CACHE_ENTRY;\n        wmGenData.encounterCursorFrmData[index] = NULL;\n        wmGenData.encounterCursorFrmWidths[index] = 0;\n        wmGenData.encounterCursorFrmHeights[index] = 0;\n    }\n\n    wmGenData.viewportMaxY = 0;\n    wmGenData.tabsBackgroundFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.tabsBackgroundFrmData = NULL;\n    wmGenData.tabsBackgroundFrmWidth = 0;\n    wmGenData.tabsBackgroundFrmHeight = 0;\n    wmGenData.tabsOffsetY = 0;\n    wmGenData.tabsBorderFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.tabsBorderFrmData = 0;\n    wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.dialFrm = NULL;\n    wmGenData.dialFrmWidth = 0;\n    wmGenData.dialFrmHeight = 0;\n    wmGenData.dialFrmCurrentFrameIndex = 0;\n    wmGenData.carImageOverlayFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.carImageOverlayFrmData = NULL;\n    wmGenData.carImageOverlayFrmWidth = 0;\n    wmGenData.carImageOverlayFrmHeight = 0;\n    wmGenData.globeOverlayFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.globeOverlayFrmData = NULL;\n    wmGenData.globeOverlayFrmWidth = 0;\n    wmGenData.globeOverlayFrmHeight = 0;\n    wmGenData.oldTabsOffsetY = 0;\n    wmGenData.tabsScrollingDelta = 0;\n    wmGenData.littleRedButtonNormalFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.littleRedButtonPressedFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.littleRedButtonNormalFrmData = NULL;\n    wmGenData.littleRedButtonPressedFrmData = NULL;\n    wmGenData.viewportMaxX = 0;\n\n    for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) {\n        wmGenData.scrollDownButtonFrmHandle[index] = INVALID_CACHE_ENTRY;\n        wmGenData.scrollUpButtonFrmHandle[index] = INVALID_CACHE_ENTRY;\n        wmGenData.scrollUpButtonFrmData[index] = NULL;\n        wmGenData.scrollDownButtonFrmData[index] = NULL;\n    }\n\n    wmGenData.scrollUpButtonFrmHeight = 0;\n    wmGenData.scrollDownButtonFrmWidth = 0;\n    wmGenData.scrollDownButtonFrmHeight = 0;\n    wmGenData.monthsFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.monthsFrm = NULL;\n    wmGenData.numbersFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.numbersFrm = NULL;\n    wmGenData.scrollUpButtonFrmWidth = 0;\n\n    return 0;\n}\n\n// 0x4BCBFC\nstatic int wmGenDataReset()\n{\n    wmGenData.didMeetFrankHorrigan = false;\n    wmGenData.currentSubtile = NULL;\n    wmGenData.dword_672E18 = 0;\n    wmGenData.isWalking = false;\n    wmGenData.walkDistance = 0;\n    wmGenData.walkLineDelta = 0;\n    wmGenData.walkLineDeltaMainAxisStep = 0;\n    wmGenData.walkLineDeltaCrossAxisStep = 0;\n    wmGenData.walkWorldPosMainAxisStepX = 0;\n    wmGenData.walkWorldPosMainAxisStepY = 0;\n    wmGenData.walkWorldPosCrossAxisStepY = 0;\n    wmGenData.encounterIconIsVisible = 0;\n    wmGenData.mousePressed = false;\n    wmGenData.currentAreaId = -1;\n    wmGenData.worldPosX = 173;\n    wmGenData.worldPosY = 122;\n    wmGenData.walkDestinationX = -1;\n    wmGenData.walkDestinationY = -1;\n    wmGenData.encounterMapId = -1;\n    wmGenData.encounterTableId = -1;\n    wmGenData.encounterEntryId = -1;\n    wmGenData.encounterCursorId = -1;\n    wmGenData.currentCarAreaId = -1;\n    wmGenData.carFuel = CAR_FUEL_MAX;\n    wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.tabsBackgroundFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.tabsBorderFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.carImageOverlayFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.globeOverlayFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.littleRedButtonNormalFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.littleRedButtonPressedFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.walkWorldPosCrossAxisStepX = 0;\n    wmGenData.oldWorldPosX = 0;\n    wmGenData.oldWorldPosY = 0;\n    wmGenData.isInCar = false;\n    wmGenData.carImageFrmWidth = 0;\n    wmGenData.carImageFrmHeight = 0;\n    wmGenData.carImageCurrentFrameIndex = 0;\n    wmGenData.tabsBackgroundFrmData = NULL;\n    wmGenData.tabsBackgroundFrmHeight = 0;\n    wmGenData.tabsOffsetY = 0;\n    wmGenData.tabsBorderFrmData = 0;\n    wmGenData.dialFrm = NULL;\n    wmGenData.dialFrmWidth = 0;\n    wmGenData.dialFrmHeight = 0;\n    wmGenData.dialFrmCurrentFrameIndex = 0;\n    wmGenData.carImageOverlayFrmData = NULL;\n    wmGenData.carImageOverlayFrmWidth = 0;\n    wmGenData.carImageOverlayFrmHeight = 0;\n    wmGenData.globeOverlayFrmData = NULL;\n    wmGenData.globeOverlayFrmWidth = 0;\n    wmGenData.globeOverlayFrmHeight = 0;\n    wmGenData.oldTabsOffsetY = 0;\n    wmGenData.tabsScrollingDelta = 0;\n    wmGenData.littleRedButtonNormalFrmData = NULL;\n    wmGenData.littleRedButtonPressedFrmData = NULL;\n    wmGenData.tabsBackgroundFrmWidth = 0;\n    wmGenData.carImageFrm = NULL;\n\n    for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) {\n        wmGenData.scrollUpButtonFrmData[index] = NULL;\n        wmGenData.scrollDownButtonFrmHandle[index] = INVALID_CACHE_ENTRY;\n\n        wmGenData.scrollDownButtonFrmData[index] = NULL;\n        wmGenData.scrollUpButtonFrmHandle[index] = INVALID_CACHE_ENTRY;\n    }\n\n    wmGenData.monthsFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.numbersFrmHandle = INVALID_CACHE_ENTRY;\n    wmGenData.scrollUpButtonFrmWidth = 0;\n    wmGenData.scrollUpButtonFrmHeight = 0;\n    wmGenData.scrollDownButtonFrmWidth = 0;\n    wmGenData.scrollDownButtonFrmHeight = 0;\n    wmGenData.monthsFrm = NULL;\n    wmGenData.numbersFrm = NULL;\n\n    wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY);\n\n    return 0;\n}\n\n// 0x4BCE00\nvoid wmWorldMap_exit()\n{\n    if (wmTerrainTypeList != NULL) {\n        mem_free(wmTerrainTypeList);\n        wmTerrainTypeList = NULL;\n    }\n\n    if (wmTileInfoList) {\n        mem_free(wmTileInfoList);\n        wmTileInfoList = NULL;\n    }\n\n    wmNumHorizontalTiles = 0;\n    wmMaxTileNum = 0;\n\n    if (wmEncounterTableList != NULL) {\n        mem_free(wmEncounterTableList);\n        wmEncounterTableList = NULL;\n    }\n\n    wmMaxEncounterInfoTables = 0;\n\n    if (wmEncBaseTypeList != NULL) {\n        mem_free(wmEncBaseTypeList);\n        wmEncBaseTypeList = NULL;\n    }\n\n    _wmMaxEncBaseTypes = 0;\n\n    if (wmAreaInfoList != NULL) {\n        mem_free(wmAreaInfoList);\n        wmAreaInfoList = NULL;\n    }\n\n    wmMaxAreaNum = 0;\n\n    if (wmMapInfoList != NULL) {\n        mem_free(wmMapInfoList);\n    }\n\n    wmMaxMapNum = 0;\n\n    if (circleBlendTable != NULL) {\n        freeColorBlendTable(colorTable[992]);\n        circleBlendTable = NULL;\n    }\n\n    message_exit(&wmMsgFile);\n}\n\n// 0x4BCEF8\nint wmWorldMap_reset()\n{\n    wmWorldOffsetX = 0;\n    wmWorldOffsetY = 0;\n\n    wmWorldMapLoadTempData();\n    wmMarkAllSubTiles(0);\n\n    return wmGenDataReset();\n}\n\n// 0x4BCF28\nint wmWorldMap_save(File* stream)\n{\n    int i;\n    int j;\n    int k;\n    EncounterTable* encounter_table;\n    EncounterEntry* encounter_entry;\n\n    if (fileWriteBool(stream, wmGenData.didMeetFrankHorrigan) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.currentAreaId) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.worldPosX) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.worldPosY) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.encounterIconIsVisible) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.encounterMapId) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.encounterTableId) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.encounterEntryId) == -1) return -1;\n    if (fileWriteBool(stream, wmGenData.isInCar) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.currentCarAreaId) == -1) return -1;\n    if (db_fwriteInt(stream, wmGenData.carFuel) == -1) return -1;\n    if (db_fwriteInt(stream, wmMaxAreaNum) == -1) return -1;\n\n    for (int cityIdx = 0; cityIdx < wmMaxAreaNum; cityIdx++) {\n        CityInfo* cityInfo = &(wmAreaInfoList[cityIdx]);\n        if (db_fwriteInt(stream, cityInfo->x) == -1) return -1;\n        if (db_fwriteInt(stream, cityInfo->y) == -1) return -1;\n        if (db_fwriteInt(stream, cityInfo->state) == -1) return -1;\n        if (db_fwriteInt(stream, cityInfo->visitedState) == -1) return -1;\n        if (db_fwriteInt(stream, cityInfo->entrancesLength) == -1) return -1;\n\n        for (int entranceIdx = 0; entranceIdx < cityInfo->entrancesLength; entranceIdx++) {\n            EntranceInfo* entrance = &(cityInfo->entrances[entranceIdx]);\n            if (db_fwriteInt(stream, entrance->state) == -1) return -1;\n        }\n    }\n\n    if (db_fwriteInt(stream, wmMaxTileNum) == -1) return -1;\n    if (db_fwriteInt(stream, wmNumHorizontalTiles) == -1) return -1;\n\n    for (int tileIndex = 0; tileIndex < wmMaxTileNum; tileIndex++) {\n        TileInfo* tileInfo = &(wmTileInfoList[tileIndex]);\n\n        for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) {\n            for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) {\n                SubtileInfo* subtile = &(tileInfo->subtiles[column][row]);\n\n                if (db_fwriteInt(stream, subtile->state) == -1) return -1;\n            }\n        }\n    }\n\n    k = 0;\n    for (i = 0; i < wmMaxEncounterInfoTables; i++) {\n        encounter_table = &(wmEncounterTableList[i]);\n\n        for (j = 0; j < encounter_table->entriesLength; j++) {\n            encounter_entry = &(encounter_table->entries[j]);\n\n            if (encounter_entry->counter != -1) {\n                k++;\n            }\n        }\n    }\n\n    if (db_fwriteInt(stream, k) == -1) return -1;\n\n    for (i = 0; i < wmMaxEncounterInfoTables; i++) {\n        encounter_table = &(wmEncounterTableList[i]);\n\n        for (j = 0; j < encounter_table->entriesLength; j++) {\n            encounter_entry = &(encounter_table->entries[j]);\n\n            if (encounter_entry->counter != -1) {\n                if (db_fwriteInt(stream, i) == -1) return -1;\n                if (db_fwriteInt(stream, j) == -1) return -1;\n                if (db_fwriteInt(stream, encounter_entry->counter) == -1) return -1;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4BD28C\nint wmWorldMap_load(File* stream)\n{\n    int i;\n    int j;\n    int k;\n    int cities_count;\n    int v38;\n    int v39;\n    int v35;\n    EncounterTable* encounter_table;\n    EncounterEntry* encounter_entry;\n\n    if (fileReadBool(stream, &(wmGenData.didMeetFrankHorrigan)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.currentAreaId)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.worldPosX)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.worldPosY)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.encounterIconIsVisible)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.encounterMapId)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.encounterTableId)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.encounterEntryId)) == -1) return -1;\n    if (fileReadBool(stream, &(wmGenData.isInCar)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.currentCarAreaId)) == -1) return -1;\n    if (db_freadInt(stream, &(wmGenData.carFuel)) == -1) return -1;\n    if (db_freadInt(stream, &(cities_count)) == -1) return -1;\n\n    for (int cityIdx = 0; cityIdx < cities_count; cityIdx++) {\n        CityInfo* city = &(wmAreaInfoList[cityIdx]);\n\n        if (db_freadInt(stream, &(city->x)) == -1) return -1;\n        if (db_freadInt(stream, &(city->y)) == -1) return -1;\n        if (db_freadInt(stream, &(city->state)) == -1) return -1;\n        if (db_freadInt(stream, &(city->visitedState)) == -1) return -1;\n\n        int entranceCount;\n        if (db_freadInt(stream, &(entranceCount)) == -1) {\n            return -1;\n        }\n\n        for (int entranceIdx = 0; entranceIdx < entranceCount; entranceIdx++) {\n            EntranceInfo* entrance = &(city->entrances[entranceIdx]);\n\n            if (db_freadInt(stream, &(entrance->state)) == -1) {\n                return -1;\n            }\n        }\n    }\n\n    if (db_freadInt(stream, &(v39)) == -1) return -1;\n    if (db_freadInt(stream, &(v38)) == -1) return -1;\n\n    for (int tileIndex = 0; tileIndex < v39; tileIndex++) {\n        TileInfo* tile = &(wmTileInfoList[tileIndex]);\n\n        for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) {\n            for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) {\n                SubtileInfo* subtile = &(tile->subtiles[column][row]);\n\n                if (db_freadInt(stream, &(subtile->state)) == -1) return -1;\n            }\n        }\n    }\n\n    if (db_freadInt(stream, &(v35)) == -1) return -1;\n\n    for (i = 0; i < v35; i++) {\n        if (db_freadInt(stream, &(j)) == -1) return -1;\n        encounter_table = &(wmEncounterTableList[j]);\n\n        if (db_freadInt(stream, &(k)) == -1) return -1;\n        encounter_entry = &(encounter_table->entries[k]);\n\n        if (db_freadInt(stream, &(encounter_entry->counter)) == -1) return -1;\n    }\n\n    wmInterfaceCenterOnParty();\n\n    return 0;\n}\n\n// 0x4BD678\nstatic int wmWorldMapSaveTempData()\n{\n    File* stream = db_fopen(\"worldmap.dat\", \"wb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int rc = 0;\n    if (wmWorldMap_save(stream) == -1) {\n        rc = -1;\n    }\n\n    db_fclose(stream);\n\n    return rc;\n}\n\n// 0x4BD6B4\nstatic int wmWorldMapLoadTempData()\n{\n    File* stream = db_fopen(\"worldmap.dat\", \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int rc = 0;\n    if (wmWorldMap_load(stream) == -1) {\n        rc = -1;\n    }\n\n    db_fclose(stream);\n\n    return rc;\n}\n\n// 0x4BD6F0\nstatic int wmConfigInit()\n{\n    if (wmAreaInit() == -1) {\n        return -1;\n    }\n\n    Config config;\n    if (!config_init(&config)) {\n        return -1;\n    }\n\n    if (config_load(&config, \"data\\\\worldmap.txt\", true)) {\n        for (int index = 0; index < ENCOUNTER_FREQUENCY_TYPE_COUNT; index++) {\n            if (!config_get_value(&config, \"data\", wmFreqStrs[index], &(wmFreqValues[index]))) {\n                break;\n            }\n        }\n\n        char* terrainTypes;\n        config_get_string(&config, \"data\", \"terrain_types\", &terrainTypes);\n        wmParseTerrainTypes(&config, terrainTypes);\n\n        for (int index = 0;; index++) {\n            char section[40];\n            sprintf(section, \"Encounter Table %d\", index);\n\n            char* lookupName;\n            if (!config_get_string(&config, section, \"lookup_name\", &lookupName)) {\n                break;\n            }\n\n            if (wmReadEncounterType(&config, lookupName, section) == -1) {\n                return -1;\n            }\n        }\n\n        if (!config_get_value(&config, \"Tile Data\", \"num_horizontal_tiles\", &wmNumHorizontalTiles)) {\n            GNWSystemError(\"\\nwmConfigInit::Error loading tile data!\");\n            return -1;\n        }\n\n        for (int tileIndex = 0; tileIndex < 9999; tileIndex++) {\n            char section[40];\n            sprintf(section, \"Tile %d\", tileIndex);\n\n            int artIndex;\n            if (!config_get_value(&config, section, \"art_idx\", &artIndex)) {\n                break;\n            }\n\n            wmMaxTileNum++;\n\n            TileInfo* worldmapTiles = (TileInfo*)mem_realloc(wmTileInfoList, sizeof(*wmTileInfoList) * wmMaxTileNum);\n            if (worldmapTiles == NULL) {\n                GNWSystemError(\"\\nwmConfigInit::Error loading tiles!\");\n                exit(1);\n            }\n\n            wmTileInfoList = worldmapTiles;\n\n            TileInfo* tile = &(worldmapTiles[wmMaxTileNum - 1]);\n\n            // NOTE: Uninline.\n            wmTileSlotInit(tile);\n\n            tile->fid = art_id(OBJ_TYPE_INTERFACE, artIndex, 0, 0, 0);\n\n            int encounterDifficulty;\n            if (config_get_value(&config, section, \"encounter_difficulty\", &encounterDifficulty)) {\n                tile->encounterDifficultyModifier = encounterDifficulty;\n            }\n\n            char* walkMaskName;\n            if (config_get_string(&config, section, \"walk_mask_name\", &walkMaskName)) {\n                strncpy(tile->walkMaskName, walkMaskName, TILE_WALK_MASK_NAME_SIZE);\n            }\n\n            for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) {\n                for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) {\n                    char key[40];\n                    sprintf(key, \"%d_%d\", row, column);\n\n                    char* subtileProps;\n                    if (!config_get_string(&config, section, key, &subtileProps)) {\n                        GNWSystemError(\"\\nwmConfigInit::Error loading tiles!\");\n                        exit(1);\n                    }\n\n                    if (wmParseSubTileInfo(tile, row, column, subtileProps) == -1) {\n                        GNWSystemError(\"\\nwmConfigInit::Error loading tiles!\");\n                        exit(1);\n                    }\n                }\n            }\n        }\n    }\n\n    config_exit(&config);\n\n    return 0;\n}\n\n// 0x4BD9F0\nstatic int wmReadEncounterType(Config* config, char* lookupName, char* sectionKey)\n{\n    wmMaxEncounterInfoTables++;\n\n    EncounterTable* encounterTables = (EncounterTable*)mem_realloc(wmEncounterTableList, sizeof(EncounterTable) * wmMaxEncounterInfoTables);\n    if (encounterTables == NULL) {\n        GNWSystemError(\"\\nwmConfigInit::Error loading Encounter Table!\");\n        exit(1);\n    }\n\n    wmEncounterTableList = encounterTables;\n\n    EncounterTable* encounterTable = &(encounterTables[wmMaxEncounterInfoTables - 1]);\n\n    // NOTE: Uninline.\n    wmEncounterTableSlotInit(encounterTable);\n\n    encounterTable->field_28 = wmMaxEncounterInfoTables - 1;\n    strncpy(encounterTable->lookupName, lookupName, 40);\n\n    char* str;\n    if (config_get_string(config, sectionKey, \"maps\", &str)) {\n        while (*str != '\\0') {\n            if (encounterTable->mapsLength >= 6) {\n                break;\n            }\n\n            if (strParseStrFromFunc(&str, &(encounterTable->maps[encounterTable->mapsLength]), wmParseFindMapIdxMatch) == -1) {\n                break;\n            }\n\n            encounterTable->mapsLength++;\n        }\n    }\n\n    for (;;) {\n        char key[40];\n        sprintf(key, \"enc_%02d\", encounterTable->entriesLength);\n\n        char* str;\n        if (!config_get_string(config, sectionKey, key, &str)) {\n            break;\n        }\n\n        if (encounterTable->entriesLength >= 40) {\n            GNWSystemError(\"\\nwmConfigInit::Error: Encounter Table: Too many table indexes!!\");\n            exit(1);\n        }\n\n        pConfigCfg = config;\n\n        if (wmParseEncounterTableIndex(&(encounterTable->entries[encounterTable->entriesLength]), str) == -1) {\n            return -1;\n        }\n\n        encounterTable->entriesLength++;\n    }\n\n    return 0;\n}\n\n// 0x4BDB64\nstatic int wmParseEncounterTableIndex(EncounterEntry* entry, char* string)\n{\n    // NOTE: Uninline.\n    if (wmEncounterTypeSlotInit(entry) == -1) {\n        return -1;\n    }\n\n    while (string != NULL && *string != '\\0') {\n        strParseStrSepVal(&string, \"chance\", &(entry->chance), \":\");\n        strParseStrSepVal(&string, \"counter\", &(entry->counter), \":\");\n\n        if (strstr(string, \"special\")) {\n            entry->flags |= ENCOUNTER_ENTRY_SPECIAL;\n            string += 8;\n        }\n\n        if (string != NULL) {\n            char* pch = strstr(string, \"map:\");\n            if (pch != NULL) {\n                string = pch + 4;\n                strParseStrFromFunc(&string, &(entry->map), wmParseFindMapIdxMatch);\n            }\n        }\n\n        if (wmParseEncounterSubEncStr(entry, &string) == -1) {\n            break;\n        }\n\n        if (string != NULL) {\n            char* pch = strstr(string, \"scenery:\");\n            if (pch != NULL) {\n                string = pch + 8;\n                strParseStrFromList(&string, &(entry->scenery), wmSceneryStrs, ENCOUNTER_SCENERY_TYPE_COUNT);\n            }\n        }\n\n        wmParseConditional(&string, \"if\", &(entry->condition));\n    }\n\n    return 0;\n}\n\n// 0x4BDCA8\nstatic int wmParseEncounterSubEncStr(EncounterEntry* encounterEntry, char** stringPtr)\n{\n    char* string = *stringPtr;\n    if (strnicmp(string, \"enc:\", 4) != 0) {\n        return -1;\n    }\n\n    // Consume \"enc:\".\n    string += 4;\n\n    char* comma = strstr(string, \",\");\n    if (comma != NULL) {\n        // Comma is present, position string pointer to the next chunk.\n        *stringPtr = comma + 1;\n        *comma = '\\0';\n    } else {\n        // No comma, this chunk is the last one.\n        *stringPtr = NULL;\n    }\n\n    while (string != NULL) {\n        ENCOUNTER_ENTRY_ENC* entry = &(encounterEntry->field_54[encounterEntry->field_50]);\n\n        // NOTE: Uninline.\n        wmEncounterSubEncSlotInit(entry);\n\n        if (*string == '(') {\n            string++;\n            entry->minQuantity = atoi(string);\n\n            while (*string != '\\0' && *string != '-') {\n                string++;\n            }\n\n            if (*string == '-') {\n                string++;\n            }\n\n            entry->maxQuantity = atoi(string);\n\n            while (*string != '\\0' && *string != ')') {\n                string++;\n            }\n\n            if (*string == ')') {\n                string++;\n            }\n        }\n\n        while (*string == ' ') {\n            string++;\n        }\n\n        char* end = string;\n        while (*end != '\\0' && *end != ' ') {\n            end++;\n        }\n\n        char ch = *end;\n        *end = '\\0';\n\n        if (strParseStrFromFunc(&string, &(entry->field_8), wmParseFindSubEncTypeMatch) == -1) {\n            return -1;\n        }\n\n        *end = ch;\n\n        if (ch == ' ') {\n            string++;\n        }\n\n        end = string;\n        while (*end != '\\0' && *end != ' ') {\n            end++;\n        }\n\n        ch = *end;\n        *end = '\\0';\n\n        if (*string != '\\0') {\n            strParseStrFromList(&string, &(entry->situation), wmEncOpStrs, ENCOUNTER_SITUATION_COUNT);\n        }\n\n        *end = ch;\n\n        encounterEntry->field_50++;\n\n        while (*string == ' ') {\n            string++;\n        }\n\n        if (*string == '\\0') {\n            string = NULL;\n        }\n    }\n\n    if (comma != NULL) {\n        *comma = ',';\n    }\n\n    return 0;\n}\n\n// 0x4BDE94\nstatic int wmParseFindSubEncTypeMatch(char* str, int* valuePtr)\n{\n    *valuePtr = 0;\n\n    if (stricmp(str, \"player\") == 0) {\n        *valuePtr = -1;\n        return 0;\n    }\n\n    if (wmFindEncBaseTypeMatch(str, valuePtr) == 0) {\n        return 0;\n    }\n\n    if (wmReadEncBaseType(str, valuePtr) == 0) {\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x4BDED8\nstatic int wmFindEncBaseTypeMatch(char* str, int* valuePtr)\n{\n    for (int index = 0; index < _wmMaxEncBaseTypes; index++) {\n        if (stricmp(wmEncBaseTypeList[index].name, str) == 0) {\n            *valuePtr = index;\n            return 0;\n        }\n    }\n\n    *valuePtr = -1;\n    return -1;\n}\n\n// 0x4BDF34\nstatic int wmReadEncBaseType(char* name, int* valuePtr)\n{\n    char section[40];\n    sprintf(section, \"Encounter: %s\", name);\n\n    char key[40];\n    sprintf(key, \"type_00\");\n\n    char* string;\n    if (!config_get_string(pConfigCfg, section, key, &string)) {\n        return -1;\n    }\n\n    _wmMaxEncBaseTypes++;\n\n    ENC_BASE_TYPE* arr = (ENC_BASE_TYPE*)mem_realloc(wmEncBaseTypeList, sizeof(*wmEncBaseTypeList) * _wmMaxEncBaseTypes);\n    if (arr == NULL) {\n        GNWSystemError(\"\\nwmConfigInit::Error Reading EncBaseType!\");\n        exit(1);\n    }\n\n    wmEncBaseTypeList = arr;\n\n    ENC_BASE_TYPE* entry = &(arr[_wmMaxEncBaseTypes - 1]);\n\n    // NOTE: Uninline.\n    wmEncBaseTypeSlotInit(entry);\n\n    strncpy(entry->name, name, 40);\n\n    while (1) {\n        if (wmParseEncBaseSubTypeStr(&(entry->field_38[entry->field_34]), &string) == -1) {\n            return -1;\n        }\n\n        entry->field_34++;\n\n        sprintf(key, \"type_%02d\", entry->field_34);\n\n        if (!config_get_string(pConfigCfg, section, key, &string)) {\n            int team;\n            config_get_value(pConfigCfg, section, \"team_num\", &team);\n\n            for (int index = 0; index < entry->field_34; index++) {\n                ENC_BASE_TYPE_38* ptr = &(entry->field_38[index]);\n                if (PID_TYPE(ptr->pid) == OBJ_TYPE_CRITTER) {\n                    ptr->team = team;\n                }\n            }\n\n            if (config_get_string(pConfigCfg, section, \"position\", &string)) {\n                strParseStrFromList(&string, &(entry->position), wmFormationStrs, ENCOUNTER_FORMATION_TYPE_COUNT);\n                strParseStrSepVal(&string, \"spacing\", &(entry->spacing), \":\");\n                strParseStrSepVal(&string, \"distance\", &(entry->distance), \":\");\n            }\n\n            *valuePtr = _wmMaxEncBaseTypes - 1;\n\n            return 0;\n        }\n    }\n\n    return -1;\n}\n\n// 0x4BE140\nstatic int wmParseEncBaseSubTypeStr(ENC_BASE_TYPE_38* ptr, char** stringPtr)\n{\n    char* string = *stringPtr;\n\n    // NOTE: Uninline.\n    if (wmEncBaseSubTypeSlotInit(ptr) == -1) {\n        return -1;\n    }\n\n    if (strParseStrSepVal(&string, \"ratio\", &(ptr->ratio), \":\") == 0) {\n        ptr->field_2C = 0;\n    }\n\n    if (strstr(string, \"dead,\") == string) {\n        ptr->flags |= ENCOUNTER_SUBINFO_DEAD;\n        string += 5;\n    }\n\n    strParseStrSepVal(&string, \"pid\", &(ptr->pid), \":\");\n    if (ptr->pid == 0) {\n        ptr->pid = -1;\n    }\n\n    strParseStrSepVal(&string, \"distance\", &(ptr->distance), \":\");\n    strParseStrSepVal(&string, \"tilenum\", &(ptr->tile), \":\");\n\n    for (int index = 0; index < 10; index++) {\n        if (strstr(string, \"item:\") == NULL) {\n            break;\n        }\n\n        wmParseEncounterItemType(&string, &(ptr->items[ptr->itemsLength]), &(ptr->itemsLength), \":\");\n    }\n\n    strParseStrSepVal(&string, \"script\", &(ptr->script), \":\");\n    wmParseConditional(&string, \"if\", &(ptr->condition));\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4BE2A0\nstatic int wmEncBaseTypeSlotInit(ENC_BASE_TYPE* entry)\n{\n    entry->name[0] = '\\0';\n    entry->position = ENCOUNTER_FORMATION_TYPE_SURROUNDING;\n    entry->spacing = 1;\n    entry->distance = -1;\n    entry->field_34 = 0;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4BE2C4\nstatic int wmEncBaseSubTypeSlotInit(ENC_BASE_TYPE_38* entry)\n{\n    entry->field_28 = -1;\n    entry->field_2C = 1;\n    entry->ratio = 100;\n    entry->pid = -1;\n    entry->flags = 0;\n    entry->distance = 0;\n    entry->tile = -1;\n    entry->itemsLength = 0;\n    entry->script = -1;\n    entry->team = -1;\n\n    return wmConditionalDataInit(&(entry->condition));\n}\n\n// NOTE: Inlined.\n//\n// 0x4BE32C\nstatic int wmEncounterSubEncSlotInit(ENCOUNTER_ENTRY_ENC* entry)\n{\n    entry->minQuantity = 1;\n    entry->maxQuantity = 1;\n    entry->field_8 = -1;\n    entry->situation = ENCOUNTER_SITUATION_NOTHING;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4BE34C\nstatic int wmEncounterTypeSlotInit(EncounterEntry* entry)\n{\n    entry->flags = 0;\n    entry->map = -1;\n    entry->scenery = ENCOUNTER_SCENERY_TYPE_NORMAL;\n    entry->chance = 0;\n    entry->counter = -1;\n    entry->field_50 = 0;\n\n    return wmConditionalDataInit(&(entry->condition));\n}\n\n// NOTE: Inlined.\n//\n// 0x4BE3B8\nstatic int wmEncounterTableSlotInit(EncounterTable* encounterTable)\n{\n    encounterTable->lookupName[0] = '\\0';\n    encounterTable->mapsLength = 0;\n    encounterTable->field_48 = 0;\n    encounterTable->entriesLength = 0;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4BE3D4\nstatic int wmTileSlotInit(TileInfo* tile)\n{\n    tile->fid = -1;\n    tile->handle = INVALID_CACHE_ENTRY;\n    tile->data = NULL;\n    tile->walkMaskName[0] = '\\0';\n    tile->walkMaskData = NULL;\n    tile->encounterDifficultyModifier = 0;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4BE400\nstatic int wmTerrainTypeSlotInit(Terrain* terrain)\n{\n    terrain->lookupName[0] = '\\0';\n    terrain->type = 0;\n    terrain->mapsLength = 0;\n\n    return 0;\n}\n\n// 0x4BE378\nstatic int wmConditionalDataInit(EncounterCondition* condition)\n{\n    condition->entriesLength = 0;\n\n    for (int index = 0; index < 3; index++) {\n        EncounterConditionEntry* conditionEntry = &(condition->entries[index]);\n        conditionEntry->type = ENCOUNTER_CONDITION_TYPE_NONE;\n        conditionEntry->conditionalOperator = ENCOUNTER_CONDITIONAL_OPERATOR_NONE;\n        conditionEntry->param = 0;\n        conditionEntry->value = 0;\n    }\n\n    for (int index = 0; index < 2; index++) {\n        condition->logicalOperators[index] = ENCOUNTER_LOGICAL_OPERATOR_NONE;\n    }\n\n    return 0;\n}\n\n// 0x4BE414\nstatic int wmParseTerrainTypes(Config* config, char* string)\n{\n    if (*string == '\\0') {\n        return -1;\n    }\n\n    int terrainCount = 1;\n\n    char* pch = string;\n    while (*pch != '\\0') {\n        if (*pch == ',') {\n            terrainCount++;\n        }\n        pch++;\n    }\n\n    wmMaxTerrainTypes = terrainCount;\n\n    wmTerrainTypeList = (Terrain*)mem_malloc(sizeof(*wmTerrainTypeList) * terrainCount);\n    if (wmTerrainTypeList == NULL) {\n        return -1;\n    }\n\n    for (int index = 0; index < wmMaxTerrainTypes; index++) {\n        Terrain* terrain = &(wmTerrainTypeList[index]);\n\n        // NOTE: Uninline.\n        wmTerrainTypeSlotInit(terrain);\n    }\n\n    strlwr(string);\n\n    pch = string;\n    for (int index = 0; index < wmMaxTerrainTypes; index++) {\n        Terrain* terrain = &(wmTerrainTypeList[index]);\n\n        pch += strspn(pch, \" \");\n\n        int endPos = strcspn(pch, \",\");\n        char end = pch[endPos];\n        pch[endPos] = '\\0';\n\n        int delimeterPos = strcspn(pch, \":\");\n        char delimeter = pch[delimeterPos];\n        pch[delimeterPos] = '\\0';\n\n        strncpy(terrain->lookupName, pch, 40);\n        terrain->type = atoi(pch + delimeterPos + 1);\n\n        pch[delimeterPos] = delimeter;\n        pch[endPos] = end;\n\n        if (end == ',') {\n            pch += endPos + 1;\n        }\n    }\n\n    for (int index = 0; index < wmMaxTerrainTypes; index++) {\n        wmParseTerrainRndMaps(config, &(wmTerrainTypeList[index]));\n    }\n\n    return 0;\n}\n\n// 0x4BE598\nstatic int wmParseTerrainRndMaps(Config* config, Terrain* terrain)\n{\n    char section[40];\n    sprintf(section, \"Random Maps: %s\", terrain->lookupName);\n\n    for (;;) {\n        char key[40];\n        sprintf(key, \"map_%02d\", terrain->mapsLength);\n\n        char* string;\n        if (!config_get_string(config, section, key, &string)) {\n            break;\n        }\n\n        if (strParseStrFromFunc(&string, &(terrain->maps[terrain->mapsLength]), wmParseFindMapIdxMatch) == -1) {\n            return -1;\n        }\n\n        terrain->mapsLength++;\n\n        if (terrain->mapsLength >= 20) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4BE61C\nstatic int wmParseSubTileInfo(TileInfo* tile, int row, int column, char* string)\n{\n    SubtileInfo* subtile = &(tile->subtiles[column][row]);\n    subtile->state = SUBTILE_STATE_UNKNOWN;\n\n    if (strParseStrFromFunc(&string, &(subtile->terrain), wmParseFindTerrainTypeMatch) == -1) {\n        return -1;\n    }\n\n    if (strParseStrFromList(&string, &(subtile->field_4), wmFillStrs, 9) == -1) {\n        return -1;\n    }\n\n    for (int index = 0; index < DAY_PART_COUNT; index++) {\n        if (strParseStrFromList(&string, &(subtile->encounterChance[index]), wmFreqStrs, ENCOUNTER_FREQUENCY_TYPE_COUNT) == -1) {\n            return -1;\n        }\n    }\n\n    if (strParseStrFromFunc(&string, &(subtile->encounterType), wmParseFindEncounterTypeMatch) == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4BE6D4\nstatic int wmParseFindEncounterTypeMatch(char* string, int* valuePtr)\n{\n    for (int index = 0; index < wmMaxEncounterInfoTables; index++) {\n        if (stricmp(string, wmEncounterTableList[index].lookupName) == 0) {\n            *valuePtr = index;\n            return 0;\n        }\n    }\n\n    debug_printf(\"WorldMap Error: Couldn't find match for Encounter Type!\");\n\n    *valuePtr = -1;\n\n    return -1;\n}\n\n// 0x4BE73C\nstatic int wmParseFindTerrainTypeMatch(char* string, int* valuePtr)\n{\n    for (int index = 0; index < wmMaxTerrainTypes; index++) {\n        Terrain* terrain = &(wmTerrainTypeList[index]);\n        if (stricmp(string, terrain->lookupName) == 0) {\n            *valuePtr = index;\n            return 0;\n        }\n    }\n\n    debug_printf(\"WorldMap Error: Couldn't find match for Terrain Type!\");\n\n    *valuePtr = -1;\n\n    return -1;\n}\n\n// 0x4BE7A4\nstatic int wmParseEncounterItemType(char** stringPtr, ENC_BASE_TYPE_38_48* a2, int* a3, const char* delim)\n{\n    char* string;\n    int v2, v3;\n    char tmp, tmp2;\n    int v20;\n\n    string = *stringPtr;\n    v20 = 0;\n\n    if (*string == '\\0') {\n        return -1;\n    }\n\n    strlwr(string);\n\n    if (*string == ',') {\n        string++;\n        *stringPtr += 1;\n    }\n\n    string += strspn(string, \" \");\n\n    v2 = strcspn(string, \",\");\n\n    tmp = string[v2];\n    string[v2] = '\\0';\n\n    v3 = strcspn(string, delim);\n    tmp2 = string[v3];\n    string[v3] = '\\0';\n\n    if (strcmp(string, \"item\") == 0) {\n        *stringPtr += v2 + 1;\n        v20 = 1;\n        wmParseItemType(string + v3 + 1, a2);\n        *a3 = *a3 + 1;\n    }\n\n    string[v3] = tmp2;\n    string[v2] = tmp;\n\n    return v20 ? 0 : -1;\n}\n\n// 0x4BE888\nstatic int wmParseItemType(char* string, ENC_BASE_TYPE_38_48* ptr)\n{\n    while (*string == ' ') {\n        string++;\n    }\n\n    ptr->minimumQuantity = 1;\n    ptr->maximumQuantity = 1;\n    ptr->isEquipped = false;\n\n    if (*string == '(') {\n        string++;\n\n        ptr->minimumQuantity = atoi(string);\n\n        while (isdigit(*string)) {\n            string++;\n        }\n\n        if (*string == '-') {\n            string++;\n\n            ptr->maximumQuantity = atoi(string);\n\n            while (isdigit(*string)) {\n                string++;\n            }\n        } else {\n            ptr->maximumQuantity = ptr->minimumQuantity;\n        }\n\n        if (*string == ')') {\n            string++;\n        }\n    }\n\n    while (*string == ' ') {\n        string++;\n    }\n\n    ptr->pid = atoi(string);\n\n    while (isdigit(*string)) {\n        string++;\n    }\n\n    while (*string == ' ') {\n        string++;\n    }\n\n    if (strstr(string, \"{wielded}\") != NULL\n        || strstr(string, \"(wielded)\") != NULL\n        || strstr(string, \"{worn}\") != NULL\n        || strstr(string, \"(worn)\") != NULL) {\n        ptr->isEquipped = true;\n    }\n\n    return 0;\n}\n\n// 0x4BE988\nstatic int wmParseConditional(char** stringPtr, const char* a2, EncounterCondition* condition)\n{\n    while (condition->entriesLength < 3) {\n        EncounterConditionEntry* conditionEntry = &(condition->entries[condition->entriesLength]);\n        if (wmParseSubConditional(stringPtr, a2, &(conditionEntry->type), &(conditionEntry->conditionalOperator), &(conditionEntry->param), &(conditionEntry->value)) == -1) {\n            return -1;\n        }\n\n        condition->entriesLength++;\n\n        char* andStatement = strstr(*stringPtr, \"and\");\n        if (andStatement != NULL) {\n            *stringPtr = andStatement + 3;\n            condition->logicalOperators[condition->entriesLength - 1] = ENCOUNTER_LOGICAL_OPERATOR_AND;\n            continue;\n        }\n\n        char* orStatement = strstr(*stringPtr, \"or\");\n        if (orStatement != NULL) {\n            *stringPtr = orStatement + 2;\n            condition->logicalOperators[condition->entriesLength - 1] = ENCOUNTER_LOGICAL_OPERATOR_OR;\n            continue;\n        }\n\n        break;\n    }\n\n    return 0;\n}\n\n// 0x4BEA24\nstatic int wmParseSubConditional(char** stringPtr, const char* a2, int* typePtr, int* operatorPtr, int* paramPtr, int* valuePtr)\n{\n    char* pch;\n    int v2;\n    int v3;\n    char tmp;\n    char tmp2;\n    int v57;\n\n    char* string = *stringPtr;\n\n    if (string == NULL) {\n        return -1;\n    }\n\n    if (*string == '\\0') {\n        return -1;\n    }\n\n    strlwr(string);\n\n    if (*string == ',') {\n        string++;\n        *stringPtr = string;\n    }\n\n    string += strspn(string, \" \");\n\n    v2 = strcspn(string, \",\");\n\n    tmp = *(string + v2);\n    *(string + v2) = '\\0';\n\n    v3 = strcspn(string, \"(\");\n    tmp2 = *(string + v3);\n    *(string + v3) = '\\0';\n\n    v57 = 0;\n    if (strstr(string, a2) == string) {\n        v57 = 1;\n    }\n\n    *(string + v3) = tmp2;\n    *(string + v2) = tmp;\n\n    if (v57 == 0) {\n        return -1;\n    }\n\n    string += v3 + 1;\n\n    if (strstr(string, \"rand(\") == string) {\n        string += 5;\n        *typePtr = ENCOUNTER_CONDITION_TYPE_RANDOM;\n        *operatorPtr = ENCOUNTER_CONDITIONAL_OPERATOR_NONE;\n        *paramPtr = atoi(string);\n\n        pch = strstr(string, \")\");\n        if (pch != NULL) {\n            string = pch + 1;\n        }\n\n        pch = strstr(string, \")\");\n        if (pch != NULL) {\n            string = pch + 1;\n        }\n\n        pch = strstr(string, \",\");\n        if (pch != NULL) {\n            string = pch + 1;\n        }\n\n        *stringPtr = string;\n        return 0;\n    } else if (strstr(string, \"global(\") == string) {\n        string += 7;\n        *typePtr = ENCOUNTER_CONDITION_TYPE_GLOBAL;\n        *paramPtr = atoi(string);\n\n        pch = strstr(string, \")\");\n        if (pch != NULL) {\n            string = pch + 1;\n        }\n\n        while (*string == ' ') {\n            string++;\n        }\n\n        if (wmParseConditionalEval(&string, operatorPtr) != -1) {\n            *valuePtr = atoi(string);\n\n            pch = strstr(string, \")\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n\n            pch = strstr(string, \",\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n            *stringPtr = string;\n            return 0;\n        }\n    } else if (strstr(string, \"player(level)\") == string) {\n        string += 13;\n        *typePtr = ENCOUNTER_CONDITION_TYPE_PLAYER;\n\n        while (*string == ' ') {\n            string++;\n        }\n\n        if (wmParseConditionalEval(&string, operatorPtr) != -1) {\n            *valuePtr = atoi(string);\n\n            pch = strstr(string, \")\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n\n            pch = strstr(string, \",\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n            *stringPtr = string;\n            return 0;\n        }\n    } else if (strstr(string, \"days_played\") == string) {\n        string += 11;\n        *typePtr = ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED;\n\n        while (*string == ' ') {\n            string++;\n        }\n\n        if (wmParseConditionalEval(&string, operatorPtr) != -1) {\n            *valuePtr = atoi(string);\n\n            pch = strstr(string, \")\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n\n            pch = strstr(string, \",\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n            *stringPtr = string;\n            return 0;\n        }\n    } else if (strstr(string, \"time_of_day\") == string) {\n        string += 11;\n        *typePtr = ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY;\n\n        while (*string == ' ') {\n            string++;\n        }\n\n        if (wmParseConditionalEval(&string, operatorPtr) != -1) {\n            *valuePtr = atoi(string);\n\n            pch = strstr(string, \")\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n\n            pch = strstr(string, \",\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n            *stringPtr = string;\n            return 0;\n        }\n    } else if (strstr(string, \"enctr(num_critters)\") == string) {\n        string += 19;\n        *typePtr = ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS;\n\n        while (*string == ' ') {\n            string++;\n        }\n\n        if (wmParseConditionalEval(&string, operatorPtr) != -1) {\n            *valuePtr = atoi(string);\n\n            pch = strstr(string, \")\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n\n            pch = strstr(string, \",\");\n            if (pch != NULL) {\n                string = pch + 1;\n            }\n            *stringPtr = string;\n            return 0;\n        }\n    } else {\n        *stringPtr = string;\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x4BEEBC\nstatic int wmParseConditionalEval(char** stringPtr, int* conditionalOperatorPtr)\n{\n    char* string = *stringPtr;\n\n    *conditionalOperatorPtr = ENCOUNTER_CONDITIONAL_OPERATOR_NONE;\n\n    int index;\n    for (index = 0; index < ENCOUNTER_CONDITIONAL_OPERATOR_COUNT; index++) {\n        if (strstr(string, wmConditionalOpStrs[index]) == string) {\n            break;\n        }\n    }\n\n    if (index == ENCOUNTER_CONDITIONAL_OPERATOR_COUNT) {\n        return -1;\n    }\n\n    *conditionalOperatorPtr = index;\n\n    string += strlen(wmConditionalOpStrs[index]);\n    while (*string == ' ') {\n        string++;\n    }\n\n    *stringPtr = string;\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4BEF1C\nstatic int wmAreaSlotInit(CityInfo* area)\n{\n    area->name[0] = '\\0';\n    area->areaId = -1;\n    area->x = 0;\n    area->y = 0;\n    area->size = CITY_SIZE_LARGE;\n    area->state = CITY_STATE_UNKNOWN;\n    area->lockState = LOCK_STATE_UNLOCKED;\n    area->visitedState = 0;\n    area->mapFid = -1;\n    area->labelFid = -1;\n    area->entrancesLength = 0;\n\n    return 0;\n}\n\n// 0x4BEF68\nstatic int wmAreaInit()\n{\n    Config cfg;\n    char section[40];\n    char key[40];\n    int area_idx;\n    int num;\n    char* str;\n    CityInfo* cities;\n    CityInfo* city;\n    EntranceInfo* entrance;\n\n    if (wmMapInit() == -1) {\n        return -1;\n    }\n\n    if (!config_init(&cfg)) {\n        return -1;\n    }\n\n    if (config_load(&cfg, \"data\\\\city.txt\", true)) {\n        area_idx = 0;\n        do {\n            sprintf(section, \"Area %02d\", area_idx);\n            if (!config_get_value(&cfg, section, \"townmap_art_idx\", &num)) {\n                break;\n            }\n\n            wmMaxAreaNum++;\n\n            cities = (CityInfo*)mem_realloc(wmAreaInfoList, sizeof(CityInfo) * wmMaxAreaNum);\n            if (cities == NULL) {\n                GNWSystemError(\"\\nwmConfigInit::Error loading areas!\");\n                exit(1);\n            }\n\n            wmAreaInfoList = cities;\n\n            city = &(cities[wmMaxAreaNum - 1]);\n\n            // NOTE: Uninline.\n            wmAreaSlotInit(city);\n\n            city->areaId = area_idx;\n\n            if (num != -1) {\n                num = art_id(OBJ_TYPE_INTERFACE, num, 0, 0, 0);\n            }\n\n            city->mapFid = num;\n\n            if (config_get_value(&cfg, section, \"townmap_label_art_idx\", &num)) {\n                if (num != -1) {\n                    num = art_id(OBJ_TYPE_INTERFACE, num, 0, 0, 0);\n                }\n\n                city->labelFid = num;\n            }\n\n            if (!config_get_string(&cfg, section, \"area_name\", &str)) {\n                GNWSystemError(\"\\nwmConfigInit::Error loading areas!\");\n                exit(1);\n            }\n\n            strncpy(city->name, str, 40);\n\n            if (!config_get_string(&cfg, section, \"world_pos\", &str)) {\n                GNWSystemError(\"\\nwmConfigInit::Error loading areas!\");\n                exit(1);\n            }\n\n            if (strParseValue(&str, &(city->x)) == -1) {\n                return -1;\n            }\n\n            if (strParseValue(&str, &(city->y)) == -1) {\n                return -1;\n            }\n\n            if (!config_get_string(&cfg, section, \"start_state\", &str)) {\n                GNWSystemError(\"\\nwmConfigInit::Error loading areas!\");\n                exit(1);\n            }\n\n            if (strParseStrFromList(&str, &(city->state), wmStateStrs, 2) == -1) {\n                return -1;\n            }\n\n            if (config_get_string(&cfg, section, \"lock_state\", &str)) {\n                if (strParseStrFromList(&str, &(city->lockState), wmStateStrs, 2) == -1) {\n                    return -1;\n                }\n            }\n\n            if (!config_get_string(&cfg, section, \"size\", &str)) {\n                GNWSystemError(\"\\nwmConfigInit::Error loading areas!\");\n                exit(1);\n            }\n\n            if (strParseStrFromList(&str, &(city->size), wmAreaSizeStrs, 3) == -1) {\n                return -1;\n            }\n\n            while (city->entrancesLength < ENTRANCE_LIST_CAPACITY) {\n                sprintf(key, \"entrance_%d\", city->entrancesLength);\n\n                if (!config_get_string(&cfg, section, key, &str)) {\n                    break;\n                }\n\n                entrance = &(city->entrances[city->entrancesLength]);\n\n                // NOTE: Uninline.\n                wmEntranceSlotInit(entrance);\n\n                if (strParseStrFromList(&str, &(entrance->state), wmStateStrs, 2) == -1) {\n                    return -1;\n                }\n\n                if (strParseValue(&str, &(entrance->x)) == -1) {\n                    return -1;\n                }\n\n                if (strParseValue(&str, &(entrance->y)) == -1) {\n                    return -1;\n                }\n\n                if (strParseStrFromFunc(&str, &(entrance->map), &wmParseFindMapIdxMatch) == -1) {\n                    return -1;\n                }\n\n                if (strParseValue(&str, &(entrance->elevation)) == -1) {\n                    return -1;\n                }\n\n                if (strParseValue(&str, &(entrance->tile)) == -1) {\n                    return -1;\n                }\n\n                if (strParseValue(&str, &(entrance->rotation)) == -1) {\n                    return -1;\n                }\n\n                city->entrancesLength++;\n            }\n\n            area_idx++;\n        } while (area_idx < 5000);\n    }\n\n    config_exit(&cfg);\n\n    if (wmMaxAreaNum != CITY_COUNT) {\n        GNWSystemError(\"\\nwmAreaInit::Error loading Cities!\");\n        exit(1);\n    }\n\n    return 0;\n}\n\n// 0x4BF3E0\nstatic int wmParseFindMapIdxMatch(char* string, int* valuePtr)\n{\n    for (int index = 0; index < wmMaxMapNum; index++) {\n        MapInfo* map = &(wmMapInfoList[index]);\n        if (stricmp(string, map->lookupName) == 0) {\n            *valuePtr = index;\n            return 0;\n        }\n    }\n\n    debug_printf(\"\\nWorldMap Error: Couldn't find match for Map Index!\");\n\n    *valuePtr = -1;\n    return -1;\n}\n\n// NOTE: Inlined.\n//\n// 0x4BF448\nstatic int wmEntranceSlotInit(EntranceInfo* entrance)\n{\n    entrance->state = 0;\n    entrance->x = 0;\n    entrance->y = 0;\n    entrance->map = -1;\n    entrance->elevation = 0;\n    entrance->tile = 0;\n    entrance->rotation = 0;\n\n    return 0;\n}\n\n// 0x4BF47C\nstatic int wmMapSlotInit(MapInfo* map)\n{\n    map->lookupName[0] = '\\0';\n    map->field_28 = -1;\n    map->field_2C = -1;\n    map->mapFileName[0] = '\\0';\n    map->music[0] = '\\0';\n    map->flags = 0x3F;\n    map->ambientSoundEffectsLength = 0;\n    map->startPointsLength = 0;\n\n    return 0;\n}\n\n// 0x4BF4BC\nstatic int wmMapInit()\n{\n    char* str;\n    int num;\n    MapInfo* maps;\n    MapInfo* map;\n    int j;\n    MapAmbientSoundEffectInfo* sfx;\n    MapStartPointInfo* rsp;\n\n    Config config;\n    if (!config_init(&config)) {\n        return -1;\n    }\n\n    if (config_load(&config, \"data\\\\maps.txt\", true)) {\n        for (int mapIdx = 0;; mapIdx++) {\n            char section[40];\n            sprintf(section, \"Map %03d\", mapIdx);\n\n            if (!config_get_string(&config, section, \"lookup_name\", &str)) {\n                break;\n            }\n\n            wmMaxMapNum++;\n\n            maps = (MapInfo*)mem_realloc(wmMapInfoList, sizeof(*wmMapInfoList) * wmMaxMapNum);\n            if (maps == NULL) {\n                GNWSystemError(\"\\nwmConfigInit::Error loading maps!\");\n                exit(1);\n            }\n\n            wmMapInfoList = maps;\n\n            map = &(maps[wmMaxMapNum - 1]);\n            wmMapSlotInit(map);\n\n            strncpy(map->lookupName, str, 40);\n\n            if (!config_get_string(&config, section, \"map_name\", &str)) {\n                GNWSystemError(\"\\nwmConfigInit::Error loading maps!\");\n                exit(1);\n            }\n\n            strlwr(str);\n            strncpy(map->mapFileName, str, 40);\n\n            if (config_get_string(&config, section, \"music\", &str)) {\n                strncpy(map->music, str, 40);\n            }\n\n            if (config_get_string(&config, section, \"ambient_sfx\", &str)) {\n                while (str) {\n                    sfx = &(map->ambientSoundEffects[map->ambientSoundEffectsLength]);\n                    if (strParseStrAndSepVal(&str, sfx->name, &(sfx->chance), \":\") == -1) {\n                        return -1;\n                    }\n\n                    map->ambientSoundEffectsLength++;\n\n                    if (*str == '\\0') {\n                        str = NULL;\n                    }\n\n                    if (map->ambientSoundEffectsLength >= MAP_AMBIENT_SOUND_EFFECTS_CAPACITY) {\n                        if (str != NULL) {\n                            debug_printf(\"\\nwmMapInit::Error reading ambient sfx.  Too many!  Str: %s, MapIdx: %d\", map->lookupName, mapIdx);\n                            str = NULL;\n                        }\n                    }\n                }\n            }\n\n            if (config_get_string(&config, section, \"saved\", &str)) {\n                if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) {\n                    return -1;\n                }\n\n                // NOTE: Uninline.\n                wmSetFlags(&(map->flags), MAP_SAVED, num);\n            }\n\n            if (config_get_string(&config, section, \"dead_bodies_age\", &str)) {\n                if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) {\n                    return -1;\n                }\n\n                // NOTE: Uninline.\n                wmSetFlags(&(map->flags), MAP_DEAD_BODIES_AGE, num);\n            }\n\n            if (config_get_string(&config, section, \"can_rest_here\", &str)) {\n                if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) {\n                    return -1;\n                }\n\n                // NOTE: Uninline.\n                wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_0, num);\n\n                if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) {\n                    return -1;\n                }\n\n                // NOTE: Uninline.\n                wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_1, num);\n\n                if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) {\n                    return -1;\n                }\n\n                // NOTE: Uninline.\n                wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_2, num);\n            }\n\n            if (config_get_string(&config, section, \"pipbody_active\", &str)) {\n                if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) {\n                    return -1;\n                }\n\n                // NOTE: Uninline.\n                wmSetFlags(&(map->flags), MAP_PIPBOY_ACTIVE, num);\n            }\n\n            if (config_get_string(&config, section, \"random_start_point_0\", &str)) {\n                j = 0;\n                while (str != NULL) {\n                    while (*str != '\\0') {\n                        if (map->startPointsLength >= MAP_STARTING_POINTS_CAPACITY) {\n                            break;\n                        }\n\n                        rsp = &(map->startPoints[map->startPointsLength]);\n\n                        // NOTE: Uninline.\n                        wmRStartSlotInit(rsp);\n\n                        strParseStrSepVal(&str, \"elev\", &(rsp->elevation), \":\");\n                        strParseStrSepVal(&str, \"tile_num\", &(rsp->tile), \":\");\n\n                        map->startPointsLength++;\n                    }\n\n                    char key[40];\n                    sprintf(key, \"random_start_point_%1d\", ++j);\n\n                    if (!config_get_string(&config, section, key, &str)) {\n                        str = NULL;\n                    }\n                }\n            }\n        }\n    }\n\n    config_exit(&config);\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4BF954\nstatic int wmRStartSlotInit(MapStartPointInfo* rsp)\n{\n    rsp->elevation = 0;\n    rsp->tile = -1;\n    rsp->field_8 = -1;\n\n    return 0;\n}\n\n// 0x4BF96C\nint wmMapMaxCount()\n{\n    return wmMaxMapNum;\n}\n\n// 0x4BF974\nint wmMapIdxToName(int mapIdx, char* dest)\n{\n    if (mapIdx == -1 || mapIdx > wmMaxMapNum) {\n        dest[0] = '\\0';\n        return -1;\n    }\n\n    sprintf(dest, \"%s.MAP\", wmMapInfoList[mapIdx].mapFileName);\n    return 0;\n}\n\n// 0x4BF9BC\nint wmMapMatchNameToIdx(char* name)\n{\n    strlwr(name);\n\n    char* pch = name;\n    while (*pch != '\\0' && *pch != '.') {\n        pch++;\n    }\n\n    bool truncated = false;\n    if (*pch != '\\0') {\n        *pch = '\\0';\n        truncated = true;\n    }\n\n    int map = -1;\n\n    for (int index = 0; index < wmMaxMapNum; index++) {\n        if (strcmp(wmMapInfoList[index].mapFileName, name) == 0) {\n            map = index;\n            break;\n        }\n    }\n\n    if (truncated) {\n        *pch = '.';\n    }\n\n    return map;\n}\n\n// 0x4BFA44\nbool wmMapIdxIsSaveable(int mapIdx)\n{\n    return (wmMapInfoList[mapIdx].flags & MAP_SAVED) != 0;\n}\n\n// 0x4BFA64\nbool wmMapIsSaveable()\n{\n    return (wmMapInfoList[map_data.field_34].flags & MAP_SAVED) != 0;\n}\n\n// 0x4BFA90\nbool wmMapDeadBodiesAge()\n{\n    return (wmMapInfoList[map_data.field_34].flags & MAP_DEAD_BODIES_AGE) != 0;\n}\n\n// 0x4BFABC\nbool wmMapCanRestHere(int elevation)\n{\n    // 0x4BC860\n    static const int flags[ELEVATION_COUNT] = {\n        MAP_CAN_REST_ELEVATION_0,\n        MAP_CAN_REST_ELEVATION_1,\n        MAP_CAN_REST_ELEVATION_2,\n    };\n\n    MapInfo* map = &(wmMapInfoList[map_data.field_34]);\n\n    return (map->flags & flags[elevation]) != 0;\n}\n\n// 0x4BFAFC\nbool wmMapPipboyActive()\n{\n    return gmovie_has_been_played(MOVIE_VSUIT);\n}\n\n// 0x4BFB08\nint wmMapMarkVisited(int mapIdx)\n{\n    if (mapIdx < 0 || mapIdx >= wmMaxMapNum) {\n        return -1;\n    }\n\n    MapInfo* map = &(wmMapInfoList[mapIdx]);\n    if ((map->flags & MAP_SAVED) == 0) {\n        return 0;\n    }\n\n    int areaIdx;\n    if (wmMatchAreaContainingMapIdx(mapIdx, &areaIdx) == -1) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    wmAreaMarkVisited(areaIdx);\n\n    return 0;\n}\n\n// 0x4BFB64\nstatic int wmMatchEntranceFromMap(int areaIdx, int mapIdx, int* entranceIdxPtr)\n{\n    CityInfo* city = &(wmAreaInfoList[areaIdx]);\n\n    for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) {\n        EntranceInfo* entrance = &(city->entrances[entranceIdx]);\n\n        if (mapIdx == entrance->map) {\n            *entranceIdxPtr = entranceIdx;\n            return 0;\n        }\n    }\n\n    *entranceIdxPtr = -1;\n    return -1;\n}\n\n// 0x4BFBE8\nstatic int wmMatchEntranceElevFromMap(int cityIdx, int map, int elevation, int* entranceIdxPtr)\n{\n    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n\n    for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) {\n        EntranceInfo* entrance = &(city->entrances[entranceIdx]);\n        if (entrance->map == map) {\n            if (elevation == -1 || entrance->elevation == -1 || elevation == entrance->elevation) {\n                *entranceIdxPtr = entranceIdx;\n                return 0;\n            }\n        }\n    }\n\n    *entranceIdxPtr = -1;\n    return -1;\n}\n\n// 0x4BFC7C\nstatic int wmMatchAreaFromMap(int mapIdx, int* cityIdxPtr)\n{\n    for (int cityIdx = 0; cityIdx < wmMaxAreaNum; cityIdx++) {\n        CityInfo* city = &(wmAreaInfoList[cityIdx]);\n\n        for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) {\n            EntranceInfo* entrance = &(city->entrances[entranceIdx]);\n            if (mapIdx == entrance->map) {\n                *cityIdxPtr = cityIdx;\n                return 0;\n            }\n        }\n    }\n\n    *cityIdxPtr = -1;\n    return -1;\n}\n\n// Mark map entrance.\n//\n// 0x4BFD50\nint wmMapMarkMapEntranceState(int mapIdx, int elevation, int state)\n{\n    if (mapIdx < 0 || mapIdx >= wmMaxMapNum) {\n        return -1;\n    }\n\n    MapInfo* map = &(wmMapInfoList[mapIdx]);\n    if ((map->flags & MAP_SAVED) == 0) {\n        return -1;\n    }\n\n    int cityIdx;\n    if (wmMatchAreaContainingMapIdx(mapIdx, &cityIdx) == -1) {\n        return -1;\n    }\n\n    int entranceIdx;\n    if (wmMatchEntranceElevFromMap(cityIdx, mapIdx, elevation, &entranceIdx) == -1) {\n        return -1;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n    EntranceInfo* entrance = &(city->entrances[entranceIdx]);\n    entrance->state = state;\n\n    return 0;\n}\n\n// 0x4BFE0C\nvoid wmWorldMap()\n{\n    wmWorldMapFunc(0);\n}\n\n// 0x4BFE10\nstatic int wmWorldMapFunc(int a1)\n{\n    if (wmInterfaceInit() == -1) {\n        wmInterfaceExit();\n        return -1;\n    }\n\n    wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentAreaId));\n\n    unsigned int v24 = 0;\n    int map = -1;\n    int v25 = 0;\n\n    int rc = 0;\n    for (;;) {\n        int keyCode = get_input();\n        unsigned int tick = get_time();\n\n        int mouseX;\n        int mouseY;\n        mouse_get_position(&mouseX, &mouseY);\n\n        int v4 = wmWorldOffsetX + mouseX - WM_VIEW_X;\n        int v5 = wmWorldOffsetY + mouseY - WM_VIEW_Y;\n\n        if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n            game_quit_with_confirm();\n        }\n\n        // NOTE: Uninline.\n        wmCheckGameEvents();\n\n        if (game_user_wants_to_quit != 0) {\n            break;\n        }\n\n        int mouseEvent = mouse_get_buttons();\n\n        if (wmGenData.isWalking) {\n            wmPartyWalkingStep();\n\n            if (wmGenData.isInCar) {\n                wmPartyWalkingStep();\n                wmPartyWalkingStep();\n                wmPartyWalkingStep();\n\n                if (game_get_global_var(GVAR_CAR_BLOWER)) {\n                    wmPartyWalkingStep();\n                }\n\n                if (game_get_global_var(GVAR_NEW_RENO_CAR_UPGRADE)) {\n                    wmPartyWalkingStep();\n                }\n\n                if (game_get_global_var(GVAR_NEW_RENO_SUPER_CAR)) {\n                    wmPartyWalkingStep();\n                    wmPartyWalkingStep();\n                    wmPartyWalkingStep();\n                }\n\n                wmGenData.carImageCurrentFrameIndex++;\n                if (wmGenData.carImageCurrentFrameIndex >= art_frame_max_frame(wmGenData.carImageFrm)) {\n                    wmGenData.carImageCurrentFrameIndex = 0;\n                }\n\n                wmCarUseGas(100);\n\n                if (wmGenData.carFuel <= 0) {\n                    wmGenData.walkDestinationX = 0;\n                    wmGenData.walkDestinationY = 0;\n                    wmGenData.isWalking = false;\n\n                    wmMatchWorldPosToArea(v4, v5, &(wmGenData.currentAreaId));\n\n                    wmGenData.isInCar = false;\n\n                    if (wmGenData.currentAreaId == -1) {\n                        wmGenData.currentCarAreaId = CITY_CAR_OUT_OF_GAS;\n\n                        CityInfo* city = &(wmAreaInfoList[CITY_CAR_OUT_OF_GAS]);\n\n                        CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]);\n                        int worldmapX = wmGenData.worldPosX + wmGenData.hotspotFrmWidth / 2 + citySizeDescription->width / 2;\n                        int worldmapY = wmGenData.worldPosY + wmGenData.hotspotFrmHeight / 2 + citySizeDescription->height / 2;\n                        wmAreaSetWorldPos(CITY_CAR_OUT_OF_GAS, worldmapX, worldmapY);\n\n                        city->state = CITY_STATE_KNOWN;\n                        city->visitedState = 1;\n\n                        wmGenData.currentAreaId = CITY_CAR_OUT_OF_GAS;\n                    } else {\n                        wmGenData.currentCarAreaId = wmGenData.currentAreaId;\n                    }\n\n                    debug_printf(\"\\nRan outta gas!\");\n                }\n            }\n\n            wmInterfaceRefresh();\n\n            if (elapsed_tocks(tick, v24) > 1000) {\n                if (partyMemberRestingHeal(3)) {\n                    intface_update_hit_points(false);\n                    v24 = tick;\n                }\n            }\n\n            wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY);\n\n            if (wmGenData.walkDistance <= 0) {\n                wmGenData.isWalking = false;\n                wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentAreaId));\n            }\n\n            wmInterfaceRefresh();\n\n            if (wmGameTimeIncrement(18000)) {\n                if (game_user_wants_to_quit != 0) {\n                    break;\n                }\n            }\n\n            if (wmGenData.isWalking) {\n                if (wmRndEncounterOccurred()) {\n                    if (wmGenData.encounterMapId != -1) {\n                        if (wmGenData.isInCar) {\n                            wmMatchAreaContainingMapIdx(wmGenData.encounterMapId, &(wmGenData.currentCarAreaId));\n                        }\n                        map_load_idx(wmGenData.encounterMapId);\n                    }\n                    break;\n                }\n            }\n        }\n\n        if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0 && (mouseEvent & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0) {\n            if (mouse_click_in(WM_VIEW_X, WM_VIEW_Y, 472, 465)) {\n                if (!wmGenData.isWalking && !wmGenData.mousePressed && abs(wmGenData.worldPosX - v4) < 5 && abs(wmGenData.worldPosY - v5) < 5) {\n                    wmGenData.mousePressed = true;\n                    wmInterfaceRefresh();\n                }\n            } else {\n                continue;\n            }\n        }\n\n        if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n            if (wmGenData.mousePressed) {\n                wmGenData.mousePressed = false;\n                wmInterfaceRefresh();\n\n                if (abs(wmGenData.worldPosX - v4) < 5 && abs(wmGenData.worldPosY - v5) < 5) {\n                    if (wmGenData.currentAreaId != -1) {\n                        CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]);\n                        if (city->visitedState == 2 && city->mapFid != -1) {\n                            if (wmTownMapFunc(&map) == -1) {\n                                v25 = -1;\n                                break;\n                            }\n                        } else {\n                            if (wmAreaFindFirstValidMap(&map) == -1) {\n                                v25 = -1;\n                                break;\n                            }\n\n                            city->visitedState = 2;\n                        }\n                    } else {\n                        map = 0;\n                    }\n\n                    if (map != -1) {\n                        if (wmGenData.isInCar) {\n                            wmGenData.isInCar = false;\n                            if (wmGenData.currentAreaId == -1) {\n                                wmMatchAreaContainingMapIdx(map, &(wmGenData.currentCarAreaId));\n                            } else {\n                                wmGenData.currentCarAreaId = wmGenData.currentAreaId;\n                            }\n                        }\n                        map_load_idx(map);\n                        break;\n                    }\n                }\n            } else {\n                if (mouse_click_in(WM_VIEW_X, WM_VIEW_Y, 472, 465)) {\n                    wmPartyInitWalking(v4, v5);\n                }\n\n                wmGenData.mousePressed = false;\n            }\n        }\n\n        // NOTE: Uninline.\n        wmInterfaceScrollTabsUpdate();\n\n        if (keyCode == KEY_UPPERCASE_T || keyCode == KEY_LOWERCASE_T) {\n            if (!wmGenData.isWalking && wmGenData.currentAreaId != -1) {\n                CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]);\n                if (city->visitedState == 2 && city->mapFid != -1) {\n                    if (wmTownMapFunc(&map) == -1) {\n                        rc = -1;\n                    }\n\n                    if (map != -1) {\n                        if (wmGenData.isInCar) {\n                            wmMatchAreaContainingMapIdx(map, &(wmGenData.currentCarAreaId));\n                        }\n\n                        map_load_idx(map);\n                    }\n                }\n            }\n        } else if (keyCode == KEY_HOME) {\n            wmInterfaceCenterOnParty();\n        } else if (keyCode == KEY_ARROW_UP) {\n            // NOTE: Uninline.\n            wmInterfaceScroll(0, -1, NULL);\n        } else if (keyCode == KEY_ARROW_LEFT) {\n            // NOTE: Uninline.\n            wmInterfaceScroll(-1, 0, NULL);\n        } else if (keyCode == KEY_ARROW_DOWN) {\n            // NOTE: Uninline.\n            wmInterfaceScroll(0, 1, NULL);\n        } else if (keyCode == KEY_ARROW_RIGHT) {\n            // NOTE: Uninline.\n            wmInterfaceScroll(1, 0, NULL);\n        } else if (keyCode == KEY_CTRL_ARROW_UP) {\n            wmInterfaceScrollTabsStart(-27);\n        } else if (keyCode == KEY_CTRL_ARROW_DOWN) {\n            wmInterfaceScrollTabsStart(27);\n        } else if (keyCode >= KEY_CTRL_F1 && keyCode <= KEY_CTRL_F7) {\n            int quickDestinationIndex = wmGenData.tabsOffsetY / 27 + (keyCode - KEY_CTRL_F1);\n            if (quickDestinationIndex < wmLabelCount) {\n                int cityIdx = wmLabelList[quickDestinationIndex];\n                CityInfo* city = &(wmAreaInfoList[cityIdx]);\n                if (wmAreaIsKnown(city->areaId)) {\n                    if (wmGenData.currentAreaId != cityIdx) {\n                        wmPartyInitWalking(city->x, city->y);\n                        wmGenData.mousePressed = false;\n                    }\n                }\n            }\n        }\n\n        if (map != -1 || v25 == -1) {\n            break;\n        }\n    }\n\n    if (wmInterfaceExit() == -1) {\n        return -1;\n    }\n\n    return rc;\n}\n\n// 0x4C056C\nint wmCheckGameAreaEvents()\n{\n    if (wmGenData.currentAreaId == CITY_FAKE_VAULT_13_A) {\n        if (wmGenData.currentAreaId < wmMaxAreaNum) {\n            wmAreaInfoList[CITY_FAKE_VAULT_13_A].state = CITY_STATE_UNKNOWN;\n        }\n\n        if (wmMaxAreaNum > CITY_FAKE_VAULT_13_B) {\n            wmAreaInfoList[CITY_FAKE_VAULT_13_B].state = CITY_STATE_KNOWN;\n        }\n\n        wmAreaMarkVisitedState(CITY_FAKE_VAULT_13_B, 2);\n    }\n\n    return 0;\n}\n\n// 0x4C05C4\nstatic int wmInterfaceCenterOnParty()\n{\n    int v0;\n    int v1;\n\n    v0 = wmGenData.worldPosX - 203;\n    if ((v0 & 0x80000000) == 0) {\n        if (v0 > wmGenData.viewportMaxX) {\n            v0 = wmGenData.viewportMaxX;\n        }\n    } else {\n        v0 = 0;\n    }\n\n    v1 = wmGenData.worldPosY - 200;\n    if ((v1 & 0x80000000) == 0) {\n        if (v1 > wmGenData.viewportMaxY) {\n            v1 = wmGenData.viewportMaxY;\n        }\n    } else {\n        v1 = 0;\n    }\n\n    wmWorldOffsetX = v0;\n    wmWorldOffsetY = v1;\n\n    wmInterfaceRefresh();\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4C0624\nstatic void wmCheckGameEvents()\n{\n    scriptsCheckGameEvents(NULL, wmBkWin);\n}\n\n// 0x4C0634\nstatic int wmRndEncounterOccurred()\n{\n    unsigned int v0 = get_time();\n    if (elapsed_tocks(v0, wmLastRndTime) < 1500) {\n        return 0;\n    }\n\n    wmLastRndTime = v0;\n\n    if (abs(wmGenData.oldWorldPosX - wmGenData.worldPosX) < 3) {\n        return 0;\n    }\n\n    if (abs(wmGenData.oldWorldPosY - wmGenData.worldPosY) < 3) {\n        return 0;\n    }\n\n    int v26;\n    wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &v26);\n    if (v26 != -1) {\n        return 0;\n    }\n\n    if (!wmGenData.didMeetFrankHorrigan) {\n        unsigned int gameTime = game_time();\n        if (gameTime / GAME_TIME_TICKS_PER_DAY > 35) {\n            wmGenData.encounterMapId = v26;\n            wmGenData.didMeetFrankHorrigan = true;\n            if (wmGenData.isInCar) {\n                wmMatchAreaContainingMapIdx(MAP_IN_GAME_MOVIE1, &(wmGenData.currentCarAreaId));\n            }\n            map_load_idx(MAP_IN_GAME_MOVIE1);\n            return 1;\n        }\n    }\n\n    // NOTE: Uninline.\n    wmPartyFindCurSubTile();\n\n    int dayPart;\n    int gameTimeHour = game_time_hour();\n    if (gameTimeHour >= 1800 || gameTimeHour < 600) {\n        dayPart = DAY_PART_NIGHT;\n    } else if (gameTimeHour >= 1200) {\n        dayPart = DAY_PART_AFTERNOON;\n    } else {\n        dayPart = DAY_PART_MORNING;\n    }\n\n    int frequency = wmFreqValues[wmGenData.currentSubtile->encounterChance[dayPart]];\n    if (frequency > 0 && frequency < 100) {\n        int gameDifficulty = GAME_DIFFICULTY_NORMAL;\n        if (config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty)) {\n            int modifier = frequency / 15;\n            switch (gameDifficulty) {\n            case GAME_DIFFICULTY_EASY:\n                frequency -= modifier;\n                break;\n            case GAME_DIFFICULTY_HARD:\n                frequency += modifier;\n                break;\n            }\n        }\n    }\n\n    int chance = roll_random(0, 100);\n    if (chance >= frequency) {\n        return 0;\n    }\n\n    wmRndEncounterPick();\n\n    int v8 = 1;\n    wmGenData.encounterIconIsVisible = 1;\n    wmGenData.encounterCursorId = 0;\n\n    EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]);\n    EncounterEntry* encounter = &(encounterTable->entries[wmGenData.encounterEntryId]);\n    if ((encounter->flags & ENCOUNTER_ENTRY_SPECIAL) != 0) {\n        wmGenData.encounterCursorId = 2;\n        wmMatchAreaContainingMapIdx(wmGenData.encounterMapId, &v26);\n\n        CityInfo* city = &(wmAreaInfoList[v26]);\n        CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]);\n        int worldmapX = wmGenData.worldPosX + wmGenData.hotspotFrmWidth / 2 + citySizeDescription->width / 2;\n        int worldmapY = wmGenData.worldPosY + wmGenData.hotspotFrmHeight / 2 + citySizeDescription->height / 2;\n        wmAreaSetWorldPos(v26, worldmapX, worldmapY);\n        v8 = 3;\n\n        if (v26 >= 0 && v26 < wmMaxAreaNum) {\n            CityInfo* city = &(wmAreaInfoList[v26]);\n            if (city->lockState != LOCK_STATE_LOCKED) {\n                city->state = CITY_STATE_KNOWN;\n            }\n        }\n    }\n\n    // Blinking.\n    for (int index = 0; index < 7; index++) {\n        wmGenData.encounterCursorId = v8 - wmGenData.encounterCursorId;\n\n        if (wmInterfaceRefresh() == -1) {\n            return -1;\n        }\n\n        block_for_tocks(200);\n    }\n\n    if (wmGenData.isInCar) {\n        // 0x4BC86C\n        static const int modifiers[DAY_PART_COUNT] = {\n            40,\n            30,\n            0,\n        };\n\n        frequency -= modifiers[dayPart];\n    }\n\n    bool randomEncounterIsDetected = false;\n    if (frequency > chance) {\n        int outdoorsman = partyMemberHighestSkillLevel(SKILL_OUTDOORSMAN);\n        Object* scanner = inven_pid_is_carried(obj_dude, PROTO_ID_MOTION_SENSOR);\n        if (scanner != NULL) {\n            if (obj_dude == scanner->owner) {\n                outdoorsman += 20;\n            }\n        }\n\n        if (outdoorsman > 95) {\n            outdoorsman = 95;\n        }\n\n        TileInfo* tile;\n        // NOTE: Uninline.\n        wmFindCurTileFromPos(wmGenData.worldPosX, wmGenData.worldPosY, &tile);\n        debug_printf(\"\\nEncounter Difficulty Mod: %d\", tile->encounterDifficultyModifier);\n\n        outdoorsman += tile->encounterDifficultyModifier;\n\n        if (roll_random(1, 100) < outdoorsman) {\n            randomEncounterIsDetected = true;\n\n            int xp = 100 - outdoorsman;\n            if (xp > 0) {\n                MessageListItem messageListItem;\n                char* text = getmsg(&misc_message_file, &messageListItem, 8500);\n                if (strlen(text) < 110) {\n                    char formattedText[120];\n                    sprintf(formattedText, text, xp);\n                    display_print(formattedText);\n                } else {\n                    debug_printf(\"WorldMap: Error: Rnd Encounter string too long!\");\n                }\n\n                debug_printf(\"WorldMap: Giving Player [%d] Experience For Catching Rnd Encounter!\", xp);\n\n                if (xp < 100) {\n                    stat_pc_add_experience(xp);\n                }\n            }\n        }\n    } else {\n        randomEncounterIsDetected = true;\n    }\n\n    wmGenData.oldWorldPosX = wmGenData.worldPosX;\n    wmGenData.oldWorldPosY = wmGenData.worldPosY;\n\n    if (randomEncounterIsDetected) {\n        MessageListItem messageListItem;\n\n        const char* title = gWorldmapEncDefaultMsg[0];\n        const char* body = gWorldmapEncDefaultMsg[1];\n\n        title = getmsg(&wmMsgFile, &messageListItem, 2999);\n        body = getmsg(&wmMsgFile, &messageListItem, 3000 + 50 * wmGenData.encounterTableId + wmGenData.encounterEntryId);\n        if (dialog_out(title, &body, 1, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE | DIALOG_BOX_YES_NO) == 0) {\n            wmGenData.encounterIconIsVisible = 0;\n            wmGenData.encounterMapId = -1;\n            wmGenData.encounterTableId = -1;\n            wmGenData.encounterEntryId = -1;\n            return 0;\n        }\n    }\n\n    return 1;\n}\n\n// NOTE: Inlined.\n//\n// 0x4C0BE4\nstatic int wmPartyFindCurSubTile()\n{\n    return wmFindCurSubTileFromPos(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentSubtile));\n}\n\n// 0x4C0C00\nstatic int wmFindCurSubTileFromPos(int x, int y, SubtileInfo** subtilePtr)\n{\n    int tileIndex = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles;\n    TileInfo* tile = &(wmTileInfoList[tileIndex]);\n\n    int column = y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE;\n    int row = x % WM_TILE_WIDTH / WM_SUBTILE_SIZE;\n    *subtilePtr = &(tile->subtiles[column][row]);\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4C0CA8\nstatic int wmFindCurTileFromPos(int x, int y, TileInfo** tilePtr)\n{\n    int tileIndex = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles;\n    *tilePtr = &(wmTileInfoList[tileIndex]);\n\n    return 0;\n}\n\n// 0x4C0CF4\nstatic int wmRndEncounterPick()\n{\n    if (wmGenData.currentSubtile == NULL) {\n        // NOTE: Uninline.\n        wmPartyFindCurSubTile();\n    }\n\n    wmGenData.encounterTableId = wmGenData.currentSubtile->encounterType;\n\n    EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]);\n\n    int candidates[41];\n    int candidatesLength = 0;\n    int totalChance = 0;\n    for (int index = 0; index < encounterTable->entriesLength; index++) {\n        EncounterEntry* encounterTableEntry = &(encounterTable->entries[index]);\n\n        bool selected = true;\n        if (wmEvalConditional(&(encounterTableEntry->condition), NULL) == 0) {\n            selected = false;\n        }\n\n        if (encounterTableEntry->counter == 0) {\n            selected = false;\n        }\n\n        if (selected) {\n            candidates[candidatesLength++] = index;\n            totalChance += encounterTableEntry->chance;\n        }\n    }\n\n    int v1 = critterGetStat(obj_dude, STAT_LUCK) - 5;\n    int v2 = roll_random(0, totalChance) + v1;\n\n    if (perkHasRank(obj_dude, PERK_EXPLORER)) {\n        v2 += 2;\n    }\n\n    if (perkHasRank(obj_dude, PERK_RANGER)) {\n        v2++;\n    }\n\n    if (perkHasRank(obj_dude, PERK_SCOUT)) {\n        v2++;\n    }\n\n    int gameDifficulty;\n    if (config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty)) {\n        switch (gameDifficulty) {\n        case GAME_DIFFICULTY_EASY:\n            v2 += 5;\n            if (v2 > totalChance) {\n                v2 = totalChance;\n            }\n            break;\n        case GAME_DIFFICULTY_HARD:\n            v2 -= 5;\n            if (v2 < 0) {\n                v2 = 0;\n            }\n            break;\n        }\n    }\n\n    int index;\n    for (index = 0; index < candidatesLength; index++) {\n        EncounterEntry* encounterTableEntry = &(encounterTable->entries[candidates[index]]);\n        if (v2 < encounterTableEntry->chance) {\n            break;\n        }\n\n        v2 -= encounterTableEntry->chance;\n    }\n\n    if (index == candidatesLength) {\n        index = candidatesLength - 1;\n    }\n\n    wmGenData.encounterEntryId = candidates[index];\n\n    EncounterEntry* encounterTableEntry = &(encounterTable->entries[wmGenData.encounterEntryId]);\n    if (encounterTableEntry->counter > 0) {\n        encounterTableEntry->counter--;\n    }\n\n    if (encounterTableEntry->map == -1) {\n        if (encounterTable->mapsLength <= 0) {\n            Terrain* terrain = &(wmTerrainTypeList[wmGenData.currentSubtile->terrain]);\n            int randommapIdx = roll_random(0, terrain->mapsLength - 1);\n            wmGenData.encounterMapId = terrain->maps[randommapIdx];\n        } else {\n            int randommapIdx = roll_random(0, encounterTable->mapsLength - 1);\n            wmGenData.encounterMapId = encounterTable->maps[randommapIdx];\n        }\n    } else {\n        wmGenData.encounterMapId = encounterTableEntry->map;\n    }\n\n    return 0;\n}\n\n// 0x4C0FA4\nint wmSetupRandomEncounter()\n{\n    MessageListItem messageListItem;\n    char* msg;\n\n    if (wmGenData.encounterMapId == -1) {\n        return 0;\n    }\n\n    EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]);\n    EncounterEntry* encounterTableEntry = &(encounterTable->entries[wmGenData.encounterEntryId]);\n\n    // You encounter:\n    msg = getmsg(&wmMsgFile, &messageListItem, 2998);\n    display_print(msg);\n\n    msg = getmsg(&wmMsgFile, &messageListItem, 3000 + 50 * wmGenData.encounterTableId + wmGenData.encounterEntryId);\n    display_print(msg);\n\n    int gameDifficulty;\n    switch (encounterTableEntry->scenery) {\n    case ENCOUNTER_SCENERY_TYPE_NONE:\n    case ENCOUNTER_SCENERY_TYPE_LIGHT:\n    case ENCOUNTER_SCENERY_TYPE_NORMAL:\n    case ENCOUNTER_SCENERY_TYPE_HEAVY:\n        debug_printf(\"\\nwmSetupRandomEncounter: Scenery Type: %s\", wmSceneryStrs[encounterTableEntry->scenery]);\n        config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty);\n        break;\n    default:\n        debug_printf(\"\\nERROR: wmSetupRandomEncounter: invalid Scenery Type!\");\n        return -1;\n    }\n\n    Object* v0 = NULL;\n    for (int i = 0; i < encounterTableEntry->field_50; i++) {\n        ENCOUNTER_ENTRY_ENC* v3 = &(encounterTableEntry->field_54[i]);\n\n        int v9 = roll_random(v3->minQuantity, v3->maxQuantity);\n\n        switch (gameDifficulty) {\n        case GAME_DIFFICULTY_EASY:\n            v9 -= 2;\n            if (v9 < v3->minQuantity) {\n                v9 = v3->minQuantity;\n            }\n            break;\n        case GAME_DIFFICULTY_HARD:\n            v9 += 2;\n            break;\n        }\n\n        int partyMemberCount = getPartyMemberCount();\n        if (partyMemberCount > 2) {\n            v9 += 2;\n        }\n\n        if (v9 != 0) {\n            Object* v35;\n            if (wmSetupCritterObjs(v3->field_8, &v35, v9) == -1) {\n                scripts_request_worldmap();\n                return -1;\n            }\n\n            if (i > 0) {\n                if (v0 != NULL) {\n                    if (v0 != v35) {\n                        if (encounterTableEntry->field_50 != 1) {\n                            if (encounterTableEntry->field_50 == 2 && !isInCombat()) {\n                                v0->data.critter.combat.whoHitMe = v35;\n                                v35->data.critter.combat.whoHitMe = v0;\n\n                                STRUCT_664980 combat;\n                                combat.attacker = v0;\n                                combat.defender = v35;\n                                combat.actionPointsBonus = 0;\n                                combat.accuracyBonus = 0;\n                                combat.damageBonus = 0;\n                                combat.minDamage = 0;\n                                combat.maxDamage = 500;\n                                combat.field_1C = 0;\n\n                                caiSetupTeamCombat(v35, v0);\n                                scripts_request_combat_locked(&combat);\n                            }\n                        } else {\n                            if (!isInCombat()) {\n                                v0->data.critter.combat.whoHitMe = obj_dude;\n\n                                STRUCT_664980 combat;\n                                combat.attacker = v0;\n                                combat.defender = obj_dude;\n                                combat.actionPointsBonus = 0;\n                                combat.accuracyBonus = 0;\n                                combat.damageBonus = 0;\n                                combat.minDamage = 0;\n                                combat.maxDamage = 500;\n                                combat.field_1C = 0;\n\n                                caiSetupTeamCombat(obj_dude, v0);\n                                scripts_request_combat_locked(&combat);\n                            }\n                        }\n                    }\n                }\n            }\n\n            v0 = v35;\n        }\n    }\n\n    return 0;\n}\n\n// wmSetupCritterObjs\n// 0x4C11FC\nstatic int wmSetupCritterObjs(int type_idx, Object** critterPtr, int critterCount)\n{\n    if (type_idx == -1) {\n        return 0;\n    }\n\n    *critterPtr = 0;\n\n    ENC_BASE_TYPE* v25 = &(wmEncBaseTypeList[type_idx]);\n\n    debug_printf(\"\\nwmSetupCritterObjs: typeIdx: %d, Formation: %s\", type_idx, wmFormationStrs[v25->position]);\n\n    if (wmSetupRndNextTileNumInit(v25) == -1) {\n        return -1;\n    }\n\n    for (int i = 0; i < v25->field_34; i++) {\n        ENC_BASE_TYPE_38* v5 = &(v25->field_38[i]);\n\n        if (v5->pid == -1) {\n            continue;\n        }\n\n        if (!wmEvalConditional(&(v5->condition), &critterCount)) {\n            continue;\n        }\n\n        int v23;\n        switch (v5->field_2C) {\n        case 0:\n            v23 = v5->ratio * critterCount / 100;\n            break;\n        case 1:\n            v23 = 1;\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        if (v23 < 1) {\n            v23 = 1;\n        }\n\n        for (int j = 0; j < v23; j++) {\n            int tile;\n            if (wmSetupRndNextTileNum(v25, v5, &tile) == -1) {\n                debug_printf(\"\\nERROR: wmSetupCritterObjs: wmSetupRndNextTileNum:\");\n                continue;\n            }\n\n            if (v5->pid == -1) {\n                continue;\n            }\n\n            Object* object;\n            if (obj_pid_new(&object, v5->pid) == -1) {\n                return -1;\n            }\n\n            if (*critterPtr == NULL) {\n                if (PID_TYPE(v5->pid) == OBJ_TYPE_CRITTER) {\n                    *critterPtr = object;\n                }\n            }\n\n            if (v5->team != -1) {\n                if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n                    object->data.critter.combat.team = v5->team;\n                }\n            }\n\n            if (v5->script != -1) {\n                if (object->sid != -1) {\n                    scr_remove(object->sid);\n                    object->sid = -1;\n                }\n\n                obj_new_sid_inst(object, SCRIPT_TYPE_CRITTER, v5->script - 1);\n            }\n\n            if (v25->position != ENCOUNTER_FORMATION_TYPE_SURROUNDING) {\n                obj_move_to_tile(object, tile, map_elevation, NULL);\n            } else {\n                obj_attempt_placement(object, tile, 0, 0);\n            }\n\n            int direction = tile_dir(tile, obj_dude->tile);\n            obj_set_rotation(object, direction, NULL);\n\n            for (int itemIndex = 0; itemIndex < v5->itemsLength; itemIndex++) {\n                ENC_BASE_TYPE_38_48* v10 = &(v5->items[itemIndex]);\n\n                int quantity;\n                if (v10->maximumQuantity == v10->minimumQuantity) {\n                    quantity = v10->maximumQuantity;\n                } else {\n                    quantity = roll_random(v10->minimumQuantity, v10->maximumQuantity);\n                }\n\n                if (quantity == 0) {\n                    continue;\n                }\n\n                Object* item;\n                if (obj_pid_new(&item, v10->pid) == -1) {\n                    return -1;\n                }\n\n                if (v10->pid == PROTO_ID_MONEY) {\n                    if (perkHasRank(obj_dude, PERK_FORTUNE_FINDER)) {\n                        quantity *= 2;\n                    }\n                }\n\n                if (item_add_force(object, item, quantity) == -1) {\n                    return -1;\n                }\n\n                obj_disconnect(item, NULL);\n\n                if (v10->isEquipped) {\n                    if (inven_wield(object, item, 1) == -1) {\n                        debug_printf(\"\\nERROR: wmSetupCritterObjs: Inven Wield Failed: %d on %s: Critter Fid: %d\", item->pid, critter_name(object), object->fid);\n                    }\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C155C\nstatic int wmSetupRndNextTileNumInit(ENC_BASE_TYPE* a1)\n{\n    for (int index = 0; index < 2; index++) {\n        wmRndCenterRotations[index] = 0;\n        wmRndTileDirs[index] = 0;\n        wmRndCenterTiles[index] = -1;\n\n        if (index & 1) {\n            wmRndRotOffsets[index] = 5;\n        } else {\n            wmRndRotOffsets[index] = 1;\n        }\n    }\n\n    wmRndCallCount = 0;\n\n    switch (a1->position) {\n    case ENCOUNTER_FORMATION_TYPE_SURROUNDING:\n        wmRndCenterTiles[0] = obj_dude->tile;\n        wmRndTileDirs[0] = roll_random(0, ROTATION_COUNT - 1);\n\n        wmRndOriginalCenterTile = wmRndCenterTiles[0];\n\n        return 0;\n    case ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE:\n    case ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE:\n    case ENCOUNTER_FORMATION_TYPE_WEDGE:\n    case ENCOUNTER_FORMATION_TYPE_CONE:\n    case ENCOUNTER_FORMATION_TYPE_HUDDLE:\n        if (1) {\n            MapInfo* map = &(wmMapInfoList[map_data.field_34]);\n            if (map->startPointsLength != 0) {\n                int rspIndex = roll_random(0, map->startPointsLength - 1);\n                MapStartPointInfo* rsp = &(map->startPoints[rspIndex]);\n\n                wmRndCenterTiles[0] = rsp->tile;\n                wmRndCenterTiles[1] = wmRndCenterTiles[0];\n\n                wmRndCenterRotations[0] = rsp->field_8;\n                wmRndCenterRotations[1] = wmRndCenterRotations[0];\n            } else {\n                wmRndCenterRotations[0] = 0;\n                wmRndCenterRotations[1] = 0;\n\n                wmRndCenterTiles[0] = obj_dude->tile;\n                wmRndCenterTiles[1] = obj_dude->tile;\n            }\n\n            wmRndTileDirs[0] = tile_dir(wmRndCenterTiles[0], obj_dude->tile);\n            wmRndTileDirs[1] = tile_dir(wmRndCenterTiles[1], obj_dude->tile);\n\n            wmRndOriginalCenterTile = wmRndCenterTiles[0];\n\n            return 0;\n        }\n    default:\n        debug_printf(\"\\nERROR: wmSetupCritterObjs: invalid Formation Type!\");\n\n        return -1;\n    }\n}\n\n// wmSetupRndNextTileNum\n// 0x4C16F0\nstatic int wmSetupRndNextTileNum(ENC_BASE_TYPE* a1, ENC_BASE_TYPE_38* a2, int* out_tile_num)\n{\n    int tile_num;\n\n    int attempt = 0;\n    while (1) {\n        switch (a1->position) {\n        case ENCOUNTER_FORMATION_TYPE_SURROUNDING:\n            if (1) {\n                int distance;\n                if (a2->distance != 0) {\n                    distance = a2->distance;\n                } else {\n                    distance = roll_random(-2, 2);\n\n                    distance += critterGetStat(obj_dude, STAT_PERCEPTION);\n\n                    if (perkHasRank(obj_dude, PERK_CAUTIOUS_NATURE)) {\n                        distance += 3;\n                    }\n                }\n\n                if (distance < 0) {\n                    distance = 0;\n                }\n\n                int origin = a2->tile;\n                if (origin == -1) {\n                    origin = tile_num_in_direction(obj_dude->tile, wmRndTileDirs[0], distance);\n                }\n\n                if (++wmRndTileDirs[0] >= ROTATION_COUNT) {\n                    wmRndTileDirs[0] = 0;\n                }\n\n                int randomizedDistance = roll_random(0, distance / 2);\n                int randomizedRotation = roll_random(0, ROTATION_COUNT - 1);\n                tile_num = tile_num_in_direction(origin, (randomizedRotation + wmRndTileDirs[0]) % ROTATION_COUNT, randomizedDistance);\n            }\n            break;\n        case ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE:\n            tile_num = wmRndCenterTiles[wmRndIndex];\n            if (wmRndCallCount != 0) {\n                int rotation = (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT;\n                int origin = tile_num_in_direction(wmRndCenterTiles[wmRndIndex], rotation, a1->spacing);\n                int v13 = tile_num_in_direction(origin, (rotation + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, a1->spacing);\n                wmRndCenterTiles[wmRndIndex] = v13;\n                wmRndIndex = 1 - wmRndIndex;\n                tile_num = v13;\n            }\n            break;\n        case ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE:\n            tile_num = wmRndCenterTiles[wmRndIndex];\n            if (wmRndCallCount != 0) {\n                int rotation = (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT;\n                int origin = tile_num_in_direction(wmRndCenterTiles[wmRndIndex], rotation, a1->spacing);\n                int v17 = tile_num_in_direction(origin, (rotation + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, a1->spacing);\n                wmRndCenterTiles[wmRndIndex] = v17;\n                wmRndIndex = 1 - wmRndIndex;\n                tile_num = v17;\n            }\n            break;\n        case ENCOUNTER_FORMATION_TYPE_WEDGE:\n            tile_num = wmRndCenterTiles[wmRndIndex];\n            if (wmRndCallCount != 0) {\n                tile_num = tile_num_in_direction(wmRndCenterTiles[wmRndIndex], (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT, a1->spacing);\n                wmRndCenterTiles[wmRndIndex] = tile_num;\n                wmRndIndex = 1 - wmRndIndex;\n            }\n            break;\n        case ENCOUNTER_FORMATION_TYPE_CONE:\n            tile_num = wmRndCenterTiles[wmRndIndex];\n            if (wmRndCallCount != 0) {\n                tile_num = tile_num_in_direction(wmRndCenterTiles[wmRndIndex], (wmRndTileDirs[wmRndIndex] + 3 + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, a1->spacing);\n                wmRndCenterTiles[wmRndIndex] = tile_num;\n                wmRndIndex = 1 - wmRndIndex;\n            }\n            break;\n        case ENCOUNTER_FORMATION_TYPE_HUDDLE:\n            tile_num = wmRndCenterTiles[0];\n            if (wmRndCallCount != 0) {\n                wmRndTileDirs[0] = (wmRndTileDirs[0] + 1) % ROTATION_COUNT;\n                tile_num = tile_num_in_direction(wmRndCenterTiles[0], wmRndTileDirs[0], a1->spacing);\n                wmRndCenterTiles[0] = tile_num;\n            }\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        ++attempt;\n        ++wmRndCallCount;\n\n        if (wmEvalTileNumForPlacement(tile_num)) {\n            break;\n        }\n\n        debug_printf(\"\\nWARNING: EVAL-TILE-NUM FAILED!\");\n\n        if (tile_dist(wmRndOriginalCenterTile, wmRndCenterTiles[wmRndIndex]) > 25) {\n            return -1;\n        }\n\n        if (attempt > 25) {\n            return -1;\n        }\n    }\n\n    debug_printf(\"\\nwmSetupRndNextTileNum:TileNum: %d\", tile_num);\n\n    *out_tile_num = tile_num;\n\n    return 0;\n}\n\n// 0x4C1A64\nbool wmEvalTileNumForPlacement(int tile)\n{\n    if (obj_blocking_at(obj_dude, tile, map_elevation) != NULL) {\n        return false;\n    }\n\n    if (make_path_func(obj_dude, obj_dude->tile, tile, NULL, 0, obj_shoot_blocking_at) == 0) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4C1AC8\nstatic bool wmEvalConditional(EncounterCondition* a1, int* a2)\n{\n    int value;\n\n    bool matches = true;\n    for (int index = 0; index < a1->entriesLength; index++) {\n        EncounterConditionEntry* ptr = &(a1->entries[index]);\n\n        matches = true;\n        switch (ptr->type) {\n        case ENCOUNTER_CONDITION_TYPE_GLOBAL:\n            value = game_get_global_var(ptr->param);\n            if (!wmEvalSubConditional(value, ptr->conditionalOperator, ptr->value)) {\n                matches = false;\n            }\n            break;\n        case ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS:\n            if (!wmEvalSubConditional(*a2, ptr->conditionalOperator, ptr->value)) {\n                matches = false;\n            }\n            break;\n        case ENCOUNTER_CONDITION_TYPE_RANDOM:\n            value = roll_random(0, 100);\n            if (value > ptr->param) {\n                matches = false;\n            }\n            break;\n        case ENCOUNTER_CONDITION_TYPE_PLAYER:\n            value = stat_pc_get(PC_STAT_LEVEL);\n            if (!wmEvalSubConditional(value, ptr->conditionalOperator, ptr->value)) {\n                matches = false;\n            }\n            break;\n        case ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED:\n            value = game_time();\n            if (!wmEvalSubConditional(value / GAME_TIME_TICKS_PER_DAY, ptr->conditionalOperator, ptr->value)) {\n                matches = false;\n            }\n            break;\n        case ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY:\n            value = game_time_hour();\n            if (!wmEvalSubConditional(value / 100, ptr->conditionalOperator, ptr->value)) {\n                matches = false;\n            }\n            break;\n        }\n\n        if (!matches) {\n            // FIXME: Can overflow with all 3 conditions specified.\n            if (a1->logicalOperators[index] == ENCOUNTER_LOGICAL_OPERATOR_AND) {\n                break;\n            }\n        }\n    }\n\n    return matches;\n}\n\n// 0x4C1C0C\nstatic bool wmEvalSubConditional(int operand1, int condionalOperator, int operand2)\n{\n    switch (condionalOperator) {\n    case ENCOUNTER_CONDITIONAL_OPERATOR_EQUAL:\n        return operand1 == operand2;\n    case ENCOUNTER_CONDITIONAL_OPERATOR_NOT_EQUAL:\n        return operand1 != operand2;\n    case ENCOUNTER_CONDITIONAL_OPERATOR_LESS_THAN:\n        return operand1 < operand2;\n    case ENCOUNTER_CONDITIONAL_OPERATOR_GREATER_THAN:\n        return operand1 > operand2;\n    }\n\n    return false;\n}\n\n// 0x4C1C50\nstatic bool wmGameTimeIncrement(int a1)\n{\n    if (a1 == 0) {\n        return false;\n    }\n\n    while (a1 != 0) {\n        unsigned int gameTime = game_time();\n        unsigned int nextEventTime = queue_next_time();\n        int v1 = nextEventTime >= gameTime ? a1 : nextEventTime - gameTime;\n        a1 -= v1;\n\n        inc_game_time(v1);\n\n        // NOTE: Uninline.\n        wmInterfaceDialSyncTime(true);\n\n        wmInterfaceRefreshDate(true);\n\n        if (queue_process()) {\n            break;\n        }\n    }\n\n    return true;\n}\n\n// Reads .msk file if needed.\n//\n// 0x4C1CE8\nstatic int wmGrabTileWalkMask(int tileIdx)\n{\n    TileInfo* tileInfo = &(wmTileInfoList[tileIdx]);\n    if (tileInfo->walkMaskData != NULL) {\n        return 0;\n    }\n\n    if (*tileInfo->walkMaskName == '\\0') {\n        return 0;\n    }\n\n    tileInfo->walkMaskData = (unsigned char*)mem_malloc(13200);\n    if (tileInfo->walkMaskData == NULL) {\n        return -1;\n    }\n\n    char path[MAX_PATH];\n    sprintf(path, \"data\\\\%s.msk\", tileInfo->walkMaskName);\n\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int rc = 0;\n\n    if (db_freadByteCount(stream, tileInfo->walkMaskData, 13200) == -1) {\n        rc = -1;\n    }\n\n    db_fclose(stream);\n\n    return rc;\n}\n\n// 0x4C1D9C\nstatic bool wmWorldPosInvalid(int x, int y)\n{\n    int tileIdx = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles;\n    if (wmGrabTileWalkMask(tileIdx) == -1) {\n        return false;\n    }\n\n    TileInfo* tileDescription = &(wmTileInfoList[tileIdx]);\n    unsigned char* mask = tileDescription->walkMaskData;\n    if (mask == NULL) {\n        return false;\n    }\n\n    // Mask length is 13200, which is 300 * 44\n    // 44 * 8 is 352, which is probably left 2 bytes intact\n    // TODO: Check math.\n    int pos = (y % WM_TILE_HEIGHT) * 44 + (x % WM_TILE_WIDTH) / 8;\n    int bit = 1 << (((x % WM_TILE_WIDTH) / 8) & 3);\n    return (mask[pos] & bit) != 0;\n}\n\n// 0x4C1E54\nstatic void wmPartyInitWalking(int x, int y)\n{\n    wmGenData.walkDestinationX = x;\n    wmGenData.walkDestinationY = y;\n    wmGenData.currentAreaId = -1;\n    wmGenData.isWalking = true;\n\n    int dx = abs(x - wmGenData.worldPosX);\n    int dy = abs(y - wmGenData.worldPosY);\n\n    if (dx < dy) {\n        wmGenData.walkDistance = dy;\n        wmGenData.walkLineDeltaMainAxisStep = 2 * dx;\n        wmGenData.walkWorldPosMainAxisStepX = 0;\n        wmGenData.walkLineDelta = 2 * dx - dy;\n        wmGenData.walkLineDeltaCrossAxisStep = 2 * (dx - dy);\n        wmGenData.walkWorldPosCrossAxisStepX = 1;\n        wmGenData.walkWorldPosMainAxisStepY = 1;\n        wmGenData.walkWorldPosCrossAxisStepY = 1;\n    } else {\n        wmGenData.walkDistance = dx;\n        wmGenData.walkLineDeltaMainAxisStep = 2 * dy;\n        wmGenData.walkWorldPosMainAxisStepY = 0;\n        wmGenData.walkLineDelta = 2 * dy - dx;\n        wmGenData.walkLineDeltaCrossAxisStep = 2 * (dy - dx);\n        wmGenData.walkWorldPosMainAxisStepX = 1;\n        wmGenData.walkWorldPosCrossAxisStepX = 1;\n        wmGenData.walkWorldPosCrossAxisStepY = 1;\n    }\n\n    if (wmGenData.walkDestinationX < wmGenData.worldPosX) {\n        wmGenData.walkWorldPosCrossAxisStepX = -wmGenData.walkWorldPosCrossAxisStepX;\n        wmGenData.walkWorldPosMainAxisStepX = -wmGenData.walkWorldPosMainAxisStepX;\n    }\n\n    if (wmGenData.walkDestinationY < wmGenData.worldPosY) {\n        wmGenData.walkWorldPosCrossAxisStepY = -wmGenData.walkWorldPosCrossAxisStepY;\n        wmGenData.walkWorldPosMainAxisStepY = -wmGenData.walkWorldPosMainAxisStepY;\n    }\n\n    if (!wmCursorIsVisible()) {\n        wmInterfaceCenterOnParty();\n    }\n}\n\n// 0x4C1F90\nstatic void wmPartyWalkingStep()\n{\n    // 0x51DEAC\n    static int terrainCounter = 1;\n\n    if (wmGenData.walkDistance <= 0) {\n        return;\n    }\n\n    terrainCounter++;\n    if (terrainCounter > 4) {\n        terrainCounter = 1;\n    }\n\n    // NOTE: Uninline.\n    wmPartyFindCurSubTile();\n\n    Terrain* terrain = &(wmTerrainTypeList[wmGenData.currentSubtile->terrain]);\n    int v1 = terrain->type - perk_level(obj_dude, PERK_PATHFINDER);\n    if (v1 < 1) {\n        v1 = 1;\n    }\n\n    if (terrainCounter / v1 >= 1) {\n        int v3;\n        int v4;\n        if (wmGenData.walkLineDelta >= 0) {\n            if (wmWorldPosInvalid(wmGenData.walkWorldPosCrossAxisStepX + wmGenData.worldPosX, wmGenData.walkWorldPosCrossAxisStepY + wmGenData.worldPosY)) {\n                wmGenData.walkDestinationX = 0;\n                wmGenData.walkDestinationY = 0;\n                wmGenData.isWalking = false;\n                wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosX, &(wmGenData.currentAreaId));\n                wmGenData.walkDistance = 0;\n                return;\n            }\n\n            v3 = wmGenData.walkWorldPosCrossAxisStepX;\n            wmGenData.walkLineDelta += wmGenData.walkLineDeltaCrossAxisStep;\n            wmGenData.worldPosX += wmGenData.walkWorldPosCrossAxisStepX;\n            v4 = wmGenData.walkWorldPosCrossAxisStepY;\n            wmGenData.worldPosY += wmGenData.walkWorldPosCrossAxisStepY;\n        } else {\n            if (wmWorldPosInvalid(wmGenData.walkWorldPosMainAxisStepX + wmGenData.worldPosX, wmGenData.walkWorldPosMainAxisStepY + wmGenData.worldPosY) == 1) {\n                wmGenData.walkDestinationX = 0;\n                wmGenData.walkDestinationY = 0;\n                wmGenData.isWalking = false;\n                wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosX, &(wmGenData.currentAreaId));\n                wmGenData.walkDistance = 0;\n                return;\n            }\n\n            v3 = wmGenData.walkWorldPosMainAxisStepX;\n            wmGenData.walkLineDelta += wmGenData.walkLineDeltaMainAxisStep;\n            wmGenData.worldPosY += wmGenData.walkWorldPosMainAxisStepY;\n            v4 = wmGenData.walkWorldPosMainAxisStepY;\n            wmGenData.worldPosX += wmGenData.walkWorldPosMainAxisStepX;\n        }\n\n        wmInterfaceScrollPixel(1, 1, v3, v4, NULL, false);\n\n        wmGenData.walkDistance -= 1;\n        if (wmGenData.walkDistance == 0) {\n            wmGenData.walkDestinationY = 0;\n            wmGenData.isWalking = false;\n            wmGenData.walkDestinationX = 0;\n        }\n    }\n}\n\n// 0x4C219C\nstatic void wmInterfaceScrollTabsStart(int delta)\n{\n    int i;\n    int v3;\n\n    for (i = 0; i < 7; i++) {\n        win_disable_button(wmTownMapSubButtonIds[i]);\n    }\n\n    wmGenData.oldTabsOffsetY = wmGenData.tabsOffsetY;\n\n    v3 = wmGenData.tabsOffsetY + 7 * delta;\n\n    if (delta >= 0) {\n        if (wmGenData.tabsBackgroundFrmHeight - 230 <= wmGenData.oldTabsOffsetY) {\n            goto L11;\n        } else {\n            wmGenData.oldTabsOffsetY = wmGenData.tabsOffsetY + 7 * delta;\n            if (v3 > wmGenData.tabsBackgroundFrmHeight - 230) {\n            }\n        }\n    } else {\n        if (wmGenData.tabsOffsetY <= 0) {\n            goto L11;\n        } else {\n            wmGenData.oldTabsOffsetY = wmGenData.tabsOffsetY + 7 * delta;\n            if (v3 < 0) {\n                wmGenData.oldTabsOffsetY = 0;\n            }\n        }\n    }\n\n    wmGenData.tabsScrollingDelta = delta;\n\nL11:\n\n    // NOTE: Uninline.\n    wmInterfaceScrollTabsUpdate();\n}\n\n// 0x4C2270\nstatic void wmInterfaceScrollTabsStop()\n{\n    int i;\n\n    wmGenData.tabsScrollingDelta = 0;\n\n    for (i = 0; i < 7; i++) {\n        win_enable_button(wmTownMapSubButtonIds[i]);\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4C2290\nstatic void wmInterfaceScrollTabsUpdate()\n{\n    if (wmGenData.tabsScrollingDelta != 0) {\n        wmGenData.tabsOffsetY += wmGenData.tabsScrollingDelta;\n        wmRefreshInterfaceOverlay(1);\n\n        if (wmGenData.tabsScrollingDelta >= 0) {\n            if (wmGenData.oldTabsOffsetY <= wmGenData.tabsOffsetY) {\n                // NOTE: Uninline.\n                wmInterfaceScrollTabsStop();\n            }\n        } else {\n            if (wmGenData.oldTabsOffsetY >= wmGenData.tabsOffsetY) {\n                // NOTE: Uninline.\n                wmInterfaceScrollTabsStop();\n            }\n        }\n    }\n}\n\n// 0x4C2324\nstatic int wmInterfaceInit()\n{\n    int fid;\n    Art* frm;\n    CacheEntry* frmHandle;\n\n    wmLastRndTime = get_time();\n    wmGenData.oldFont = text_curr();\n    text_font(0);\n\n    map_save_in_game(true);\n\n    const char* backgroundSoundFileName = wmGenData.isInCar ? \"20car\" : \"23world\";\n    gsound_background_play_level_music(backgroundSoundFileName, 12);\n\n    disable_box_bar_win();\n    map_disable_bk_processes();\n    cycle_disable();\n    gmouse_set_cursor(MOUSE_CURSOR_ARROW);\n\n    int worldmapWindowX = 0;\n    int worldmapWindowY = 0;\n    wmBkWin = win_add(worldmapWindowX, worldmapWindowY, WM_WINDOW_WIDTH, WM_WINDOW_HEIGHT, colorTable[0], WINDOW_FLAG_0x04);\n    if (wmBkWin == -1) {\n        return -1;\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 136, 0, 0, 0);\n    frm = art_ptr_lock(fid, &wmBkKey);\n    if (frm == NULL) {\n        return -1;\n    }\n\n    wmBkWidth = art_frame_width(frm, 0, 0);\n    wmBkHeight = art_frame_length(frm, 0, 0);\n\n    art_ptr_unlock(wmBkKey);\n    wmBkKey = INVALID_CACHE_ENTRY;\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 136, 0, 0, 0);\n    wmBkArtBuf = art_ptr_lock_data(fid, 0, 0, &wmBkKey);\n    if (wmBkArtBuf == NULL) {\n        return -1;\n    }\n\n    wmBkWinBuf = win_get_buf(wmBkWin);\n    if (wmBkWinBuf == NULL) {\n        return -1;\n    }\n\n    buf_to_buf(wmBkArtBuf, wmBkWidth, wmBkHeight, wmBkWidth, wmBkWinBuf, WM_WINDOW_WIDTH);\n\n    for (int citySize = 0; citySize < CITY_SIZE_COUNT; citySize++) {\n        CitySizeDescription* citySizeDescription = &(wmSphereData[citySize]);\n\n        fid = art_id(OBJ_TYPE_INTERFACE, 336 + citySize, 0, 0, 0);\n        citySizeDescription->fid = fid;\n\n        frm = art_ptr_lock(fid, &(citySizeDescription->handle));\n        if (frm == NULL) {\n            return -1;\n        }\n\n        citySizeDescription->width = art_frame_width(frm, 0, 0);\n        citySizeDescription->height = art_frame_length(frm, 0, 0);\n\n        art_ptr_unlock(citySizeDescription->handle);\n        citySizeDescription->handle = INVALID_CACHE_ENTRY;\n\n        citySizeDescription->data = art_ptr_lock_data(fid, 0, 0, &(citySizeDescription->handle));\n        // FIXME: check is obviously wrong, should be citySizeDescription->data.\n        if (frm == NULL) {\n            return -1;\n        }\n    }\n\n    fid = art_id(OBJ_TYPE_INTERFACE, 168, 0, 0, 0);\n    frm = art_ptr_lock(fid, &(wmGenData.hotspotNormalFrmHandle));\n    if (frm == NULL) {\n        return -1;\n    }\n\n    wmGenData.hotspotFrmWidth = art_frame_width(frm, 0, 0);\n    wmGenData.hotspotFrmHeight = art_frame_length(frm, 0, 0);\n\n    art_ptr_unlock(wmGenData.hotspotNormalFrmHandle);\n    wmGenData.hotspotNormalFrmHandle = INVALID_CACHE_ENTRY;\n\n    // hotspot1.frm - town map selector shape #1\n    fid = art_id(OBJ_TYPE_INTERFACE, 168, 0, 0, 0);\n    wmGenData.hotspotNormalFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.hotspotNormalFrmHandle));\n\n    // hotspot2.frm - town map selector shape #2\n    fid = art_id(OBJ_TYPE_INTERFACE, 223, 0, 0, 0);\n    wmGenData.hotspotPressedFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.hotspotPressedFrmHandle));\n    if (wmGenData.hotspotPressedFrmData == NULL) {\n        return -1;\n    }\n\n    // wmaptarg.frm - world map move target maker #1\n    fid = art_id(OBJ_TYPE_INTERFACE, 139, 0, 0, 0);\n    frm = art_ptr_lock(fid, &(wmGenData.destinationMarkerFrmHandle));\n    if (frm == NULL) {\n        return -1;\n    }\n\n    wmGenData.destinationMarkerFrmWidth = art_frame_width(frm, 0, 0);\n    wmGenData.destinationMarkerFrmHeight = art_frame_length(frm, 0, 0);\n\n    art_ptr_unlock(wmGenData.destinationMarkerFrmHandle);\n    wmGenData.destinationMarkerFrmHandle = INVALID_CACHE_ENTRY;\n\n    // wmaploc.frm - world map location marker\n    fid = art_id(OBJ_TYPE_INTERFACE, 138, 0, 0, 0);\n    frm = art_ptr_lock(fid, &(wmGenData.locationMarkerFrmHandle));\n    if (frm == NULL) {\n        return -1;\n    }\n\n    wmGenData.locationMarkerFrmWidth = art_frame_width(frm, 0, 0);\n    wmGenData.locationMarkerFrmHeight = art_frame_length(frm, 0, 0);\n\n    art_ptr_unlock(wmGenData.locationMarkerFrmHandle);\n    wmGenData.locationMarkerFrmHandle = INVALID_CACHE_ENTRY;\n\n    // wmaptarg.frm - world map move target maker #1\n    fid = art_id(OBJ_TYPE_INTERFACE, 139, 0, 0, 0);\n    wmGenData.destinationMarkerFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.destinationMarkerFrmHandle));\n    if (wmGenData.destinationMarkerFrmData == NULL) {\n        return -1;\n    }\n\n    // wmaploc.frm - world map location marker\n    fid = art_id(OBJ_TYPE_INTERFACE, 138, 0, 0, 0);\n    wmGenData.locationMarkerFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.locationMarkerFrmHandle));\n    if (wmGenData.locationMarkerFrmData == NULL) {\n        return -1;\n    }\n\n    for (int index = 0; index < WORLD_MAP_ENCOUNTER_FRM_COUNT; index++) {\n        fid = art_id(OBJ_TYPE_INTERFACE, wmRndCursorFids[index], 0, 0, 0);\n        frm = art_ptr_lock(fid, &(wmGenData.encounterCursorFrmHandle[index]));\n        if (frm == NULL) {\n            return -1;\n        }\n\n        wmGenData.encounterCursorFrmWidths[index] = art_frame_width(frm, 0, 0);\n        wmGenData.encounterCursorFrmHeights[index] = art_frame_length(frm, 0, 0);\n\n        art_ptr_unlock(wmGenData.encounterCursorFrmHandle[index]);\n\n        wmGenData.encounterCursorFrmHandle[index] = INVALID_CACHE_ENTRY;\n        wmGenData.encounterCursorFrmData[index] = art_ptr_lock_data(fid, 0, 0, &(wmGenData.encounterCursorFrmHandle[index]));\n    }\n\n    for (int index = 0; index < wmMaxTileNum; index++) {\n        wmTileInfoList[index].handle = INVALID_CACHE_ENTRY;\n    }\n\n    // wmtabs.frm - worldmap town tabs underlay\n    fid = art_id(OBJ_TYPE_INTERFACE, 364, 0, 0, 0);\n    frm = art_ptr_lock(fid, &frmHandle);\n    if (frm == NULL) {\n        return -1;\n    }\n\n    wmGenData.tabsBackgroundFrmWidth = art_frame_width(frm, 0, 0);\n    wmGenData.tabsBackgroundFrmHeight = art_frame_length(frm, 0, 0);\n\n    art_ptr_unlock(frmHandle);\n\n    wmGenData.tabsBackgroundFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.tabsBackgroundFrmHandle)) + wmGenData.tabsBackgroundFrmWidth * 27;\n    if (wmGenData.tabsBackgroundFrmData == NULL) {\n        return -1;\n    }\n\n    // wmtbedge.frm - worldmap town tabs edging overlay\n    fid = art_id(OBJ_TYPE_INTERFACE, 367, 0, 0, 0);\n    wmGenData.tabsBorderFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.tabsBorderFrmHandle));\n    if (wmGenData.tabsBorderFrmData == NULL) {\n        return -1;\n    }\n\n    // wmdial.frm - worldmap night/day dial\n    fid = art_id(OBJ_TYPE_INTERFACE, 365, 0, 0, 0);\n    wmGenData.dialFrm = art_ptr_lock(fid, &(wmGenData.dialFrmHandle));\n    if (wmGenData.dialFrm == NULL) {\n        return -1;\n    }\n\n    wmGenData.dialFrmWidth = art_frame_width(wmGenData.dialFrm, 0, 0);\n    wmGenData.dialFrmHeight = art_frame_length(wmGenData.dialFrm, 0, 0);\n\n    // wmscreen - worldmap overlay screen\n    fid = art_id(OBJ_TYPE_INTERFACE, 363, 0, 0, 0);\n    frm = art_ptr_lock(fid, &frmHandle);\n    if (frm == NULL) {\n        return -1;\n    }\n\n    wmGenData.carImageOverlayFrmWidth = art_frame_width(frm, 0, 0);\n    wmGenData.carImageOverlayFrmHeight = art_frame_length(frm, 0, 0);\n\n    art_ptr_unlock(frmHandle);\n\n    wmGenData.carImageOverlayFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.carImageOverlayFrmHandle));\n    if (wmGenData.carImageOverlayFrmData == NULL) {\n        return -1;\n    }\n\n    // wmglobe.frm - worldmap globe stamp overlay\n    fid = art_id(OBJ_TYPE_INTERFACE, 366, 0, 0, 0);\n    frm = art_ptr_lock(fid, &frmHandle);\n    if (frm == NULL) {\n        return -1;\n    }\n\n    wmGenData.globeOverlayFrmWidth = art_frame_width(frm, 0, 0);\n    wmGenData.globeOverlayFrmHeight = art_frame_length(frm, 0, 0);\n\n    art_ptr_unlock(frmHandle);\n\n    wmGenData.globeOverlayFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.globeOverlayFrmHandle));\n    if (wmGenData.globeOverlayFrmData == NULL) {\n        return -1;\n    }\n\n    // lilredup.frm - little red button up\n    fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0);\n    frm = art_ptr_lock(fid, &frmHandle);\n    if (frm == NULL) {\n        return -1;\n    }\n\n    int littleRedButtonUpWidth = art_frame_width(frm, 0, 0);\n    int littleRedButtonUpHeight = art_frame_length(frm, 0, 0);\n\n    art_ptr_unlock(frmHandle);\n\n    wmGenData.littleRedButtonNormalFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.littleRedButtonNormalFrmHandle));\n\n    // lilreddn.frm - little red button down\n    fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0);\n    wmGenData.littleRedButtonPressedFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.littleRedButtonPressedFrmHandle));\n\n    // months.frm - month strings for pip boy\n    fid = art_id(OBJ_TYPE_INTERFACE, 129, 0, 0, 0);\n    wmGenData.monthsFrm = art_ptr_lock(fid, &(wmGenData.monthsFrmHandle));\n    if (wmGenData.monthsFrm == NULL) {\n        return -1;\n    }\n\n    // numbers.frm - numbers for the hit points and fatigue counters\n    fid = art_id(OBJ_TYPE_INTERFACE, 82, 0, 0, 0);\n    wmGenData.numbersFrm = art_ptr_lock(fid, &(wmGenData.numbersFrmHandle));\n    if (wmGenData.numbersFrm == NULL) {\n        return -1;\n    }\n\n    // create town/world switch button\n    win_register_button(wmBkWin,\n        WM_TOWN_WORLD_SWITCH_X,\n        WM_TOWN_WORLD_SWITCH_Y,\n        littleRedButtonUpWidth,\n        littleRedButtonUpHeight,\n        -1,\n        -1,\n        -1,\n        KEY_UPPERCASE_T,\n        wmGenData.littleRedButtonNormalFrmData,\n        wmGenData.littleRedButtonPressedFrmData,\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n\n    for (int index = 0; index < 7; index++) {\n        wmTownMapSubButtonIds[index] = win_register_button(wmBkWin,\n            508,\n            138 + 27 * index,\n            littleRedButtonUpWidth,\n            littleRedButtonUpHeight,\n            -1,\n            -1,\n            -1,\n            KEY_CTRL_F1 + index,\n            wmGenData.littleRedButtonNormalFrmData,\n            wmGenData.littleRedButtonPressedFrmData,\n            NULL,\n            BUTTON_FLAG_TRANSPARENT);\n    }\n\n    for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) {\n        // 200 - uparwon.frm - character editor\n        // 199 - uparwoff.frm - character editor\n        fid = art_id(OBJ_TYPE_INTERFACE, 200 - index, 0, 0, 0);\n        frm = art_ptr_lock(fid, &(wmGenData.scrollUpButtonFrmHandle[index]));\n        if (frm == NULL) {\n            return -1;\n        }\n\n        wmGenData.scrollUpButtonFrmWidth = art_frame_width(frm, 0, 0);\n        wmGenData.scrollUpButtonFrmHeight = art_frame_length(frm, 0, 0);\n        wmGenData.scrollUpButtonFrmData[index] = art_frame_data(frm, 0, 0);\n    }\n\n    for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) {\n        // 182 - dnarwon.frm - character editor\n        // 181 - dnarwoff.frm - character editor\n        fid = art_id(OBJ_TYPE_INTERFACE, 182 - index, 0, 0, 0);\n        frm = art_ptr_lock(fid, &(wmGenData.scrollDownButtonFrmHandle[index]));\n        if (frm == NULL) {\n            return -1;\n        }\n\n        wmGenData.scrollDownButtonFrmWidth = art_frame_width(frm, 0, 0);\n        wmGenData.scrollDownButtonFrmHeight = art_frame_length(frm, 0, 0);\n        wmGenData.scrollDownButtonFrmData[index] = art_frame_data(frm, 0, 0);\n    }\n\n    // Scroll up button.\n    win_register_button(wmBkWin,\n        WM_TOWN_LIST_SCROLL_UP_X,\n        WM_TOWN_LIST_SCROLL_UP_Y,\n        wmGenData.scrollUpButtonFrmWidth,\n        wmGenData.scrollUpButtonFrmHeight,\n        -1,\n        -1,\n        -1,\n        KEY_CTRL_ARROW_UP,\n        wmGenData.scrollUpButtonFrmData[WORLDMAP_ARROW_FRM_NORMAL],\n        wmGenData.scrollUpButtonFrmData[WORLDMAP_ARROW_FRM_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n\n    // Scroll down button.\n    win_register_button(wmBkWin,\n        WM_TOWN_LIST_SCROLL_DOWN_X,\n        WM_TOWN_LIST_SCROLL_DOWN_Y,\n        wmGenData.scrollDownButtonFrmWidth,\n        wmGenData.scrollDownButtonFrmHeight,\n        -1,\n        -1,\n        -1,\n        KEY_CTRL_ARROW_DOWN,\n        wmGenData.scrollDownButtonFrmData[WORLDMAP_ARROW_FRM_NORMAL],\n        wmGenData.scrollDownButtonFrmData[WORLDMAP_ARROW_FRM_PRESSED],\n        NULL,\n        BUTTON_FLAG_TRANSPARENT);\n\n    if (wmGenData.isInCar) {\n        // wmcarmve.frm - worldmap car movie\n        fid = art_id(OBJ_TYPE_INTERFACE, 433, 0, 0, 0);\n        wmGenData.carImageFrm = art_ptr_lock(fid, &(wmGenData.carImageFrmHandle));\n        if (wmGenData.carImageFrm == NULL) {\n            return -1;\n        }\n\n        wmGenData.carImageFrmWidth = art_frame_width(wmGenData.carImageFrm, 0, 0);\n        wmGenData.carImageFrmHeight = art_frame_length(wmGenData.carImageFrm, 0, 0);\n    }\n\n    add_bk_process(wmMouseBkProc);\n\n    if (wmMakeTabsLabelList(&wmLabelList, &wmLabelCount) == -1) {\n        return -1;\n    }\n\n    wmInterfaceWasInitialized = 1;\n\n    if (wmInterfaceRefresh() == -1) {\n        return -1;\n    }\n\n    win_draw(wmBkWin);\n    scr_disable();\n    scr_remove_all();\n\n    return 0;\n}\n\n// 0x4C2E44\nstatic int wmInterfaceExit()\n{\n    int i;\n    TileInfo* tile;\n\n    remove_bk_process(wmMouseBkProc);\n\n    if (wmBkArtBuf != NULL) {\n        art_ptr_unlock(wmBkKey);\n        wmBkArtBuf = NULL;\n    }\n    wmBkKey = INVALID_CACHE_ENTRY;\n\n    if (wmBkWin != -1) {\n        win_delete(wmBkWin);\n        wmBkWin = -1;\n    }\n\n    if (wmGenData.hotspotNormalFrmHandle != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(wmGenData.hotspotNormalFrmHandle);\n    }\n    wmGenData.hotspotNormalFrmData = NULL;\n\n    if (wmGenData.hotspotPressedFrmHandle != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(wmGenData.hotspotPressedFrmHandle);\n    }\n    wmGenData.hotspotPressedFrmData = NULL;\n\n    if (wmGenData.destinationMarkerFrmHandle != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(wmGenData.destinationMarkerFrmHandle);\n    }\n    wmGenData.destinationMarkerFrmData = NULL;\n\n    if (wmGenData.locationMarkerFrmHandle != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(wmGenData.locationMarkerFrmHandle);\n    }\n    wmGenData.locationMarkerFrmData = NULL;\n\n    for (i = 0; i < 4; i++) {\n        if (wmGenData.encounterCursorFrmHandle[i] != INVALID_CACHE_ENTRY) {\n            art_ptr_unlock(wmGenData.encounterCursorFrmHandle[i]);\n        }\n        wmGenData.encounterCursorFrmData[i] = NULL;\n    }\n\n    for (i = 0; i < CITY_SIZE_COUNT; i++) {\n        CitySizeDescription* citySizeDescription = &(wmSphereData[i]);\n        // FIXME: probably unsafe code, no check for -1\n        art_ptr_unlock(citySizeDescription->handle);\n        citySizeDescription->handle = INVALID_CACHE_ENTRY;\n        citySizeDescription->data = NULL;\n    }\n\n    for (i = 0; i < wmMaxTileNum; i++) {\n        tile = &(wmTileInfoList[i]);\n        if (tile->handle != INVALID_CACHE_ENTRY) {\n            art_ptr_unlock(tile->handle);\n            tile->handle = INVALID_CACHE_ENTRY;\n            tile->data = NULL;\n\n            if (tile->walkMaskData != NULL) {\n                mem_free(tile->walkMaskData);\n                tile->walkMaskData = NULL;\n            }\n        }\n    }\n\n    if (wmGenData.tabsBackgroundFrmData != NULL) {\n        art_ptr_unlock(wmGenData.tabsBackgroundFrmHandle);\n        wmGenData.tabsBackgroundFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.tabsBackgroundFrmData = NULL;\n    }\n\n    if (wmGenData.tabsBorderFrmData != NULL) {\n        art_ptr_unlock(wmGenData.tabsBorderFrmHandle);\n        wmGenData.tabsBorderFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.tabsBorderFrmData = NULL;\n    }\n\n    if (wmGenData.dialFrm != NULL) {\n        art_ptr_unlock(wmGenData.dialFrmHandle);\n        wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.dialFrm = NULL;\n    }\n\n    if (wmGenData.carImageOverlayFrmData != NULL) {\n        art_ptr_unlock(wmGenData.carImageOverlayFrmHandle);\n        wmGenData.carImageOverlayFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.carImageOverlayFrmData = NULL;\n    }\n    if (wmGenData.globeOverlayFrmData != NULL) {\n        art_ptr_unlock(wmGenData.globeOverlayFrmHandle);\n        wmGenData.globeOverlayFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.globeOverlayFrmData = NULL;\n    }\n\n    if (wmGenData.littleRedButtonNormalFrmData != NULL) {\n        art_ptr_unlock(wmGenData.littleRedButtonNormalFrmHandle);\n        wmGenData.littleRedButtonNormalFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.littleRedButtonNormalFrmData = NULL;\n    }\n\n    if (wmGenData.littleRedButtonPressedFrmData != NULL) {\n        art_ptr_unlock(wmGenData.littleRedButtonPressedFrmHandle);\n        wmGenData.littleRedButtonPressedFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.littleRedButtonPressedFrmData = NULL;\n    }\n\n    for (i = 0; i < 2; i++) {\n        art_ptr_unlock(wmGenData.scrollUpButtonFrmHandle[i]);\n        wmGenData.scrollUpButtonFrmHandle[i] = INVALID_CACHE_ENTRY;\n        wmGenData.scrollUpButtonFrmData[i] = NULL;\n\n        art_ptr_unlock(wmGenData.scrollDownButtonFrmHandle[i]);\n        wmGenData.scrollDownButtonFrmHandle[i] = INVALID_CACHE_ENTRY;\n        wmGenData.scrollDownButtonFrmData[i] = NULL;\n    }\n\n    wmGenData.scrollUpButtonFrmHeight = 0;\n    wmGenData.scrollDownButtonFrmWidth = 0;\n    wmGenData.scrollDownButtonFrmHeight = 0;\n    wmGenData.scrollUpButtonFrmWidth = 0;\n\n    if (wmGenData.monthsFrm != NULL) {\n        art_ptr_unlock(wmGenData.monthsFrmHandle);\n        wmGenData.monthsFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.monthsFrm = NULL;\n    }\n\n    if (wmGenData.numbersFrm != NULL) {\n        art_ptr_unlock(wmGenData.numbersFrmHandle);\n        wmGenData.numbersFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.numbersFrm = NULL;\n    }\n\n    if (wmGenData.carImageFrm != NULL) {\n        art_ptr_unlock(wmGenData.carImageFrmHandle);\n        wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY;\n        wmGenData.carImageFrm = NULL;\n\n        wmGenData.carImageFrmWidth = 0;\n        wmGenData.carImageFrmHeight = 0;\n    }\n\n    wmGenData.encounterIconIsVisible = 0;\n    wmGenData.encounterMapId = -1;\n    wmGenData.encounterTableId = -1;\n    wmGenData.encounterEntryId = -1;\n\n    enable_box_bar_win();\n    map_enable_bk_processes();\n    cycle_enable();\n\n    text_font(wmGenData.oldFont);\n\n    // NOTE: Uninline.\n    wmFreeTabsLabelList(&wmLabelList, &wmLabelCount);\n\n    wmInterfaceWasInitialized = 0;\n\n    scr_enable();\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4C31E8\nstatic int wmInterfaceScroll(int dx, int dy, bool* successPtr)\n{\n    return wmInterfaceScrollPixel(20, 20, dx, dy, successPtr, 1);\n}\n\n// FIXME: There is small bug in this function. There is [success] flag returned\n// by reference so that calling code can update scrolling mouse cursor to invalid\n// range. It works OK on straight directions. But in diagonals when scrolling in\n// one direction is possible (and in fact occured), it will still be reported as\n// error.\n//\n// 0x4C3200\nstatic int wmInterfaceScrollPixel(int stepX, int stepY, int dx, int dy, bool* success, bool shouldRefresh)\n{\n    int v6 = wmWorldOffsetY;\n    int v7 = wmWorldOffsetX;\n\n    if (success != NULL) {\n        *success = true;\n    }\n\n    if (dy < 0) {\n        if (v6 > 0) {\n            v6 -= stepY;\n            if (v6 < 0) {\n                v6 = 0;\n            }\n        } else {\n            if (success != NULL) {\n                *success = false;\n            }\n        }\n    } else if (dy > 0) {\n        if (v6 < wmGenData.viewportMaxY) {\n            v6 += stepY;\n            if (v6 > wmGenData.viewportMaxY) {\n                v6 = wmGenData.viewportMaxY;\n            }\n        } else {\n            if (success != NULL) {\n                *success = false;\n            }\n        }\n    }\n\n    if (dx < 0) {\n        if (v7 > 0) {\n            v7 -= stepX;\n            if (v7 < 0) {\n                v7 = 0;\n            }\n        } else {\n            if (success != NULL) {\n                *success = false;\n            }\n        }\n    } else if (dx > 0) {\n        if (v7 < wmGenData.viewportMaxX) {\n            v7 += stepX;\n            if (v7 > wmGenData.viewportMaxX) {\n                v7 = wmGenData.viewportMaxX;\n            }\n        } else {\n            if (success != NULL) {\n                *success = false;\n            }\n        }\n    }\n\n    wmWorldOffsetY = v6;\n    wmWorldOffsetX = v7;\n\n    if (shouldRefresh) {\n        if (wmInterfaceRefresh() == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C32EC\nstatic void wmMouseBkProc()\n{\n    // 0x51DEB0\n    static unsigned int lastTime = 0;\n\n    // 0x51DEB4\n    static bool couldScroll = true;\n\n    int x;\n    int y;\n    mouse_get_position(&x, &y);\n\n    int dx = 0;\n    if (x == 639) {\n        dx = 1;\n    } else if (x == 0) {\n        dx = -1;\n    }\n\n    int dy = 0;\n    if (y == 479) {\n        dy = 1;\n    } else if (y == 0) {\n        dy = -1;\n    }\n\n    int oldMouseCursor = gmouse_get_cursor();\n    int newMouseCursor = oldMouseCursor;\n\n    if (dx != 0 || dy != 0) {\n        if (dx > 0) {\n            if (dy > 0) {\n                newMouseCursor = MOUSE_CURSOR_SCROLL_SE;\n            } else if (dy < 0) {\n                newMouseCursor = MOUSE_CURSOR_SCROLL_NE;\n            } else {\n                newMouseCursor = MOUSE_CURSOR_SCROLL_E;\n            }\n        } else if (dx < 0) {\n            if (dy > 0) {\n                newMouseCursor = MOUSE_CURSOR_SCROLL_SW;\n            } else if (dy < 0) {\n                newMouseCursor = MOUSE_CURSOR_SCROLL_NW;\n            } else {\n                newMouseCursor = MOUSE_CURSOR_SCROLL_W;\n            }\n        } else {\n            if (dy < 0) {\n                newMouseCursor = MOUSE_CURSOR_SCROLL_N;\n            } else if (dy > 0) {\n                newMouseCursor = MOUSE_CURSOR_SCROLL_S;\n            }\n        }\n\n        unsigned int tick = get_bk_time();\n        if (elapsed_tocks(tick, lastTime) > 50) {\n            lastTime = get_bk_time();\n            // NOTE: Uninline.\n            wmInterfaceScroll(dx, dy, &couldScroll);\n        }\n\n        if (!couldScroll) {\n            newMouseCursor += 8;\n        }\n    } else {\n        if (oldMouseCursor != MOUSE_CURSOR_ARROW) {\n            newMouseCursor = MOUSE_CURSOR_ARROW;\n        }\n    }\n\n    if (oldMouseCursor != newMouseCursor) {\n        gmouse_set_cursor(newMouseCursor);\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4C340C\nstatic int wmMarkSubTileOffsetVisited(int tile, int subtileX, int subtileY, int offsetX, int offsetY)\n{\n    return wmMarkSubTileOffsetVisitedFunc(tile, subtileX, subtileY, offsetX, offsetY, SUBTILE_STATE_VISITED);\n}\n\n// NOTE: Inlined.\n//\n// 0x4C3420\nstatic int wmMarkSubTileOffsetKnown(int tile, int subtileX, int subtileY, int offsetX, int offsetY)\n{\n    return wmMarkSubTileOffsetVisitedFunc(tile, subtileX, subtileY, offsetX, offsetY, SUBTILE_STATE_KNOWN);\n}\n\n// 0x4C3434\nstatic int wmMarkSubTileOffsetVisitedFunc(int tile, int subtileX, int subtileY, int offsetX, int offsetY, int subtileState)\n{\n    int actualTile;\n    int actualSubtileX;\n    int actualSubtileY;\n    TileInfo* tileInfo;\n    SubtileInfo* subtileInfo;\n\n    actualSubtileX = subtileX + offsetX;\n    actualTile = tile;\n    actualSubtileY = subtileY + offsetY;\n\n    if (actualSubtileX >= 0) {\n        if (actualSubtileX >= SUBTILE_GRID_WIDTH) {\n            if (tile % wmNumHorizontalTiles == wmNumHorizontalTiles - 1) {\n                return -1;\n            }\n\n            actualTile = tile + 1;\n            actualSubtileX %= SUBTILE_GRID_WIDTH;\n        }\n    } else {\n        if (!(tile % wmNumHorizontalTiles)) {\n            return -1;\n        }\n\n        actualSubtileX += SUBTILE_GRID_WIDTH;\n        actualTile = tile - 1;\n    }\n\n    if (actualSubtileY >= 0) {\n        if (actualSubtileY >= SUBTILE_GRID_HEIGHT) {\n            if (actualTile > wmMaxTileNum - wmNumHorizontalTiles - 1) {\n                return -1;\n            }\n\n            actualTile += wmNumHorizontalTiles;\n            actualSubtileY %= SUBTILE_GRID_HEIGHT;\n        }\n    } else {\n        if (actualTile < wmNumHorizontalTiles) {\n            return -1;\n        }\n\n        actualSubtileY += SUBTILE_GRID_HEIGHT;\n        actualTile -= wmNumHorizontalTiles;\n    }\n\n    tileInfo = &(wmTileInfoList[actualTile]);\n    subtileInfo = &(tileInfo->subtiles[actualSubtileY][actualSubtileX]);\n    if (subtileState != SUBTILE_STATE_KNOWN || subtileInfo->state == SUBTILE_STATE_UNKNOWN) {\n        subtileInfo->state = subtileState;\n    }\n\n    return 0;\n}\n\n// 0x4C3550\nstatic void wmMarkSubTileRadiusVisited(int x, int y)\n{\n    int radius = 1;\n\n    if (perkHasRank(obj_dude, PERK_SCOUT)) {\n        radius = 2;\n    }\n\n    wmSubTileMarkRadiusVisited(x, y, radius);\n}\n\n// 0x4C35A8\nint wmSubTileMarkRadiusVisited(int x, int y, int radius)\n{\n    int tile;\n    int subtileX;\n    int subtileY;\n    int offsetX;\n    int offsetY;\n    SubtileInfo* subtile;\n\n    tile = x / WM_TILE_WIDTH % wmNumHorizontalTiles + y / WM_TILE_HEIGHT * wmNumHorizontalTiles;\n    subtileX = x % WM_TILE_WIDTH / WM_SUBTILE_SIZE;\n    subtileY = y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE;\n\n    for (offsetY = -radius; offsetY <= radius; offsetY++) {\n        for (offsetX = -radius; offsetX <= radius; offsetX++) {\n            // NOTE: Uninline.\n            wmMarkSubTileOffsetKnown(tile, subtileX, subtileY, offsetX, offsetY);\n        }\n    }\n\n    subtile = &(wmTileInfoList[tile].subtiles[subtileY][subtileX]);\n    subtile->state = SUBTILE_STATE_VISITED;\n\n    switch (subtile->field_4) {\n    case 2:\n        while (subtileY-- > 0) {\n            // NOTE: Uninline.\n            wmMarkSubTileOffsetVisited(tile, subtileX, subtileY, 0, 0);\n        }\n        break;\n    case 4:\n        while (subtileX-- >= 0) {\n            // NOTE: Uninline.\n            wmMarkSubTileOffsetVisited(tile, subtileX, subtileY, 0, 0);\n        }\n\n        if (tile % wmNumHorizontalTiles > 0) {\n            for (subtileX = 0; subtileX < SUBTILE_GRID_WIDTH; subtileX++) {\n                // NOTE: Uninline.\n                wmMarkSubTileOffsetVisited(tile - 1, subtileX, subtileY, 0, 0);\n            }\n        }\n        break;\n    }\n\n    return 0;\n}\n\n// 0x4C3740\nint wmSubTileGetVisitedState(int x, int y, int* statePtr)\n{\n    TileInfo* tile;\n    SubtileInfo* subtile;\n\n    tile = &(wmTileInfoList[y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles]);\n    subtile = &(tile->subtiles[y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE][x % WM_TILE_WIDTH / WM_SUBTILE_SIZE]);\n    *statePtr = subtile->state;\n\n    return 0;\n}\n\n// Load tile art if needed.\n//\n// 0x4C37EC\nstatic int wmTileGrabArt(int tileIdx)\n{\n    TileInfo* tile = &(wmTileInfoList[tileIdx]);\n    if (tile->data != NULL) {\n        return 0;\n    }\n\n    tile->data = art_ptr_lock_data(tile->fid, 0, 0, &(tile->handle));\n    if (tile->data != NULL) {\n        return 0;\n    }\n\n    wmInterfaceExit();\n\n    return -1;\n}\n\n// 0x4C3830\nstatic int wmInterfaceRefresh()\n{\n    if (wmInterfaceWasInitialized != 1) {\n        return 0;\n    }\n\n    int v17 = wmWorldOffsetX % WM_TILE_WIDTH;\n    int v18 = wmWorldOffsetY % WM_TILE_HEIGHT;\n    int v20 = WM_TILE_HEIGHT - v18;\n    int v21 = WM_TILE_WIDTH * v18;\n    int v19 = WM_TILE_WIDTH - v17;\n\n    // Render tiles.\n    int y = 0;\n    int x = 0;\n    int v0 = wmWorldOffsetY / WM_TILE_HEIGHT * wmNumHorizontalTiles + wmWorldOffsetX / WM_TILE_WIDTH % wmNumHorizontalTiles;\n    while (y < WM_VIEW_HEIGHT) {\n        x = 0;\n        int v23 = 0;\n        int height;\n        while (x < WM_VIEW_WIDTH) {\n            if (wmTileGrabArt(v0) == -1) {\n                return -1;\n            }\n\n            int width = WM_TILE_WIDTH;\n\n            int srcX = 0;\n            if (x == 0) {\n                srcX = v17;\n                width = v19;\n            }\n\n            if (width + x > WM_VIEW_WIDTH) {\n                width = WM_VIEW_WIDTH - x;\n            }\n\n            height = WM_TILE_HEIGHT;\n            if (y == 0) {\n                height = v20;\n                srcX += v21;\n            }\n\n            if (height + y > WM_VIEW_HEIGHT) {\n                height = WM_VIEW_HEIGHT - y;\n            }\n\n            TileInfo* tileInfo = &(wmTileInfoList[v0]);\n            buf_to_buf(tileInfo->data + srcX,\n                width,\n                height,\n                WM_TILE_WIDTH,\n                wmBkWinBuf + WM_WINDOW_WIDTH * (y + WM_VIEW_Y) + WM_VIEW_X + x,\n                WM_WINDOW_WIDTH);\n            v0++;\n\n            x += width;\n            v23++;\n        }\n\n        v0 += wmNumHorizontalTiles - v23;\n        y += height;\n    }\n\n    // Render cities.\n    for (int index = 0; index < wmMaxAreaNum; index++) {\n        CityInfo* cityInfo = &(wmAreaInfoList[index]);\n        if (cityInfo->state != CITY_STATE_UNKNOWN) {\n            CitySizeDescription* citySizeDescription = &(wmSphereData[cityInfo->size]);\n            int cityX = cityInfo->x - wmWorldOffsetX;\n            int cityY = cityInfo->y - wmWorldOffsetY;\n            if (cityX >= 0 && cityX <= 472 - citySizeDescription->width\n                && cityY >= 0 && cityY <= 465 - citySizeDescription->height) {\n                wmInterfaceDrawCircleOverlay(cityInfo, citySizeDescription, wmBkWinBuf, cityX, cityY);\n            }\n        }\n    }\n\n    // Hide unknown subtiles, dim unvisited.\n    int v25 = wmWorldOffsetX / WM_TILE_WIDTH % wmNumHorizontalTiles + wmWorldOffsetY / WM_TILE_HEIGHT * wmNumHorizontalTiles;\n    int v30 = 0;\n    while (v30 < WM_VIEW_HEIGHT) {\n        int v24 = 0;\n        int v33 = 0;\n        int v29;\n        while (v33 < WM_VIEW_WIDTH) {\n            int v31 = WM_TILE_WIDTH;\n            if (v33 == 0) {\n                v31 = WM_TILE_WIDTH - v17;\n            }\n\n            if (v33 + v31 > WM_VIEW_WIDTH) {\n                v31 = WM_VIEW_WIDTH - v33;\n            }\n\n            v29 = WM_TILE_HEIGHT;\n            if (v30 == 0) {\n                v29 -= v18;\n            }\n\n            if (v30 + v29 > WM_VIEW_HEIGHT) {\n                v29 = WM_VIEW_HEIGHT - v30;\n            }\n\n            int v32;\n            if (v30 != 0) {\n                v32 = WM_VIEW_Y;\n            } else {\n                v32 = WM_VIEW_Y - v18;\n            }\n\n            int v13 = 0;\n            int v34 = v30 + v32;\n\n            for (int row = 0; row < SUBTILE_GRID_HEIGHT; row++) {\n                int v35;\n                if (v33 != 0) {\n                    v35 = WM_VIEW_X;\n                } else {\n                    v35 = WM_VIEW_X - v17;\n                }\n\n                int v15 = v33 + v35;\n                for (int column = 0; column < SUBTILE_GRID_WIDTH; column++) {\n                    TileInfo* tileInfo = &(wmTileInfoList[v25]);\n                    wmInterfaceDrawSubTileList(tileInfo, column, row, v15, v34, 1);\n\n                    v15 += WM_SUBTILE_SIZE;\n                    v35 += WM_SUBTILE_SIZE;\n                }\n\n                v32 += WM_SUBTILE_SIZE;\n                v34 += WM_SUBTILE_SIZE;\n            }\n\n            v25++;\n            v24++;\n            v33 += v31;\n        }\n\n        v25 += wmNumHorizontalTiles - v24;\n        v30 += v29;\n    }\n\n    wmDrawCursorStopped();\n\n    wmRefreshInterfaceOverlay(true);\n\n    return 0;\n}\n\n// 0x4C3C9C\nstatic void wmInterfaceRefreshDate(bool shouldRefreshWindow)\n{\n    int month;\n    int day;\n    int year;\n    game_time_date(&month, &day, &year);\n\n    month--;\n\n    unsigned char* dest = wmBkWinBuf;\n\n    int numbersFrmWidth = art_frame_width(wmGenData.numbersFrm, 0, 0);\n    int numbersFrmHeight = art_frame_length(wmGenData.numbersFrm, 0, 0);\n    unsigned char* numbersFrmData = art_frame_data(wmGenData.numbersFrm, 0, 0);\n\n    dest += WM_WINDOW_WIDTH * 12 + 487;\n    buf_to_buf(numbersFrmData + 9 * (day / 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH);\n    buf_to_buf(numbersFrmData + 9 * (day % 10), 9, numbersFrmHeight, numbersFrmWidth, dest + 9, WM_WINDOW_WIDTH);\n\n    int monthsFrmWidth = art_frame_width(wmGenData.monthsFrm, 0, 0);\n    unsigned char* monthsFrmData = art_frame_data(wmGenData.monthsFrm, 0, 0);\n    buf_to_buf(monthsFrmData + monthsFrmWidth * 15 * month, 29, 14, 29, dest + WM_WINDOW_WIDTH + 26, WM_WINDOW_WIDTH);\n\n    dest += 98;\n    for (int index = 0; index < 4; index++) {\n        dest -= 9;\n        buf_to_buf(numbersFrmData + 9 * (year % 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH);\n        year /= 10;\n    }\n\n    int gameTimeHour = game_time_hour();\n    dest += 72;\n    for (int index = 0; index < 4; index++) {\n        buf_to_buf(numbersFrmData + 9 * (gameTimeHour % 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH);\n        dest -= 9;\n        gameTimeHour /= 10;\n    }\n\n    if (shouldRefreshWindow) {\n        Rect rect;\n        rect.ulx = 487;\n        rect.uly = 12;\n        rect.lry = numbersFrmHeight + 12;\n        rect.lrx = 630;\n        win_draw_rect(wmBkWin, &rect);\n    }\n}\n\n// 0x4C3F00\nstatic int wmMatchWorldPosToArea(int x, int y, int* areaIdxPtr)\n{\n    int v3 = y + WM_VIEW_Y;\n    int v4 = x + WM_VIEW_X;\n\n    int index;\n    for (index = 0; index < wmMaxAreaNum; index++) {\n        CityInfo* city = &(wmAreaInfoList[index]);\n        if (city->state) {\n            if (v4 >= city->x && v3 >= city->y) {\n                CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]);\n                if (v4 <= city->x + citySizeDescription->width && v3 <= city->y + citySizeDescription->height) {\n                    break;\n                }\n            }\n        }\n    }\n\n    if (index == wmMaxAreaNum) {\n        *areaIdxPtr = -1;\n    } else {\n        *areaIdxPtr = index;\n    }\n\n    return 0;\n}\n\n// FIXME: This function does not set current font, which is a bit unusual for a\n// function which draw text. I doubt it was done on purpose, likely simply\n// forgotten. Because of this, city names are rendered with current font, which\n// can be any, but in this case it uses default text font, not interface font.\n//\n// 0x4C3FA8\nstatic int wmInterfaceDrawCircleOverlay(CityInfo* city, CitySizeDescription* citySizeDescription, unsigned char* dest, int x, int y)\n{\n    MessageListItem messageListItem;\n    char name[40];\n    int nameY;\n    int maxY;\n    int width;\n\n    dark_translucent_trans_buf_to_buf(citySizeDescription->data,\n        citySizeDescription->width,\n        citySizeDescription->height,\n        citySizeDescription->width,\n        dest,\n        x,\n        y,\n        WM_WINDOW_WIDTH,\n        0x10000,\n        circleBlendTable,\n        commonGrayTable);\n\n    nameY = y + citySizeDescription->height + 1;\n    maxY = 464 - text_height();\n    if (nameY < maxY) {\n        if (wmAreaIsKnown(city->areaId)) {\n            // NOTE: Uninline.\n            wmGetAreaName(city, name);\n        } else {\n            strncpy(name, getmsg(&wmMsgFile, &messageListItem, 1004), 40);\n        }\n\n        width = text_width(name);\n        text_to_buf(dest + WM_WINDOW_WIDTH * nameY + x + citySizeDescription->width / 2 - width / 2,\n            name,\n            width,\n            WM_WINDOW_WIDTH,\n            colorTable[992]);\n    }\n\n    return 0;\n}\n\n// Helper function that dims specified rectangle in given buffer. It's used to\n// slightly darken subtile which is known, but not visited.\n//\n// 0x4C40A8\nstatic void wmInterfaceDrawSubTileRectFogged(unsigned char* dest, int width, int height, int pitch)\n{\n    int skipY = pitch - width;\n\n    for (int y = 0; y < height; y++) {\n        for (int x = 0; x < width; x++) {\n            unsigned char byte = *dest;\n            *dest++ = intensityColorTable[byte][75];\n        }\n        dest += skipY;\n    }\n}\n\n// 0x4C40E4\nstatic int wmInterfaceDrawSubTileList(TileInfo* tileInfo, int column, int row, int x, int y, int a6)\n{\n    SubtileInfo* subtileInfo = &(tileInfo->subtiles[row][column]);\n\n    int destY = y;\n    int destX = x;\n\n    int height = WM_SUBTILE_SIZE;\n    if (y < WM_VIEW_Y) {\n        if (y < 0) {\n            height = y + 29;\n        } else {\n            height = WM_SUBTILE_SIZE - (WM_VIEW_Y - y);\n        }\n        destY = WM_VIEW_Y;\n    }\n\n    if (height + y > 464) {\n        height -= height + y - 464;\n    }\n\n    int width = WM_SUBTILE_SIZE * a6;\n    if (x < WM_VIEW_X) {\n        destX = WM_VIEW_X;\n        width -= WM_VIEW_X - x;\n    }\n\n    if (width + x > 472) {\n        width -= width + x - 472;\n    }\n\n    if (width > 0 && height > 0) {\n        unsigned char* dest = wmBkWinBuf + WM_WINDOW_WIDTH * destY + destX;\n        switch (subtileInfo->state) {\n        case SUBTILE_STATE_UNKNOWN:\n            buf_fill(dest, width, height, WM_WINDOW_WIDTH, colorTable[0]);\n            break;\n        case SUBTILE_STATE_KNOWN:\n            wmInterfaceDrawSubTileRectFogged(dest, width, height, WM_WINDOW_WIDTH);\n            break;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C41EC\nstatic int wmDrawCursorStopped()\n{\n    unsigned char* src;\n    int width;\n    int height;\n\n    if (wmGenData.walkDestinationX >= 1 || wmGenData.walkDestinationY >= 1) {\n\n        if (wmGenData.encounterIconIsVisible == 1) {\n            src = wmGenData.encounterCursorFrmData[wmGenData.encounterCursorId];\n            width = wmGenData.encounterCursorFrmWidths[wmGenData.encounterCursorId];\n            height = wmGenData.encounterCursorFrmHeights[wmGenData.encounterCursorId];\n        } else {\n            src = wmGenData.locationMarkerFrmData;\n            width = wmGenData.locationMarkerFrmWidth;\n            height = wmGenData.locationMarkerFrmHeight;\n        }\n\n        if (wmGenData.worldPosX >= wmWorldOffsetX && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH\n            && wmGenData.worldPosY >= wmWorldOffsetY && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT) {\n            trans_buf_to_buf(src, width, height, width, wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.worldPosY - height / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.worldPosX - width / 2, WM_WINDOW_WIDTH);\n        }\n\n        if (wmGenData.walkDestinationX >= wmWorldOffsetX && wmGenData.walkDestinationX < wmWorldOffsetX + WM_VIEW_WIDTH\n            && wmGenData.walkDestinationY >= wmWorldOffsetY && wmGenData.walkDestinationY < wmWorldOffsetY + WM_VIEW_HEIGHT) {\n            trans_buf_to_buf(wmGenData.destinationMarkerFrmData, wmGenData.destinationMarkerFrmWidth, wmGenData.destinationMarkerFrmHeight, wmGenData.destinationMarkerFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.walkDestinationY - wmGenData.destinationMarkerFrmHeight / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.walkDestinationX - wmGenData.destinationMarkerFrmWidth / 2, WM_WINDOW_WIDTH);\n        }\n    } else {\n        if (wmGenData.encounterIconIsVisible == 1) {\n            src = wmGenData.encounterCursorFrmData[wmGenData.encounterCursorId];\n            width = wmGenData.encounterCursorFrmWidths[wmGenData.encounterCursorId];\n            height = wmGenData.encounterCursorFrmHeights[wmGenData.encounterCursorId];\n        } else {\n            src = wmGenData.mousePressed ? wmGenData.hotspotPressedFrmData : wmGenData.hotspotNormalFrmData;\n            width = wmGenData.hotspotFrmWidth;\n            height = wmGenData.hotspotFrmHeight;\n        }\n\n        if (wmGenData.worldPosX >= wmWorldOffsetX && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH\n            && wmGenData.worldPosY >= wmWorldOffsetY && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT) {\n            trans_buf_to_buf(src, width, height, width, wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.worldPosY - height / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.worldPosX - width / 2, WM_WINDOW_WIDTH);\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C4490\nstatic bool wmCursorIsVisible()\n{\n    return wmGenData.worldPosX >= wmWorldOffsetX\n        && wmGenData.worldPosY >= wmWorldOffsetY\n        && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH\n        && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT;\n}\n\n// NOTE: Inlined.\n//\n// 0x4C44D8\nstatic int wmGetAreaName(CityInfo* city, char* name)\n{\n    MessageListItem messageListItem;\n\n    getmsg(&map_msg_file, &messageListItem, city->areaId + 1500);\n    strncpy(name, messageListItem.text, 40);\n\n    return 0;\n}\n\n// Copy city short name.\n//\n// 0x4C450C\nint wmGetAreaIdxName(int areaIdx, char* name)\n{\n    MessageListItem messageListItem;\n\n    getmsg(&map_msg_file, &messageListItem, 1500 + areaIdx);\n    strncpy(name, messageListItem.text, 40);\n\n    return 0;\n}\n\n// Returns true if world area is known.\n//\n// 0x4C453C\nbool wmAreaIsKnown(int cityIdx)\n{\n    if (!cityIsValid(cityIdx)) {\n        return false;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n    if (city->visitedState) {\n        if (city->state == CITY_STATE_KNOWN) {\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x4C457C\nint wmAreaVisitedState(int areaIdx)\n{\n    if (!cityIsValid(areaIdx)) {\n        return 0;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[areaIdx]);\n    if (city->visitedState && city->state == CITY_STATE_KNOWN) {\n        return city->visitedState;\n    }\n\n    return 0;\n}\n\n// 0x4C45BC\nbool wmMapIsKnown(int mapIdx)\n{\n    int cityIdx;\n    if (wmMatchAreaFromMap(mapIdx, &cityIdx) != 0) {\n        return false;\n    }\n\n    int entranceIdx;\n    if (wmMatchEntranceFromMap(cityIdx, mapIdx, &entranceIdx) != 0) {\n        return false;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n    EntranceInfo* entrance = &(city->entrances[entranceIdx]);\n\n    if (entrance->state != 1) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4C4624\nint wmAreaMarkVisited(int cityIdx)\n{\n    return wmAreaMarkVisitedState(cityIdx, CITY_STATE_VISITED);\n}\n\n// 0x4C4634\nbool wmAreaMarkVisitedState(int cityIdx, int state)\n{\n    if (!cityIsValid(cityIdx)) {\n        return false;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n    int v5 = city->visitedState;\n    if (city->state == CITY_STATE_KNOWN && state != 0) {\n        wmMarkSubTileRadiusVisited(city->x, city->y);\n    }\n\n    city->visitedState = state;\n\n    SubtileInfo* subtile;\n    if (wmFindCurSubTileFromPos(city->x, city->y, &subtile) == -1) {\n        return false;\n    }\n\n    if (state == 1) {\n        subtile->state = SUBTILE_STATE_KNOWN;\n    } else if (state == 2 && v5 == 0) {\n        city->visitedState = 1;\n    }\n\n    return true;\n}\n\n// 0x4C46CC\nbool wmAreaSetVisibleState(int cityIdx, int state, bool force)\n{\n    if (!cityIsValid(cityIdx)) {\n        return false;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n    if (city->lockState != LOCK_STATE_LOCKED || force) {\n        city->state = state;\n        return true;\n    }\n\n    return false;\n}\n\n// 0x4C4710\nint wmAreaSetWorldPos(int cityIdx, int x, int y)\n{\n    if (!cityIsValid(cityIdx)) {\n        return -1;\n    }\n\n    if (x < 0 || x >= WM_TILE_WIDTH * wmNumHorizontalTiles) {\n        return -1;\n    }\n\n    if (y < 0 || y >= WM_TILE_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles)) {\n        return -1;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n    city->x = x;\n    city->y = y;\n\n    return 0;\n}\n\n// Returns current town x/y.\n//\n// 0x4C47A4\nint wmGetPartyWorldPos(int* xPtr, int* yPtr)\n{\n    if (xPtr != NULL) {\n        *xPtr = wmGenData.worldPosX;\n    }\n\n    if (yPtr != NULL) {\n        *yPtr = wmGenData.worldPosY;\n    }\n\n    return 0;\n}\n\n// Returns current town.\n//\n// 0x4C47C0\nint wmGetPartyCurArea(int* areaIdxPtr)\n{\n    if (areaIdxPtr != NULL) {\n        *areaIdxPtr = wmGenData.currentAreaId;\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x4C47D8\nstatic void wmMarkAllSubTiles(int state)\n{\n    for (int tileIndex = 0; tileIndex < wmMaxTileNum; tileIndex++) {\n        TileInfo* tile = &(wmTileInfoList[tileIndex]);\n        for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) {\n            for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) {\n                SubtileInfo* subtile = &(tile->subtiles[column][row]);\n                subtile->state = state;\n            }\n        }\n    }\n}\n\n// 0x4C4850\nvoid wmTownMap()\n{\n    wmWorldMapFunc(1);\n}\n\n// 0x4C485C\nstatic int wmTownMapFunc(int* mapIdxPtr)\n{\n    *mapIdxPtr = -1;\n\n    if (wmTownMapInit() == -1) {\n        wmTownMapExit();\n        return -1;\n    }\n\n    if (wmGenData.currentAreaId == -1) {\n        return -1;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]);\n\n    for (;;) {\n        int keyCode = get_input();\n        if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n            game_quit_with_confirm();\n        }\n\n        if (game_user_wants_to_quit) {\n            break;\n        }\n\n        if (keyCode != -1) {\n            if (keyCode == KEY_ESCAPE) {\n                break;\n            }\n\n            if (keyCode >= KEY_1 && keyCode < KEY_1 + city->entrancesLength) {\n                EntranceInfo* entrance = &(city->entrances[keyCode - KEY_1]);\n\n                *mapIdxPtr = entrance->map;\n\n                mapSetEntranceInfo(entrance->elevation, entrance->tile, entrance->rotation);\n\n                break;\n            }\n\n            if (keyCode >= KEY_CTRL_F1 && keyCode <= KEY_CTRL_F7) {\n                int quickDestinationIndex = wmGenData.tabsOffsetY / 27 + keyCode - KEY_CTRL_F1;\n                if (quickDestinationIndex < wmLabelCount) {\n                    int cityIdx = wmLabelList[quickDestinationIndex];\n                    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n                    if (!wmAreaIsKnown(city->areaId)) {\n                        break;\n                    }\n\n                    if (cityIdx != wmGenData.currentAreaId) {\n                        wmPartyInitWalking(city->x, city->y);\n\n                        wmGenData.mousePressed = false;\n\n                        break;\n                    }\n                }\n            } else {\n                if (keyCode == KEY_CTRL_ARROW_UP) {\n                    wmInterfaceScrollTabsStart(-27);\n                } else if (keyCode == KEY_CTRL_ARROW_DOWN) {\n                    wmInterfaceScrollTabsStart(27);\n                } else if (keyCode == 2069) {\n                    if (wmTownMapRefresh() == -1) {\n                        return -1;\n                    }\n                }\n\n                if (keyCode == KEY_UPPERCASE_T || keyCode == KEY_LOWERCASE_T || keyCode == KEY_UPPERCASE_W || keyCode == KEY_LOWERCASE_W) {\n                    keyCode = KEY_ESCAPE;\n                }\n\n                if (keyCode == KEY_ESCAPE) {\n                    break;\n                }\n            }\n        }\n    }\n\n    if (wmTownMapExit() == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4C4A6C\nstatic int wmTownMapInit()\n{\n    wmTownMapCurArea = wmGenData.currentAreaId;\n\n    CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]);\n\n    Art* mapFrm = art_ptr_lock(city->mapFid, &wmTownKey);\n    if (mapFrm == NULL) {\n        return -1;\n    }\n\n    wmTownWidth = art_frame_width(mapFrm, 0, 0);\n    wmTownHeight = art_frame_length(mapFrm, 0, 0);\n\n    art_ptr_unlock(wmTownKey);\n    wmTownKey = INVALID_CACHE_ENTRY;\n\n    wmTownBuffer = art_ptr_lock_data(city->mapFid, 0, 0, &wmTownKey);\n    if (wmTownBuffer == NULL) {\n        return -1;\n    }\n\n    for (int index = 0; index < city->entrancesLength; index++) {\n        wmTownMapButtonId[index] = -1;\n    }\n\n    for (int index = 0; index < city->entrancesLength; index++) {\n        EntranceInfo* entrance = &(city->entrances[index]);\n        if (entrance->state == 0) {\n            continue;\n        }\n\n        if (entrance->x == -1 || entrance->y == -1) {\n            continue;\n        }\n\n        wmTownMapButtonId[index] = win_register_button(wmBkWin,\n            entrance->x,\n            entrance->y,\n            wmGenData.hotspotFrmWidth,\n            wmGenData.hotspotFrmHeight,\n            -1,\n            2069,\n            -1,\n            KEY_1 + index,\n            wmGenData.hotspotNormalFrmData,\n            wmGenData.hotspotPressedFrmData,\n            NULL,\n            BUTTON_FLAG_TRANSPARENT);\n\n        if (wmTownMapButtonId[index] == -1) {\n            return -1;\n        }\n    }\n\n    remove_bk_process(wmMouseBkProc);\n\n    if (wmTownMapRefresh() == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4C4BD0\nstatic int wmTownMapRefresh()\n{\n    buf_to_buf(wmTownBuffer,\n        wmTownWidth,\n        wmTownHeight,\n        wmTownWidth,\n        wmBkWinBuf + WM_WINDOW_WIDTH * WM_VIEW_Y + WM_VIEW_X,\n        WM_WINDOW_WIDTH);\n\n    wmRefreshInterfaceOverlay(false);\n\n    CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]);\n\n    for (int index = 0; index < city->entrancesLength; index++) {\n        EntranceInfo* entrance = &(city->entrances[index]);\n        if (entrance->state == 0) {\n            continue;\n        }\n\n        if (entrance->x == -1 || entrance->y == -1) {\n            continue;\n        }\n\n        MessageListItem messageListItem;\n        messageListItem.num = 200 + 10 * wmTownMapCurArea + index;\n        if (message_search(&wmMsgFile, &messageListItem)) {\n            if (messageListItem.text != NULL) {\n                int width = text_width(messageListItem.text);\n                win_print(wmBkWin, messageListItem.text, width, wmGenData.hotspotFrmWidth / 2 + entrance->x - width / 2, wmGenData.hotspotFrmHeight + entrance->y + 2, colorTable[992] | 0x2010000);\n            }\n        }\n    }\n\n    win_draw(wmBkWin);\n\n    return 0;\n}\n\n// 0x4C4D00\nstatic int wmTownMapExit()\n{\n    if (wmTownKey != INVALID_CACHE_ENTRY) {\n        art_ptr_unlock(wmTownKey);\n        wmTownKey = INVALID_CACHE_ENTRY;\n        wmTownBuffer = NULL;\n        wmTownWidth = 0;\n        wmTownHeight = 0;\n    }\n\n    if (wmTownMapCurArea != -1) {\n        CityInfo* city = &(wmAreaInfoList[wmTownMapCurArea]);\n        for (int index = 0; index < city->entrancesLength; index++) {\n            if (wmTownMapButtonId[index] != -1) {\n                win_delete_button(wmTownMapButtonId[index]);\n                wmTownMapButtonId[index] = -1;\n            }\n        }\n    }\n\n    if (wmInterfaceRefresh() == -1) {\n        return -1;\n    }\n\n    add_bk_process(wmMouseBkProc);\n\n    return 0;\n}\n\n// 0x4C4DA4\nint wmCarUseGas(int amount)\n{\n    if (game_get_global_var(GVAR_NEW_RENO_SUPER_CAR) != 0) {\n        amount -= amount * 90 / 100;\n    }\n\n    if (game_get_global_var(GVAR_NEW_RENO_CAR_UPGRADE) != 0) {\n        amount -= amount * 10 / 100;\n    }\n\n    if (game_get_global_var(GVAR_CAR_UPGRADE_FUEL_CELL_REGULATOR) != 0) {\n        amount /= 2;\n    }\n\n    wmGenData.carFuel -= amount;\n\n    if (wmGenData.carFuel < 0) {\n        wmGenData.carFuel = 0;\n    }\n\n    return 0;\n}\n\n// Returns amount of fuel that does not fit into tank.\n//\n// 0x4C4E34\nint wmCarFillGas(int amount)\n{\n    if ((amount + wmGenData.carFuel) <= CAR_FUEL_MAX) {\n        wmGenData.carFuel += amount;\n        return 0;\n    }\n\n    int remaining = CAR_FUEL_MAX - wmGenData.carFuel;\n\n    wmGenData.carFuel = CAR_FUEL_MAX;\n\n    return remaining;\n}\n\n// 0x4C4E74\nint wmCarGasAmount()\n{\n    return wmGenData.carFuel;\n}\n\n// 0x4C4E7C\nbool wmCarIsOutOfGas()\n{\n    return wmGenData.carFuel <= 0;\n}\n\n// 0x4C4E8C\nint wmCarCurrentArea()\n{\n    return wmGenData.currentCarAreaId;\n}\n\n// 0x4C4E94\nint wmCarGiveToParty()\n{\n    // 0x4BC880\n    static MessageListItem messageListItem;\n\n    if (wmGenData.carFuel <= 0) {\n        // The car is out of power.\n        char* msg = getmsg(&wmMsgFile, &messageListItem, 1502);\n        display_print(msg);\n        return -1;\n    }\n\n    wmGenData.isInCar = true;\n\n    MapTransition transition;\n    memset(&transition, 0, sizeof(transition));\n\n    transition.map = -2;\n    map_leave_map(&transition);\n\n    CityInfo* city = &(wmAreaInfoList[CITY_CAR_OUT_OF_GAS]);\n    city->state = CITY_STATE_UNKNOWN;\n    city->visitedState = 0;\n\n    return 0;\n}\n\n// 0x4C4F28\nint wmSfxMaxCount()\n{\n    int mapIdx = map_get_index_number();\n    if (mapIdx < 0 || mapIdx >= wmMaxMapNum) {\n        return -1;\n    }\n\n    MapInfo* map = &(wmMapInfoList[mapIdx]);\n    return map->ambientSoundEffectsLength;\n}\n\n// 0x4C4F5C\nint wmSfxRollNextIdx()\n{\n    int mapIdx = map_get_index_number();\n    if (mapIdx < 0 || mapIdx >= wmMaxMapNum) {\n        return -1;\n    }\n\n    MapInfo* map = &(wmMapInfoList[mapIdx]);\n\n    int totalChances = 0;\n    for (int index = 0; index < map->ambientSoundEffectsLength; index++) {\n        MapAmbientSoundEffectInfo* sfx = &(map->ambientSoundEffects[index]);\n        totalChances += sfx->chance;\n    }\n\n    int chance = roll_random(0, totalChances);\n    for (int index = 0; index < map->ambientSoundEffectsLength; index++) {\n        MapAmbientSoundEffectInfo* sfx = &(map->ambientSoundEffects[index]);\n        if (chance >= sfx->chance) {\n            chance -= sfx->chance;\n            continue;\n        }\n\n        return index;\n    }\n\n    return -1;\n}\n\n// 0x4C5004\nint wmSfxIdxName(int sfxIdx, char** namePtr)\n{\n    if (namePtr == NULL) {\n        return -1;\n    }\n\n    *namePtr = NULL;\n\n    int mapIdx = map_get_index_number();\n    if (mapIdx < 0 || mapIdx >= wmMaxMapNum) {\n        return -1;\n    }\n\n    MapInfo* map = &(wmMapInfoList[mapIdx]);\n    if (sfxIdx < 0 || sfxIdx >= map->ambientSoundEffectsLength) {\n        return -1;\n    }\n\n    MapAmbientSoundEffectInfo* ambientSoundEffectInfo = &(map->ambientSoundEffects[sfxIdx]);\n    *namePtr = ambientSoundEffectInfo->name;\n\n    int v1 = 0;\n    if (strcmp(ambientSoundEffectInfo->name, \"brdchir1\") == 0) {\n        v1 = 1;\n    } else if (strcmp(ambientSoundEffectInfo->name, \"brdchirp\") == 0) {\n        v1 = 2;\n    }\n\n    if (v1 != 0) {\n        int dayPart;\n\n        int gameTimeHour = game_time_hour();\n        if (gameTimeHour <= 600 || gameTimeHour >= 1800) {\n            dayPart = DAY_PART_NIGHT;\n        } else if (gameTimeHour >= 1200) {\n            dayPart = DAY_PART_AFTERNOON;\n        } else {\n            dayPart = DAY_PART_MORNING;\n        }\n\n        if (dayPart == DAY_PART_NIGHT) {\n            *namePtr = wmRemapSfxList[v1 - 1];\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C50F4\nstatic int wmRefreshInterfaceOverlay(bool shouldRefreshWindow)\n{\n    trans_buf_to_buf(wmBkArtBuf,\n        wmBkWidth,\n        wmBkHeight,\n        wmBkWidth,\n        wmBkWinBuf,\n        WM_WINDOW_WIDTH);\n\n    wmRefreshTabs();\n\n    // NOTE: Uninline.\n    wmInterfaceDialSyncTime(false);\n\n    wmRefreshInterfaceDial(false);\n\n    if (wmGenData.isInCar) {\n        unsigned char* data = art_frame_data(wmGenData.carImageFrm, wmGenData.carImageCurrentFrameIndex, 0);\n        if (data == NULL) {\n            return -1;\n        }\n\n        buf_to_buf(data,\n            wmGenData.carImageFrmWidth,\n            wmGenData.carImageFrmHeight,\n            wmGenData.carImageFrmWidth,\n            wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_Y + WM_WINDOW_CAR_X,\n            WM_WINDOW_WIDTH);\n\n        trans_buf_to_buf(wmGenData.carImageOverlayFrmData,\n            wmGenData.carImageOverlayFrmWidth,\n            wmGenData.carImageOverlayFrmHeight,\n            wmGenData.carImageOverlayFrmWidth,\n            wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_OVERLAY_Y + WM_WINDOW_CAR_OVERLAY_X,\n            WM_WINDOW_WIDTH);\n\n        wmInterfaceRefreshCarFuel();\n    } else {\n        trans_buf_to_buf(wmGenData.globeOverlayFrmData,\n            wmGenData.globeOverlayFrmWidth,\n            wmGenData.globeOverlayFrmHeight,\n            wmGenData.globeOverlayFrmWidth,\n            wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_GLOBE_OVERLAY_Y + WM_WINDOW_GLOBE_OVERLAY_X,\n            WM_WINDOW_WIDTH);\n    }\n\n    wmInterfaceRefreshDate(false);\n\n    if (shouldRefreshWindow) {\n        win_draw(wmBkWin);\n    }\n\n    return 0;\n}\n\n// 0x4C5244\nstatic void wmInterfaceRefreshCarFuel()\n{\n    int ratio = (WM_WINDOW_CAR_FUEL_BAR_HEIGHT * wmGenData.carFuel) / CAR_FUEL_MAX;\n    if ((ratio & 1) != 0) {\n        ratio -= 1;\n    }\n\n    unsigned char* dest = wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_FUEL_BAR_Y + WM_WINDOW_CAR_FUEL_BAR_X;\n\n    for (int index = WM_WINDOW_CAR_FUEL_BAR_HEIGHT; index > ratio; index--) {\n        *dest = 14;\n        dest += 640;\n    }\n\n    while (ratio > 0) {\n        *dest = 196;\n        dest += WM_WINDOW_WIDTH;\n\n        *dest = 14;\n        dest += WM_WINDOW_WIDTH;\n\n        ratio -= 2;\n    }\n}\n\n// 0x4C52B0\nstatic int wmRefreshTabs()\n{\n    unsigned char* v30;\n    unsigned char* v0;\n    int v31;\n    CityInfo* city;\n    Art* art;\n    CacheEntry* cache_entry;\n    int width;\n    int height;\n    unsigned char* buf;\n    int v10;\n    unsigned char* v11;\n    unsigned char* v12;\n    int v32;\n    unsigned char* v13;\n\n    trans_buf_to_buf(wmGenData.tabsBackgroundFrmData + wmGenData.tabsBackgroundFrmWidth * wmGenData.tabsOffsetY + 9, 119, 178, wmGenData.tabsBackgroundFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * 135 + 501, WM_WINDOW_WIDTH);\n\n    v30 = wmBkWinBuf + WM_WINDOW_WIDTH * 138 + 530;\n    v0 = wmBkWinBuf + WM_WINDOW_WIDTH * 138 + 530 - WM_WINDOW_WIDTH * (wmGenData.tabsOffsetY % 27);\n    v31 = wmGenData.tabsOffsetY / 27;\n\n    if (v31 < wmLabelCount) {\n        city = &(wmAreaInfoList[wmLabelList[v31]]);\n        if (city->labelFid != -1) {\n            art = art_ptr_lock(city->labelFid, &cache_entry);\n            if (art == NULL) {\n                return -1;\n            }\n\n            width = art_frame_width(art, 0, 0);\n            height = art_frame_length(art, 0, 0);\n            buf = art_frame_data(art, 0, 0);\n            if (buf == NULL) {\n                return -1;\n            }\n\n            v10 = height - wmGenData.tabsOffsetY % 27;\n            v11 = buf + width * (wmGenData.tabsOffsetY % 27);\n\n            v12 = v0;\n            if (v0 < v30 - WM_WINDOW_WIDTH) {\n                v12 = v30 - WM_WINDOW_WIDTH;\n            }\n\n            buf_to_buf(v11, width, v10, width, v12, WM_WINDOW_WIDTH);\n            art_ptr_unlock(cache_entry);\n            cache_entry = INVALID_CACHE_ENTRY;\n        }\n    }\n\n    v13 = v0 + WM_WINDOW_WIDTH * 27;\n    v32 = v31 + 6;\n\n    for (int v14 = v31 + 1; v14 < v32; v14++) {\n        if (v14 < wmLabelCount) {\n            city = &(wmAreaInfoList[wmLabelList[v14]]);\n            if (city->labelFid != -1) {\n                art = art_ptr_lock(city->labelFid, &cache_entry);\n                if (art == NULL) {\n                    return -1;\n                }\n\n                width = art_frame_width(art, 0, 0);\n                height = art_frame_length(art, 0, 0);\n                buf = art_frame_data(art, 0, 0);\n                if (buf == NULL) {\n                    return -1;\n                }\n\n                buf_to_buf(buf, width, height, width, v13, WM_WINDOW_WIDTH);\n                art_ptr_unlock(cache_entry);\n\n                cache_entry = INVALID_CACHE_ENTRY;\n            }\n        }\n        v13 += WM_WINDOW_WIDTH * 27;\n    }\n\n    if (v31 + 6 < wmLabelCount) {\n        city = &(wmAreaInfoList[wmLabelList[v31 + 6]]);\n        if (city->labelFid != -1) {\n            art = art_ptr_lock(city->labelFid, &cache_entry);\n            if (art == NULL) {\n                return -1;\n            }\n\n            width = art_frame_width(art, 0, 0);\n            height = art_frame_length(art, 0, 0);\n            buf = art_frame_data(art, 0, 0);\n            if (buf == NULL) {\n                return -1;\n            }\n\n            buf_to_buf(buf, width, height, width, v13, WM_WINDOW_WIDTH);\n            art_ptr_unlock(cache_entry);\n\n            cache_entry = INVALID_CACHE_ENTRY;\n        }\n    }\n\n    trans_buf_to_buf(wmGenData.tabsBorderFrmData, 119, 178, 119, wmBkWinBuf + WM_WINDOW_WIDTH * 135 + 501, WM_WINDOW_WIDTH);\n\n    return 0;\n}\n\n// Creates array of cities available as quick destinations.\n//\n// 0x4C55D4\nstatic int wmMakeTabsLabelList(int** quickDestinationsPtr, int* quickDestinationsLengthPtr)\n{\n    int* quickDestinations = *quickDestinationsPtr;\n\n    // NOTE: Uninline.\n    wmFreeTabsLabelList(quickDestinationsPtr, quickDestinationsLengthPtr);\n\n    int capacity = 10;\n\n    quickDestinations = (int*)mem_malloc(sizeof(*quickDestinations) * capacity);\n    *quickDestinationsPtr = quickDestinations;\n\n    if (quickDestinations == NULL) {\n        return -1;\n    }\n\n    int quickDestinationsLength = *quickDestinationsLengthPtr;\n    for (int index = 0; index < wmMaxAreaNum; index++) {\n        if (wmAreaIsKnown(index) && wmAreaInfoList[index].labelFid != -1) {\n            quickDestinationsLength++;\n            *quickDestinationsLengthPtr = quickDestinationsLength;\n\n            if (capacity <= quickDestinationsLength) {\n                capacity += 10;\n\n                quickDestinations = (int*)mem_realloc(quickDestinations, sizeof(*quickDestinations) * capacity);\n                if (quickDestinations == NULL) {\n                    return -1;\n                }\n\n                *quickDestinationsPtr = quickDestinations;\n            }\n\n            quickDestinations[quickDestinationsLength - 1] = index;\n        }\n    }\n\n    qsort(quickDestinations, quickDestinationsLength, sizeof(*quickDestinations), wmTabsCompareNames);\n\n    return 0;\n}\n\n// 0x4C56C8\nstatic int wmTabsCompareNames(const void* a1, const void* a2)\n{\n    int v1 = *(int*)a1;\n    int v2 = *(int*)a2;\n\n    CityInfo* city1 = &(wmAreaInfoList[v1]);\n    CityInfo* city2 = &(wmAreaInfoList[v2]);\n\n    return stricmp(city1->name, city2->name);\n}\n\n// NOTE: Inlined.\n//\n// 0x4C5710\nstatic int wmFreeTabsLabelList(int** quickDestinationsListPtr, int* quickDestinationsLengthPtr)\n{\n    if (*quickDestinationsListPtr != NULL) {\n        mem_free(*quickDestinationsListPtr);\n        *quickDestinationsListPtr = NULL;\n    }\n\n    *quickDestinationsLengthPtr = 0;\n\n    return 0;\n}\n\n// 0x4C5734\nstatic void wmRefreshInterfaceDial(bool shouldRefreshWindow)\n{\n    unsigned char* data = art_frame_data(wmGenData.dialFrm, wmGenData.dialFrmCurrentFrameIndex, 0);\n    trans_buf_to_buf(data,\n        wmGenData.dialFrmWidth,\n        wmGenData.dialFrmHeight,\n        wmGenData.dialFrmWidth,\n        wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_DIAL_Y + WM_WINDOW_DIAL_X,\n        WM_WINDOW_WIDTH);\n\n    if (shouldRefreshWindow) {\n        Rect rect;\n        rect.ulx = WM_WINDOW_DIAL_X;\n        rect.uly = WM_WINDOW_DIAL_Y - 1;\n        rect.lrx = rect.ulx + wmGenData.dialFrmWidth;\n        rect.lry = rect.uly + wmGenData.dialFrmHeight;\n        win_draw_rect(wmBkWin, &rect);\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4C57BC\nstatic void wmInterfaceDialSyncTime(bool shouldRefreshWindow)\n{\n    int gameHour;\n    int frame;\n\n    gameHour = game_time_hour();\n    frame = (gameHour / 100 + 12) % art_frame_max_frame(wmGenData.dialFrm);\n    if (frame != wmGenData.dialFrmCurrentFrameIndex) {\n        wmGenData.dialFrmCurrentFrameIndex = frame;\n        wmRefreshInterfaceDial(shouldRefreshWindow);\n    }\n}\n\n// 0x4C5804\nstatic int wmAreaFindFirstValidMap(int* mapIdxPtr)\n{\n    *mapIdxPtr = -1;\n\n    if (wmGenData.currentAreaId == -1) {\n        return -1;\n    }\n\n    CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]);\n    if (city->entrancesLength == 0) {\n        return -1;\n    }\n\n    for (int index = 0; index < city->entrancesLength; index++) {\n        EntranceInfo* entrance = &(city->entrances[index]);\n        if (entrance->state != 0) {\n            *mapIdxPtr = entrance->map;\n            return 0;\n        }\n    }\n\n    EntranceInfo* entrance = &(city->entrances[0]);\n    entrance->state = 1;\n\n    *mapIdxPtr = entrance->map;\n    return 0;\n}\n\n// 0x4C58C0\nint wmMapMusicStart()\n{\n    do {\n        int mapIdx = map_get_index_number();\n        if (mapIdx == -1 || mapIdx >= wmMaxMapNum) {\n            break;\n        }\n\n        MapInfo* map = &(wmMapInfoList[mapIdx]);\n        if (strlen(map->music) == 0) {\n            break;\n        }\n\n        if (gsound_background_play_level_music(map->music, 12) == -1) {\n            break;\n        }\n\n        return 0;\n    } while (0);\n\n    debug_printf(\"\\nWorldMap Error: Couldn't start map Music!\");\n\n    return -1;\n}\n\n// 0x4C5928\nint wmSetMapMusic(int mapIdx, const char* name)\n{\n    if (mapIdx == -1 || mapIdx >= wmMaxMapNum) {\n        return -1;\n    }\n\n    if (name == NULL) {\n        return -1;\n    }\n\n    debug_printf(\"\\nwmSetMapMusic: %d, %s\", mapIdx, name);\n\n    MapInfo* map = &(wmMapInfoList[mapIdx]);\n\n    strncpy(map->music, name, 40);\n    map->music[39] = '\\0';\n\n    if (map_get_index_number() == mapIdx) {\n        gsound_background_stop();\n        wmMapMusicStart();\n    }\n\n    return 0;\n}\n\n// 0x4C59A4\nint wmMatchAreaContainingMapIdx(int mapIdx, int* cityIdxPtr)\n{\n    *cityIdxPtr = 0;\n\n    for (int cityIdx = 0; cityIdx < wmMaxAreaNum; cityIdx++) {\n        CityInfo* cityInfo = &(wmAreaInfoList[cityIdx]);\n        for (int entranceIdx = 0; entranceIdx < cityInfo->entrancesLength; entranceIdx++) {\n            EntranceInfo* entranceInfo = &(cityInfo->entrances[entranceIdx]);\n            if (entranceInfo->map == mapIdx) {\n                *cityIdxPtr = cityIdx;\n                return 0;\n            }\n        }\n    }\n\n    return -1;\n}\n\n// 0x4C5A1C\nint wmTeleportToArea(int cityIdx)\n{\n    if (!cityIsValid(cityIdx)) {\n        return -1;\n    }\n\n    wmGenData.currentAreaId = cityIdx;\n    wmGenData.walkDestinationX = 0;\n    wmGenData.walkDestinationY = 0;\n    wmGenData.isWalking = false;\n\n    CityInfo* city = &(wmAreaInfoList[cityIdx]);\n    wmGenData.worldPosX = city->x;\n    wmGenData.worldPosY = city->y;\n\n    return 0;\n}\n\n// TODO: Remove.\nstatic bool cityIsValid(int areaIdx)\n{\n    return areaIdx >= 0 && areaIdx < wmMaxAreaNum;\n}\n"
  },
  {
    "path": "src/game/worldmap.h",
    "content": "#ifndef FALLOUT_GAME_WORLDMAP_H_\n#define FALLOUT_GAME_WORLDMAP_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n\n#define CAR_FUEL_MAX (80000)\n\ntypedef enum MapFlags {\n    MAP_SAVED = 0x01,\n    MAP_DEAD_BODIES_AGE = 0x02,\n    MAP_PIPBOY_ACTIVE = 0x04,\n    MAP_CAN_REST_ELEVATION_0 = 0x08,\n    MAP_CAN_REST_ELEVATION_1 = 0x10,\n    MAP_CAN_REST_ELEVATION_2 = 0x20,\n} MapFlags;\n\ntypedef enum CityState {\n    CITY_STATE_UNKNOWN,\n    CITY_STATE_KNOWN,\n    CITY_STATE_VISITED,\n    CITY_STATE_INVISIBLE = -66,\n} CityState;\n\ntypedef enum City {\n    CITY_ARROYO,\n    CITY_DEN,\n    CITY_KLAMATH,\n    CITY_MODOC,\n    CITY_VAULT_CITY,\n    CITY_GECKO,\n    CITY_BROKEN_HILLS,\n    CITY_NEW_RENO,\n    CITY_SIERRA_ARMY_BASE,\n    CITY_VAULT_15,\n    CITY_NEW_CALIFORNIA_REPUBLIC,\n    CITY_VAULT_13,\n    CITY_MILITARY_BASE,\n    CITY_REDDING,\n    CITY_SAN_FRANCISCO,\n    CITY_NAVARRO,\n    CITY_ENCLAVE,\n    CITY_ABBEY,\n    CITY_PRIMITIVE_TRIBE,\n    CITY_ENV_PROTECTION_AGENCY,\n    CITY_MODOC_GHOST_TOWN,\n    CITY_CAR_OUT_OF_GAS,\n    CITY_DESTROYED_ARROYO,\n    CITY_KLAMATH_TOXIC_CAVES,\n    CITY_DEN_SLAVE_RUN,\n    CITY_RAIDERS,\n    CITY_RANDOM_ENCOUNTER_DESERT,\n    CITY_RANDOM_ENCOUNTER_MOUNTAIN,\n    CITY_RANDOM_ENCOUNTER_CITY,\n    CITY_RANDOM_ENCOUNTER_COAST,\n    CITY_GOLGOTHA,\n    CITY_SPECIAL_ENCOUNTER_WHALE,\n    CITY_SPECIAL_ENCOUNTER_TIN_WOODSMAN,\n    CITY_SPECIAL_ENCOUNTER_BIG_HEAD,\n    CITY_SPECIAL_ENCOUNTER_FEDERATION_SHUTTLE,\n    CITY_SPECIAL_ENCOUNTER_UNWASHED_VILLAGERS,\n    CITY_SPECIAL_ENCOUNTER_MONTY_PYTHON_BRIDGE,\n    CITY_SPECIAL_ENCOUNTER_CAFE_OF_BROKEN_DREAMS,\n    CITY_SPECIAL_ENCOUNTER_HOLY_HAND_GRANADE_I,\n    CITY_SPECIAL_ENCOUNTER_HOLY_HAND_GRANADE_II,\n    CITY_SPECIAL_ENCOUNTER_GUARDIAN_OF_FOREVER,\n    CITY_SPECIAL_ENCOUNTER_TOXIC_WASTE_DUMP,\n    CITY_SPECIAL_ENCOUNTER_PARIAHS,\n    CITY_SPECIAL_ENCOUNTER_MAD_COWS,\n    CITY_CARAVAN_ENCOUNTERS,\n    CITY_FAKE_VAULT_13_A,\n    CITY_FAKE_VAULT_13_B,\n    CITY_SHADOW_WORLDS,\n    CITY_RENO_STABLES,\n    CITY_COUNT,\n} City;\n\ntypedef enum Map {\n    MAP_RND_DESERT_1 = 0,\n    MAP_RND_DESERT_2 = 1,\n    MAP_RND_DESERT_3 = 2,\n    MAP_ARROYO_CAVES = 3,\n    MAP_ARROYO_VILLAGE = 4,\n    MAP_ARROYO_BRIDGE = 5,\n    MAP_DEN_ENTRANCE = 6,\n    MAP_DEN_BUSINESS = 7,\n    MAP_DEN_RESIDENTIAL = 8,\n    MAP_KLAMATH_1 = 9,\n    MAP_KLAMATH_MALL = 10,\n    MAP_KLAMATH_RATCAVES = 11,\n    MAP_KLAMATH_TOXICCAVES = 12,\n    MAP_KLAMATH_TRAPCAVES = 13,\n    MAP_KLAMATH_GRAZE = 14,\n    MAP_VAULTCITY_COURTYARD = 15,\n    MAP_VAULTCITY_DOWNTOWN = 16,\n    MAP_VAULTCITY_COUNCIL = 17,\n    MAP_MODOC_MAINSTREET = 18,\n    MAP_MODOC_BEDNBREAKFAST = 19,\n    MAP_MODOC_BRAHMINPASTURES = 20,\n    MAP_MODOC_GARDEN = 21,\n    MAP_MODOC_DOWNTHESHITTER = 22,\n    MAP_MODOC_WELL = 23,\n    MAP_GHOST_FARM = 24,\n    MAP_GHOST_CAVERN = 25,\n    MAP_GHOST_LAKE = 26,\n    MAP_SIERRA_BATTLE = 27,\n    MAP_SIERRA_123 = 28,\n    MAP_SIERRA_4 = 29,\n    MAP_VAULT_CITY_VAULT = 30,\n    MAP_GECKO_SETTLEMENT = 31,\n    MAP_GECKO_POWER_PLANT = 32,\n    MAP_GECKO_JUNKYARD = 33,\n    MAP_GECKO_ACCESS_TUNNELS = 34,\n    MAP_ARROYO_WILDERNESS = 35,\n    MAP_VAULT_15 = 36,\n    MAP_THE_SQUAT_A = 37,\n    MAP_THE_SQUAT_B = 38,\n    MAP_VAULT_15_EAST_ENTRANCE = 39,\n    MAP_VAULT_13 = 40,\n    MAP_VAULT_13_ENTRANCE = 41,\n    MAP_NCR_DOWNTOWN = 42,\n    MAP_NCR_COUNCIL_1 = 43,\n    MAP_NCR_WESTIN_RANCH = 44,\n    MAP_NCR_GRAZING_LANDS = 45,\n    MAP_NCR_BAZAAR = 46,\n    MAP_NCR_COUNCIL_2 = 47,\n    MAP_KLAMATH_CANYON = 48,\n    MAP_MILITARY_BASE_12 = 49,\n    MAP_MILITARY_BASE_34 = 50,\n    MAP_MILITARY_BASE_ENTRANCE = 51,\n    MAP_DEN_SLAVE_RUN = 52,\n    MAP_CAR_DESERT = 53,\n    MAP_NEW_RENO_1 = 54,\n    MAP_NEW_RENO_2 = 55,\n    MAP_NEW_RENO_3 = 56,\n    MAP_NEW_RENO_4 = 57,\n    MAP_NEW_RENO_CHOP_SHOP = 58,\n    MAP_NEW_RENO_GOLGATHA = 59,\n    MAP_NEW_RENO_STABLES = 60,\n    MAP_NEW_RENO_BOXING = 61,\n    MAP_REDDING_WANAMINGO_ENT = 62,\n    MAP_REDDING_WANAMINGO_12 = 63,\n    MAP_REDDING_DOWNTOWN = 64,\n    MAP_REDDING_MINE_ENT = 65,\n    MAP_REDDING_DTOWN_TUNNEL = 66,\n    MAP_REDDING_MINE_TUNNEL = 67,\n    MAP_RND_CITY1 = 68,\n    MAP_RND_CAVERN0 = 69,\n    MAP_RND_CAVERN1 = 70,\n    MAP_RND_CAVERN2 = 71,\n    MAP_RND_CAVERN3 = 72,\n    MAP_RND_CAVERN4 = 73,\n    MAP_RND_MOUNTAIN1 = 74,\n    MAP_RND_MOUNTAIN2 = 75,\n    MAP_RND_COAST1 = 76,\n    MAP_RND_COAST2 = 77,\n    MAP_BROKEN_HILLS1 = 78,\n    MAP_BROKEN_HILLS2 = 79,\n    MAP_RND_CAVERN5 = 80,\n    MAP_RND_DESERT4 = 81,\n    MAP_RND_DESERT5 = 82,\n    MAP_RND_DESERT6 = 83,\n    MAP_RND_DESERT7 = 84,\n    MAP_RND_COAST3 = 85,\n    MAP_RND_COAST4 = 86,\n    MAP_RND_COAST5 = 87,\n    MAP_RND_COAST6 = 88,\n    MAP_RND_COAST7 = 89,\n    MAP_RND_COAST8 = 90,\n    MAP_RND_COAST9 = 91,\n    MAP_RAIDERS_CAMP1 = 92,\n    MAP_RAIDERS_CAMP2 = 93,\n    MAP_BH_RND_DESERT = 94,\n    MAP_BH_RND_MOUNTAIN = 95,\n    MAP_SPECIAL_RND_WHALE = 96,\n    MAP_SPECIAL_RND_WOODSMAN = 97,\n    MAP_SPECIAL_RND_HEAD = 98,\n    MAP_SPECIAL_RND_SHUTTLE = 99,\n    MAP_SPECIAL_RND_UNWASHED = 100,\n    MAP_SPECIAL_RND_BRIDGE = 101,\n    MAP_SPECIAL_RND_CAFE = 102,\n    MAP_SPECIAL_RND_HOLY1 = 103,\n    MAP_SPECIAL_RND_HOLY2 = 104,\n    MAP_SPECIAL_RND_GUARDIAN = 105,\n    MAP_SPECIAL_RND_TOXIC = 106,\n    MAP_SPECIAL_RND_PARIAH = 107,\n    MAP_SPECIAL_RND_MAD_COW = 108,\n    MAP_NAVARRO_ENTRANCE = 109,\n    MAP_RND_COAST_10 = 110,\n    MAP_RND_COAST_11 = 111,\n    MAP_RND_COAST_12 = 112,\n    MAP_RND_DESERT_8 = 113,\n    MAP_RND_DESERT_9 = 114,\n    MAP_RND_DESERT_10 = 115,\n    MAP_RND_DESERT_11 = 116,\n    MAP_RND_DESERT_12 = 117,\n    MAP_RND_CAVERN_5 = 118,\n    MAP_RND_CAVERN_6 = 119,\n    MAP_RND_CAVERN_7 = 120,\n    MAP_RND_MOUNTAIN_3 = 121,\n    MAP_RND_MOUNTAIN_4 = 122,\n    MAP_RND_MOUNTAIN_5 = 123,\n    MAP_RND_MOUNTAIN_6 = 124,\n    MAP_RND_CITY_2 = 125,\n    MAP_ARROYO_TEMPLE = 126,\n    MAP_DESTROYED_ARROYO_BRIDGE = 127,\n    MAP_ENCLAVE_DETENTION = 128,\n    MAP_ENCLAVE_DOCK = 129,\n    MAP_ENCLAVE_END_FIGHT = 130,\n    MAP_ENCLAVE_BARRACKS = 131,\n    MAP_ENCLAVE_PRESIDENT = 132,\n    MAP_ENCLAVE_REACTOR = 133,\n    MAP_ENCLAVE_TRAP_ROOM = 134,\n    MAP_SAN_FRAN_TANKER = 135,\n    MAP_SAN_FRAN_DOCK = 136,\n    MAP_SAN_FRAN_CHINATOWN = 137,\n    MAP_SHUTTLE_EXTERIOR = 138,\n    MAP_SHUTTLE_INTERIOR = 139,\n    MAP_ELRONOLOGIST_BASE = 140,\n    MAP_RND_CITY_3 = 141,\n    MAP_RND_CITY_4 = 142,\n    MAP_RND_CITY_5 = 143,\n    MAP_RND_CITY_6 = 144,\n    MAP_RND_CITY_7 = 145,\n    MAP_RND_CITY_8 = 146,\n    MAP_NEW_RENO_VB = 147,\n    MAP_SHI_TEMPLE = 148,\n    MAP_IN_GAME_MOVIE1 = 149,\n} Map;\n\nextern unsigned char* circleBlendTable;\n\nint wmWorldMap_init();\nvoid wmWorldMap_exit();\nint wmWorldMap_reset();\nint wmWorldMap_save(File* stream);\nint wmWorldMap_load(File* stream);\nint wmMapMaxCount();\nint wmMapIdxToName(int mapIdx, char* dest);\nint wmMapMatchNameToIdx(char* name);\nbool wmMapIdxIsSaveable(int mapIdx);\nbool wmMapIsSaveable();\nbool wmMapDeadBodiesAge();\nbool wmMapCanRestHere(int elevation);\nbool wmMapPipboyActive();\nint wmMapMarkVisited(int mapIdx);\nint wmMapMarkMapEntranceState(int mapIdx, int elevation, int state);\nvoid wmWorldMap();\nint wmCheckGameAreaEvents();\nint wmSetupRandomEncounter();\nbool wmEvalTileNumForPlacement(int tile);\nint wmSubTileMarkRadiusVisited(int x, int y, int radius);\nint wmSubTileGetVisitedState(int x, int y, int* statePtr);\nint wmGetAreaIdxName(int areaIdx, char* name);\nbool wmAreaIsKnown(int areaIdx);\nint wmAreaVisitedState(int areaIdx);\nbool wmMapIsKnown(int mapIdx);\nint wmAreaMarkVisited(int areaIdx);\nbool wmAreaMarkVisitedState(int areaIdx, int state);\nbool wmAreaSetVisibleState(int areaIdx, int state, bool force);\nint wmAreaSetWorldPos(int areaIdx, int x, int y);\nint wmGetPartyWorldPos(int* xPtr, int* yPtr);\nint wmGetPartyCurArea(int* areaIdxPtr);\nvoid wmTownMap();\nint wmCarUseGas(int amount);\nint wmCarFillGas(int amout);\nint wmCarGasAmount();\nbool wmCarIsOutOfGas();\nint wmCarCurrentArea();\nint wmCarGiveToParty();\nint wmSfxMaxCount();\nint wmSfxRollNextIdx();\nint wmSfxIdxName(int sfxIdx, char** namePtr);\nint wmMapMusicStart();\nint wmSetMapMusic(int mapIdx, const char* name);\nint wmMatchAreaContainingMapIdx(int mapIdx, int* areaIdxPtr);\nint wmTeleportToArea(int areaIdx);\n\n#endif /* FALLOUT_GAME_WORLDMAP_H_ */\n"
  },
  {
    "path": "src/int/audio.c",
    "content": "#include \"int/audio.h\"\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/memdbg.h\"\n#include \"int/sound.h\"\n\nstatic bool defaultCompressionFunc(char* filePath);\nstatic int decodeRead(int fileHandle, void* buf, unsigned int size);\n\n// 0x5108BC\nstatic AudioFileIsCompressedProc* queryCompressedFunc = defaultCompressionFunc;\n\n// 0x56CB00\nstatic int numAudio;\n\n// 0x56CB04\nstatic AudioFile* audio;\n\n// 0x41A2B0\nbool defaultCompressionFunc(char* filePath)\n{\n    char* pch = strrchr(filePath, '.');\n    if (pch != NULL) {\n        strcpy(pch + 1, \"war\");\n    }\n\n    return false;\n}\n\n// 0x41A2D0\nint decodeRead(int fileHandle, void* buffer, unsigned int size)\n{\n    return db_fread(buffer, 1, size, (File*)fileHandle);\n}\n\n// 0x41A2EC\nint audioOpen(const char* fname, int flags, ...)\n{\n    char path[80];\n    sprintf(path, fname);\n\n    int compression;\n    if (queryCompressedFunc(path)) {\n        compression = 2;\n    } else {\n        compression = 0;\n    }\n\n    char mode[4];\n    memset(mode, 0, 4);\n\n    // NOTE: Original implementation is slightly different, it uses separate\n    // variable to track index where to set 't' and 'b'.\n    char* pm = mode;\n    if (flags & 1) {\n        *pm++ = 'w';\n    } else if (flags & 2) {\n        *pm++ = 'w';\n        *pm++ = '+';\n    } else {\n        *pm++ = 'r';\n    }\n\n    if (flags & 0x100) {\n        *pm++ = 't';\n    } else if (flags & 0x200) {\n        *pm++ = 'b';\n    }\n\n    File* stream = db_fopen(path, mode);\n    if (stream == NULL) {\n        debug_printf(\"AudioOpen: Couldn't open %s for read\\n\", path);\n        return -1;\n    }\n\n    int index;\n    for (index = 0; index < numAudio; index++) {\n        if ((audio[index].flags & AUDIO_FILE_IN_USE) == 0) {\n            break;\n        }\n    }\n\n    if (index == numAudio) {\n        if (audio != NULL) {\n            audio = (AudioFile*)myrealloc(audio, sizeof(*audio) * (numAudio + 1), __FILE__, __LINE__); // \"..\\int\\audio.c\", 216\n        } else {\n            audio = (AudioFile*)mymalloc(sizeof(*audio), __FILE__, __LINE__); // \"..\\int\\audio.c\", 218\n        }\n        numAudio++;\n    }\n\n    AudioFile* audioFile = &(audio[index]);\n    audioFile->flags = AUDIO_FILE_IN_USE;\n    audioFile->fileHandle = (int)stream;\n\n    if (compression == 2) {\n        audioFile->flags |= AUDIO_FILE_COMPRESSED;\n        audioFile->soundDecoder = soundDecoderInit(decodeRead, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize));\n        audioFile->fileSize *= 2;\n    } else {\n        audioFile->fileSize = db_filelength(stream);\n    }\n\n    audioFile->position = 0;\n\n    return index + 1;\n}\n\n// 0x41A50C\nint audioCloseFile(int fileHandle)\n{\n    AudioFile* audioFile = &(audio[fileHandle - 1]);\n    db_fclose((File*)audioFile->fileHandle);\n\n    if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {\n        soundDecoderFree(audioFile->soundDecoder);\n    }\n\n    memset(audioFile, 0, sizeof(AudioFile));\n\n    return 0;\n}\n\n// 0x41A574\nint audioRead(int fileHandle, void* buffer, unsigned int size)\n{\n    AudioFile* audioFile = &(audio[fileHandle - 1]);\n\n    int bytesRead;\n    if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {\n        bytesRead = soundDecoderDecode(audioFile->soundDecoder, buffer, size);\n    } else {\n        bytesRead = db_fread(buffer, 1, size, (File*)audioFile->fileHandle);\n    }\n\n    audioFile->position += bytesRead;\n\n    return bytesRead;\n}\n\n// 0x41A5E0\nlong audioSeek(int fileHandle, long offset, int origin)\n{\n    int pos;\n    unsigned char* buf;\n    int v10;\n\n    AudioFile* audioFile = &(audio[fileHandle - 1]);\n\n    switch (origin) {\n    case SEEK_SET:\n        pos = offset;\n        break;\n    case SEEK_CUR:\n        pos = offset + audioFile->position;\n        break;\n    case SEEK_END:\n        pos = offset + audioFile->fileSize;\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {\n        if (pos < audioFile->position) {\n            soundDecoderFree(audioFile->soundDecoder);\n            db_fseek((File*)audioFile->fileHandle, 0, SEEK_SET);\n            audioFile->soundDecoder = soundDecoderInit(decodeRead, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize));\n            audioFile->position = 0;\n            audioFile->fileSize *= 2;\n\n            if (pos != 0) {\n                buf = (unsigned char*)mymalloc(4096, __FILE__, __LINE__); // \"..\\int\\audio.c\", 361\n                while (pos > 4096) {\n                    pos -= 4096;\n                    audioRead(fileHandle, buf, 4096);\n                }\n\n                if (pos != 0) {\n                    audioRead(fileHandle, buf, pos);\n                }\n\n                myfree(buf, __FILE__, __LINE__); // // \"..\\int\\audio.c\", 367\n            }\n        } else {\n            buf = (unsigned char*)mymalloc(1024, __FILE__, __LINE__); // \"..\\int\\audio.c\", 321\n            v10 = audioFile->position - pos;\n            while (v10 > 1024) {\n                v10 -= 1024;\n                audioRead(fileHandle, buf, 1024);\n            }\n\n            if (v10 != 0) {\n                audioRead(fileHandle, buf, v10);\n            }\n\n            // TODO: Probably leaks memory.\n        }\n\n        return audioFile->position;\n    } else {\n        return db_fseek((File*)audioFile->fileHandle, offset, origin);\n    }\n}\n\n// 0x41A78C\nlong audioFileSize(int fileHandle)\n{\n    AudioFile* audioFile = &(audio[fileHandle - 1]);\n    return audioFile->fileSize;\n}\n\n// 0x41A7A8\nlong audioTell(int fileHandle)\n{\n    AudioFile* audioFile = &(audio[fileHandle - 1]);\n    return audioFile->position;\n}\n\n// 0x41A7C4\nint audioWrite(int handle, const void* buf, unsigned int size)\n{\n    debug_printf(\"AudioWrite shouldn't be ever called\\n\");\n    return 0;\n}\n\n// 0x41A7D4\nint initAudio(AudioFileIsCompressedProc* isCompressedProc)\n{\n    queryCompressedFunc = isCompressedProc;\n    audio = NULL;\n    numAudio = 0;\n\n    return soundSetDefaultFileIO(audioOpen, audioCloseFile, audioRead, audioWrite, audioSeek, audioTell, audioFileSize);\n}\n\n// 0x41A818\nvoid audioClose()\n{\n    if (audio != NULL) {\n        myfree(audio, __FILE__, __LINE__); // \"..\\int\\audio.c\", 406\n    }\n\n    numAudio = 0;\n    audio = NULL;\n}\n"
  },
  {
    "path": "src/int/audio.h",
    "content": "#ifndef FALLOUT_INT_AUDIO_H_\n#define FALLOUT_INT_AUDIO_H_\n\n#include \"int/audiof.h\"\n\nint audioOpen(const char* fname, int mode, ...);\nint audioCloseFile(int fileHandle);\nint audioRead(int fileHandle, void* buffer, unsigned int size);\nlong audioSeek(int fileHandle, long offset, int origin);\nlong audioFileSize(int fileHandle);\nlong audioTell(int fileHandle);\nint audioWrite(int handle, const void* buf, unsigned int size);\nint initAudio(AudioFileIsCompressedProc* isCompressedProc);\nvoid audioClose();\n\n#endif /* FALLOUT_INT_AUDIO_H_ */\n"
  },
  {
    "path": "src/int/audiof.c",
    "content": "#include \"int/audiof.h\"\n\n#include <assert.h>\n#include <io.h>\n#include <stdio.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"int/memdbg.h\"\n#include \"int/sound.h\"\n\nstatic_assert(sizeof(AudioFile) == 28, \"wrong size\");\n\nstatic bool defaultCompressionFunc(char* filePath);\nstatic int decodeRead(int fileHandle, void* buffer, unsigned int size);\n\n// 0x5108C0\nstatic AudioFileIsCompressedProc* queryCompressedFunc = defaultCompressionFunc;\n\n// 0x56CB10\nstatic AudioFile* audiof;\n\n// 0x56CB14\nstatic int numAudiof;\n\n// 0x41A850\nstatic bool defaultCompressionFunc(char* filePath)\n{\n    char* pch = strrchr(filePath, '.');\n    if (pch != NULL) {\n        strcpy(pch + 1, \"war\");\n    }\n\n    return false;\n}\n\n// 0x41A870\nstatic int decodeRead(int fileHandle, void* buffer, unsigned int size)\n{\n    return fread(buffer, 1, size, (FILE*)fileHandle);\n}\n\n// 0x41A88C\nint audiofOpen(const char* fname, int flags, ...)\n{\n    char path[MAX_PATH];\n    strcpy(path, fname);\n\n    int compression;\n    if (queryCompressedFunc(path)) {\n        compression = 2;\n    } else {\n        compression = 0;\n    }\n\n    char mode[4];\n    memset(mode, '\\0', 4);\n\n    // NOTE: Original implementation is slightly different, it uses separate\n    // variable to track index where to set 't' and 'b'.\n    char* pm = mode;\n    if (flags & 0x01) {\n        *pm++ = 'w';\n    } else if (flags & 0x02) {\n        *pm++ = 'w';\n        *pm++ = '+';\n    } else {\n        *pm++ = 'r';\n    }\n\n    if (flags & 0x0100) {\n        *pm++ = 't';\n    } else if (flags & 0x0200) {\n        *pm++ = 'b';\n    }\n\n    FILE* stream = fopen(path, mode);\n    if (stream == NULL) {\n        return -1;\n    }\n\n    int index;\n    for (index = 0; index < numAudiof; index++) {\n        if ((audiof[index].flags & AUDIO_FILE_IN_USE) == 0) {\n            break;\n        }\n    }\n\n    if (index == numAudiof) {\n        if (audiof != NULL) {\n            audiof = (AudioFile*)myrealloc(audiof, sizeof(*audiof) * (numAudiof + 1), __FILE__, __LINE__); // \"..\\int\\audiof.c\", 207\n        } else {\n            audiof = (AudioFile*)mymalloc(sizeof(*audiof), __FILE__, __LINE__); // \"..\\int\\audiof.c\", 209\n        }\n        numAudiof++;\n    }\n\n    AudioFile* audioFile = &(audiof[index]);\n    audioFile->flags = AUDIO_FILE_IN_USE;\n    audioFile->fileHandle = (int)stream;\n\n    if (compression == 2) {\n        audioFile->flags |= AUDIO_FILE_COMPRESSED;\n        audioFile->soundDecoder = soundDecoderInit(decodeRead, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize));\n        audioFile->fileSize *= 2;\n    } else {\n        audioFile->fileSize = filelength(fileno(stream));\n    }\n\n    audioFile->position = 0;\n\n    return index + 1;\n}\n\n// 0x41AAA0\nint audiofCloseFile(int fileHandle)\n{\n    AudioFile* audioFile = &(audiof[fileHandle - 1]);\n    fclose((FILE*)audioFile->fileHandle);\n\n    if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {\n        soundDecoderFree(audioFile->soundDecoder);\n    }\n\n    // Reset audio file (which also resets it's use flag).\n    memset(audioFile, 0, sizeof(*audioFile));\n\n    return 0;\n}\n\n// 0x41AB08\nint audiofRead(int fileHandle, void* buffer, unsigned int size)\n{\n\n    AudioFile* ptr = &(audiof[fileHandle - 1]);\n\n    int bytesRead;\n    if ((ptr->flags & AUDIO_FILE_COMPRESSED) != 0) {\n        bytesRead = soundDecoderDecode(ptr->soundDecoder, buffer, size);\n    } else {\n        bytesRead = fread(buffer, 1, size, (FILE*)ptr->fileHandle);\n    }\n\n    ptr->position += bytesRead;\n\n    return bytesRead;\n}\n\n// 0x41AB74\nlong audiofSeek(int fileHandle, long offset, int origin)\n{\n    void* buf;\n    int remaining;\n    int a4;\n\n    AudioFile* audioFile = &(audiof[fileHandle - 1]);\n\n    switch (origin) {\n    case SEEK_SET:\n        a4 = offset;\n        break;\n    case SEEK_CUR:\n        a4 = audioFile->fileSize + offset;\n        break;\n    case SEEK_END:\n        a4 = audioFile->position + offset;\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) {\n        if (a4 <= audioFile->position) {\n            soundDecoderFree(audioFile->soundDecoder);\n\n            fseek((FILE*)audioFile->fileHandle, 0, 0);\n\n            audioFile->soundDecoder = soundDecoderInit(decodeRead, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize));\n            audioFile->fileSize *= 2;\n            audioFile->position = 0;\n\n            if (a4) {\n                buf = mymalloc(4096, __FILE__, __LINE__); // \"..\\int\\audiof.c\", 364\n                while (a4 > 4096) {\n                    audiofRead(fileHandle, buf, 4096);\n                    a4 -= 4096;\n                }\n                if (a4 != 0) {\n                    audiofRead(fileHandle, buf, a4);\n                }\n                myfree(buf, __FILE__, __LINE__); // \"..\\int\\audiof.c\", 370\n            }\n        } else {\n            buf = mymalloc(0x400, __FILE__, __LINE__); // \"..\\int\\audiof.c\", 316\n            remaining = audioFile->position - a4;\n            while (remaining > 1024) {\n                audiofRead(fileHandle, buf, 1024);\n                remaining -= 1024;\n            }\n            if (remaining != 0) {\n                audiofRead(fileHandle, buf, remaining);\n            }\n            // TODO: Obiously leaks memory.\n        }\n        return audioFile->position;\n    }\n\n    return fseek((FILE*)audioFile->fileHandle, offset, origin);\n}\n\n// 0x41AD20\nlong audiofFileSize(int fileHandle)\n{\n    AudioFile* audioFile = &(audiof[fileHandle - 1]);\n    return audioFile->fileSize;\n}\n\n// 0x41AD3C\nlong audiofTell(int fileHandle)\n{\n    AudioFile* audioFile = &(audiof[fileHandle - 1]);\n    return audioFile->position;\n}\n\n// 0x41AD58\nint audiofWrite(int fileHandle, const void* buffer, unsigned int size)\n{\n    debug_printf(\"AudiofWrite shouldn't be ever called\\n\");\n    return 0;\n}\n\n// 0x41AD68\nint initAudiof(AudioFileIsCompressedProc* isCompressedProc)\n{\n    queryCompressedFunc = isCompressedProc;\n    audiof = NULL;\n    numAudiof = 0;\n\n    return soundSetDefaultFileIO(audiofOpen, audiofCloseFile, audiofRead, audiofWrite, audiofSeek, audiofTell, audiofFileSize);\n}\n\n// 0x41ADAC\nvoid audiofClose()\n{\n    if (audiof != NULL) {\n        myfree(audiof, __FILE__, __LINE__); // \"..\\int\\audiof.c\", 405\n    }\n\n    numAudiof = 0;\n    audiof = NULL;\n}\n"
  },
  {
    "path": "src/int/audiof.h",
    "content": "#ifndef FALLOUT_INT_AUDIOF_H_\n#define FALLOUT_INT_AUDIOF_H_\n\n#include <stdbool.h>\n\n#include \"sound_decoder.h\"\n\ntypedef enum AudioFileFlags {\n    AUDIO_FILE_IN_USE = 0x01,\n    AUDIO_FILE_COMPRESSED = 0x02,\n} AudioFileFlags;\n\ntypedef struct AudioFile {\n    int flags;\n    int fileHandle;\n    SoundDecoder* soundDecoder;\n    int fileSize;\n    int field_10;\n    int field_14;\n    int position;\n} AudioFile;\n\ntypedef bool(AudioFileIsCompressedProc)(char* filePath);\n\nint audiofOpen(const char* fname, int flags, ...);\nint audiofCloseFile(int a1);\nint audiofRead(int a1, void* buf, unsigned int size);\nlong audiofSeek(int handle, long offset, int origin);\nlong audiofFileSize(int a1);\nlong audiofTell(int a1);\nint audiofWrite(int handle, const void* buf, unsigned int size);\nint initAudiof(AudioFileIsCompressedProc* isCompressedProc);\nvoid audiofClose();\n\n#endif /* FALLOUT_INT_AUDIOF_H_ */\n"
  },
  {
    "path": "src/int/datafile.c",
    "content": "#include \"int/datafile.h\"\n\n#include <string.h>\n\n#include \"plib/color/color.h\"\n#include \"plib/db/db.h\"\n#include \"int/memdbg.h\"\n#include \"int/pcx.h\"\n\nstatic char* defaultMangleName(char* path);\n\n// 0x5184AC\nstatic DatafileLoader* loadFunc = NULL;\n\n// 0x5184B0\nstatic DatafileNameMangler* mangleName = defaultMangleName;\n\n// 0x56D7E0\nstatic unsigned char pal[768];\n\n// 0x42EE70\nstatic char* defaultMangleName(char* path)\n{\n    return path;\n}\n\n// NOTE: Unused.\n//\n// 0x42EE74\nvoid datafileSetFilenameFunc(DatafileNameMangler* mangler)\n{\n    mangleName = mangler;\n}\n\n// NOTE: Unused.\n//\n// 0x42EE7C\nvoid setBitmapLoadFunc(DatafileLoader* loader)\n{\n    loadFunc = loader;\n}\n\n// 0x42EE84\nvoid datafileConvertData(unsigned char* data, unsigned char* palette, int width, int height)\n{\n    unsigned char indexedPalette[256];\n\n    indexedPalette[0] = 0;\n    for (int index = 1; index < 256; index++) {\n        // TODO: Check.\n        int r = palette[index * 3 + 2] >> 3;\n        int g = palette[index * 3 + 1] >> 3;\n        int b = palette[index * 3] >> 3;\n        int colorTableIndex = (r << 10) | (g << 5) | b;\n        indexedPalette[index] = colorTable[colorTableIndex];\n    }\n\n    int size = width * height;\n    for (int index = 0; index < size; index++) {\n        data[index] = indexedPalette[data[index]];\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x42EEF8\nvoid datafileConvertDataVGA(unsigned char* data, unsigned char* palette, int width, int height)\n{\n    unsigned char indexedPalette[256];\n\n    indexedPalette[0] = 0;\n    for (int index = 1; index < 256; index++) {\n        // TODO: Check.\n        int r = palette[index * 3 + 2] >> 1;\n        int g = palette[index * 3 + 1] >> 1;\n        int b = palette[index * 3] >> 1;\n        int colorTableIndex = (r << 10) | (g << 5) | b;\n        indexedPalette[index] = colorTable[colorTableIndex];\n    }\n\n    int size = width * height;\n    for (int index = 0; index < size; index++) {\n        data[index] = indexedPalette[data[index]];\n    }\n}\n\n// 0x42EF60\nunsigned char* loadRawDataFile(char* path, int* widthPtr, int* heightPtr)\n{\n    char* mangledPath = mangleName(path);\n    char* dot = strrchr(mangledPath, '.');\n    if (dot != NULL) {\n        if (stricmp(dot + 1, \"pcx\") == 0) {\n            return loadPCX(mangledPath, widthPtr, heightPtr, pal);\n        }\n    }\n\n    if (loadFunc != NULL) {\n        return loadFunc(mangledPath, pal, widthPtr, heightPtr);\n    }\n\n    return NULL;\n}\n\n// 0x42EFCC\nunsigned char* loadDataFile(char* path, int* widthPtr, int* heightPtr)\n{\n    unsigned char* v1 = loadRawDataFile(path, widthPtr, heightPtr);\n    if (v1 != NULL) {\n        datafileConvertData(v1, pal, *widthPtr, *heightPtr);\n    }\n    return v1;\n}\n\n// NOTE: Unused\n//\n// 0x42EFF4\nunsigned char* load256Palette(char* path)\n{\n    int width;\n    int height;\n    unsigned char* v3 = loadRawDataFile(path, &width, &height);\n    if (v3 != NULL) {\n        myfree(v3, __FILE__, __LINE__); // \"..\\\\int\\\\DATAFILE.C\", 148\n        return pal;\n    }\n\n    return NULL;\n}\n\n// NOTE: Unused.\n//\n// 0x42F024\nvoid trimBuffer(unsigned char* data, int* widthPtr, int* heightPtr)\n{\n    int width = *widthPtr;\n    int height = *heightPtr;\n    unsigned char* temp = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // \"..\\\\int\\\\DATAFILE.C\", 157\n\n    // NOTE: Original code does not initialize `x`.\n    int y = 0;\n    int x = 0;\n    unsigned char* src1 = data;\n\n    for (y = 0; y < height; y++) {\n        if (*src1 == 0) {\n            break;\n        }\n\n        unsigned char* src2 = src1;\n        for (x = 0; x < width; x++) {\n            if (*src2 == 0) {\n                break;\n            }\n\n            *temp++ = *src2++;\n        }\n\n        src1 += width;\n    }\n\n    memcpy(data, temp, x * y);\n    myfree(temp, __FILE__, __LINE__); // // \"..\\\\int\\\\DATAFILE.C\", 171\n}\n\n// 0x42F0E4\nunsigned char* datafileGetPalette()\n{\n    return pal;\n}\n\n// NOTE: Unused.\n//\n// 0x42F0EC\nunsigned char* datafileLoadBlock(char* path, int* sizePtr)\n{\n    const char* mangledPath = mangleName(path);\n    File* stream = db_fopen(mangledPath, \"rb\");\n    if (stream == NULL) {\n        return NULL;\n    }\n\n    int size = db_filelength(stream);\n    void* data = mymalloc(size, __FILE__, __LINE__); // \"..\\\\int\\\\DATAFILE.C\", 185\n    if (data == NULL) {\n        // NOTE: This code is unreachable, mymalloc never fails.\n        // Otherwise it leaks stream.\n        *sizePtr = 0;\n        return NULL;\n    }\n\n    db_fread(data, 1, size, stream);\n    db_fclose(stream);\n    *sizePtr = size;\n    return data;\n}\n"
  },
  {
    "path": "src/int/datafile.h",
    "content": "#ifndef FALLOUT_INT_DATAFILE_H_\n#define FALLOUT_INT_DATAFILE_H_\n\ntypedef unsigned char*(DatafileLoader)(char* path, unsigned char* palette, int* widthPtr, int* heightPtr);\ntypedef char*(DatafileNameMangler)(char* path);\n\nvoid datafileSetFilenameFunc(DatafileNameMangler* mangler);\nvoid setBitmapLoadFunc(DatafileLoader* loader);\nvoid datafileConvertData(unsigned char* data, unsigned char* palette, int width, int height);\nvoid datafileConvertDataVGA(unsigned char* data, unsigned char* palette, int width, int height);\nunsigned char* loadRawDataFile(char* path, int* widthPtr, int* heightPtr);\nunsigned char* loadDataFile(char* path, int* widthPtr, int* heightPtr);\nunsigned char* load256Palette(char* path);\nvoid trimBuffer(unsigned char* data, int* widthPtr, int* heightPtr);\nunsigned char* datafileGetPalette();\nunsigned char* datafileLoadBlock(char* path, int* sizePtr);\n\n#endif /* FALLOUT_INT_DATAFILE_H_ */\n"
  },
  {
    "path": "src/int/dialog.c",
    "content": "#include \"int/dialog.h\"\n\n#include <string.h>\n\n#include \"int/window.h\"\n#include \"plib/gnw/input.h\"\n#include \"int/memdbg.h\"\n#include \"int/movie.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/gnw.h\"\n\ntypedef struct STRUCT_56DAE0_FIELD_4_FIELD_C {\n    char* field_0;\n    union {\n        int proc;\n        char* string;\n    };\n    int kind;\n    int field_C;\n    int field_10;\n    int field_14;\n    short field_18;\n    short field_1A;\n} STRUCT_56DAE0_FIELD_4_FIELD_C;\n\ntypedef struct STRUCT_56DAE0_FIELD_4 {\n    void* field_0;\n    char* field_4;\n    void* field_8;\n    STRUCT_56DAE0_FIELD_4_FIELD_C* field_C;\n    int field_10;\n    int field_14;\n    int field_18; // probably font number\n} STRUCT_56DAE0_FIELD_4;\n\ntypedef struct STRUCT_56DAE0 {\n    Program* field_0;\n    STRUCT_56DAE0_FIELD_4* field_4;\n    int field_8;\n    int field_C;\n    int field_10;\n    int field_14;\n    int field_18;\n} STRUCT_56DAE0;\n\ntypedef struct DialogWindowData {\n    short flags;\n    int width;\n    int height;\n    int x;\n    int y;\n    char* backgroundFileName;\n} DialogWindowData;\n\n// NOTE: There are |upButton| and |downButton| variables which are definitely\n// instances of some struct. The values are set only via scripting functions,\n// however they are never read back. It's impossible to understand the meaning\n// of the fields and give them names. I've commented my guesses based on\n// intuition.\ntypedef struct DialogScrollButtonData {\n    int field_0; // x\n    int field_4; // y\n    int field_8; // flags\n    char* field_C; // normal image file name\n    char* field_10; // pressed image file name\n    char* field_14; // hover image file name\n    char* field_18; // mask or disabled image file name\n} DialogScrollButtonData;\n\nstatic STRUCT_56DAE0_FIELD_4* getReply();\nstatic void replyAddOption(const char* a1, const char* a2, int a3);\nstatic void replyAddOptionProc(const char* a1, int a2, int a3);\nstatic void optionFree(STRUCT_56DAE0_FIELD_4_FIELD_C* a1);\nstatic void replyFree();\nstatic int endDialog();\nstatic void printLine(int win, char** strings, int strings_num, int a4, int a5, int a6, int a7, int a8, int a9);\nstatic void printStr(int win, char* a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9);\nstatic int abortReply(int a1);\nstatic void endReply();\nstatic void drawStr(int win, char* a2, int font, int width, int height, int left, int top, int a8, int a9, int a10);\n\n// 0x5184B4\nstatic int tods = -1;\n\n// 0x5184B8\nstatic int topDialogLine = 0;\n\n// 0x5184BC\nstatic int topDialogReply = 0;\n\n// 0x5184E4\nDialogWinDrawCallback* replyWinDrawCallback = NULL;\n\n// 0x5184E8\nDialogWinDrawCallback* optionsWinDrawCallback = NULL;\n\n// 0x5184EC\nstatic int defaultBorderX = 7;\n\n// 0x5184F0\nstatic int defaultBorderY = 7;\n\n// 0x5184F4\nstatic int defaultSpacing = 5;\n\n// 0x5184F8\nstatic int replyRGBset = 0;\n\n// 0x5184FC\nstatic int optionRGBset = 0;\n\n// 0x518500\nstatic int exitDialog = 0;\n\n// 0x518504\nstatic int inDialog = 0;\n\n// 0x518508\nstatic int mediaFlag = 2;\n\n// 0x56DAE0\nstatic STRUCT_56DAE0 dialog[4];\n\n// 0x56DB60\nstatic DialogWindowData defaultOption;\n\n// 0x56DB78\nstatic DialogWindowData defaultReply;\n\n// 0x56DB90\nstatic int replyPlaying;\n\n// 0x56DB94\nstatic int replyWin = -1;\n\n// 0x56DB98\nstatic int replyG;\n\n// 0x56DB9C\nstatic int replyB;\n\n// 0x56DBA4\nstatic int optionG;\n\n// 0x56DBA8\nstatic int replyR;\n\n// 0x56DBAC\nstatic int optionB;\n\n// 0x56DBB0\nstatic int optionR;\n\n// 0x56DBB4\nstatic DialogScrollButtonData downButton;\n\n// 0x56DBD0\nstatic char* replyTitleDefault;\n\n// 0x56DBD4\nstatic DialogScrollButtonData upButton;\n\n// 0x42F434\nstatic STRUCT_56DAE0_FIELD_4* getReply()\n{\n    STRUCT_56DAE0_FIELD_4* v0;\n    STRUCT_56DAE0_FIELD_4_FIELD_C* v1;\n\n    v0 = &(dialog[tods].field_4[dialog[tods].field_C]);\n    if (v0->field_C == NULL) {\n        v0->field_14 = 1;\n        v1 = (STRUCT_56DAE0_FIELD_4_FIELD_C*)mymalloc(sizeof(STRUCT_56DAE0_FIELD_4_FIELD_C), __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 789\n    } else {\n        v0->field_14++;\n        v1 = (STRUCT_56DAE0_FIELD_4_FIELD_C*)myrealloc(v0->field_C, sizeof(STRUCT_56DAE0_FIELD_4_FIELD_C) * v0->field_14, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 793\n    }\n    v0->field_C = v1;\n\n    return v0;\n}\n\n// 0x42F4C0\nstatic void replyAddOption(const char* a1, const char* a2, int a3)\n{\n    STRUCT_56DAE0_FIELD_4* v18;\n    int v17;\n    char* v14;\n    char* v15;\n\n    v18 = getReply();\n    v17 = v18->field_14 - 1;\n    v18->field_C[v17].kind = 2;\n\n    if (a1 != NULL) {\n        v14 = (char*)mymalloc(strlen(a1) + 1, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 805\n        strcpy(v14, a1);\n        v18->field_C[v17].field_0 = v14;\n    } else {\n        v18->field_C[v17].field_0 = NULL;\n    }\n\n    if (a2 != NULL) {\n        v15 = (char*)mymalloc(strlen(a2) + 1, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 810\n        strcpy(v15, a2);\n        v18->field_C[v17].string = v15;\n    } else {\n        v18->field_C[v17].string = NULL;\n    }\n\n    v18->field_C[v17].field_18 = windowGetFont();\n    v18->field_C[v17].field_1A = defaultOption.flags;\n    v18->field_C[v17].field_14 = a3;\n}\n\n// 0x42F624\nstatic void replyAddOptionProc(const char* a1, int a2, int a3)\n{\n    STRUCT_56DAE0_FIELD_4* v5;\n    int v13;\n    char* v11;\n\n    v5 = getReply();\n    v13 = v5->field_14 - 1;\n\n    v5->field_C[v13].kind = 1;\n\n    if (a1 != NULL) {\n        v11 = (char*)mymalloc(strlen(a1) + 1, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 830\n        strcpy(v11, a1);\n        v5->field_C[v13].field_0 = v11;\n    } else {\n        v5->field_C[v13].field_0 = NULL;\n    }\n\n    v5->field_C[v13].proc = a2;\n\n    v5->field_C[v13].field_18 = windowGetFont();\n    v5->field_C[v13].field_1A = defaultOption.flags;\n    v5->field_C[v13].field_14 = a3;\n}\n\n// 0x42F714\nstatic void optionFree(STRUCT_56DAE0_FIELD_4_FIELD_C* a1)\n{\n    if (a1->field_0 != NULL) {\n        myfree(a1->field_0, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 844\n    }\n\n    if (a1->kind == 2) {\n        if (a1->string != NULL) {\n            myfree(a1->string, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 846\n        }\n    }\n}\n\n// 0x42F754\nstatic void replyFree()\n{\n    int i;\n    int j;\n    STRUCT_56DAE0* ptr;\n    STRUCT_56DAE0_FIELD_4* v6;\n\n    ptr = &(dialog[tods]);\n    for (i = 0; i < ptr->field_8; i++) {\n        v6 = &(dialog[tods].field_4[i]);\n\n        if (v6->field_C != NULL) {\n            for (j = 0; j < v6->field_14; j++) {\n                optionFree(&(v6->field_C[j]));\n            }\n\n            myfree(v6->field_C, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 857\n        }\n\n        if (v6->field_8 != NULL) {\n            myfree(v6->field_8, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 860\n        }\n\n        if (v6->field_4 != NULL) {\n            myfree(v6->field_4, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 862\n        }\n\n        if (v6->field_0 != NULL) {\n            myfree(v6->field_0, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 864\n        }\n    }\n\n    if (ptr->field_4 != NULL) {\n        myfree(ptr->field_4, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 867\n    }\n}\n\n// 0x42FB94\nstatic int endDialog()\n{\n    if (tods == -1) {\n        return -1;\n    }\n\n    topDialogReply = dialog[tods].field_10;\n    replyFree();\n\n    if (replyTitleDefault != NULL) {\n        myfree(replyTitleDefault, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 986\n        replyTitleDefault = NULL;\n    }\n\n    --tods;\n\n    return 0;\n}\n\n// 0x42FC70\nstatic void printLine(int win, char** strings, int strings_num, int a4, int a5, int a6, int a7, int a8, int a9)\n{\n    int i;\n    int v11;\n\n    for (i = 0; i < strings_num; i++) {\n        v11 = a7 + i * text_height();\n        windowPrintBuf(win, strings[i], strlen(strings[i]), a4, a5 + a7, a6, v11, a8, a9);\n    }\n}\n\n// 0x42FCF0\nstatic void printStr(int win, char* a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)\n{\n    char** strings;\n    int strings_num;\n\n    strings = windowWordWrap(a2, a3, 0, &strings_num);\n    printLine(win, strings, strings_num, a3, a4, a5, a6, a7, a8);\n    windowFreeWordList(strings, strings_num);\n}\n\n// 0x430104\nstatic int abortReply(int a1)\n{\n    int result;\n    int y;\n    int x;\n\n    if (replyPlaying == 2) {\n        return moviePlaying() == 0;\n    } else if (replyPlaying == 3) {\n        return 1;\n    }\n\n    result = 1;\n    if (a1) {\n        if (replyWin != -1) {\n            if (!(mouse_get_buttons() & 0x10)) {\n                result = 0;\n            } else {\n                mouse_get_position(&x, &y);\n\n                if (win_get_top_win(x, y) != replyWin) {\n                    result = 0;\n                }\n            }\n        }\n    }\n    return result;\n}\n\n// 0x430180\nstatic void endReply()\n{\n    if (replyPlaying != 2) {\n        if (replyPlaying == 1) {\n            if (!(mediaFlag & 2) && replyWin != -1) {\n                win_delete(replyWin);\n                replyWin = -1;\n            }\n        } else if (replyPlaying != 3 && replyWin != -1) {\n            win_delete(replyWin);\n            replyWin = -1;\n        }\n    }\n}\n\n// 0x4301E8\nstatic void drawStr(int win, char* str, int font, int width, int height, int left, int top, int a8, int a9, int a10)\n{\n    int old_font;\n    Rect rect;\n\n    old_font = windowGetFont();\n    windowSetFont(font);\n\n    printStr(win, str, width, height, left, top, a8, a9, a10);\n\n    rect.ulx = left;\n    rect.uly = top;\n    rect.lrx = width + left;\n    rect.lry = height + top;\n    win_draw_rect(win, &rect);\n    windowSetFont(old_font);\n}\n\n// 0x430D40\nint dialogStart(Program* a1)\n{\n    STRUCT_56DAE0* ptr;\n\n    if (tods == 3) {\n        return 1;\n    }\n\n    ptr = &(dialog[tods]);\n    ptr->field_0 = a1;\n    ptr->field_4 = 0;\n    ptr->field_8 = 0;\n    ptr->field_C = -1;\n    ptr->field_10 = -1;\n    ptr->field_14 = 1;\n    ptr->field_10 = 1;\n\n    tods++;\n\n    return 0;\n}\n\n// 0x430DB8\nint dialogRestart()\n{\n    if (tods == -1) {\n        return 1;\n    }\n\n    dialog[tods].field_10 = 0;\n\n    return 0;\n}\n\n// 0x430DE4\nint dialogGotoReply(const char* a1)\n{\n    STRUCT_56DAE0* ptr;\n    STRUCT_56DAE0_FIELD_4* v5;\n    int i;\n\n    if (tods == -1) {\n        return 1;\n    }\n\n    if (a1 != NULL) {\n        ptr = &(dialog[tods]);\n        for (i = 0; i < ptr->field_8; i++) {\n            v5 = &(ptr->field_4[i]);\n            if (v5->field_4 != NULL && stricmp(v5->field_4, a1) == 0) {\n                ptr->field_10 = i;\n                return 0;\n            }\n        }\n\n        return 1;\n    }\n\n    dialog[tods].field_10 = 0;\n\n    return 0;\n}\n\n// 0x430E84\nint dialogTitle(const char* a1)\n{\n    if (replyTitleDefault != NULL) {\n        myfree(replyTitleDefault, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2561\n    }\n\n    if (a1 != NULL) {\n        replyTitleDefault = (char*)mymalloc(strlen(a1) + 1, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2564\n        strcpy(replyTitleDefault, a1);\n    } else {\n        replyTitleDefault = NULL;\n    }\n\n    return 0;\n}\n\n// 0x430EFC\nint dialogReply(const char* a1, const char* a2)\n{\n    // TODO: Incomplete.\n    // _replyAddNew(a1, a2);\n    return 0;\n}\n\n// 0x430F04\nint dialogOption(const char* a1, const char* a2)\n{\n    if (dialog[tods].field_C == -1) {\n        return 0;\n    }\n\n    replyAddOption(a1, a2, 0);\n\n    return 0;\n}\n\n// 0x430F38\nint dialogOptionProc(const char* a1, int a2)\n{\n    if (dialog[tods].field_C == -1) {\n        return 1;\n    }\n\n    replyAddOptionProc(a1, a2, 0);\n\n    return 0;\n}\n\n// 0x430FD4\nint dialogMessage(const char* a1, const char* a2, int timeout)\n{\n    // TODO: Incomplete.\n    return -1;\n}\n\n// 0x431088\nint dialogGo(int a1)\n{\n    // TODO: Incomplete.\n    return -1;\n}\n\n// 0x431184\nint dialogGetExitPoint()\n{\n    return topDialogLine + (topDialogReply << 16);\n}\n\n// 0x431198\nint dialogQuit()\n{\n    if (inDialog) {\n        exitDialog = 1;\n    } else {\n        endDialog();\n    }\n\n    return 0;\n}\n\n// 0x4311B8\nint dialogSetOptionWindow(int x, int y, int width, int height, char* backgroundFileName)\n{\n    defaultOption.x = x;\n    defaultOption.y = y;\n    defaultOption.width = width;\n    defaultOption.height = height;\n    defaultOption.backgroundFileName = backgroundFileName;\n\n    return 0;\n}\n\n// 0x4311E0\nint dialogSetReplyWindow(int x, int y, int width, int height, char* backgroundFileName)\n{\n    defaultReply.x = x;\n    defaultReply.y = y;\n    defaultReply.width = width;\n    defaultReply.height = height;\n    defaultReply.backgroundFileName = backgroundFileName;\n\n    return 0;\n}\n\n// 0x431208\nint dialogSetBorder(int a1, int a2)\n{\n    defaultBorderX = a1;\n    defaultBorderY = a2;\n\n    return 0;\n}\n\n// 0x431218\nint dialogSetScrollUp(int a1, int a2, char* a3, char* a4, char* a5, char* a6, int a7)\n{\n    upButton.field_0 = a1;\n    upButton.field_4 = a2;\n\n    if (upButton.field_C != NULL) {\n        myfree(upButton.field_C, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2750\n    }\n    upButton.field_C = a3;\n\n    if (upButton.field_10 != NULL) {\n        myfree(upButton.field_10, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2752\n    }\n    upButton.field_10 = a4;\n\n    if (upButton.field_14 != NULL) {\n        myfree(upButton.field_14, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2754\n    }\n    upButton.field_14 = a5;\n\n    if (upButton.field_18 != NULL) {\n        myfree(upButton.field_18, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2756\n    }\n    upButton.field_18 = a6;\n\n    upButton.field_8 = a7;\n\n    return 0;\n}\n\n// 0x4312C0\nint dialogSetScrollDown(int a1, int a2, char* a3, char* a4, char* a5, char* a6, int a7)\n{\n    downButton.field_0 = a1;\n    downButton.field_4 = a2;\n\n    if (downButton.field_C != NULL) {\n        myfree(downButton.field_C, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2765\n    }\n    downButton.field_C = a3;\n\n    if (downButton.field_10 != NULL) {\n        myfree(downButton.field_10, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2767\n    }\n    downButton.field_10 = a4;\n\n    if (downButton.field_14 != NULL) {\n        myfree(downButton.field_14, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2769\n    }\n    downButton.field_14 = a5;\n\n    if (downButton.field_18 != NULL) {\n        myfree(downButton.field_18, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2771\n    }\n    downButton.field_18 = a6;\n\n    downButton.field_8 = a7;\n\n    return 0;\n}\n\n// 0x431368\nint dialogSetSpacing(int value)\n{\n    defaultSpacing = value;\n\n    return 0;\n}\n\n// 0x431370\nint dialogSetOptionColor(float a1, float a2, float a3)\n{\n    optionR = (int)(a1 * 31.0);\n    optionG = (int)(a2 * 31.0);\n    optionB = (int)(a3 * 31.0);\n\n    optionRGBset = 1;\n\n    return 0;\n}\n\n// 0x4313C8\nint dialogSetReplyColor(float a1, float a2, float a3)\n{\n    replyR = (int)(a1 * 31.0);\n    replyG = (int)(a2 * 31.0);\n    replyB = (int)(a3 * 31.0);\n\n    replyRGBset = 1;\n\n    return 0;\n}\n\n// 0x431420\nint dialogSetOptionFlags(short flags)\n{\n    defaultOption.flags = flags;\n\n    return 1;\n}\n\n// 0x431420\nint dialogSetReplyFlags(short flags)\n{\n    // FIXME: Obvious error, flags should be set on |defaultReply|.\n    defaultOption.flags = flags;\n\n    return 1;\n}\n\n// 0x431430\nvoid initDialog()\n{\n}\n\n// 0x431434\nvoid dialogClose()\n{\n    if (upButton.field_C) {\n        myfree(upButton.field_C, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2818\n    }\n\n    if (upButton.field_10) {\n        myfree(upButton.field_10, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2819\n    }\n\n    if (upButton.field_14) {\n        myfree(upButton.field_14, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2820\n    }\n\n    if (upButton.field_18) {\n        myfree(upButton.field_18, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2821\n    }\n\n    if (downButton.field_C) {\n        myfree(downButton.field_C, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2823\n    }\n\n    if (downButton.field_10) {\n        myfree(downButton.field_10, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2824\n    }\n\n    if (downButton.field_14) {\n        myfree(downButton.field_14, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2825\n    }\n\n    if (downButton.field_18) {\n        myfree(downButton.field_18, __FILE__, __LINE__); // \"..\\\\int\\\\DIALOG.C\", 2826\n    }\n}\n\n// 0x431518\nint dialogGetDialogDepth()\n{\n    return tods;\n}\n\n// 0x431520\nvoid dialogRegisterWinDrawCallbacks(DialogWinDrawCallback* reply, DialogWinDrawCallback* options)\n{\n    replyWinDrawCallback = reply;\n    optionsWinDrawCallback = options;\n}\n\n// 0x431530\nint dialogToggleMediaFlag(int a1)\n{\n    if ((a1 & mediaFlag) == a1) {\n        mediaFlag &= ~a1;\n    } else {\n        mediaFlag |= a1;\n    }\n\n    return mediaFlag;\n}\n\n// 0x431554\nint dialogGetMediaFlag()\n{\n    return mediaFlag;\n}\n"
  },
  {
    "path": "src/int/dialog.h",
    "content": "#ifndef FALLOUT_INT_DIALOG_H_\n#define FALLOUT_INT_DIALOG_H_\n\n#include \"int/intrpret.h\"\n\ntypedef void DialogWinDrawCallback(int win);\n\nextern DialogWinDrawCallback* replyWinDrawCallback;\nextern DialogWinDrawCallback* optionsWinDrawCallback;\n\nint dialogStart(Program* a1);\nint dialogRestart();\nint dialogGotoReply(const char* a1);\nint dialogTitle(const char* a1);\nint dialogReply(const char* a1, const char* a2);\nint dialogOption(const char* a1, const char* a2);\nint dialogOptionProc(const char* a1, int a2);\nint dialogMessage(const char* a1, const char* a2, int timeout);\nint dialogGo(int a1);\nint dialogGetExitPoint();\nint dialogQuit();\nint dialogSetOptionWindow(int x, int y, int width, int height, char* backgroundFileName);\nint dialogSetReplyWindow(int x, int y, int width, int height, char* backgroundFileName);\nint dialogSetBorder(int a1, int a2);\nint dialogSetScrollUp(int a1, int a2, char* a3, char* a4, char* a5, char* a6, int a7);\nint dialogSetScrollDown(int a1, int a2, char* a3, char* a4, char* a5, char* a6, int a7);\nint dialogSetSpacing(int value);\nint dialogSetOptionColor(float a1, float a2, float a3);\nint dialogSetReplyColor(float a1, float a2, float a3);\nint dialogSetOptionFlags(short flags);\nint dialogSetReplyFlags(short flags);\nvoid initDialog();\nvoid dialogClose();\nint dialogGetDialogDepth();\nvoid dialogRegisterWinDrawCallbacks(DialogWinDrawCallback* reply, DialogWinDrawCallback* options);\nint dialogToggleMediaFlag(int a1);\nint dialogGetMediaFlag();\n\n#endif /* FALLOUT_INT_DIALOG_H_ */\n"
  },
  {
    "path": "src/int/export.c",
    "content": "#include \"int/export.h\"\n\n#include <ctype.h>\n#include <string.h>\n\n#include \"int/intlib.h\"\n#include \"int/memdbg.h\"\n\ntypedef struct ExternalVariable {\n    char name[32];\n    char* programName;\n    opcode_t type;\n    union {\n        int value;\n        char* stringValue;\n    };\n} ExternalVariable;\n\ntypedef struct ExternalProcedure {\n    char name[32];\n    Program* program;\n    int argumentCount;\n    int address;\n} ExternalProcedure;\n\nstatic unsigned int hashName(const char* identifier);\nstatic ExternalProcedure* findProc(const char* identifier);\nstatic ExternalProcedure* findEmptyProc(const char* identifier);\nstatic ExternalVariable* findVar(const char* identifier);\nstatic ExternalVariable* findEmptyVar(const char* identifier);\nstatic void removeProgramReferences(Program* program);\n\n// 0x570C00\nstatic ExternalProcedure procHashTable[1013];\n\n// 0x57BA1C\nstatic ExternalVariable varHashTable[1013];\n\n// NOTE: Inlined.\n//\n// 0x440F10\nstatic unsigned int hashName(const char* identifier)\n{\n    unsigned int v1 = 0;\n    const char* pch = identifier;\n    while (*pch != '\\0') {\n        int ch = *pch & 0xFF;\n        v1 += (tolower(ch) & 0xFF) + (v1 * 8) + (v1 >> 29);\n        pch++;\n    }\n\n    v1 = v1 % 1013;\n    return v1;\n}\n\n// 0x440F58\nstatic ExternalProcedure* findProc(const char* identifier)\n{\n    // NOTE: Uninline.\n    unsigned int v1 = hashName(identifier);\n    unsigned int v2 = v1;\n\n    ExternalProcedure* externalProcedure = &(procHashTable[v1]);\n    if (externalProcedure->program != NULL) {\n        if (stricmp(externalProcedure->name, identifier) == 0) {\n            return externalProcedure;\n        }\n    }\n\n    do {\n        v1 += 7;\n        if (v1 >= 1013) {\n            v1 -= 1013;\n        }\n\n        externalProcedure = &(procHashTable[v1]);\n        if (externalProcedure->program != NULL) {\n            if (stricmp(externalProcedure->name, identifier) == 0) {\n                return externalProcedure;\n            }\n        }\n    } while (v1 != v2);\n\n    return NULL;\n}\n\n// 0x441018\nstatic ExternalProcedure* findEmptyProc(const char* identifier)\n{\n    // NOTE: Uninline.\n    unsigned int v1 = hashName(identifier);\n    unsigned int a2 = v1;\n\n    ExternalProcedure* externalProcedure = &(procHashTable[v1]);\n    if (externalProcedure->name[0] == '\\0') {\n        return externalProcedure;\n    }\n\n    do {\n        v1 += 7;\n        if (v1 >= 1013) {\n            v1 -= 1013;\n        }\n\n        externalProcedure = &(procHashTable[v1]);\n        if (externalProcedure->name[0] == '\\0') {\n            return externalProcedure;\n        }\n    } while (v1 != a2);\n\n    return NULL;\n}\n\n// 0x4410AC\nstatic ExternalVariable* findVar(const char* identifier)\n{\n    // NOTE: Uninline.\n    unsigned int v1 = hashName(identifier);\n    unsigned int v2 = v1;\n\n    ExternalVariable* exportedVariable = &(varHashTable[v1]);\n    if (stricmp(exportedVariable->name, identifier) == 0) {\n        return exportedVariable;\n    }\n\n    do {\n        exportedVariable = &(varHashTable[v1]);\n        if (exportedVariable->name[0] == '\\0') {\n            break;\n        }\n\n        v1 += 7;\n        if (v1 >= 1013) {\n            v1 -= 1013;\n        }\n\n        exportedVariable = &(varHashTable[v1]);\n        if (stricmp(exportedVariable->name, identifier) == 0) {\n            return exportedVariable;\n        }\n    } while (v1 != v2);\n\n    return NULL;\n}\n\n// NOTE: Unused.\n//\n// 0x441164\nint exportGetVariable(const char* identifier, opcode_t* typePtr, int* valuePtr)\n{\n    ExternalVariable* variable;\n\n    variable = findVar(identifier);\n    if (variable != NULL) {\n        *typePtr = variable->type;\n        *valuePtr = variable->value;\n        return 1;\n    }\n\n    *typePtr = 0;\n    *valuePtr = 0;\n\n    return 0;\n}\n\n// 0x44118C\nstatic ExternalVariable* findEmptyVar(const char* identifier)\n{\n    // NOTE: Uninline.\n    unsigned int v1 = hashName(identifier);\n    unsigned int v2 = v1;\n\n    ExternalVariable* exportedVariable = &(varHashTable[v1]);\n    if (exportedVariable->name[0] == '\\0') {\n        return exportedVariable;\n    }\n\n    do {\n        v1 += 7;\n        if (v1 >= 1013) {\n            v1 -= 1013;\n        }\n\n        exportedVariable = &(varHashTable[v1]);\n        if (exportedVariable->name[0] == '\\0') {\n            return exportedVariable;\n        }\n    } while (v1 != v2);\n\n    return NULL;\n}\n\n// NOTE: Unused.\n//\n// 0x441220\nint exportStoreStringVariable(const char* identifier, const char* value)\n{\n    ExternalVariable* variable;\n\n    variable = findVar(identifier);\n    if (variable != NULL) {\n        if ((variable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n            myfree(variable->stringValue, __FILE__, __LINE__); // \"..\\int\\EXPORT.C\", 155\n        }\n\n        variable->type = VALUE_TYPE_DYNAMIC_STRING;\n        variable->stringValue = mystrdup(value, __FILE__, __LINE__); // \"..\\int\\EXPORT.C\", 159\n\n        return 0;\n    }\n\n    return 1;\n}\n\n// 0x44127C\nint exportStoreVariable(Program* program, const char* name, opcode_t opcode, int data)\n{\n    ExternalVariable* exportedVariable = findVar(name);\n    if (exportedVariable == NULL) {\n        return 1;\n    }\n\n    if ((exportedVariable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        myfree(exportedVariable->stringValue, __FILE__, __LINE__); // \"..\\\\int\\\\EXPORT.C\", 169\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        if (program != NULL) {\n            const char* stringValue = interpretGetString(program, opcode, data);\n            exportedVariable->type = VALUE_TYPE_DYNAMIC_STRING;\n\n            exportedVariable->stringValue = (char*)mymalloc(strlen(stringValue) + 1, __FILE__, __LINE__); // \"..\\\\int\\\\EXPORT.C\", 175\n            strcpy(exportedVariable->stringValue, stringValue);\n        }\n    } else {\n        exportedVariable->value = data;\n        exportedVariable->type = opcode;\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x441330\nint exportStoreVariableByTag(const char* identifier, opcode_t type, int value)\n{\n    ExternalVariable* variable;\n\n    variable = findVar(identifier);\n    if (variable != NULL) {\n        if ((variable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n            myfree(variable->stringValue, __FILE__, __LINE__); // \"..\\int\\EXPORT.C\", 191\n        }\n\n        if ((type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n            variable->type = VALUE_TYPE_DYNAMIC_STRING;\n            variable->stringValue = (char*)mymalloc(strlen((char*)value) + 1, __FILE__, __LINE__); // \"..\\int\\EXPORT.C\", 196\n            strcpy(variable->stringValue, (char*)value);\n        } else {\n            variable->value = value;\n            variable->type = type;\n        }\n\n        return 0;\n    }\n\n    return 1;\n}\n\n// 0x4413D4\nint exportFetchVariable(Program* program, const char* name, opcode_t* opcodePtr, int* dataPtr)\n{\n    ExternalVariable* exportedVariable = findVar(name);\n    if (exportedVariable == NULL) {\n        return 1;\n    }\n\n    *opcodePtr = exportedVariable->type;\n\n    if ((exportedVariable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        *dataPtr = interpretAddString(program, exportedVariable->stringValue);\n    } else {\n        *dataPtr = exportedVariable->value;\n    }\n\n    return 0;\n}\n\n// 0x4414B8\nint exportExportVariable(Program* program, const char* identifier)\n{\n    const char* programName = program->name;\n    ExternalVariable* exportedVariable = findVar(identifier);\n\n    if (exportedVariable != NULL) {\n        if (stricmp(exportedVariable->programName, programName) != 0) {\n            return 1;\n        }\n\n        if ((exportedVariable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n            myfree(exportedVariable->stringValue, __FILE__, __LINE__); // \"..\\\\int\\\\EXPORT.C\", 234\n        }\n    } else {\n        exportedVariable = findEmptyVar(identifier);\n        if (exportedVariable == NULL) {\n            return 1;\n        }\n\n        strncpy(exportedVariable->name, identifier, 31);\n\n        exportedVariable->programName = (char*)mymalloc(strlen(programName) + 1, __FILE__, __LINE__); // // \"..\\\\int\\\\EXPORT.C\", 243\n        strcpy(exportedVariable->programName, programName);\n    }\n\n    exportedVariable->type = VALUE_TYPE_INT;\n    exportedVariable->value = 0;\n\n    return 0;\n}\n\n// 0x4414FC\nstatic void removeProgramReferences(Program* program)\n{\n    for (int index = 0; index < 1013; index++) {\n        ExternalProcedure* externalProcedure = &(procHashTable[index]);\n        if (externalProcedure->program == program) {\n            externalProcedure->name[0] = '\\0';\n            externalProcedure->program = NULL;\n        }\n    }\n}\n\n// 0x44152C\nvoid initExport()\n{\n    interpretRegisterProgramDeleteCallback(removeProgramReferences);\n}\n\n// 0x441538\nvoid exportClose()\n{\n    for (int index = 0; index < 1013; index++) {\n        ExternalVariable* exportedVariable = &(varHashTable[index]);\n\n        if (exportedVariable->name[0] != '\\0') {\n            myfree(exportedVariable->programName, __FILE__, __LINE__); // ..\\\\int\\\\EXPORT.C, 274\n        }\n\n        if (exportedVariable->type == VALUE_TYPE_DYNAMIC_STRING) {\n            myfree(exportedVariable->stringValue, __FILE__, __LINE__); // ..\\\\int\\\\EXPORT.C, 276\n        }\n    }\n}\n\n// 0x44158C\nProgram* exportFindProcedure(const char* identifier, int* addressPtr, int* argumentCountPtr)\n{\n    ExternalProcedure* externalProcedure = findProc(identifier);\n    if (externalProcedure == NULL) {\n        return NULL;\n    }\n\n    if (externalProcedure->program == NULL) {\n        return NULL;\n    }\n\n    *addressPtr = externalProcedure->address;\n    *argumentCountPtr = externalProcedure->argumentCount;\n\n    return externalProcedure->program;\n}\n\n// 0x4415B0\nint exportExportProcedure(Program* program, const char* identifier, int address, int argumentCount)\n{\n    ExternalProcedure* externalProcedure = findProc(identifier);\n    if (externalProcedure != NULL) {\n        if (program != externalProcedure->program) {\n            return 1;\n        }\n    } else {\n        externalProcedure = findEmptyProc(identifier);\n        if (externalProcedure == NULL) {\n            return 1;\n        }\n\n        strncpy(externalProcedure->name, identifier, 31);\n    }\n\n    externalProcedure->argumentCount = argumentCount;\n    externalProcedure->address = address;\n    externalProcedure->program = program;\n\n    return 0;\n}\n\n// 0x441824\nvoid exportClearAllVariables()\n{\n    for (int index = 0; index < 1013; index++) {\n        ExternalVariable* exportedVariable = &(varHashTable[index]);\n        if (exportedVariable->name[0] != '\\0') {\n            if ((exportedVariable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n                if (exportedVariable->stringValue != NULL) {\n                    myfree(exportedVariable->stringValue, __FILE__, __LINE__); // \"..\\\\int\\\\EXPORT.C\", 387\n                }\n            }\n\n            if (exportedVariable->programName != NULL) {\n                myfree(exportedVariable->programName, __FILE__, __LINE__); // \"..\\\\int\\\\EXPORT.C\", 393\n                exportedVariable->programName = NULL;\n            }\n\n            exportedVariable->name[0] = '\\0';\n            exportedVariable->type = 0;\n        }\n    }\n}\n"
  },
  {
    "path": "src/int/export.h",
    "content": "#ifndef FALLOUT_INT_EXPORT_H_\n#define FALLOUT_INT_EXPORT_H_\n\n#include \"int/intrpret.h\"\n\nint exportGetVariable(const char* identifier, opcode_t* typePtr, int* valuePtr);\nint exportStoreStringVariable(const char* identifier, const char* value);\nint exportStoreVariable(Program* program, const char* identifier, opcode_t opcode, int data);\nint exportStoreVariableByTag(const char* identifier, opcode_t type, int value);\nint exportFetchVariable(Program* program, const char* name, opcode_t* opcodePtr, int* dataPtr);\nint exportExportVariable(Program* program, const char* identifier);\nvoid initExport();\nvoid exportClose();\nProgram* exportFindProcedure(const char* identifier, int* addressPtr, int* argumentCountPtr);\nint exportExportProcedure(Program* program, const char* identifier, int address, int argumentCount);\nvoid exportClearAllVariables();\n\n#endif /* FALLOUT_INT_EXPORT_H_ */\n"
  },
  {
    "path": "src/int/intlib.c",
    "content": "#include \"int/intlib.h\"\n\n#include <stdio.h>\n\n#include \"int/window.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"int/datafile.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/dialog.h\"\n#include \"int/support/intextra.h\"\n#include \"int/memdbg.h\"\n#include \"int/mousemgr.h\"\n#include \"int/nevs.h\"\n#include \"int/share1.h\"\n#include \"int/sound.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/intrface.h\"\n\n#define INT_LIB_SOUNDS_CAPACITY 32\n#define INT_LIB_KEY_HANDLERS_CAPACITY 256\n\ntypedef struct IntLibKeyHandlerEntry {\n    Program* program;\n    int proc;\n} IntLibKeyHandlerEntry;\n\nstatic void op_fillwin3x3(Program* program);\nstatic void op_format(Program* program);\nstatic void op_print(Program* program);\nstatic void op_selectfilelist(Program* program);\nstatic void op_tokenize(Program* program);\nstatic void op_printrect(Program* program);\nstatic void op_selectwin(Program* program);\nstatic void op_display(Program* program);\nstatic void op_displayraw(Program* program);\nstatic void interpretFadePaletteBK(unsigned char* oldPalette, unsigned char* newPalette, int a3, float duration, int shouldProcessBk);\nstatic void op_fadein(Program* program);\nstatic void op_fadeout(Program* program);\nstatic void op_movieflags(Program* program);\nstatic void op_playmovie(Program* program);\nstatic void op_playmovierect(Program* program);\nstatic void op_stopmovie(Program* program);\nstatic void op_addregionproc(Program* program);\nstatic void op_addregionrightproc(Program* program);\nstatic void op_createwin(Program* program);\nstatic void op_resizewin(Program* program);\nstatic void op_scalewin(Program* program);\nstatic void op_deletewin(Program* program);\nstatic void op_saystart(Program* program);\nstatic void op_deleteregion(Program* program);\nstatic void op_activateregion(Program* program);\nstatic void op_checkregion(Program* program);\nstatic void op_addregion(Program* program);\nstatic void op_saystartpos(Program* program);\nstatic void op_sayreplytitle(Program* program);\nstatic void op_saygotoreply(Program* program);\nstatic void op_sayreply(Program* program);\nstatic void op_sayoption(Program* program);\nstatic int checkDialog(Program* program);\nstatic void op_sayend(Program* program);\nstatic void op_saygetlastpos(Program* program);\nstatic void op_sayquit(Program* program);\nstatic void op_saymessagetimeout(Program* program);\nstatic void op_saymessage(Program* program);\nstatic void op_gotoxy(Program* program);\nstatic void op_addbuttonflag(Program* program);\nstatic void op_addregionflag(Program* program);\nstatic void op_addbutton(Program* program);\nstatic void op_addbuttontext(Program* program);\nstatic void op_addbuttongfx(Program* program);\nstatic void op_addbuttonproc(Program* program);\nstatic void op_addbuttonrightproc(Program* program);\nstatic void op_showwin(Program* program);\nstatic void op_deletebutton(Program* program);\nstatic void op_fillwin(Program* program);\nstatic void op_fillrect(Program* program);\nstatic void op_hidemouse(Program* program);\nstatic void op_showmouse(Program* program);\nstatic void op_mouseshape(Program* program);\nstatic void op_setglobalmousefunc(Program* Program);\nstatic void op_displaygfx(Program* program);\nstatic void op_loadpalettetable(Program* program);\nstatic void op_addNamedEvent(Program* program);\nstatic void op_addNamedHandler(Program* program);\nstatic void op_clearNamed(Program* program);\nstatic void op_signalNamed(Program* program);\nstatic void op_addkey(Program* program);\nstatic void op_deletekey(Program* program);\nstatic void op_refreshmouse(Program* program);\nstatic void op_setfont(Program* program);\nstatic void op_settextflags(Program* program);\nstatic void op_settextcolor(Program* program);\nstatic void op_sayoptioncolor(Program* program);\nstatic void op_sayreplycolor(Program* program);\nstatic void op_sethighlightcolor(Program* program);\nstatic void op_sayreplywindow(Program* program);\nstatic void op_sayreplyflags(Program* program);\nstatic void op_sayoptionflags(Program* program);\nstatic void op_sayoptionwindow(Program* program);\nstatic void op_sayborder(Program* program);\nstatic void op_sayscrollup(Program* program);\nstatic void op_sayscrolldown(Program* program);\nstatic void op_saysetspacing(Program* program);\nstatic void op_sayrestart(Program* program);\nstatic void soundCallbackInterpret(void* userData, int a2);\nstatic int soundDeleteInterpret(int value);\nstatic int soundPauseInterpret(int value);\nstatic int soundRewindInterpret(int value);\nstatic int soundUnpauseInterpret(int value);\nstatic void op_soundplay(Program* program);\nstatic void op_soundpause(Program* program);\nstatic void op_soundresume(Program* program);\nstatic void op_soundstop(Program* program);\nstatic void op_soundrewind(Program* program);\nstatic void op_sounddelete(Program* program);\nstatic void op_setoneoptpause(Program* program);\nstatic bool intLibDoInput(int key);\n\n// 0x519038\nstatic int TimeOut = 0;\n\n// 0x59D5D0\nstatic Sound* interpretSounds[INT_LIB_SOUNDS_CAPACITY];\n\n// 0x59D650\nstatic unsigned char blackPal[256 * 3];\n\n// 0x59D950\nstatic IntLibKeyHandlerEntry inputProc[INT_LIB_KEY_HANDLERS_CAPACITY];\n\n// 0x59E150\nstatic bool currentlyFadedIn;\n\n// 0x59E154\nstatic int anyKeyOffset;\n\n// 0x59E158\nstatic int numCallbacks;\n\n// 0x59E15C\nstatic Program* anyKeyProg;\n\n// 0x59E160\nstatic IntLibProgramDeleteCallback** callbacks;\n\n// 0x59E164\nstatic int sayStartingPosition;\n\n// 0x461780\nstatic void op_fillwin3x3(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to fillwin3x3\");\n    }\n\n    char* fileName = interpretGetString(program, opcode, data);\n    char* mangledFileName = interpretMangleName(fileName);\n\n    int imageWidth;\n    int imageHeight;\n    unsigned char* imageData = loadDataFile(mangledFileName, &imageWidth, &imageHeight);\n    if (imageData == NULL) {\n        interpretError(\"cannot load 3x3 file '%s'\", mangledFileName);\n    }\n\n    selectWindowID(program->windowId);\n\n    alphaBltBufRect(imageData,\n        imageWidth,\n        imageHeight,\n        windowGetBuffer(),\n        windowWidth(),\n        windowHeight());\n\n    myfree(imageData, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 94\n}\n\n// 0x461850\nstatic void op_format(Program* program)\n{\n    opcode_t opcode[6];\n    int data[6];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 6; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 6 given to format\\n\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 5 given to format\\n\");\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 4 given to format\\n\");\n    }\n\n    if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 3 given to format\\n\");\n    }\n\n    if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 2 given to format\\n\");\n    }\n\n    if ((opcode[5] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid arg 1 given to format\\n\");\n    }\n\n    char* string = interpretGetString(program, opcode[5], data[5]);\n    int x = data[4];\n    int y = data[3];\n    int width = data[2];\n    int height = data[1];\n    int textAlignment = data[0];\n\n    if (!windowFormatMessage(string, x, y, width, height, textAlignment)) {\n        interpretError(\"Error formatting message\\n\");\n    }\n}\n\n// 0x461A5C\nstatic void op_print(Program* program)\n{\n    selectWindowID(program->windowId);\n\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    switch (opcode & VALUE_TYPE_MASK) {\n    case VALUE_TYPE_STRING:\n        interpretOutput(\"%s\", interpretGetString(program, opcode, data));\n        break;\n    case VALUE_TYPE_FLOAT:\n        interpretOutput(\"%.5f\", *((float*)&data));\n        break;\n    case VALUE_TYPE_INT:\n        interpretOutput(\"%d\", data);\n        break;\n    }\n}\n\n// 0x461B10\nstatic void op_selectfilelist(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Error, invalid arg 2 given to selectfilelist\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Error, invalid arg 1 given to selectfilelist\");\n    }\n\n    char* pattern = interpretGetString(program, opcode[0], data[0]);\n    char* title = interpretGetString(program, opcode[1], data[1]);\n\n    int fileListLength;\n    char** fileList = getFileList(interpretMangleName(pattern), &fileListLength);\n    if (fileList != NULL && fileListLength != 0) {\n        int selectedIndex = win_list_select(title,\n            fileList,\n            fileListLength,\n            NULL,\n            320 - text_width(title) / 2,\n            200,\n            colorTable[0x7FFF] | 0x10000);\n\n        if (selectedIndex != -1) {\n            interpretPushLong(program, interpretAddString(program, fileList[selectedIndex]));\n            interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n        } else {\n            interpretPushLong(program, 0);\n            interpretPushShort(program, VALUE_TYPE_INT);\n        }\n\n        freeFileList(fileList);\n    } else {\n        interpretPushLong(program, 0);\n        interpretPushShort(program, VALUE_TYPE_INT);\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x461CA0\nstatic void op_tokenize(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    opcode[0] = interpretPopShort(program);\n    data[0] = interpretPopLong(program);\n\n    if (opcode[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[0], data[0]);\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Error, invalid arg 3 to tokenize.\");\n    }\n\n    opcode[1] = interpretPopShort(program);\n    data[1] = interpretPopLong(program);\n\n    if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[1], data[1]);\n    }\n\n    char* prev = NULL;\n    if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n        if (data[1] != 0) {\n            interpretError(\"Error, invalid arg 2 to tokenize. (only accept 0 for int value)\");\n        }\n    } else if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        prev = interpretGetString(program, opcode[1], data[1]);\n    } else {\n        interpretError(\"Error, invalid arg 2 to tokenize. (string)\");\n    }\n\n    opcode[2] = interpretPopShort(program);\n    data[2] = interpretPopLong(program);\n\n    if (opcode[2] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[2], data[2]);\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Error, invalid arg 1 to tokenize.\");\n    }\n\n    char* string = interpretGetString(program, opcode[2], data[2]);\n    char* temp = NULL;\n\n    if (prev != NULL) {\n        char* start = strstr(string, prev);\n        if (start != NULL) {\n            start += strlen(prev);\n            while (*start != data[0] && *start != '\\0') {\n                start++;\n            }\n        }\n\n        if (*start == data[0]) {\n            int length = 0;\n            char* end = start + 1;\n            while (*end != data[0] && *end != '\\0') {\n                end++;\n                length++;\n            }\n\n            temp = (char*)mycalloc(1, length + 1, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C, 230\n            strncpy(temp, start, length);\n            interpretPushLong(program, interpretAddString(program, temp));\n            interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n        } else {\n            interpretPushLong(program, 0);\n            interpretPushShort(program, VALUE_TYPE_INT);\n        }\n    } else {\n        int length = 0;\n        char* end = string;\n        while (*end != data[0] && *end != '\\0') {\n            end++;\n            length++;\n        }\n\n        if (string != NULL) {\n            temp = (char*)mycalloc(1, length + 1, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 248\n            strncpy(temp, string, length);\n            interpretPushLong(program, interpretAddString(program, temp));\n            interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n        } else {\n            interpretPushLong(program, 0);\n            interpretPushShort(program, VALUE_TYPE_INT);\n        }\n    }\n\n    if (temp != NULL) {\n        myfree(temp, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\" , 260\n    }\n}\n\n// 0x461F1C\nstatic void op_printrect(Program* program)\n{\n    selectWindowID(program->windowId);\n\n    opcode_t opcode[3];\n    int data[3];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT || data[0] > 2) {\n        interpretError(\"Invalid arg 3 given to printrect, expecting int\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 2 given to printrect, expecting int\");\n    }\n\n    char string[80];\n    switch (opcode[2] & VALUE_TYPE_MASK) {\n    case VALUE_TYPE_STRING:\n        sprintf(string, \"%s\", interpretGetString(program, opcode[2], data[2]));\n        break;\n    case VALUE_TYPE_FLOAT:\n        sprintf(string, \"%.5f\", *((float*)&data[2]));\n        break;\n    case VALUE_TYPE_INT:\n        sprintf(string, \"%d\", data[2]);\n        break;\n    }\n\n    if (!windowPrintRect(string, data[1], data[0])) {\n        interpretError(\"Error in printrect\");\n    }\n}\n\n// 0x46209C\nstatic void op_selectwin(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to select\");\n    }\n\n    const char* windowName = interpretGetString(program, opcode, data);\n    int win = pushWindow(windowName);\n    if (win == -1) {\n        interpretError(\"Error selecing window %s\\n\", interpretGetString(program, opcode, data));\n    }\n\n    program->windowId = win;\n\n    interpretOutputFunc(windowOutput);\n}\n\n// 0x46213C\nstatic void op_display(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to display\");\n    }\n\n    char* fileName = interpretGetString(program, opcode, data);\n\n    selectWindowID(program->windowId);\n\n    char* mangledFileName = interpretMangleName(fileName);\n    displayFile(mangledFileName);\n}\n\n// 0x4621B4\nstatic void op_displayraw(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to displayraw\");\n    }\n\n    char* fileName = interpretGetString(program, opcode, data);\n\n    selectWindowID(program->windowId);\n\n    char* mangledFileName = interpretMangleName(fileName);\n    displayFileRaw(mangledFileName);\n}\n\n// 0x46222C\nstatic void interpretFadePaletteBK(unsigned char* oldPalette, unsigned char* newPalette, int a3, float duration, int shouldProcessBk)\n{\n    unsigned int time;\n    unsigned int previousTime;\n    unsigned int delta;\n    int step;\n    int steps;\n    int index;\n    unsigned char palette[256 * 3];\n\n    time = get_time();\n    previousTime = time;\n    steps = (int)duration;\n    step = 0;\n    delta = 0;\n\n    if (duration != 0.0) {\n        while (step < steps) {\n            if (delta != 0) {\n                for (index = 0; index < 768; index++) {\n                    palette[index] = oldPalette[index] - (oldPalette[index] - newPalette[index]) * step / steps;\n                }\n\n                setSystemPalette(palette);\n\n                previousTime = time;\n                step += delta;\n            }\n\n            if (shouldProcessBk) {\n                process_bk();\n            }\n\n            time = get_time();\n            delta = time - previousTime;\n        }\n    }\n\n    setSystemPalette(newPalette);\n}\n\n// NOTE: Unused.\n//\n// 0x462330\nvoid interpretFadePalette(unsigned char* oldPalette, unsigned char* newPalette, int a3, float duration)\n{\n    interpretFadePaletteBK(oldPalette, newPalette, a3, duration, 1);\n}\n\n// NOTE: Unused.\nint intlibGetFadeIn()\n{\n    return currentlyFadedIn;\n}\n\n// NOTE: Inlined.\n//\n// 0x462348\nvoid interpretFadeOut(float duration)\n{\n    int cursorWasHidden;\n\n    cursorWasHidden = mouse_hidden();\n    mouse_hide();\n\n    interpretFadePaletteBK(getSystemPalette(), blackPal, 64, duration, 1);\n\n    if (!cursorWasHidden) {\n        mouse_show();\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x462380\nvoid interpretFadeIn(float duration)\n{\n    interpretFadePaletteBK(blackPal, cmap, 64, duration, 1);\n}\n\n// NOTE: Unused.\n//\n// 0x4623A4\nvoid interpretFadeOutNoBK(float duration)\n{\n    int cursorWasHidden;\n\n    cursorWasHidden = mouse_hidden();\n    mouse_hide();\n\n    interpretFadePaletteBK(getSystemPalette(), blackPal, 64, duration, 0);\n\n    if (!cursorWasHidden) {\n        mouse_show();\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4623DC\nvoid interpretFadeInNoBK(float duration)\n{\n    interpretFadePaletteBK(blackPal, cmap, 64, duration, 0);\n}\n\n// 0x462400\nstatic void op_fadein(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid type given to fadein\\n\");\n    }\n\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    setSystemPalette(blackPal);\n\n    // NOTE: Uninline.\n    interpretFadeIn((float)data);\n\n    currentlyFadedIn = true;\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x4624B4\nstatic void op_fadeout(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        // FIXME: Wrong function name, should be fadeout.\n        interpretError(\"Invalid type given to fadein\\n\");\n    }\n\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    // NOTE: Uninline.\n    interpretFadeOut((float)data);\n\n    currentlyFadedIn = false;\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x462570\nint checkMovie(Program* program)\n{\n    if (dialogGetDialogDepth() > 0) {\n        return 1;\n    }\n\n    return windowMoviePlaying();\n}\n\n// 0x462584\nstatic void op_movieflags(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if (!windowSetMovieFlags(data)) {\n        interpretError(\"Error setting movie flags\\n\");\n    }\n}\n\n// 0x4625D0\nstatic void op_playmovie(Program* program)\n{\n    // 0x59E168\n    static char name[100];\n\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to playmovie\");\n    }\n\n    strcpy(name, interpretGetString(program, opcode, data));\n\n    if (strrchr(name, '.') == NULL) {\n        strcat(name, \".mve\");\n    }\n\n    selectWindowID(program->windowId);\n\n    program->flags |= PROGRAM_IS_WAITING;\n    program->checkWaitFunc = checkMovie;\n\n    char* mangledFileName = interpretMangleName(name);\n    if (!windowPlayMovie(mangledFileName)) {\n        interpretError(\"Error playing movie\");\n    }\n}\n\n// 0x4626C4\nstatic void op_playmovierect(Program* program)\n{\n    // 0x59E1CC\n    static char name[100];\n\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        // FIXME: Wrong function name, should playmovierect.\n        interpretError(\"Invalid arg given to playmovie\");\n    }\n\n    strcpy(name, interpretGetString(program, opcode[4], data[4]));\n\n    if (strrchr(name, '.') == NULL) {\n        strcat(name, \".mve\");\n    }\n\n    selectWindowID(program->windowId);\n\n    program->checkWaitFunc = checkMovie;\n    program->flags |= PROGRAM_IS_WAITING;\n\n    char* mangledFileName = interpretMangleName(name);\n    if (!windowPlayMovieRect(mangledFileName, data[3], data[2], data[1], data[0])) {\n        interpretError(\"Error playing movie\");\n    }\n}\n\n// 0x46287C\nstatic void op_stopmovie(Program* program)\n{\n    windowStopMovie();\n    program->flags |= PROGRAM_FLAG_0x40;\n}\n\n// 0x462890\nstatic void op_deleteregion(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data != -1) {\n            interpretError(\"Invalid type given to deleteregion\");\n        }\n    }\n\n    selectWindowID(program->windowId);\n\n    const char* regionName = data != -1 ? interpretGetString(program, opcode, data) : NULL;\n    windowDeleteRegion(regionName);\n}\n\n// 0x462924\nstatic void op_activateregion(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    const char* regionName = interpretGetString(program, opcode[1], data[1]);\n    windowActivateRegion(regionName, data[0]);\n}\n\n// 0x4629A0\nstatic void op_checkregion(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid arg 1 given to checkregion();\\n\");\n    }\n\n    const char* regionName = interpretGetString(program, opcode, data);\n\n    bool regionExists = windowCheckRegionExists(regionName);\n    interpretPushLong(program, regionExists);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x462A1C\nstatic void op_addregion(Program* program)\n{\n    opcode_t opcode;\n    int data;\n\n    opcode = interpretPopShort(program);\n    data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid number of elements given to region\");\n    }\n\n    int args = data;\n\n    if (args < 2) {\n        interpretError(\"addregion call without enough points!\");\n    }\n\n    selectWindowID(program->windowId);\n\n    windowStartRegion(args / 2);\n\n    while (args >= 2) {\n        opcode = interpretPopShort(program);\n        data = interpretPopLong(program);\n\n        if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode, data);\n        }\n\n        if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"Invalid y value given to region\");\n        }\n\n        int y = data;\n\n        opcode = interpretPopShort(program);\n        data = interpretPopLong(program);\n\n        if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode, data);\n        }\n\n        if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"Invalid x value given to region\");\n        }\n\n        int x = data;\n\n        y = (y * windowGetYres() + 479) / 480;\n        x = (x * windowGetXres() + 639) / 640;\n        args -= 2;\n\n        windowAddRegionPoint(x, y, true);\n    }\n\n    if (args == 0) {\n        interpretError(\"Unnamed regions not allowed\\n\");\n        windowEndRegion();\n    } else {\n        opcode = interpretPopShort(program);\n        data = interpretPopLong(program);\n\n        if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode, data);\n        }\n\n        if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && opcode == VALUE_TYPE_INT) {\n            if (data != 0) {\n                interpretError(\"Invalid name given to region\");\n            }\n        }\n\n        const char* regionName = interpretGetString(program, opcode, data);\n        windowAddRegionName(regionName);\n        windowEndRegion();\n    }\n}\n\n// 0x462C10\nstatic void op_addregionproc(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 4 name given to addregionproc\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 3 name given to addregionproc\");\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 2 name given to addregionproc\");\n    }\n\n    if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 1 name given to addregionproc\");\n    }\n\n    if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid name given to addregionproc\");\n    }\n\n    const char* regionName = interpretGetString(program, opcode[4], data[4]);\n    selectWindowID(program->windowId);\n\n    if (!windowAddRegionProc(regionName, program, data[3], data[2], data[1], data[0])) {\n        interpretError(\"Error setting procedures to region %s\\n\", regionName);\n    }\n}\n\n// 0x462DDC\nstatic void op_addregionrightproc(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 2 name given to addregionrightproc\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 1 name given to addregionrightproc\");\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid name given to addregionrightproc\");\n    }\n\n    const char* regionName = interpretGetString(program, opcode[2], data[2]);\n    selectWindowID(program->windowId);\n\n    if (!windowAddRegionRightProc(regionName, program, data[1], data[0])) {\n        interpretError(\"ErrorError setting right button procedures to region %s\\n\", regionName);\n    }\n}\n\n// 0x462F08\nstatic void op_createwin(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    const char* windowName = interpretGetString(program, opcode[4], data[4]);\n    int x = (data[3] * windowGetXres() + 639) / 640;\n    int y = (data[2] * windowGetYres() + 479) / 480;\n    int width = (data[1] * windowGetXres() + 639) / 640;\n    int height = (data[0] * windowGetYres() + 479) / 480;\n\n    if (createWindow(windowName, x, y, width, height, colorTable[0], 0) == -1) {\n        interpretError(\"Couldn't create window.\");\n    }\n}\n\n// 0x46308C\nstatic void op_resizewin(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    const char* windowName = interpretGetString(program, opcode[4], data[4]);\n    int x = (data[3] * windowGetXres() + 639) / 640;\n    int y = (data[2] * windowGetYres() + 479) / 480;\n    int width = (data[1] * windowGetXres() + 639) / 640;\n    int height = (data[0] * windowGetYres() + 479) / 480;\n\n    if (resizeWindow(windowName, x, y, width, height) == -1) {\n        interpretError(\"Couldn't resize window.\");\n    }\n}\n\n// 0x463204\nstatic void op_scalewin(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    const char* windowName = interpretGetString(program, opcode[4], data[4]);\n    int x = (data[3] * windowGetXres() + 639) / 640;\n    int y = (data[2] * windowGetYres() + 479) / 480;\n    int width = (data[1] * windowGetXres() + 639) / 640;\n    int height = (data[0] * windowGetYres() + 479) / 480;\n\n    if (scaleWindow(windowName, x, y, width, height) == -1) {\n        interpretError(\"Couldn't scale window.\");\n    }\n}\n\n// 0x46337C\nstatic void op_deletewin(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    const char* windowName = interpretGetString(program, opcode, data);\n\n    if (!deleteWindow(windowName)) {\n        interpretError(\"Error deleting window %s\\n\", windowName);\n    }\n\n    program->windowId = popWindow();\n}\n\n// 0x4633E4\nstatic void op_saystart(Program* program)\n{\n    sayStartingPosition = 0;\n\n    program->flags |= PROGRAM_FLAG_0x20;\n    int rc = dialogStart(program);\n    program->flags &= ~PROGRAM_FLAG_0x20;\n\n    if (rc != 0) {\n        interpretError(\"Error starting dialog.\");\n    }\n}\n\n// 0x463430\nstatic void op_saystartpos(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    sayStartingPosition = data;\n\n    program->flags |= PROGRAM_FLAG_0x20;\n    int rc = dialogStart(program);\n    program->flags &= ~PROGRAM_FLAG_0x20;\n\n    if (rc != 0) {\n        interpretError(\"Error starting dialog.\");\n    }\n}\n\n// 0x46349C\nstatic void op_sayreplytitle(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    char* string = NULL;\n    if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        string = interpretGetString(program, opcode, data);\n    }\n\n    if (dialogTitle(string) != 0) {\n        interpretError(\"Error setting title.\");\n    }\n}\n\n// 0x463510\nstatic void op_saygotoreply(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    char* string = NULL;\n    if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        string = interpretGetString(program, opcode, data);\n    }\n\n    if (dialogGotoReply(string) != 0) {\n        interpretError(\"Error during goto, couldn't find reply target %s\", string);\n    }\n}\n\n// 0x463584\nstatic void op_sayreply(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    const char* v1;\n    if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v1 = interpretGetString(program, opcode[1], data[1]);\n    } else {\n        v1 = NULL;\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        const char* v2 = interpretGetString(program, opcode[0], data[0]);\n        if (dialogOption(v1, v2) != 0) {\n            program->flags &= ~PROGRAM_FLAG_0x20;\n            interpretError(\"Error setting option.\");\n        }\n    } else if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n        if (dialogOptionProc(v1, data[0]) != 0) {\n            program->flags &= ~PROGRAM_FLAG_0x20;\n            interpretError(\"Error setting option.\");\n        }\n    } else {\n        interpretError(\"Invalid arg 2 to sayOption\");\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x4636A0\nstatic void op_sayoption(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    const char* v1;\n    if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v1 = interpretGetString(program, opcode[1], data[1]);\n    } else {\n        v1 = NULL;\n    }\n\n    const char* v2;\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v2 = interpretGetString(program, opcode[0], data[0]);\n    } else {\n        v2 = NULL;\n    }\n\n    if (dialogReply(v1, v2) != 0) {\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        interpretError(\"Error setting option.\");\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x46378C\nstatic int checkDialog(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x40;\n    return dialogGetDialogDepth() != -1;\n}\n\n// 0x4637A4\nstatic void op_sayend(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n    int rc = dialogGo(sayStartingPosition);\n    program->flags &= ~PROGRAM_FLAG_0x20;\n\n    if (rc == -2) {\n        program->checkWaitFunc = checkDialog;\n        program->flags |= PROGRAM_IS_WAITING;\n    }\n}\n\n// 0x4637EC\nstatic void op_saygetlastpos(Program* program)\n{\n    int value = dialogGetExitPoint();\n    interpretPushLong(program, value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x463810\nstatic void op_sayquit(Program* program)\n{\n    if (dialogQuit() != 0) {\n        interpretError(\"Error quitting option.\");\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x463828\nint getTimeOut()\n{\n    return TimeOut;\n}\n\n// NOTE: Unused.\n//\n// 0x463830\nvoid setTimeOut(int value)\n{\n    TimeOut = value;\n}\n\n// 0x463838\nstatic void op_saymessagetimeout(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    // TODO: What the hell is this?\n    if ((opcode & VALUE_TYPE_MASK) == 0x4000) {\n        interpretError(\"sayMsgTimeout:  invalid var type passed.\");\n    }\n\n    TimeOut = data;\n}\n\n// 0x463890\nstatic void op_saymessage(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    const char* v1;\n    if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v1 = interpretGetString(program, opcode[1], data[1]);\n    } else {\n        v1 = NULL;\n    }\n\n    const char* v2;\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v2 = interpretGetString(program, opcode[0], data[0]);\n    } else {\n        v2 = NULL;\n    }\n\n    if (dialogMessage(v1, v2, TimeOut) != 0) {\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        interpretError(\"Error setting option.\");\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x463980\nstatic void op_gotoxy(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT\n        || (opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid operand given to gotoxy\");\n    }\n\n    selectWindowID(program->windowId);\n\n    int x = data[1];\n    int y = data[0];\n    windowGotoXY(x, y);\n}\n\n// 0x463A38\nstatic void op_addbuttonflag(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 2 given to addbuttonflag\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid arg 1 given to addbuttonflag\");\n    }\n\n    const char* buttonName = interpretGetString(program, opcode[1], data[1]);\n    if (!windowSetButtonFlag(buttonName, data[0])) {\n        // NOTE: Original code calls interpretGetString one more time with the\n        // same params.\n        interpretError(\"Error setting flag on button %s\", buttonName);\n    }\n}\n\n// 0x463B10\nstatic void op_addregionflag(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 2 given to addregionflag\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid arg 1 given to addregionflag\");\n    }\n\n    const char* regionName = interpretGetString(program, opcode[1], data[1]);\n    if (!windowSetRegionFlag(regionName, data[0])) {\n        // NOTE: Original code calls interpretGetString one more time with the\n        // same params.\n        interpretError(\"Error setting flag on region %s\", regionName);\n    }\n}\n\n// 0x463BE8\nstatic void op_addbutton(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    opcode[0] = interpretPopShort(program);\n    data[0] = interpretPopLong(program);\n\n    if (opcode[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[0], data[0]);\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid height given to addbutton\");\n    }\n\n    opcode[1] = interpretPopShort(program);\n    data[1] = interpretPopLong(program);\n\n    if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[1], data[1]);\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid width given to addbutton\");\n    }\n\n    opcode[2] = interpretPopShort(program);\n    data[2] = interpretPopLong(program);\n\n    if (opcode[2] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[2], data[2]);\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid y given to addbutton\");\n    }\n\n    opcode[3] = interpretPopShort(program);\n    data[3] = interpretPopLong(program);\n\n    if (opcode[3] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[3], data[3]);\n    }\n\n    if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid x given to addbutton\");\n    }\n\n    opcode[4] = interpretPopShort(program);\n    data[4] = interpretPopLong(program);\n\n    if (opcode[4] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[4], data[4]);\n    }\n\n    if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid name given to addbutton\");\n    }\n\n    selectWindowID(program->windowId);\n\n    int height = (data[0] * windowGetYres() + 479) / 480;\n    int width = (data[1] * windowGetXres() + 639) / 640;\n    int y = (data[2] * windowGetYres() + 479) / 480;\n    int x = (data[3] * windowGetXres() + 639) / 640;\n    const char* buttonName = interpretGetString(program, opcode[4], data[4]);\n\n    windowAddButton(buttonName, x, y, width, height, 0);\n}\n\n// 0x463DF4\nstatic void op_addbuttontext(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid text string given to addbuttontext\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid name string given to addbuttontext\");\n    }\n\n    const char* text = interpretGetString(program, opcode[0], data[0]);\n    const char* buttonName = interpretGetString(program, opcode[1], data[1]);\n\n    if (!windowAddButtonText(buttonName, text)) {\n        interpretError(\"Error setting text to button %s\\n\",\n            interpretGetString(program, opcode[1], data[1]));\n    }\n}\n\n// 0x463EEC\nstatic void op_addbuttongfx(Program* program)\n{\n    opcode_t opcode[4];\n    int data[4];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if (((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING || ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[2] == 0))\n        || ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING || ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[1] == 0))\n        || ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING || ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[0] == 0))) {\n\n        if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n            // FIXME: Wrong function name, should be addbuttongfx.\n            interpretError(\"Invalid name given to addbuttontext\");\n        }\n\n        const char* buttonName = interpretGetString(program, opcode[3], data[3]);\n        char* pressedFileName = interpretMangleName(interpretGetString(program, opcode[2], data[2]));\n        char* normalFileName = interpretMangleName(interpretGetString(program, opcode[1], data[1]));\n        char* hoverFileName = interpretMangleName(interpretGetString(program, opcode[0], data[0]));\n\n        selectWindowID(program->windowId);\n\n        if (!windowAddButtonGfx(buttonName, pressedFileName, normalFileName, hoverFileName)) {\n            interpretError(\"Error setting graphics to button %s\\n\", buttonName);\n        }\n    } else {\n        interpretError(\"Invalid filename given to addbuttongfx\");\n    }\n}\n\n// 0x4640DC\nstatic void op_addbuttonproc(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 4 name given to addbuttonproc\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 3 name given to addbuttonproc\");\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 2 name given to addbuttonproc\");\n    }\n\n    if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 1 name given to addbuttonproc\");\n    }\n\n    if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid name given to addbuttonproc\");\n    }\n\n    const char* buttonName = interpretGetString(program, opcode[4], data[4]);\n    selectWindowID(program->windowId);\n\n    if (!windowAddButtonProc(buttonName, program, data[3], data[2], data[1], data[0])) {\n        interpretError(\"Error setting procedures to button %s\\n\", buttonName);\n    }\n}\n\n// 0x4642A8\nstatic void op_addbuttonrightproc(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 2 name given to addbuttonrightproc\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure 1 name given to addbuttonrightproc\");\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid name given to addbuttonrightproc\");\n    }\n\n    const char* regionName = interpretGetString(program, opcode[2], data[2]);\n    selectWindowID(program->windowId);\n\n    if (!windowAddRegionRightProc(regionName, program, data[1], data[0])) {\n        interpretError(\"Error setting right button procedures to button %s\\n\", regionName);\n    }\n}\n\n// 0x4643D4\nstatic void op_showwin(Program* program)\n{\n    selectWindowID(program->windowId);\n    windowDraw();\n}\n\n// 0x4643E4\nstatic void op_deletebutton(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data != -1) {\n            interpretError(\"Invalid type given to delete button\");\n        }\n    }\n\n    selectWindowID(program->windowId);\n\n    if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n        if (windowDeleteButton(NULL)) {\n            return;\n        }\n    } else {\n        const char* buttonName = interpretGetString(program, opcode, data);\n        if (windowDeleteButton(buttonName)) {\n            return;\n        }\n    }\n\n    interpretError(\"Error deleting button\");\n}\n\n// 0x46449C\nstatic void op_fillwin(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n    float* floats = (float*)data;\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) {\n        if ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n            if (data[2] == 1) {\n                floats[2] = 1.0;\n            } else if (data[2] != 0) {\n                interpretError(\"Invalid red value given to fillwin\");\n            }\n        }\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) {\n        if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n            if (data[1] == 1) {\n                floats[1] = 1.0;\n            } else if (data[1] != 0) {\n                interpretError(\"Invalid green value given to fillwin\");\n            }\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) {\n        if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n            if (data[0] == 1) {\n                floats[0] = 1.0;\n            } else if (data[0] != 0) {\n                interpretError(\"Invalid blue value given to fillwin\");\n            }\n        }\n    }\n\n    selectWindowID(program->windowId);\n\n    windowFill(floats[2], floats[1], floats[0]);\n}\n\n// 0x4645FC\nstatic void op_fillrect(Program* program)\n{\n    opcode_t opcode[7];\n    int data[7];\n    float* floats = (float*)data;\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 7; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) {\n        if ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n            if (data[2] == 1) {\n                floats[2] = 1.0;\n            } else if (data[2] != 0) {\n                interpretError(\"Invalid red value given to fillrect\");\n            }\n        }\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) {\n        if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n            if (data[1] == 1) {\n                floats[1] = 1.0;\n            } else if (data[1] != 0) {\n                interpretError(\"Invalid green value given to fillrect\");\n            }\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) {\n        if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n            if (data[0] == 1) {\n                floats[0] = 1.0;\n            } else if (data[0] != 0) {\n                interpretError(\"Invalid blue value given to fillrect\");\n            }\n        }\n    }\n\n    if ((opcode[6] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to fillrect\");\n    }\n\n    if ((opcode[5] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 2 given to fillrect\");\n    }\n\n    if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 3 given to fillrect\");\n    }\n    if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 4 given to fillrect\");\n    }\n\n    selectWindowID(program->windowId);\n\n    windowFillRect(data[6], data[5], data[4], data[3], floats[2], floats[1], floats[0]);\n}\n\n// 0x46489C\nstatic void op_hidemouse(Program* program)\n{\n    mouse_hide();\n}\n\n// 0x4648A4\nstatic void op_showmouse(Program* program)\n{\n    mouse_show();\n}\n\n// 0x4648AC\nstatic void op_mouseshape(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 3 given to mouseshape\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 2 given to mouseshape\");\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid filename given to mouseshape\");\n    }\n\n    char* fileName = interpretGetString(program, opcode[2], data[2]);\n    if (!mouseSetMouseShape(fileName, data[1], data[0])) {\n        interpretError(\"Error loading mouse shape.\");\n    }\n}\n\n// 0x4649C4\nstatic void op_setglobalmousefunc(Program* Program)\n{\n    interpretError(\"setglobalmousefunc not defined\");\n}\n\n// 0x4649D4\nstatic void op_displaygfx(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    char* fileName = interpretGetString(program, opcode[4], data[4]);\n    char* mangledFileName = interpretMangleName(fileName);\n    windowDisplay(mangledFileName, data[3], data[2], data[1], data[0]);\n}\n\n// 0x464ADC\nstatic void op_loadpalettetable(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data != -1) {\n            interpretError(\"Invalid type given to loadpalettetable\");\n        }\n    }\n\n    char* path = interpretGetString(program, opcode, data);\n    if (!loadColorTable(path)) {\n        interpretError(colorError());\n    }\n}\n\n// 0x464B54\nstatic void op_addNamedEvent(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to addnamedevent\");\n    }\n\n    const char* v1 = interpretGetString(program, opcode[1], data[1]);\n    nevs_addevent(v1, program, data[0], NEVS_TYPE_EVENT);\n}\n\n// 0x464BE8\nstatic void op_addNamedHandler(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to addnamedhandler\");\n    }\n\n    const char* v1 = interpretGetString(program, opcode[1], data[1]);\n    nevs_addevent(v1, program, data[0], NEVS_TYPE_HANDLER);\n}\n\n// 0x464C80\nstatic void op_clearNamed(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to clearnamed\");\n    }\n\n    char* string = interpretGetString(program, opcode, data);\n    nevs_clearevent(string);\n}\n\n// 0x464CE4\nstatic void op_signalNamed(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to signalnamed\");\n    }\n\n    char* str = interpretGetString(program, opcode, data);\n    nevs_signal(str);\n}\n\n// 0x464D48\nstatic void op_addkey(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    for (int arg = 0; arg < 2; arg++) {\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"Invalid arg %d given to addkey\", arg + 1);\n        }\n    }\n\n    int key = data[1];\n    int proc = data[0];\n\n    if (key == -1) {\n        anyKeyOffset = proc;\n        anyKeyProg = program;\n    } else {\n        if (key > INT_LIB_KEY_HANDLERS_CAPACITY - 1) {\n            interpretError(\"Key out of range\");\n        }\n\n        inputProc[key].program = program;\n        inputProc[key].proc = proc;\n    }\n}\n\n// 0x464E24\nstatic void op_deletekey(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to deletekey\");\n    }\n\n    int key = data;\n\n    if (key == -1) {\n        anyKeyOffset = 0;\n        anyKeyProg = NULL;\n    } else {\n        if (key > INT_LIB_KEY_HANDLERS_CAPACITY - 1) {\n            interpretError(\"Key out of range\");\n        }\n\n        inputProc[key].program = NULL;\n        inputProc[key].proc = 0;\n    }\n}\n\n// 0x464EB0\nstatic void op_refreshmouse(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to refreshmouse\");\n    }\n\n    if (!windowRefreshRegions()) {\n        executeProc(program, data);\n    }\n}\n\n// 0x464F18\nstatic void op_setfont(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to setfont\");\n    }\n\n    if (!windowSetFont(data)) {\n        interpretError(\"Error setting font\");\n    }\n}\n\n// 0x464F84\nstatic void op_settextflags(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to setflags\");\n    }\n\n    if (!windowSetTextFlags(data)) {\n        interpretError(\"Error setting text flags\");\n    }\n}\n\n// 0x464FF0\nstatic void op_settextcolor(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n    float* floats = (float*)data;\n\n    // NOTE: Original code does not use loops.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    for (int arg = 0; arg < 3; arg++) {\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT\n            && (opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_INT\n            && data[arg] != 0) {\n            interpretError(\"Invalid type given to settextcolor\");\n        }\n    }\n\n    float r = floats[2];\n    float g = floats[1];\n    float b = floats[0];\n\n    if (!windowSetTextColor(r, g, b)) {\n        interpretError(\"Error setting text color\");\n    }\n}\n\n// 0x465140\nstatic void op_sayoptioncolor(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n    float* floats = (float*)data;\n\n    // NOTE: Original code does not use loops.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    for (int arg = 0; arg < 3; arg++) {\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT\n            && (opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_INT\n            && data[arg] != 0) {\n            interpretError(\"Invalid type given to sayoptioncolor\");\n        }\n    }\n\n    float r = floats[2];\n    float g = floats[1];\n    float b = floats[0];\n\n    if (dialogSetOptionColor(r, g, b)) {\n        interpretError(\"Error setting option color\");\n    }\n}\n\n// 0x465290\nstatic void op_sayreplycolor(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n    float* floats = (float*)data;\n\n    // NOTE: Original code does not use loops.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n        ;\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    for (int arg = 0; arg < 3; arg++) {\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT\n            && (opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_INT\n            && data[arg] != 0) {\n            interpretError(\"Invalid type given to sayreplycolor\");\n        }\n    }\n\n    float r = floats[2];\n    float g = floats[1];\n    float b = floats[0];\n\n    if (dialogSetReplyColor(r, g, b) != 0) {\n        interpretError(\"Error setting reply color\");\n    }\n}\n\n// 0x4653E0\nstatic void op_sethighlightcolor(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n    float* floats = (float*)data;\n\n    // NOTE: Original code does not use loops.\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    for (int arg = 0; arg < 3; arg++) {\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT\n            && (opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_INT\n            && data[arg] != 0) {\n            interpretError(\"Invalid type given to sethighlightcolor\");\n        }\n    }\n\n    float r = floats[2];\n    float g = floats[1];\n    float b = floats[0];\n\n    if (!windowSetHighlightColor(r, g, b)) {\n        interpretError(\"Error setting text highlight color\");\n    }\n}\n\n// 0x465530\nstatic void op_sayreplywindow(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    char* v1;\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v1 = interpretGetString(program, opcode[0], data[0]);\n        v1 = interpretMangleName(v1);\n        v1 = mystrdup(v1, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1510\n    } else if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[0] == 0) {\n        v1 = NULL;\n    } else {\n        interpretError(\"Invalid arg 5 given to sayreplywindow\");\n    }\n\n    if (dialogSetReplyWindow(data[4], data[3], data[2], data[1], v1) != 0) {\n        interpretError(\"Error setting reply window\");\n    }\n}\n\n// 0x465688\nstatic void op_sayreplyflags(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to sayreplyflags\");\n    }\n\n    if (!dialogSetReplyFlags(data)) {\n        interpretError(\"Error setting reply flags\");\n    }\n}\n\n// 0x4656F4\nstatic void op_sayoptionflags(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to sayoptionflags\");\n    }\n\n    if (!dialogSetOptionFlags(data)) {\n        interpretError(\"Error setting option flags\");\n    }\n}\n\n// 0x465760\nstatic void op_sayoptionwindow(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    char* v1;\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v1 = interpretGetString(program, opcode[0], data[0]);\n        v1 = interpretMangleName(v1);\n        v1 = mystrdup(v1, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1556\n    } else if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[0] == 0) {\n        v1 = NULL;\n    } else {\n        interpretError(\"Invalid arg 5 given to sayoptionwindow\");\n    }\n\n    if (dialogSetOptionWindow(data[4], data[3], data[2], data[1], v1) != 0) {\n        interpretError(\"Error setting option window\");\n    }\n}\n\n// 0x4658B8\nstatic void op_sayborder(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loops.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    for (int arg = 0; arg < 2; arg++) {\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"Invalid arg %d given to sayborder\", arg + 1);\n        }\n    }\n\n    if (dialogSetBorder(data[1], data[0]) != 0) {\n        interpretError(\"Error setting dialog border\");\n    }\n}\n\n// 0x465978\nstatic void op_sayscrollup(Program* program)\n{\n    opcode_t opcode[6];\n    int data[6];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 6; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    char* v1 = NULL;\n    char* v2 = NULL;\n    char* v3 = NULL;\n    char* v4 = NULL;\n    int v5 = 0;\n\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n        if (data[0] != -1 && data[0] != 0) {\n            interpretError(\"Invalid arg 4 given to sayscrollup\");\n        }\n\n        if (data[0] == -1) {\n            v5 = 1;\n        }\n    } else {\n        if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n            interpretError(\"Invalid arg 4 given to sayscrollup\");\n        }\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[1] != 0) {\n        interpretError(\"Invalid arg 3 given to sayscrollup\");\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[2] != 0) {\n        interpretError(\"Invalid arg 2 given to sayscrollup\");\n    }\n\n    if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[3] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[3] != 0) {\n        interpretError(\"Invalid arg 1 given to sayscrollup\");\n    }\n\n    if ((opcode[3] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v1 = interpretGetString(program, opcode[3], data[3]);\n        v1 = interpretMangleName(v1);\n        v1 = mystrdup(v1, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1611\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v2 = interpretGetString(program, opcode[2], data[2]);\n        v2 = interpretMangleName(v2);\n        v2 = mystrdup(v2, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1613\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v3 = interpretGetString(program, opcode[1], data[1]);\n        v3 = interpretMangleName(v3);\n        v3 = mystrdup(v3, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1615\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v4 = interpretGetString(program, opcode[0], data[0]);\n        v4 = interpretMangleName(v4);\n        v4 = mystrdup(v4, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1617\n    }\n\n    if (dialogSetScrollUp(data[5], data[4], v1, v2, v3, v4, v5) != 0) {\n        interpretError(\"Error setting scroll up\");\n    }\n}\n\n// 0x465CAC\nstatic void op_sayscrolldown(Program* program)\n{\n    opcode_t opcode[6];\n    int data[6];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 6; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    char* v1 = NULL;\n    char* v2 = NULL;\n    char* v3 = NULL;\n    char* v4 = NULL;\n    int v5 = 0;\n\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) {\n        if (data[0] != -1 && data[0] != 0) {\n            // FIXME: Wrong function name, should be sayscrolldown.\n            interpretError(\"Invalid arg 4 given to sayscrollup\");\n        }\n\n        if (data[0] == -1) {\n            v5 = 1;\n        }\n    } else {\n        if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n            // FIXME: Wrong function name, should be sayscrolldown.\n            interpretError(\"Invalid arg 4 given to sayscrollup\");\n        }\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[1] != 0) {\n        interpretError(\"Invalid arg 3 given to sayscrolldown\");\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[2] != 0) {\n        interpretError(\"Invalid arg 2 given to sayscrolldown\");\n    }\n\n    if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[3] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[3] != 0) {\n        interpretError(\"Invalid arg 1 given to sayscrolldown\");\n    }\n\n    if ((opcode[3] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v1 = interpretGetString(program, opcode[3], data[3]);\n        v1 = interpretMangleName(v1);\n        v1 = mystrdup(v1, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1652\n    }\n\n    if ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v2 = interpretGetString(program, opcode[2], data[2]);\n        v2 = interpretMangleName(v2);\n        v2 = mystrdup(v2, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1654\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v3 = interpretGetString(program, opcode[1], data[1]);\n        v3 = interpretMangleName(v3);\n        v3 = mystrdup(v3, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1656\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        v4 = interpretGetString(program, opcode[0], data[0]);\n        v4 = interpretMangleName(v4);\n        v4 = mystrdup(v4, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1658\n    }\n\n    if (dialogSetScrollDown(data[5], data[4], v1, v2, v3, v4, v5) != 0) {\n        interpretError(\"Error setting scroll down\");\n    }\n}\n\n// 0x465FE0\nstatic void op_saysetspacing(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to saysetspacing\");\n    }\n\n    if (dialogSetSpacing(data) != 0) {\n        interpretError(\"Error setting option spacing\");\n    }\n}\n\n// 0x46604C\nstatic void op_sayrestart(Program* program)\n{\n    if (dialogRestart() != 0) {\n        interpretError(\"Error restarting option\");\n    }\n}\n\n// 0x466064\nstatic void soundCallbackInterpret(void* userData, int a2)\n{\n    if (a2 == 1) {\n        Sound** sound = (Sound**)userData;\n        *sound = NULL;\n    }\n}\n\n// 0x466070\nstatic int soundDeleteInterpret(int value)\n{\n    if (value == -1) {\n        return 1;\n    }\n\n    if ((value & 0xA0000000) == 0) {\n        return 0;\n    }\n\n    int index = value & ~0xA0000000;\n    Sound* sound = interpretSounds[index];\n    if (sound == NULL) {\n        return 0;\n    }\n\n    if (soundPlaying(sound)) {\n        soundStop(sound);\n    }\n\n    soundDelete(sound);\n\n    interpretSounds[index] = NULL;\n\n    return 1;\n}\n\n// NOTE: Inlined.\n//\n// 0x4660E8\nvoid soundCloseInterpret()\n{\n    int index;\n\n    for (index = 0; index < INT_LIB_SOUNDS_CAPACITY; index++) {\n        if (interpretSounds[index] != NULL) {\n            soundDeleteInterpret(index | 0xA0000000);\n        }\n    }\n}\n\n// 0x466110\nint soundStartInterpret(char* fileName, int mode)\n{\n    int v3 = 1;\n    int v5 = 0;\n\n    if (mode & 0x01) {\n        // looping\n        v5 |= 0x20;\n    } else {\n        v3 = 5;\n    }\n\n    if (mode & 0x02) {\n        v5 |= 0x08;\n    } else {\n        v5 |= 0x10;\n    }\n\n    if (mode & 0x0100) {\n        // memory\n        v3 &= ~0x03;\n        v3 |= 0x01;\n    }\n\n    if (mode & 0x0200) {\n        // streamed\n        v3 &= ~0x03;\n        v3 |= 0x02;\n    }\n\n    int index;\n    for (index = 0; index < INT_LIB_SOUNDS_CAPACITY; index++) {\n        if (interpretSounds[index] == NULL) {\n            break;\n        }\n    }\n\n    if (index == INT_LIB_SOUNDS_CAPACITY) {\n        return -1;\n    }\n\n    Sound* sound = interpretSounds[index] = soundAllocate(v3, v5);\n    if (sound == NULL) {\n        return -1;\n    }\n\n    soundSetCallback(sound, soundCallbackInterpret, &(interpretSounds[index]));\n\n    if (mode & 0x01) {\n        soundLoop(sound, 0xFFFF);\n    }\n\n    if (mode & 0x1000) {\n        // mono\n        soundSetChannel(sound, 2);\n    }\n\n    if (mode & 0x2000) {\n        // stereo\n        soundSetChannel(sound, 3);\n    }\n\n    int rc = soundLoad(sound, fileName);\n    if (rc != SOUND_NO_ERROR) {\n        goto err;\n    }\n\n    rc = soundPlay(sound);\n\n    // TODO: Maybe wrong.\n    switch (rc) {\n    case SOUND_NO_DEVICE:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_NO_DEVICE\");\n        goto err;\n    case SOUND_NOT_INITIALIZED:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_NOT_INITIALIZED\");\n        goto err;\n    case SOUND_NO_SOUND:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_NO_SOUND\");\n        goto err;\n    case SOUND_FUNCTION_NOT_SUPPORTED:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_FUNC_NOT_SUPPORTED\");\n        goto err;\n    case SOUND_NO_BUFFERS_AVAILABLE:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_NO_BUFFERS_AVAILABLE\");\n        goto err;\n    case SOUND_FILE_NOT_FOUND:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_FILE_NOT_FOUND\");\n        goto err;\n    case SOUND_ALREADY_PLAYING:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_ALREADY_PLAYING\");\n        goto err;\n    case SOUND_NOT_PLAYING:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_NOT_PLAYING\");\n        goto err;\n    case SOUND_ALREADY_PAUSED:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_ALREADY_PAUSED\");\n        goto err;\n    case SOUND_NOT_PAUSED:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_NOT_PAUSED\");\n        goto err;\n    case SOUND_INVALID_HANDLE:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_INVALID_HANDLE\");\n        goto err;\n    case SOUND_NO_MEMORY_AVAILABLE:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_NO_MEMORY\");\n        goto err;\n    case SOUND_UNKNOWN_ERROR:\n        debug_printf(\"soundPlay error: %s\\n\", \"SOUND_ERROR\");\n        goto err;\n    }\n\n    return index | 0xA0000000;\n\nerr:\n\n    soundDelete(sound);\n    interpretSounds[index] = NULL;\n    return -1;\n}\n\n// 0x46655C\nstatic int soundPauseInterpret(int value)\n{\n    if (value == -1) {\n        return 1;\n    }\n\n    if ((value & 0xA0000000) == 0) {\n        return 0;\n    }\n\n    int index = value & ~0xA0000000;\n    Sound* sound = interpretSounds[index];\n    if (sound == NULL) {\n        return 0;\n    }\n\n    int rc;\n    if (soundType(sound, 0x01)) {\n        rc = soundStop(sound);\n    } else {\n        rc = soundPause(sound);\n    }\n    return rc == SOUND_NO_ERROR;\n}\n\n// 0x4665C8\nstatic int soundRewindInterpret(int value)\n{\n    if (value == -1) {\n        return 1;\n    }\n\n    if ((value & 0xA0000000) == 0) {\n        return 0;\n    }\n\n    int index = value & ~0xA0000000;\n    Sound* sound = interpretSounds[index];\n    if (sound == NULL) {\n        return 0;\n    }\n\n    if (!soundPlaying(sound)) {\n        return 1;\n    }\n\n    soundStop(sound);\n\n    return soundPlay(sound) == SOUND_NO_ERROR;\n}\n\n// 0x46662C\nstatic int soundUnpauseInterpret(int value)\n{\n    if (value == -1) {\n        return 1;\n    }\n\n    if ((value & 0xA0000000) == 0) {\n        return 0;\n    }\n\n    int index = value & ~0xA0000000;\n    Sound* sound = interpretSounds[index];\n    if (sound == NULL) {\n        return 0;\n    }\n\n    int rc;\n    if (soundType(sound, 0x01)) {\n        rc = soundPlay(sound);\n    } else {\n        rc = soundUnpause(sound);\n    }\n    return rc == SOUND_NO_ERROR;\n}\n\n// 0x466698\nstatic void op_soundplay(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 2 given to soundplay\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid arg 1 given to soundplay\");\n    }\n\n    char* fileName = interpretGetString(program, opcode[1], data[1]);\n    char* mangledFileName = interpretMangleName(fileName);\n    int rc = soundStartInterpret(mangledFileName, data[0]);\n\n    interpretPushLong(program, rc);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x466768\nstatic void op_soundpause(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (data == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to soundpause\");\n    }\n\n    soundPauseInterpret(data);\n}\n\n// 0x4667C0\nstatic void op_soundresume(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (data == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to soundresume\");\n    }\n\n    soundUnpauseInterpret(data);\n}\n\n// 0x466818\nstatic void op_soundstop(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (data == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to soundstop\");\n    }\n\n    soundPauseInterpret(data);\n}\n\n// 0x466870\nstatic void op_soundrewind(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (data == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to soundrewind\");\n    }\n\n    soundRewindInterpret(data);\n}\n\n// 0x4668C8\nstatic void op_sounddelete(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (data == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 1 given to sounddelete\");\n    }\n\n    soundDeleteInterpret(data);\n}\n\n// 0x466920\nstatic void op_setoneoptpause(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"SetOneOptPause: invalid arg passed (non-integer).\");\n    }\n\n    if (data) {\n        if ((dialogGetMediaFlag() & 8) == 0) {\n            return;\n        }\n    } else {\n        if ((dialogGetMediaFlag() & 8) != 0) {\n            return;\n        }\n    }\n\n    dialogToggleMediaFlag(8);\n}\n\n// 0x466994\nvoid updateIntLib()\n{\n    nevs_update();\n    updateIntExtra();\n}\n\n// 0x4669A0\nvoid intlibClose()\n{\n    dialogClose();\n    intExtraClose();\n\n    // NOTE: Uninline.\n    soundCloseInterpret();\n\n    nevs_close();\n\n    if (callbacks != NULL) {\n        myfree(callbacks, __FILE__, __LINE__); // \"..\\\\int\\\\INTLIB.C\", 1976\n        callbacks = NULL;\n        numCallbacks = 0;\n    }\n}\n\n// 0x466A04\nstatic bool intLibDoInput(int key)\n{\n    if (key < 0 || key >= INT_LIB_KEY_HANDLERS_CAPACITY) {\n        return false;\n    }\n\n    if (anyKeyProg != NULL) {\n        if (anyKeyOffset != 0) {\n            executeProc(anyKeyProg, anyKeyOffset);\n        }\n        return true;\n    }\n\n    IntLibKeyHandlerEntry* entry = &(inputProc[key]);\n    if (entry->program == NULL) {\n        return false;\n    }\n\n    if (entry->proc != 0) {\n        executeProc(entry->program, entry->proc);\n    }\n\n    return true;\n}\n\n// 0x466A70\nvoid initIntlib()\n{\n    windowAddInputFunc(intLibDoInput);\n\n    interpretAddFunc(0x806A, op_fillwin3x3);\n    interpretAddFunc(0x808C, op_deletebutton);\n    interpretAddFunc(0x8086, op_addbutton);\n    interpretAddFunc(0x8088, op_addbuttonflag);\n    interpretAddFunc(0x8087, op_addbuttontext);\n    interpretAddFunc(0x8089, op_addbuttongfx);\n    interpretAddFunc(0x808A, op_addbuttonproc);\n    interpretAddFunc(0x808B, op_addbuttonrightproc);\n    interpretAddFunc(0x8067, op_showwin);\n    interpretAddFunc(0x8068, op_fillwin);\n    interpretAddFunc(0x8069, op_fillrect);\n    interpretAddFunc(0x8072, op_print);\n    interpretAddFunc(0x8073, op_format);\n    interpretAddFunc(0x8074, op_printrect);\n    interpretAddFunc(0x8075, op_setfont);\n    interpretAddFunc(0x8076, op_settextflags);\n    interpretAddFunc(0x8077, op_settextcolor);\n    interpretAddFunc(0x8078, op_sethighlightcolor);\n    interpretAddFunc(0x8064, op_selectwin);\n    interpretAddFunc(0x806B, op_display);\n    interpretAddFunc(0x806D, op_displayraw);\n    interpretAddFunc(0x806C, op_displaygfx);\n    interpretAddFunc(0x806F, op_fadein);\n    interpretAddFunc(0x8070, op_fadeout);\n    interpretAddFunc(0x807A, op_playmovie);\n    interpretAddFunc(0x807B, op_movieflags);\n    interpretAddFunc(0x807C, op_playmovierect);\n    interpretAddFunc(0x8079, op_stopmovie);\n    interpretAddFunc(0x807F, op_addregion);\n    interpretAddFunc(0x8080, op_addregionflag);\n    interpretAddFunc(0x8081, op_addregionproc);\n    interpretAddFunc(0x8082, op_addregionrightproc);\n    interpretAddFunc(0x8083, op_deleteregion);\n    interpretAddFunc(0x8084, op_activateregion);\n    interpretAddFunc(0x8085, op_checkregion);\n    interpretAddFunc(0x8062, op_createwin);\n    interpretAddFunc(0x8063, op_deletewin);\n    interpretAddFunc(0x8065, op_resizewin);\n    interpretAddFunc(0x8066, op_scalewin);\n    interpretAddFunc(0x804E, op_saystart);\n    interpretAddFunc(0x804F, op_saystartpos);\n    interpretAddFunc(0x8050, op_sayreplytitle);\n    interpretAddFunc(0x8051, op_saygotoreply);\n    interpretAddFunc(0x8053, op_sayreply);\n    interpretAddFunc(0x8052, op_sayoption);\n    interpretAddFunc(0x804D, op_sayend);\n    interpretAddFunc(0x804C, op_sayquit);\n    interpretAddFunc(0x8054, op_saymessage);\n    interpretAddFunc(0x8055, op_sayreplywindow);\n    interpretAddFunc(0x8056, op_sayoptionwindow);\n    interpretAddFunc(0x805F, op_sayreplyflags);\n    interpretAddFunc(0x8060, op_sayoptionflags);\n    interpretAddFunc(0x8057, op_sayborder);\n    interpretAddFunc(0x8058, op_sayscrollup);\n    interpretAddFunc(0x8059, op_sayscrolldown);\n    interpretAddFunc(0x805A, op_saysetspacing);\n    interpretAddFunc(0x805B, op_sayoptioncolor);\n    interpretAddFunc(0x805C, op_sayreplycolor);\n    interpretAddFunc(0x805D, op_sayrestart);\n    interpretAddFunc(0x805E, op_saygetlastpos);\n    interpretAddFunc(0x8061, op_saymessagetimeout);\n    interpretAddFunc(0x8071, op_gotoxy);\n    interpretAddFunc(0x808D, op_hidemouse);\n    interpretAddFunc(0x808E, op_showmouse);\n    interpretAddFunc(0x8090, op_refreshmouse);\n    interpretAddFunc(0x808F, op_mouseshape);\n    interpretAddFunc(0x8091, op_setglobalmousefunc);\n    interpretAddFunc(0x806E, op_loadpalettetable);\n    interpretAddFunc(0x8092, op_addNamedEvent);\n    interpretAddFunc(0x8093, op_addNamedHandler);\n    interpretAddFunc(0x8094, op_clearNamed);\n    interpretAddFunc(0x8095, op_signalNamed);\n    interpretAddFunc(0x8096, op_addkey);\n    interpretAddFunc(0x8097, op_deletekey);\n    interpretAddFunc(0x8098, op_soundplay);\n    interpretAddFunc(0x8099, op_soundpause);\n    interpretAddFunc(0x809A, op_soundresume);\n    interpretAddFunc(0x809B, op_soundstop);\n    interpretAddFunc(0x809C, op_soundrewind);\n    interpretAddFunc(0x809D, op_sounddelete);\n    interpretAddFunc(0x809E, op_setoneoptpause);\n    interpretAddFunc(0x809F, op_selectfilelist);\n    interpretAddFunc(0x80A0, op_tokenize);\n\n    nevs_initonce();\n    initIntExtra();\n    initDialog();\n}\n\n// 0x466F6C\nvoid interpretRegisterProgramDeleteCallback(IntLibProgramDeleteCallback* callback)\n{\n    int index;\n    for (index = 0; index < numCallbacks; index++) {\n        if (callbacks[index] == NULL) {\n            break;\n        }\n    }\n\n    if (index == numCallbacks) {\n        if (callbacks != NULL) {\n            callbacks = (IntLibProgramDeleteCallback**)myrealloc(callbacks, sizeof(*callbacks) * (numCallbacks + 1), __FILE__, __LINE__); // ..\\\\int\\\\INTLIB.C, 2110\n        } else {\n            callbacks = (IntLibProgramDeleteCallback**)mymalloc(sizeof(*callbacks), __FILE__, __LINE__); // ..\\\\int\\\\INTLIB.C, 2112\n        }\n        numCallbacks++;\n    }\n\n    callbacks[index] = callback;\n}\n\n// 0x467040\nvoid removeProgramReferences(Program* program)\n{\n    for (int index = 0; index < INT_LIB_KEY_HANDLERS_CAPACITY; index++) {\n        if (program == inputProc[index].program) {\n            inputProc[index].program = NULL;\n        }\n    }\n\n    intExtraRemoveProgramReferences(program);\n\n    for (int index = 0; index < numCallbacks; index++) {\n        IntLibProgramDeleteCallback* callback = callbacks[index];\n        if (callback != NULL) {\n            callback(program);\n        }\n    }\n}\n"
  },
  {
    "path": "src/int/intlib.h",
    "content": "#ifndef FALLOUT_INT_INTLIB_H_\n#define FALLOUT_INT_INTLIB_H_\n\n#include <stdbool.h>\n\n#include \"int/intrpret.h\"\n\ntypedef void(IntLibProgramDeleteCallback)(Program*);\n\nvoid interpretFadePalette(unsigned char* oldPalette, unsigned char* newPalette, int a3, float duration);\nint intlibGetFadeIn();\nvoid interpretFadeOut(float duration);\nvoid interpretFadeIn(float duration);\nvoid interpretFadeOutNoBK(float duration);\nvoid interpretFadeInNoBK(float duration);\nint checkMovie(Program* program);\nint getTimeOut();\nvoid setTimeOut(int value);\nvoid soundCloseInterpret();\nint soundStartInterpret(char* fileName, int mode);\nvoid updateIntLib();\nvoid intlibClose();\nvoid initIntlib();\nvoid interpretRegisterProgramDeleteCallback(IntLibProgramDeleteCallback* callback);\nvoid removeProgramReferences(Program* program);\n\n#endif /* FALLOUT_INT_INTLIB_H_ */\n"
  },
  {
    "path": "src/int/intrpret.c",
    "content": "#include \"int/intrpret.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/input.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/export.h\"\n#include \"int/intlib.h\"\n#include \"int/memdbg.h\"\n\n// The maximum number of opcodes.\n#define OPCODE_MAX_COUNT 342\n\ntypedef struct ProgramListNode {\n    Program* program;\n    struct ProgramListNode* next; // next\n    struct ProgramListNode* prev; // prev\n} ProgramListNode;\n\nstatic unsigned int defaultTimerFunc();\nstatic char* defaultFilename(char* fileName);\nstatic int outputStr(char* string);\nstatic int checkWait(Program* program);\nstatic const char* findCurrentProc(Program* program);\nstatic opcode_t fetchWord(unsigned char* data, int pos);\nstatic int fetchLong(unsigned char* a1, int a2);\nstatic void storeWord(int value, unsigned char* a2, int a3);\nstatic void storeLong(int value, unsigned char* stack, int pos);\nstatic void pushShortStack(unsigned char* a1, int* a2, int value);\nstatic void pushLongStack(unsigned char* a1, int* a2, int value);\nstatic int popLongStack(unsigned char* a1, int* a2);\nstatic opcode_t popShortStack(unsigned char* a1, int* a2);\nstatic void rPushShort(Program* program, int value);\nstatic void rPushLong(Program* program, int value);\nstatic opcode_t rPopShort(Program* program);\nstatic int rPopLong(Program* program);\nstatic void detachProgram(Program* program);\nstatic void purgeProgram(Program* program);\nstatic opcode_t getOp(Program* program);\nstatic void checkProgramStrings(Program* program);\nstatic void op_noop(Program* program);\nstatic void op_const(Program* program);\nstatic void op_push_base(Program* program);\nstatic void op_pop_base(Program* program);\nstatic void op_pop_to_base(Program* program);\nstatic void op_set_global(Program* program);\nstatic void op_dump(Program* program);\nstatic void op_call_at(Program* program);\nstatic void op_call_condition(Program* program);\nstatic void op_wait(Program* program);\nstatic void op_cancel(Program* program);\nstatic void op_cancelall(Program* program);\nstatic void op_if(Program* program);\nstatic void op_while(Program* program);\nstatic void op_store(Program* program);\nstatic void op_fetch(Program* program);\nstatic void op_not_equal(Program* program);\nstatic void op_equal(Program* program);\nstatic void op_less_equal(Program* program);\nstatic void op_greater_equal(Program* program);\nstatic void op_less(Program* program);\nstatic void op_greater(Program* program);\nstatic void op_add(Program* program);\nstatic void op_sub(Program* program);\nstatic void op_mul(Program* program);\nstatic void op_div(Program* program);\nstatic void op_mod(Program* program);\nstatic void op_and(Program* program);\nstatic void op_or(Program* program);\nstatic void op_not(Program* program);\nstatic void op_negate(Program* program);\nstatic void op_bwnot(Program* program);\nstatic void op_floor(Program* program);\nstatic void op_bwand(Program* program);\nstatic void op_bwor(Program* program);\nstatic void op_bwxor(Program* program);\nstatic void op_swapa(Program* program);\nstatic void op_critical_done(Program* program);\nstatic void op_critical_start(Program* program);\nstatic void op_jmp(Program* program);\nstatic void op_call(Program* program);\nstatic void op_pop_flags(Program* program);\nstatic void op_pop_return(Program* program);\nstatic void op_pop_exit(Program* program);\nstatic void op_pop_flags_return(Program* program);\nstatic void op_pop_flags_exit(Program* program);\nstatic void op_pop_flags_return_val_exit(Program* program);\nstatic void op_pop_flags_return_val_exit_extern(Program* program);\nstatic void op_pop_flags_return_extern(Program* program);\nstatic void op_pop_flags_exit_extern(Program* program);\nstatic void op_pop_flags_return_val_extern(Program* program);\nstatic void op_pop_address(Program* program);\nstatic void op_a_to_d(Program* program);\nstatic void op_d_to_a(Program* program);\nstatic void op_exit_prog(Program* program);\nstatic void op_stop_prog(Program* program);\nstatic void op_fetch_global(Program* program);\nstatic void op_store_global(Program* program);\nstatic void op_swap(Program* program);\nstatic void op_fetch_proc_address(Program* program);\nstatic void op_pop(Program* program);\nstatic void op_dup(Program* program);\nstatic void op_store_external(Program* program);\nstatic void op_fetch_external(Program* program);\nstatic void op_export_proc(Program* program);\nstatic void op_export_var(Program* program);\nstatic void op_exit(Program* program);\nstatic void op_detach(Program* program);\nstatic void op_callstart(Program* program);\nstatic void op_spawn(Program* program);\nstatic Program* op_fork_helper(Program* program);\nstatic void op_fork(Program* program);\nstatic void op_exec(Program* program);\nstatic void op_check_arg_count(Program* program);\nstatic void op_lookup_string_proc(Program* program);\nstatic void setupCallWithReturnVal(Program* program, int address, int a3);\nstatic void setupCall(Program* program, int address, int returnAddress);\nstatic void setupExternalCallWithReturnVal(Program* program1, Program* program2, int address, int a4);\nstatic void setupExternalCall(Program* program1, Program* program2, int address, int a4);\nstatic void doEvents();\nstatic void removeProgList(ProgramListNode* programListNode);\nstatic void insertProgram(Program* program);\n\n// 0x51903C\nstatic int enabled = 1;\n\n// 0x519040\nstatic InterpretTimerFunc* timerFunc = defaultTimerFunc;\n\n// 0x519044\nstatic unsigned int timerTick = 1000;\n\n// 0x519048\nstatic InterpretMangleFunc* filenameFunc = defaultFilename;\n\n// 0x51904C\nstatic InterpretOutputFunc* outputFunc = outputStr;\n\n// 0x519050\nstatic int cpuBurstSize = 10;\n\n// 0x59E230\nstatic OpcodeHandler* opTable[OPCODE_MAX_COUNT];\n\n// 0x59E788\nstatic unsigned int suspendTime;\n\n// 0x59E78C\nstatic Program* currentProgram;\n\n// 0x59E790\nstatic ProgramListNode* head;\n\n// 0x59E794\nstatic int suspendEvents;\n\n// 0x4670A0\nstatic unsigned int defaultTimerFunc()\n{\n    return get_time();\n}\n\n// NOTE: Unused.\n//\n// 0x4670A8\nvoid interpretSetTimeFunc(InterpretTimerFunc* timerFunc, int timerTick)\n{\n    timerFunc = timerFunc;\n    timerTick = timerTick;\n}\n\n// 0x4670B4\nstatic char* defaultFilename(char* fileName)\n{\n    return fileName;\n}\n\n// 0x4670B8\nchar* interpretMangleName(char* fileName)\n{\n    return filenameFunc(fileName);\n}\n\n// 0x4670C0\nstatic int outputStr(char* string)\n{\n    return 1;\n}\n\n// 0x4670C8\nstatic int checkWait(Program* program)\n{\n    return 1000 * timerFunc() / timerTick <= program->waitEnd;\n}\n\n// 0x4670FC\nvoid interpretOutputFunc(InterpretOutputFunc* func)\n{\n    outputFunc = func;\n}\n\n// 0x467104\nint interpretOutput(const char* format, ...)\n{\n    if (outputFunc == NULL) {\n        return 0;\n    }\n\n    char string[260];\n\n    va_list args;\n    va_start(args, format);\n    int rc = vsprintf(string, format, args);\n    va_end(args);\n\n    debug_printf(string);\n\n    return rc;\n}\n\n// 0x467160\nstatic const char* findCurrentProc(Program* program)\n{\n    int procedureCount = fetchLong(program->procedures, 0);\n    unsigned char* ptr = program->procedures + 4;\n\n    int procedureOffset = fetchLong(ptr, 16);\n    int identifierOffset = fetchLong(ptr, 0);\n\n    for (int index = 0; index < procedureCount; index++) {\n        int nextProcedureOffset = fetchLong(ptr + sizeof(Procedure), 16);\n        if (program->instructionPointer >= procedureOffset && program->instructionPointer < nextProcedureOffset) {\n            return (const char*)(program->identifiers + identifierOffset);\n        }\n\n        ptr += sizeof(Procedure);\n        identifierOffset = fetchLong(ptr, 0);\n    }\n\n    return \"<couldn't find proc>\";\n}\n\n// 0x4671F0\nvoid interpretError(const char* format, ...)\n{\n    char string[260];\n\n    va_list argptr;\n    va_start(argptr, format);\n    vsprintf(string, format, argptr);\n    va_end(argptr);\n\n    debug_printf(\"\\nError during execution: %s\\n\", string);\n\n    if (currentProgram == NULL) {\n        debug_printf(\"No current script\");\n    } else {\n        debug_printf(\"Current script: %s, procedure %s\", currentProgram->name, findCurrentProc(currentProgram));\n    }\n\n    if (currentProgram) {\n        longjmp(currentProgram->env, 1);\n    }\n}\n\n// 0x467290\nstatic opcode_t fetchWord(unsigned char* data, int pos)\n{\n    // TODO: The return result is probably short.\n    opcode_t value = 0;\n    value |= data[pos++] << 8;\n    value |= data[pos++];\n    return value;\n}\n\n// 0x4672A4\nstatic int fetchLong(unsigned char* data, int pos)\n{\n    int value = 0;\n    value |= data[pos++] << 24;\n    value |= data[pos++] << 16;\n    value |= data[pos++] << 8;\n    value |= data[pos++] & 0xFF;\n\n    return value;\n}\n\n// 0x4672D4\nstatic void storeWord(int value, unsigned char* stack, int pos)\n{\n    stack[pos++] = (value >> 8) & 0xFF;\n    stack[pos] = value & 0xFF;\n}\n\n// NOTE: Inlined.\n//\n// 0x4672E8\nstatic void storeLong(int value, unsigned char* stack, int pos)\n{\n    stack[pos++] = (value >> 24) & 0xFF;\n    stack[pos++] = (value >> 16) & 0xFF;\n    stack[pos++] = (value >> 8) & 0xFF;\n    stack[pos] = value & 0xFF;\n}\n\n// pushShortStack\n// 0x467324\nstatic void pushShortStack(unsigned char* data, int* pointer, int value)\n{\n    if (*pointer + 2 >= 0x1000) {\n        interpretError(\"pushShortStack: Stack overflow.\");\n    }\n\n    storeWord(value, data, *pointer);\n\n    *pointer += 2;\n}\n\n// pushLongStack\n// 0x46736C\nstatic void pushLongStack(unsigned char* data, int* pointer, int value)\n{\n    int v1;\n\n    if (*pointer + 4 >= 0x1000) {\n        // FIXME: Should be pushLongStack.\n        interpretError(\"pushShortStack: Stack overflow.\");\n    }\n\n    v1 = *pointer;\n    storeWord(value >> 16, data, v1);\n    storeWord(value & 0xFFFF, data, v1 + 2);\n    *pointer = v1 + 4;\n}\n\n// popStackLong\n// 0x4673C4\nstatic int popLongStack(unsigned char* data, int* pointer)\n{\n    if (*pointer < 4) {\n        interpretError(\"\\nStack underflow long.\");\n    }\n\n    *pointer -= 4;\n\n    return fetchLong(data, *pointer);\n}\n\n// popStackShort\n// 0x4673F0\nstatic opcode_t popShortStack(unsigned char* data, int* pointer)\n{\n    if (*pointer < 2) {\n        interpretError(\"\\nStack underflow short.\");\n    }\n\n    *pointer -= 2;\n\n    // NOTE: uninline\n    return fetchWord(data, *pointer);\n}\n\n// NOTE: Inlined.\n//\n// 0x467424\nvoid _interpretIncStringRef(Program* program, opcode_t opcode, int value)\n{\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        *(short*)(program->dynamicStrings + 4 + value - 2) += 1;\n    }\n}\n\n// 0x467440\nvoid interpretDecStringRef(Program* program, opcode_t opcode, int value)\n{\n    char* string;\n    short* refcountPtr;\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        string = (char*)(program->dynamicStrings + 4 + value);\n        refcountPtr = (short*)(string - 2);\n\n        if (*refcountPtr != 0) {\n            *refcountPtr -= 1;\n        } else {\n            debug_printf(\"Reference count zero for %s!\\n\", string);\n        }\n\n        if (*refcountPtr < 0) {\n            debug_printf(\"String ref went negative, this shouldn\\'t ever happen\\n\");\n        }\n    }\n}\n\n// 0x46748C\nvoid interpretPushShort(Program* program, int value)\n{\n    int stringOffset;\n\n    pushShortStack(program->stack, &(program->stackPointer), value);\n\n    if (value == VALUE_TYPE_DYNAMIC_STRING) {\n        if (program->stackPointer >= 6) {\n            stringOffset = fetchLong(program->stack, program->stackPointer - 6);\n            // NOTE: Uninline.\n            _interpretIncStringRef(program, VALUE_TYPE_DYNAMIC_STRING, stringOffset);\n        }\n    }\n}\n\n// 0x4674DC\nvoid interpretPushLong(Program* program, int value)\n{\n    pushLongStack(program->stack, &(program->stackPointer), value);\n}\n\n// 0x4674F0\nopcode_t interpretPopShort(Program* program)\n{\n    return popShortStack(program->stack, &(program->stackPointer));\n}\n\n// 0x467500\nint interpretPopLong(Program* program)\n{\n    return popLongStack(program->stack, &(program->stackPointer));\n}\n\n// 0x467510\nstatic void rPushShort(Program* program, int value)\n{\n    int stringOffset;\n\n    pushShortStack(program->returnStack, &(program->returnStackPointer), value);\n\n    if (value == VALUE_TYPE_DYNAMIC_STRING) {\n        if (program->stackPointer >= 6) {\n            stringOffset = fetchLong(program->returnStack, program->returnStackPointer - 6);\n            // NOTE: Uninline.\n            _interpretIncStringRef(program, VALUE_TYPE_DYNAMIC_STRING, stringOffset);\n        }\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x467560\nstatic void rPushLong(Program* program, int value)\n{\n    pushLongStack(program->returnStack, &(program->returnStackPointer), value);\n}\n\n// 0x467574\nstatic opcode_t rPopShort(Program* program)\n{\n    opcode_t type;\n    int v5;\n\n    type = popShortStack(program->returnStack, &(program->returnStackPointer));\n    if (type == VALUE_TYPE_DYNAMIC_STRING && program->stackPointer >= 4) {\n        v5 = fetchLong(program->returnStack, program->returnStackPointer - 4);\n        interpretDecStringRef(program, type, v5);\n    }\n\n    return type;\n}\n\n// 0x4675B8\nstatic int rPopLong(Program* program)\n{\n    return popLongStack(program->returnStack, &(program->returnStackPointer));\n}\n\n// NOTE: Inlined.\n//\n// 0x4675C8\nstatic void detachProgram(Program* program)\n{\n    Program* parent = program->parent;\n    if (parent != NULL) {\n        parent->flags &= ~PROGRAM_FLAG_0x20;\n        parent->flags &= ~PROGRAM_FLAG_0x0100;\n        if (program == parent->child) {\n            parent->child = NULL;\n        }\n    }\n}\n\n// 0x4675F4\nstatic void purgeProgram(Program* program)\n{\n    if (!program->exited) {\n        removeProgramReferences(program);\n        program->exited = true;\n    }\n}\n\n// 0x467614\nvoid interpretFreeProgram(Program* program)\n{\n    // NOTE: Uninline.\n    detachProgram(program);\n\n    Program* curr = program->child;\n    while (curr != NULL) {\n        // NOTE: Uninline.\n        purgeProgram(curr);\n\n        curr->parent = NULL;\n\n        Program* next = curr->child;\n        curr->child = NULL;\n\n        curr = next;\n    }\n\n    // NOTE: Uninline.\n    purgeProgram(program);\n\n    if (program->dynamicStrings != NULL) {\n        myfree(program->dynamicStrings, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 429\n    }\n\n    if (program->data != NULL) {\n        myfree(program->data, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 430\n    }\n\n    if (program->name != NULL) {\n        myfree(program->name, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 431\n    }\n\n    if (program->stack != NULL) {\n        myfree(program->stack, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 432\n    }\n\n    if (program->returnStack != NULL) {\n        myfree(program->returnStack, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 433\n    }\n\n    myfree(program, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 435\n}\n\n// 0x467734\nProgram* allocateProgram(const char* path)\n{\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        char err[260];\n        sprintf(err, \"Couldn't open %s for read\\n\", path);\n        interpretError(err);\n        return NULL;\n    }\n\n    int fileSize = db_filelength(stream);\n    unsigned char* data = (unsigned char*)mymalloc(fileSize, __FILE__, __LINE__); // ..\\\\int\\\\INTRPRET.C, 458\n\n    db_fread(data, 1, fileSize, stream);\n    db_fclose(stream);\n\n    Program* program = (Program*)mymalloc(sizeof(Program), __FILE__, __LINE__); // ..\\\\int\\\\INTRPRET.C, 463\n    memset(program, 0, sizeof(Program));\n\n    program->name = (char*)mymalloc(strlen(path) + 1, __FILE__, __LINE__); // ..\\\\int\\\\INTRPRET.C, 466\n    strcpy(program->name, path);\n\n    program->child = NULL;\n    program->parent = NULL;\n    program->field_78 = -1;\n    program->stack = (unsigned char*)mycalloc(1, 4096, __FILE__, __LINE__); // ..\\\\int\\\\INTRPRET.C, 472\n    program->exited = false;\n    program->basePointer = -1;\n    program->framePointer = -1;\n    program->returnStack = (unsigned char*)mycalloc(1, 4096, __FILE__, __LINE__); // ..\\\\int\\\\INTRPRET.C, 473\n    program->data = data;\n    program->procedures = data + 42;\n    program->identifiers = sizeof(Procedure) * fetchLong(program->procedures, 0) + program->procedures + 4;\n    program->staticStrings = program->identifiers + fetchLong(program->identifiers, 0) + 4;\n\n    return program;\n}\n\n// NOTE: Inlined.\n//\n// 0x4678BC\nstatic opcode_t getOp(Program* program)\n{\n    int instructionPointer;\n\n    instructionPointer = program->instructionPointer;\n    program->instructionPointer = instructionPointer + 2;\n\n    // NOTE: Uninline.\n    return fetchWord(program->data, instructionPointer);\n}\n\n// 0x4678E0\nchar* interpretGetString(Program* program, opcode_t opcode, int offset)\n{\n    // The order of checks is important, because dynamic string flag is\n    // always used with static string flag.\n\n    if ((opcode & RAW_VALUE_TYPE_DYNAMIC_STRING) != 0) {\n        return (char*)(program->dynamicStrings + 4 + offset);\n    }\n\n    if ((opcode & RAW_VALUE_TYPE_STATIC_STRING) != 0) {\n        return (char*)(program->staticStrings + 4 + offset);\n    }\n\n    return NULL;\n}\n\n// 0x46790C\nchar* interpretGetName(Program* program, int offset)\n{\n    return (char*)(program->identifiers + offset);\n}\n\n// Loops thru heap:\n// - mark unreferenced blocks as free.\n// - merge consequtive free blocks as one large block.\n//\n// This is done by negating block length:\n// - positive block length - check for ref count.\n// - negative block length - block is free, attempt to merge with next block.\n//\n// 0x4679E0\nstatic void checkProgramStrings(Program* program)\n{\n    unsigned char* ptr;\n    short len;\n    unsigned char* next_ptr;\n    short next_len;\n    short diff;\n\n    if (program->dynamicStrings == NULL) {\n        return;\n    }\n\n    ptr = program->dynamicStrings + 4;\n    while (*(unsigned short*)ptr != 0x8000) {\n        len = *(short*)ptr;\n        if (len < 0) {\n            len = -len;\n            next_ptr = ptr + len + 4;\n\n            if (*(unsigned short*)next_ptr != 0x8000) {\n                next_len = *(short*)next_ptr;\n                if (next_len < 0) {\n                    diff = 4 - next_len;\n                    if (diff + len < 32766) {\n                        len += diff;\n                        *(short*)ptr += next_len - 4;\n                    } else {\n                        debug_printf(\"merged string would be too long, size %d %d\\n\", diff, len);\n                    }\n                }\n            }\n        } else if (*(short*)(ptr + 2) == 0) {\n            *(short*)ptr = -len;\n            *(short*)(ptr + 2) = 0;\n        }\n\n        ptr += len + 4;\n    }\n}\n\n// 0x467A80\nint interpretAddString(Program* program, char* string)\n{\n    int v27;\n    unsigned char* v20;\n    unsigned char* v23;\n\n    if (program == NULL) {\n        return 0;\n    }\n\n    v27 = strlen(string) + 1;\n\n    // Align memory\n    if (v27 & 1) {\n        v27++;\n    }\n\n    if (program->dynamicStrings != NULL) {\n        // TODO: Needs testing, lots of pointer stuff.\n        unsigned char* heap = program->dynamicStrings + 4;\n        while (*(unsigned short*)heap != 0x8000) {\n            short v2 = *(short*)heap;\n            if (v2 >= 0) {\n                if (v2 == v27) {\n                    if (strcmp(string, (char*)(heap + 4)) == 0) {\n                        return (heap + 4) - (program->dynamicStrings + 4);\n                    }\n                }\n            } else {\n                v2 = -v2;\n                if (v2 > v27) {\n                    if (v2 - v27 <= 4) {\n                        *(short*)heap = v2;\n                    } else {\n                        *(short*)(heap + v27 + 6) = 0;\n                        *(short*)(heap + v27 + 4) = -(v2 - v27 - 4);\n                        *(short*)(heap) = v27;\n                    }\n\n                    *(short*)(heap + 2) = 0;\n                    strcpy((char*)(heap + 4), string);\n\n                    *(heap + v27 + 3) = '\\0';\n                    return (heap + 4) - (program->dynamicStrings + 4);\n                }\n            }\n            heap += v2 + 4;\n        }\n    } else {\n        program->dynamicStrings = (unsigned char*)mymalloc(8, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 631\n        *(int*)(program->dynamicStrings) = 0;\n        *(unsigned short*)(program->dynamicStrings + 4) = 0x8000;\n        *(short*)(program->dynamicStrings + 6) = 1;\n    }\n\n    program->dynamicStrings = (unsigned char*)myrealloc(program->dynamicStrings, *(int*)(program->dynamicStrings) + 8 + 4 + v27, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 640\n\n    v20 = program->dynamicStrings + *(int*)(program->dynamicStrings) + 4;\n    if ((*(short*)v20 & 0xFFFF) != 0x8000) {\n        interpretError(\"Internal consistancy error, string table mangled\");\n    }\n\n    *(int*)(program->dynamicStrings) += v27 + 4;\n\n    *(short*)(v20) = v27;\n    *(short*)(v20 + 2) = 0;\n\n    strcpy((char*)(v20 + 4), string);\n\n    v23 = v20 + v27;\n    *(v23 + 3) = '\\0';\n    *(unsigned short*)(v23 + 4) = 0x8000;\n    *(short*)(v23 + 6) = 1;\n\n    return v20 + 4 - (program->dynamicStrings + 4);\n}\n\n// 0x467C90\nstatic void op_noop(Program* program)\n{\n}\n\n// 0x467C94\nstatic void op_const(Program* program)\n{\n    int pos = program->instructionPointer;\n    program->instructionPointer = pos + 4;\n\n    int value = fetchLong(program->data, pos);\n    pushLongStack(program->stack, &(program->stackPointer), value);\n    interpretPushShort(program, (program->flags >> 16) & 0xFFFF);\n}\n\n// - Pops value from stack, which is a number of arguments in the procedure.\n// - Saves current frame pointer in return stack.\n// - Sets frame pointer to the stack pointer minus number of arguments.\n//\n// 0x467CD0\nstatic void op_push_base(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int value = popLongStack(program->stack, &(program->stackPointer));\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, value);\n    }\n\n    pushLongStack(program->returnStack, &(program->returnStackPointer), program->framePointer);\n    rPushShort(program, VALUE_TYPE_INT);\n\n    program->framePointer = program->stackPointer - 6 * value;\n}\n\n// pop_base\n// 0x467D3C\nstatic void op_pop_base(Program* program)\n{\n    opcode_t opcode = rPopShort(program);\n    int data = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    if (opcode != VALUE_TYPE_INT) {\n        char err[260];\n        sprintf(err, \"Invalid type given to pop_base: %x\", opcode);\n        interpretError(err);\n    }\n\n    program->framePointer = data;\n}\n\n// 0x467D94\nstatic void op_pop_to_base(Program* program)\n{\n    while (program->stackPointer != program->framePointer) {\n        opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n        int data = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode, data);\n        }\n    }\n}\n\n// 0x467DE0\nstatic void op_set_global(Program* program)\n{\n    program->basePointer = program->stackPointer;\n}\n\n// 0x467DEC\nstatic void op_dump(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if (opcode != VALUE_TYPE_INT) {\n        char err[256];\n        sprintf(err, \"Invalid type given to dump, %x\", opcode);\n        interpretError(err);\n    }\n\n    // NOTE: Original code is slightly different - it goes backwards to -1.\n    for (int index = 0; index < data; index++) {\n        opcode = popShortStack(program->stack, &(program->stackPointer));\n        data = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode, data);\n        }\n    }\n}\n\n// 0x467EA4\nstatic void op_call_at(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = popShortStack(program->stack, &(program->stackPointer));\n        data[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if (arg == 0) {\n            if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n                interpretError(\"Invalid procedure type given to call\");\n            }\n        } else if (arg == 1) {\n            if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n                interpretError(\"Invalid time given to call\");\n            }\n        }\n    }\n\n    unsigned char* procedure_ptr = program->procedures + 4 + sizeof(Procedure) * data[0];\n\n    int delay = 1000 * data[1];\n\n    if (!suspendEvents) {\n        delay += 1000 * timerFunc() / timerTick;\n    }\n\n    int flags = fetchLong(procedure_ptr, 4);\n\n    storeLong(delay, procedure_ptr, 8);\n    storeLong(flags | PROCEDURE_FLAG_TIMED, procedure_ptr, 4);\n}\n\n// 0x468034\nstatic void op_call_condition(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = popShortStack(program->stack, &(program->stackPointer));\n        data[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid procedure type given to conditional call\");\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid address given to conditional call\");\n    }\n\n    unsigned char* procedure_ptr = program->procedures + 4 + sizeof(Procedure) * data[0];\n    int flags = fetchLong(procedure_ptr, 4);\n\n    storeLong(flags | PROCEDURE_FLAG_CONDITIONAL, procedure_ptr, 4);\n    storeLong(data[1], procedure_ptr, 12);\n}\n\n// 0x46817C\nstatic void op_wait(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid type given to wait\\n\");\n    }\n\n    program->waitStart = 1000 * timerFunc() / timerTick;\n    program->waitEnd = program->waitStart + data;\n    program->checkWaitFunc = checkWait;\n    program->flags |= PROGRAM_IS_WAITING;\n}\n\n// 0x468218\nstatic void op_cancel(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"invalid type given to cancel\");\n    }\n\n    if (data >= fetchLong(program->procedures, 0)) {\n        interpretError(\"Invalid procedure offset given to cancel\");\n    }\n\n    Procedure* proc = (Procedure*)(program->procedures + 4 + data * sizeof(*proc));\n    proc->field_4 = 0;\n    proc->field_8 = 0;\n    proc->field_C = 0;\n}\n\n// 0x468330\nstatic void op_cancelall(Program* program)\n{\n    int procedureCount = fetchLong(program->procedures, 0);\n\n    for (int index = 0; index < procedureCount; index++) {\n        // TODO: Original code uses different approach, check.\n        Procedure* proc = (Procedure*)(program->procedures + 4 + index * sizeof(*proc));\n\n        proc->field_4 = 0;\n        proc->field_8 = 0;\n        proc->field_C = 0;\n    }\n}\n\n// 0x468400\nstatic void op_if(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if (data) {\n        opcode = popShortStack(program->stack, &(program->stackPointer));\n        data = popLongStack(program->stack, &(program->stackPointer));\n        if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode, data);\n        }\n    } else {\n        opcode = popShortStack(program->stack, &(program->stackPointer));\n        data = popLongStack(program->stack, &(program->stackPointer));\n        if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode, data);\n        }\n\n        program->instructionPointer = data;\n    }\n}\n\n// 0x4684A4\nstatic void op_while(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if (data == 0) {\n        opcode = popShortStack(program->stack, &(program->stackPointer));\n        data = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode, data);\n        }\n\n        program->instructionPointer = data;\n    }\n}\n\n// 0x468518\nstatic void op_store(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = popShortStack(program->stack, &(program->stackPointer));\n        data[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    int var_address = program->framePointer + 6 * data[0];\n\n    // NOTE: original code is different, does not use reading functions\n    opcode_t var_type = fetchWord(program->stack, var_address + 4);\n    int var_value = fetchLong(program->stack, var_address);\n\n    if (var_type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, var_type, var_value);\n    }\n\n    // TODO: Original code is different, check.\n    storeLong(data[1], program->stack, var_address);\n\n    storeWord(opcode[1], program->stack, var_address + 4);\n\n    if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        // NOTE: Uninline.\n        _interpretIncStringRef(program, VALUE_TYPE_DYNAMIC_STRING, data[1]);\n    }\n}\n\n// fetch\n// 0x468678\nstatic void op_fetch(Program* program)\n{\n    char err[256];\n\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if (opcode != VALUE_TYPE_INT) {\n        sprintf(err, \"Invalid type given to fetch, %x\", opcode);\n        interpretError(err);\n    }\n\n    // NOTE: original code is a bit different\n    int variableAddress = program->framePointer + 6 * data;\n    int variableType = fetchWord(program->stack, variableAddress + 4);\n    int variableValue = fetchLong(program->stack, variableAddress);\n    interpretPushLong(program, variableValue);\n    interpretPushShort(program, variableType);\n}\n\n// 0x46873C\nstatic void op_not_equal(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n    float* floats = (float*)data;\n    char text[2][80];\n    char* str_ptr[2];\n    int res;\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = popShortStack(program->stack, &(program->stackPointer));\n        data[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    switch (opcode[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        str_ptr[1] = interpretGetString(program, opcode[1], data[1]);\n\n        switch (opcode[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            str_ptr[0] = interpretGetString(program, opcode[0], data[0]);\n            break;\n        case VALUE_TYPE_FLOAT:\n            sprintf(text[0], \"%.5f\", floats[0]);\n            str_ptr[0] = text[0];\n            break;\n        case VALUE_TYPE_INT:\n            sprintf(text[0], \"%d\", data[0]);\n            str_ptr[0] = text[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        res = strcmp(str_ptr[1], str_ptr[0]) != 0;\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (opcode[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%.5f\", floats[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, opcode[0], data[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) != 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = floats[1] != floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = floats[1] != (float)data[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (opcode[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%d\", data[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, opcode[0], data[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) != 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = (float)data[1] != floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = data[1] != data[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), res);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x468AA8\nstatic void op_equal(Program* program)\n{\n    int arg;\n    opcode_t type[2];\n    int value[2];\n    float* floats = (float*)&value;\n    char text[2][80];\n    char* str_ptr[2];\n    int res;\n\n    for (arg = 0; arg < 2; arg++) {\n        type[arg] = popShortStack(program->stack, &(program->stackPointer));\n        value[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, type[arg], value[arg]);\n        }\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        str_ptr[1] = interpretGetString(program, type[1], value[1]);\n\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            break;\n        case VALUE_TYPE_FLOAT:\n            sprintf(text[0], \"%.5f\", floats[0]);\n            str_ptr[0] = text[0];\n            break;\n        case VALUE_TYPE_INT:\n            sprintf(text[0], \"%d\", value[0]);\n            str_ptr[0] = text[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        res = strcmp(str_ptr[1], str_ptr[0]) == 0;\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%.5f\", floats[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) == 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = floats[1] == floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = floats[1] == (float)value[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%d\", value[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) == 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = (float)value[1] == floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = value[1] == value[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), res);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x468E14\nstatic void op_less_equal(Program* program)\n{\n    int arg;\n    opcode_t type[2];\n    int value[2];\n    float* floats = (float*)&value;\n    char text[2][80];\n    char* str_ptr[2];\n    int res;\n\n    for (arg = 0; arg < 2; arg++) {\n        type[arg] = popShortStack(program->stack, &(program->stackPointer));\n        value[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, type[arg], value[arg]);\n        }\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        str_ptr[1] = interpretGetString(program, type[1], value[1]);\n\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            break;\n        case VALUE_TYPE_FLOAT:\n            sprintf(text[0], \"%.5f\", floats[0]);\n            str_ptr[0] = text[0];\n            break;\n        case VALUE_TYPE_INT:\n            sprintf(text[0], \"%d\", value[0]);\n            str_ptr[0] = text[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        res = strcmp(str_ptr[1], str_ptr[0]) <= 0;\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%.5f\", floats[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) <= 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = floats[1] <= floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = floats[1] <= (float)value[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%d\", value[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) <= 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = (float)value[1] <= floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = value[1] <= value[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), res);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x469180\nstatic void op_greater_equal(Program* program)\n{\n    int arg;\n    opcode_t type[2];\n    int value[2];\n    float* floats = (float*)&value;\n    char text[2][80];\n    char* str_ptr[2];\n    int res;\n\n    // NOTE: original code does not use loop\n    for (arg = 0; arg < 2; arg++) {\n        type[arg] = popShortStack(program->stack, &(program->stackPointer));\n        value[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, type[arg], value[arg]);\n        }\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        str_ptr[1] = interpretGetString(program, type[1], value[1]);\n\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            break;\n        case VALUE_TYPE_FLOAT:\n            sprintf(text[0], \"%.5f\", floats[0]);\n            str_ptr[0] = text[0];\n            break;\n        case VALUE_TYPE_INT:\n            sprintf(text[0], \"%d\", value[0]);\n            str_ptr[0] = text[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        res = strcmp(str_ptr[1], str_ptr[0]) >= 0;\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%.5f\", floats[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) >= 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = floats[1] >= floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = floats[1] >= (float)value[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%d\", value[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) >= 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = (float)value[1] >= floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = value[1] >= value[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), res);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4694EC\nstatic void op_less(Program* program)\n{\n    opcode_t opcodes[2];\n    int values[2];\n    float* floats = (float*)&values;\n    char text[2][80];\n    char* str_ptr[2];\n    int res;\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcodes[arg] = popShortStack(program->stack, &(program->stackPointer));\n        values[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcodes[arg], values[arg]);\n        }\n    }\n\n    switch (opcodes[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        str_ptr[1] = interpretGetString(program, opcodes[1], values[1]);\n\n        switch (opcodes[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            str_ptr[0] = interpretGetString(program, opcodes[0], values[0]);\n            break;\n        case VALUE_TYPE_FLOAT:\n            sprintf(text[0], \"%.5f\", floats[0]);\n            str_ptr[0] = text[0];\n            break;\n        case VALUE_TYPE_INT:\n            sprintf(text[0], \"%d\", values[0]);\n            str_ptr[0] = text[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        res = strcmp(str_ptr[1], str_ptr[0]) < 0;\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (opcodes[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%.5f\", floats[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, opcodes[0], values[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) < 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = floats[1] < floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = floats[1] < (float)values[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (opcodes[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%d\", values[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, opcodes[0], values[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) < 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = (float)values[1] < floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = values[1] < values[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), res);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x469858\nstatic void op_greater(Program* program)\n{\n    int arg;\n    opcode_t type[2];\n    int value[2];\n    float* floats = (float*)&value;\n    char text[2][80];\n    char* str_ptr[2];\n    int res;\n\n    for (arg = 0; arg < 2; arg++) {\n        type[arg] = popShortStack(program->stack, &(program->stackPointer));\n        value[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, type[arg], value[arg]);\n        }\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        str_ptr[1] = interpretGetString(program, type[1], value[1]);\n\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            break;\n        case VALUE_TYPE_FLOAT:\n            sprintf(text[0], \"%.5f\", floats[0]);\n            str_ptr[0] = text[0];\n            break;\n        case VALUE_TYPE_INT:\n            sprintf(text[0], \"%d\", value[0]);\n            str_ptr[0] = text[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n\n        res = strcmp(str_ptr[1], str_ptr[0]) > 0;\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%.5f\", floats[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) > 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = floats[1] > floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = floats[1] > (float)value[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            sprintf(text[1], \"%d\", value[1]);\n            str_ptr[1] = text[1];\n            str_ptr[0] = interpretGetString(program, type[0], value[0]);\n            res = strcmp(str_ptr[1], str_ptr[0]) > 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            res = (float)value[1] > floats[0];\n            break;\n        case VALUE_TYPE_INT:\n            res = value[1] > value[0];\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), res);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x469BC4\nstatic void op_add(Program* program)\n{\n    // TODO: Check everything, too many conditions, variables and allocations.\n    opcode_t opcodes[2];\n    int values[2];\n    float* floats = (float*)&values;\n    char* str_ptr[2];\n    char* t;\n    float resf;\n\n    // NOTE: original code does not use loop\n    for (int arg = 0; arg < 2; arg++) {\n        opcodes[arg] = popShortStack(program->stack, &(program->stackPointer));\n        values[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcodes[arg], values[arg]);\n        }\n    }\n\n    switch (opcodes[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        str_ptr[1] = interpretGetString(program, opcodes[1], values[1]);\n\n        switch (opcodes[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            t = interpretGetString(program, opcodes[0], values[0]);\n            str_ptr[0] = (char*)mymalloc(strlen(t) + 1, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1002\n            strcpy(str_ptr[0], t);\n            break;\n        case VALUE_TYPE_FLOAT:\n            str_ptr[0] = (char*)mymalloc(80, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1011\n            sprintf(str_ptr[0], \"%.5f\", floats[0]);\n            break;\n        case VALUE_TYPE_INT:\n            str_ptr[0] = (char*)mymalloc(80, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1007\n            sprintf(str_ptr[0], \"%d\", values[0]);\n            break;\n        }\n\n        t = (char*)mymalloc(strlen(str_ptr[1]) + strlen(str_ptr[0]) + 1, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1015\n        strcpy(t, str_ptr[1]);\n        strcat(t, str_ptr[0]);\n\n        pushLongStack(program->stack, &(program->stackPointer), interpretAddString(program, t));\n        interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n\n        myfree(str_ptr[0], __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1019\n        myfree(t, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1020\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (opcodes[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            str_ptr[0] = interpretGetString(program, opcodes[0], values[0]);\n            t = (char*)mymalloc(strlen(str_ptr[0]) + 80, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1039\n            sprintf(t, \"%.5f\", floats[1]);\n            strcat(t, str_ptr[0]);\n\n            pushLongStack(program->stack, &(program->stackPointer), interpretAddString(program, t));\n            interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n\n            myfree(t, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1044\n            break;\n        case VALUE_TYPE_FLOAT:\n            resf = floats[1] + floats[0];\n            pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf);\n            interpretPushShort(program, VALUE_TYPE_FLOAT);\n            break;\n        case VALUE_TYPE_INT:\n            resf = floats[1] + (float)values[0];\n            pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf);\n            interpretPushShort(program, VALUE_TYPE_FLOAT);\n            break;\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (opcodes[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            str_ptr[0] = interpretGetString(program, opcodes[0], values[0]);\n            t = (char*)mymalloc(strlen(str_ptr[0]) + 80, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1070\n            sprintf(t, \"%d\", values[1]);\n            strcat(t, str_ptr[0]);\n\n            pushLongStack(program->stack, &(program->stackPointer), interpretAddString(program, t));\n            interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n\n            myfree(t, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 1075\n            break;\n        case VALUE_TYPE_FLOAT:\n            resf = (float)values[1] + floats[0];\n            pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf);\n            interpretPushShort(program, VALUE_TYPE_FLOAT);\n            break;\n        case VALUE_TYPE_INT:\n            if ((values[0] <= 0 || (INT_MAX - values[0]) > values[1])\n                && (values[0] >= 0 || (INT_MIN - values[0]) <= values[1])) {\n                pushLongStack(program->stack, &(program->stackPointer), values[1] + values[0]);\n                interpretPushShort(program, VALUE_TYPE_INT);\n            } else {\n                resf = (float)values[1] + (float)values[0];\n                pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf);\n                interpretPushShort(program, VALUE_TYPE_FLOAT);\n            }\n            break;\n        }\n        break;\n    }\n}\n\n// 0x46A1D8\nstatic void op_sub(Program* program)\n{\n    opcode_t type[2];\n    int value[2];\n    float* floats = (float*)&value;\n    float resf;\n\n    for (int arg = 0; arg < 2; arg++) {\n        type[arg] = popShortStack(program->stack, &(program->stackPointer));\n        value[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, type[arg], value[arg]);\n        }\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            resf = floats[1] - floats[0];\n            break;\n        default:\n            resf = floats[1] - value[0];\n            break;\n        }\n\n        pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf);\n        interpretPushShort(program, VALUE_TYPE_FLOAT);\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            resf = value[1] - floats[0];\n\n            pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf);\n            interpretPushShort(program, VALUE_TYPE_FLOAT);\n            break;\n        default:\n            pushLongStack(program->stack, &(program->stackPointer), value[1] - value[0]);\n            interpretPushShort(program, VALUE_TYPE_INT);\n            break;\n        }\n        break;\n    }\n}\n\n// 0x46A300\nstatic void op_mul(Program* program)\n{\n    int arg;\n    opcode_t type[2];\n    int value[2];\n    float* floats = (float*)&value;\n    float resf;\n\n    for (arg = 0; arg < 2; arg++) {\n        type[arg] = popShortStack(program->stack, &(program->stackPointer));\n        value[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, type[arg], value[arg]);\n        }\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            resf = floats[1] * floats[0];\n            break;\n        default:\n            resf = floats[1] * value[0];\n            break;\n        }\n\n        pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf);\n        interpretPushShort(program, VALUE_TYPE_FLOAT);\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            resf = value[1] * floats[0];\n\n            pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf);\n            interpretPushShort(program, VALUE_TYPE_FLOAT);\n            break;\n        default:\n            pushLongStack(program->stack, &(program->stackPointer), value[0] * value[1]);\n            interpretPushShort(program, VALUE_TYPE_INT);\n            break;\n        }\n        break;\n    }\n}\n\n// 0x46A424\nstatic void op_div(Program* program)\n{\n    // TODO: Check entire function, probably errors due to casts.\n    opcode_t type[2];\n    int value[2];\n    float* float_value = (float*)&value;\n    float divisor;\n    float result;\n\n    type[0] = popShortStack(program->stack, &(program->stackPointer));\n    value[0] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[0], value[0]);\n    }\n\n    type[1] = popShortStack(program->stack, &(program->stackPointer));\n    value[1] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[1], value[1]);\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_FLOAT:\n        if (type[0] == VALUE_TYPE_FLOAT) {\n            divisor = float_value[0];\n        } else {\n            divisor = (float)value[0];\n        }\n\n        // NOTE: Original code is slightly different, it performs bitwise and\n        // with 0x7FFFFFFF in order to determine if it's zero. Probably some\n        // kind of compiler optimization.\n        if (divisor == 0.0) {\n            interpretError(\"Division (DIV) by zero\");\n        }\n\n        result = float_value[1] / divisor;\n        pushLongStack(program->stack, &(program->stackPointer), *(int*)&result);\n        interpretPushShort(program, VALUE_TYPE_FLOAT);\n        break;\n    case VALUE_TYPE_INT:\n        if (type[0] == VALUE_TYPE_FLOAT) {\n            divisor = float_value[0];\n\n            // NOTE: Same as above.\n            if (divisor == 0.0) {\n                interpretError(\"Division (DIV) by zero\");\n            }\n\n            result = (float)value[1] / divisor;\n            pushLongStack(program->stack, &(program->stackPointer), *(int*)&result);\n            interpretPushShort(program, VALUE_TYPE_FLOAT);\n        } else {\n            if (value[0] == 0) {\n                interpretError(\"Division (DIV) by zero\");\n            }\n\n            pushLongStack(program->stack, &(program->stackPointer), value[1] / value[0]);\n            interpretPushShort(program, VALUE_TYPE_INT);\n        }\n        break;\n    }\n}\n\n// 0x46A5B8\nstatic void op_mod(Program* program)\n{\n    opcode_t type[2];\n    int value[2];\n\n    type[0] = popShortStack(program->stack, &(program->stackPointer));\n    value[0] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[0], value[0]);\n    }\n\n    type[1] = popShortStack(program->stack, &(program->stackPointer));\n    value[1] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[1], value[1]);\n    }\n\n    if (type[1] == VALUE_TYPE_FLOAT) {\n        interpretError(\"Trying to MOD a float\");\n    }\n\n    if (type[1] != VALUE_TYPE_INT) {\n        return;\n    }\n\n    if (type[0] == VALUE_TYPE_FLOAT) {\n        interpretError(\"Trying to MOD with a float\");\n    }\n\n    if (value[0] == 0) {\n        interpretError(\"Division (MOD) by zero\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), value[1] % value[0]);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46A6B4\nstatic void op_and(Program* program)\n{\n    opcode_t type[2];\n    int value[2];\n    int result;\n\n    type[0] = popShortStack(program->stack, &(program->stackPointer));\n    value[0] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[0], value[0]);\n    }\n\n    type[1] = popShortStack(program->stack, &(program->stackPointer));\n    value[1] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[1], value[1]);\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            result = 1;\n            break;\n        case VALUE_TYPE_FLOAT:\n            result = (value[0] & 0x7FFFFFFF) != 0;\n            break;\n        case VALUE_TYPE_INT:\n            result = value[0] != 0;\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            result = value[1] != 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            result = (value[1] & 0x7FFFFFFF) && (value[0] & 0x7FFFFFFF);\n            break;\n        case VALUE_TYPE_INT:\n            result = (value[1] & 0x7FFFFFFF) && (value[0] != 0);\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            result = value[1] != 0;\n            break;\n        case VALUE_TYPE_FLOAT:\n            result = (value[1] != 0) && (value[0] & 0x7FFFFFFF);\n            break;\n        case VALUE_TYPE_INT:\n            result = (value[1] != 0) && (value[0] != 0);\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46A8D8\nstatic void op_or(Program* program)\n{\n    opcode_t type[2];\n    int value[2];\n    int result;\n\n    type[0] = popShortStack(program->stack, &(program->stackPointer));\n    value[0] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[0], value[0]);\n    }\n\n    type[1] = popShortStack(program->stack, &(program->stackPointer));\n    value[1] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[1], value[1]);\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_STRING:\n    case VALUE_TYPE_DYNAMIC_STRING:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n        case VALUE_TYPE_FLOAT:\n        case VALUE_TYPE_INT:\n            result = 1;\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            result = 1;\n            break;\n        case VALUE_TYPE_FLOAT:\n            result = (value[1] & 0x7FFFFFFF) || (value[0] & 0x7FFFFFFF);\n            break;\n        case VALUE_TYPE_INT:\n            result = (value[1] & 0x7FFFFFFF) || (value[0] != 0);\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_STRING:\n        case VALUE_TYPE_DYNAMIC_STRING:\n            result = 1;\n            break;\n        case VALUE_TYPE_FLOAT:\n            result = (value[1] != 0) || (value[0] & 0x7FFFFFFF);\n            break;\n        case VALUE_TYPE_INT:\n            result = (value[1] != 0) || (value[0] != 0);\n            break;\n        default:\n            assert(false && \"Should be unreachable\");\n        }\n        break;\n    default:\n        assert(false && \"Should be unreachable\");\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46AACC\nstatic void op_not(Program* program)\n{\n    opcode_t type;\n    int value;\n\n    type = interpretPopShort(program);\n    value = interpretPopLong(program);\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    interpretPushLong(program, value == 0);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46AB2C\nstatic void op_negate(Program* program)\n{\n    opcode_t type;\n    int value;\n\n    type = popShortStack(program->stack, &(program->stackPointer));\n    value = popLongStack(program->stack, &(program->stackPointer));\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), -value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46AB84\nstatic void op_bwnot(Program* program)\n{\n    opcode_t type;\n    int value;\n\n    type = interpretPopShort(program);\n    value = interpretPopLong(program);\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    interpretPushLong(program, ~value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// floor\n// 0x46ABDC\nstatic void op_floor(Program* program)\n{\n    opcode_t type = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, data);\n    }\n\n    if (type == VALUE_TYPE_STRING) {\n        interpretError(\"Invalid arg given to floor()\");\n    } else if (type == VALUE_TYPE_FLOAT) {\n        type = VALUE_TYPE_INT;\n        data = (int)(*((float*)&data));\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), data);\n    interpretPushShort(program, type);\n}\n\n// 0x46AC78\nstatic void op_bwand(Program* program)\n{\n    opcode_t type[2];\n    int value[2];\n    int result;\n\n    type[0] = popShortStack(program->stack, &(program->stackPointer));\n    value[0] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[0], value[0]);\n    }\n\n    type[1] = popShortStack(program->stack, &(program->stackPointer));\n    value[1] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[1], value[1]);\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            result = (int)(float)value[1] & (int)(float)value[0];\n            break;\n        default:\n            result = (int)(float)value[1] & value[0];\n            break;\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            result = value[1] & (int)(float)value[0];\n            break;\n        default:\n            result = value[1] & value[0];\n            break;\n        }\n        break;\n    default:\n        return;\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46ADA4\nstatic void op_bwor(Program* program)\n{\n    opcode_t type[2];\n    int value[2];\n    int result;\n\n    type[0] = popShortStack(program->stack, &(program->stackPointer));\n    value[0] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[0], value[0]);\n    }\n\n    type[1] = popShortStack(program->stack, &(program->stackPointer));\n    value[1] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[1], value[1]);\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            result = (int)(float)value[1] | (int)(float)value[0];\n            break;\n        default:\n            result = (int)(float)value[1] | value[0];\n            break;\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            result = value[1] | (int)(float)value[0];\n            break;\n        default:\n            result = value[1] | value[0];\n            break;\n        }\n        break;\n    default:\n        return;\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46AED0\nstatic void op_bwxor(Program* program)\n{\n    opcode_t type[2];\n    int value[2];\n    int result;\n\n    type[0] = popShortStack(program->stack, &(program->stackPointer));\n    value[0] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[0], value[0]);\n    }\n\n    type[1] = popShortStack(program->stack, &(program->stackPointer));\n    value[1] = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type[1], value[1]);\n    }\n\n    switch (type[1]) {\n    case VALUE_TYPE_FLOAT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            result = (int)(float)value[1] ^ (int)(float)value[0];\n            break;\n        default:\n            result = (int)(float)value[1] ^ value[0];\n            break;\n        }\n        break;\n    case VALUE_TYPE_INT:\n        switch (type[0]) {\n        case VALUE_TYPE_FLOAT:\n            result = value[1] ^ (int)(float)value[0];\n            break;\n        default:\n            result = value[1] ^ value[0];\n            break;\n        }\n        break;\n    default:\n        return;\n    }\n\n    pushLongStack(program->stack, &(program->stackPointer), result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46AFFC\nstatic void op_swapa(Program* program)\n{\n    opcode_t v1;\n    int v5;\n    opcode_t a2;\n    int v10;\n\n    v1 = rPopShort(program);\n    v5 = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    a2 = rPopShort(program);\n    v10 = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    pushLongStack(program->returnStack, &(program->returnStackPointer), v5);\n    rPushShort(program, v1);\n\n    pushLongStack(program->returnStack, &(program->returnStackPointer), v10);\n    rPushShort(program, a2);\n}\n\n// 0x46B070\nstatic void op_critical_done(Program* program)\n{\n    program->flags &= ~PROGRAM_FLAG_CRITICAL_SECTION;\n}\n\n// 0x46B078\nstatic void op_critical_start(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_CRITICAL_SECTION;\n}\n\n// 0x46B080\nstatic void op_jmp(Program* program)\n{\n    opcode_t type;\n    int value;\n    char err[260];\n\n    type = popShortStack(program->stack, &(program->stackPointer));\n    value = popLongStack(program->stack, &(program->stackPointer));\n\n    // NOTE: comparing shorts (0x46B0B1)\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    // NOTE: comparing ints (0x46B0D3)\n    if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        sprintf(err, \"Invalid type given to jmp, %x\", value);\n        interpretError(err);\n    }\n\n    program->instructionPointer = value;\n}\n\n// 0x46B108\nstatic void op_call(Program* program)\n{\n    opcode_t type;\n    int data;\n    opcode_t argumentType;\n    opcode_t argumentValue;\n    unsigned char* procedurePtr;\n    int procedureFlags;\n    char* procedureIdentifier;\n    Program* externalProgram;\n    int externalProcedureAddress;\n    int externalProcedureArgumentCount;\n    Program tempProgram;\n    char err[256];\n\n    type = popShortStack(program->stack, &(program->stackPointer));\n    data = popLongStack(program->stack, &(program->stackPointer));\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, data);\n    }\n\n    if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid address given to call\");\n    }\n\n    procedurePtr = program->procedures + 4 + sizeof(Procedure) * data;\n\n    procedureFlags = fetchLong(procedurePtr, 4);\n    if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) {\n        procedureIdentifier = interpretGetName(program, fetchLong(procedurePtr, 0));\n        externalProgram = exportFindProcedure(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount);\n        if (externalProgram == NULL) {\n            interpretError(\"External procedure %s not found\", procedureIdentifier);\n        }\n\n        type = popShortStack(program->stack, &(program->stackPointer));\n        data = popLongStack(program->stack, &(program->stackPointer));\n        if (type == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, type, data);\n        }\n\n        if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_INT || data != externalProcedureArgumentCount) {\n            sprintf(err, \"Wrong number of arguments to external procedure %s.Expecting %d, got %d.\", procedureIdentifier, externalProcedureArgumentCount, data);\n        }\n\n        rPushLong(externalProgram, program->instructionPointer);\n        rPushShort(externalProgram, VALUE_TYPE_INT);\n\n        rPushLong(externalProgram, program->flags);\n        rPushShort(externalProgram, VALUE_TYPE_INT);\n\n        rPushLong(externalProgram, (int)program->checkWaitFunc);\n        rPushShort(externalProgram, VALUE_TYPE_INT);\n\n        rPushLong(externalProgram, (int)program);\n        rPushShort(externalProgram, VALUE_TYPE_INT);\n\n        rPushLong(externalProgram, 36);\n        rPushShort(externalProgram, VALUE_TYPE_INT);\n\n        interpretPushLong(externalProgram, externalProgram->flags);\n        interpretPushShort(externalProgram, VALUE_TYPE_INT);\n\n        interpretPushLong(externalProgram, (int)externalProgram->checkWaitFunc);\n        interpretPushShort(externalProgram, VALUE_TYPE_INT);\n\n        interpretPushLong(externalProgram, externalProgram->windowId);\n        interpretPushShort(externalProgram, VALUE_TYPE_INT);\n\n        externalProgram->windowId = program->windowId;\n\n        tempProgram.stackPointer = 0;\n        tempProgram.returnStackPointer = 0;\n\n        while (data-- != 0) {\n            argumentType = interpretPopShort(program);\n            argumentValue = interpretPopLong(program);\n            if (argumentType == VALUE_TYPE_DYNAMIC_STRING) {\n                interpretDecStringRef(program, argumentType, argumentValue);\n            }\n            interpretPushLong(&tempProgram, argumentValue);\n            interpretPushShort(&tempProgram, argumentType);\n        }\n\n        while (data++ < externalProcedureArgumentCount) {\n            argumentType = interpretPopShort(&tempProgram);\n            argumentValue = interpretPopLong(&tempProgram);\n            if (argumentType == VALUE_TYPE_DYNAMIC_STRING) {\n                interpretDecStringRef(&tempProgram, argumentType, argumentValue);\n            }\n            interpretPushLong(externalProgram, argumentValue);\n            interpretPushShort(externalProgram, argumentType);\n        }\n\n        interpretPushLong(externalProgram, externalProcedureArgumentCount);\n        interpretPushShort(externalProgram, VALUE_TYPE_INT);\n\n        program->flags |= PROGRAM_FLAG_0x20;\n        externalProgram->flags = 0;\n        externalProgram->instructionPointer = externalProcedureAddress;\n\n        if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0 || (program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0) {\n            // NOTE: Uninline.\n            op_critical_start(externalProgram);\n        }\n    } else {\n        program->instructionPointer = fetchLong(procedurePtr, 16);\n        if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0) {\n            // NOTE: Uninline.\n            op_critical_start(program);\n        }\n    }\n}\n\n// 0x46B590\nstatic void op_pop_flags(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = popShortStack(program->stack, &(program->stackPointer));\n        data[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    program->windowId = data[0];\n    program->checkWaitFunc = (InterpretCheckWaitFunc*)data[1];\n    program->flags = data[2] & 0xFFFF;\n}\n\n// pop stack 2 -> set program address\n// 0x46B63C\nstatic void op_pop_return(Program* program)\n{\n    rPopShort(program);\n    program->instructionPointer = popLongStack(program->returnStack, &(program->returnStackPointer));\n}\n\n// 0x46B658\nstatic void op_pop_exit(Program* program)\n{\n    rPopShort(program);\n    program->instructionPointer = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    program->flags |= PROGRAM_FLAG_0x40;\n}\n\n// 0x46B67C\nstatic void op_pop_flags_return(Program* program)\n{\n    op_pop_flags(program);\n    rPopShort(program);\n    program->instructionPointer = rPopLong(program);\n}\n\n// 0x46B698\nstatic void op_pop_flags_exit(Program* program)\n{\n    op_pop_flags(program);\n    rPopShort(program);\n    program->instructionPointer = rPopLong(program);\n    program->flags |= PROGRAM_FLAG_0x40;\n}\n\n// 0x46B6BC\nstatic void op_pop_flags_return_val_exit(Program* program)\n{\n    opcode_t type;\n    int value;\n\n    type = interpretPopShort(program);\n    value = interpretPopLong(program);\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    op_pop_flags(program);\n    rPopShort(program);\n    program->instructionPointer = rPopLong(program);\n    program->flags |= PROGRAM_FLAG_0x40;\n    interpretPushLong(program, value);\n    interpretPushShort(program, type);\n}\n\n// 0x46B73C\nstatic void op_pop_flags_return_val_exit_extern(Program* program)\n{\n    opcode_t type;\n    int value;\n    Program* v1;\n\n    type = popShortStack(program->stack, &(program->stackPointer));\n    value = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    op_pop_flags(program);\n\n    rPopShort(program);\n    v1 = (Program*)popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    v1->checkWaitFunc = (InterpretCheckWaitFunc*)popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    v1->flags = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    program->instructionPointer = rPopLong(program);\n\n    program->flags |= PROGRAM_FLAG_0x40;\n\n    pushLongStack(program->stack, &(program->stackPointer), value);\n    interpretPushShort(program, type);\n}\n\n// 0x46B808\nstatic void op_pop_flags_return_extern(Program* program)\n{\n    Program* v1;\n\n    op_pop_flags(program);\n\n    rPopShort(program);\n    v1 = (Program*)popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    v1->checkWaitFunc = (InterpretCheckWaitFunc*)popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    v1->flags = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    program->instructionPointer = rPopLong(program);\n}\n\n// 0x46B86C\nstatic void op_pop_flags_exit_extern(Program* program)\n{\n    Program* v1;\n\n    op_pop_flags(program);\n\n    rPopShort(program);\n    v1 = (Program*)popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    v1->checkWaitFunc = (InterpretCheckWaitFunc*)popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    v1->flags = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    program->instructionPointer = rPopLong(program);\n\n    program->flags |= 0x40;\n}\n\n// pop value from stack 1 and push it to script popped from stack 2\n// 0x46B8D8\nstatic void op_pop_flags_return_val_extern(Program* program)\n{\n    opcode_t type;\n    int value;\n    Program* v10;\n    char* str;\n\n    type = popShortStack(program->stack, &(program->stackPointer));\n    value = popLongStack(program->stack, &(program->stackPointer));\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    op_pop_flags(program);\n\n    rPopShort(program);\n    v10 = (Program*)popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    v10->checkWaitFunc = (InterpretCheckWaitFunc*)popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    rPopShort(program);\n    v10->flags = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    if ((type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        str = interpretGetString(program, type, value);\n        pushLongStack(v10->stack, &(v10->stackPointer), interpretAddString(v10, str));\n        type = VALUE_TYPE_DYNAMIC_STRING;\n    } else {\n        pushLongStack(v10->stack, &(v10->stackPointer), value);\n    }\n\n    interpretPushShort(v10, type);\n\n    if (v10->flags & 0x80) {\n        program->flags &= ~0x80;\n    }\n\n    rPopShort(program);\n    program->instructionPointer = rPopLong(program);\n\n    rPopShort(v10);\n    v10->instructionPointer = rPopLong(program);\n}\n\n// 0x46BA10\nstatic void op_pop_address(Program* program)\n{\n    rPopShort(program);\n    rPopLong(program);\n}\n\n// 0x46BA2C\nstatic void op_a_to_d(Program* program)\n{\n    opcode_t opcode = rPopShort(program);\n    int data = popLongStack(program->returnStack, &(program->returnStackPointer));\n\n    pushLongStack(program->stack, &(program->stackPointer), data);\n    interpretPushShort(program, opcode);\n}\n\n// 0x46BA68\nstatic void op_d_to_a(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    pushLongStack(program->returnStack, &(program->returnStackPointer), data);\n    rPushShort(program, opcode);\n}\n\n// 0x46BAC0\nstatic void op_exit_prog(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_EXITED;\n}\n\n// 0x46BAC8\nstatic void op_stop_prog(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_STOPPED;\n}\n\n// 0x46BAD0\nstatic void op_fetch_global(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    // TODO: Check.\n    int addr = program->basePointer + 6 * data;\n    int v8 = fetchLong(program->stack, addr);\n    opcode_t varType = fetchWord(program->stack, addr + 4);\n\n    interpretPushLong(program, v8);\n    // TODO: Check.\n    interpretPushShort(program, varType);\n}\n\n// 0x46BB5C\nstatic void op_store_global(Program* program)\n{\n    opcode_t type[2];\n    int value[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        type[arg] = popShortStack(program->stack, &(program->stackPointer));\n        value[arg] = popLongStack(program->stack, &(program->stackPointer));\n\n        if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, type[arg], value[arg]);\n        }\n    }\n\n    int var_address = program->basePointer + 6 * value[0];\n\n    opcode_t var_type = fetchWord(program->stack, var_address + 4);\n    int var_value = fetchLong(program->stack, var_address);\n\n    if (var_type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, var_type, var_value);\n    }\n\n    // TODO: Check offsets.\n    storeLong(value[1], program->stack, var_address);\n\n    // TODO: Check endianness.\n    storeWord(type[1], program->stack, var_address + 4);\n\n    if (type[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        // NOTE: Uninline.\n        _interpretIncStringRef(program, VALUE_TYPE_DYNAMIC_STRING, value[1]);\n    }\n}\n\n// 0x46BCAC\nstatic void op_swap(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loops.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    for (int arg = 0; arg < 2; arg++) {\n        interpretPushLong(program, data[arg]);\n        interpretPushShort(program, opcode[arg]);\n    }\n}\n\n// fetch_proc_address\n// 0x46BD60\nstatic void op_fetch_proc_address(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if (opcode != VALUE_TYPE_INT) {\n        char err[256];\n        sprintf(err, \"Invalid type given to fetch_proc_address, %x\", opcode);\n        interpretError(err);\n    }\n\n    int procedureIndex = data;\n\n    int address = fetchLong(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 16);\n    pushLongStack(program->stack, &(program->stackPointer), address);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// Pops value from stack and throws it away.\n//\n// 0x46BE10\nstatic void op_pop(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n}\n\n// 0x46BE4C\nstatic void op_dup(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    interpretPushLong(program, data);\n    interpretPushShort(program, opcode);\n\n    interpretPushLong(program, data);\n    interpretPushShort(program, opcode);\n}\n\n// 0x46BEC8\nstatic void op_store_external(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: Original code does not use loop.\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    const char* identifier = interpretGetName(program, data[0]);\n\n    if (exportStoreVariable(program, identifier, opcode[1], data[1])) {\n        char err[256];\n        sprintf(err, \"External variable %s does not exist\\n\", identifier);\n        interpretError(err);\n    }\n}\n\n// 0x46BF90\nstatic void op_fetch_external(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    const char* identifier = interpretGetName(program, data);\n\n    opcode_t variableOpcode;\n    int variableData;\n    if (exportFetchVariable(program, identifier, &variableOpcode, &variableData) != 0) {\n        char err[256];\n        sprintf(err, \"External variable %s does not exist\\n\", identifier);\n        interpretError(err);\n    }\n\n    interpretPushLong(program, variableData);\n    interpretPushShort(program, variableOpcode);\n}\n\n// 0x46C044\nstatic void op_export_proc(Program* program)\n{\n    opcode_t type;\n    int value;\n    int proc_index;\n    unsigned char* proc_ptr;\n    char* v9;\n    int v10;\n    char err[256];\n\n    type = interpretPopShort(program);\n    value = interpretPopLong(program);\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    proc_index = value;\n\n    type = interpretPopShort(program);\n    value = interpretPopLong(program);\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    proc_ptr = program->procedures + 4 + sizeof(Procedure) * proc_index;\n\n    v9 = (char*)(program->identifiers + fetchLong(proc_ptr, 0));\n    v10 = fetchLong(proc_ptr, 16);\n\n    if (exportExportProcedure(program, v9, v10, value) != 0) {\n        sprintf(err, \"Error exporting procedure %s\", v9);\n        interpretError(err);\n    }\n}\n\n// 0x46C120\nstatic void op_export_var(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if (exportExportVariable(program, interpretGetName(program, data))) {\n        char err[256];\n        sprintf(err, \"External variable %s already exists\", interpretGetName(program, data));\n        interpretError(err);\n    }\n}\n\n// 0x46C1A0\nstatic void op_exit(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_EXITED;\n\n    Program* parent = program->parent;\n    if (parent != NULL) {\n        if ((parent->flags & PROGRAM_FLAG_0x0100) != 0) {\n            parent->flags &= ~PROGRAM_FLAG_0x0100;\n        }\n    }\n\n    if (!program->exited) {\n        removeProgramReferences(program);\n        program->exited = true;\n    }\n}\n\n// 0x46C1EC\nstatic void op_detach(Program* program)\n{\n    Program* parent = program->parent;\n    if (parent == NULL) {\n        return;\n    }\n\n    parent->flags &= ~PROGRAM_FLAG_0x20;\n    parent->flags &= ~PROGRAM_FLAG_0x0100;\n\n    if (parent->child == program) {\n        parent->child = NULL;\n    }\n}\n\n// callstart\n// 0x46C218\nstatic void op_callstart(Program* program)\n{\n    opcode_t type;\n    int value;\n    char* name;\n    char err[260];\n\n    if (program->child) {\n        interpretError(\"Error, already have a child process\\n\");\n    }\n\n    type = interpretPopShort(program);\n    value = interpretPopLong(program);\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to callstart\");\n    }\n\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    name = interpretGetString(program, type, value);\n\n    // NOTE: Uninline.\n    program->child = runScript(name);\n    if (program->child == NULL) {\n        sprintf(err, \"Error spawning child %s\", interpretGetString(program, type, value));\n        interpretError(err);\n    }\n\n    program->child->parent = program;\n    program->child->windowId = program->windowId;\n}\n\n// spawn\n// 0x46C344\nstatic void op_spawn(Program* program)\n{\n    opcode_t type;\n    int value;\n    char* name;\n    char err[256];\n\n    if (program->child) {\n        interpretError(\"Error, already have a child process\\n\");\n    }\n\n    type = interpretPopShort(program);\n    value = interpretPopLong(program);\n\n    if (type == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, type, value);\n    }\n\n    if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Invalid type given to spawn\");\n    }\n\n    program->flags |= PROGRAM_FLAG_0x0100;\n\n    if ((type >> 8) & 8) {\n        name = (char*)program->dynamicStrings + 4 + value;\n    } else if ((type >> 8) & 16) {\n        name = (char*)program->staticStrings + 4 + value;\n    } else {\n        name = NULL;\n    }\n\n    // NOTE: Uninline.\n    program->child = runScript(name);\n    if (program->child == NULL) {\n        sprintf(err, \"Error spawning child %s\", interpretGetString(program, type, value));\n        interpretError(err);\n    }\n\n    program->child->parent = program;\n    program->child->windowId = program->windowId;\n\n    if ((program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0) {\n        program->child->flags |= PROGRAM_FLAG_CRITICAL_SECTION;\n        interpret(program->child, -1);\n    }\n}\n\n// fork\n// 0x46C490\nstatic Program* op_fork_helper(Program* program)\n{\n    opcode_t opcode = popShortStack(program->stack, &(program->stackPointer));\n    int data = popLongStack(program->stack, &(program->stackPointer));\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    char* name = interpretGetString(program, opcode, data);\n    Program* forked = runScript(name);\n\n    if (forked == NULL) {\n        char err[256];\n        sprintf(err, \"couldn't fork script '%s'\", interpretGetString(program, opcode, data));\n        interpretError(err);\n    }\n\n    forked->windowId = program->windowId;\n\n    return forked;\n}\n\n// NOTE: Uncollapsed 0x46C490 with different signature.\n//\n// 0x46C490\nstatic void op_fork(Program* program)\n{\n    op_fork_helper(program);\n}\n\n// 0x46C574\nstatic void op_exec(Program* program)\n{\n    Program* parent = program->parent;\n    Program* fork = op_fork_helper(program);\n\n    if (parent != NULL) {\n        fork->parent = parent;\n        parent->child = fork;\n    }\n\n    fork->child = NULL;\n\n    program->parent = NULL;\n    program->flags |= PROGRAM_FLAG_EXITED;\n\n    // probably inlining due to check for null\n    parent = program->parent;\n    if (parent != NULL) {\n        if ((parent->flags & PROGRAM_FLAG_0x0100) != 0) {\n            parent->flags &= ~PROGRAM_FLAG_0x0100;\n        }\n    }\n\n    purgeProgram(program);\n}\n\n// 0x46C5D8\nstatic void op_check_arg_count(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    // NOTE: original code does not use loop\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    int expectedArgumentCount = data[0];\n    int procedureIndex = data[1];\n\n    int actualArgumentCount = fetchLong(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 20);\n    if (actualArgumentCount != expectedArgumentCount) {\n        const char* identifier = interpretGetName(program, fetchLong(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 0));\n        char err[260];\n        sprintf(err, \"Wrong number of args to procedure %s\\n\", identifier);\n        interpretError(err);\n    }\n}\n\n// lookup_string_proc\n// 0x46C6B4\nstatic void op_lookup_string_proc(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"Wrong type given to lookup_string_proc\\n\");\n    }\n\n    const char* procedureNameToLookup = interpretGetString(program, opcode, data);\n\n    int procedureCount = fetchLong(program->procedures, 0);\n\n    // Skip procedure count (4 bytes) and main procedure, which cannot be\n    // looked up.\n    unsigned char* procedurePtr = program->procedures + 4 + sizeof(Procedure);\n\n    // Start with 1 since we've skipped main procedure, which is always at\n    // index 0.\n    for (int index = 1; index < procedureCount; index++) {\n        int offset = fetchLong(procedurePtr, 0);\n        const char* procedureName = interpretGetName(program, offset);\n        if (stricmp(procedureName, procedureNameToLookup) == 0) {\n            interpretPushLong(program, index);\n            interpretPushShort(program, VALUE_TYPE_INT);\n            return;\n        }\n\n        procedurePtr += sizeof(Procedure);\n    }\n\n    char err[260];\n    sprintf(err, \"Couldn't find string procedure %s\\n\", procedureNameToLookup);\n    interpretError(err);\n}\n\n// 0x46C7DC\nvoid initInterpreter()\n{\n    enabled = 1;\n\n    // NOTE: The original code has different sorting.\n    interpretAddFunc(OPCODE_NOOP, op_noop);\n    interpretAddFunc(OPCODE_PUSH, op_const);\n    interpretAddFunc(OPCODE_ENTER_CRITICAL_SECTION, op_critical_start);\n    interpretAddFunc(OPCODE_LEAVE_CRITICAL_SECTION, op_critical_done);\n    interpretAddFunc(OPCODE_JUMP, op_jmp);\n    interpretAddFunc(OPCODE_CALL, op_call);\n    interpretAddFunc(OPCODE_CALL_AT, op_call_at);\n    interpretAddFunc(OPCODE_CALL_WHEN, op_call_condition);\n    interpretAddFunc(OPCODE_CALLSTART, op_callstart);\n    interpretAddFunc(OPCODE_EXEC, op_exec);\n    interpretAddFunc(OPCODE_SPAWN, op_spawn);\n    interpretAddFunc(OPCODE_FORK, op_fork);\n    interpretAddFunc(OPCODE_A_TO_D, op_a_to_d);\n    interpretAddFunc(OPCODE_D_TO_A, op_d_to_a);\n    interpretAddFunc(OPCODE_EXIT, op_exit);\n    interpretAddFunc(OPCODE_DETACH, op_detach);\n    interpretAddFunc(OPCODE_EXIT_PROGRAM, op_exit_prog);\n    interpretAddFunc(OPCODE_STOP_PROGRAM, op_stop_prog);\n    interpretAddFunc(OPCODE_FETCH_GLOBAL, op_fetch_global);\n    interpretAddFunc(OPCODE_STORE_GLOBAL, op_store_global);\n    interpretAddFunc(OPCODE_FETCH_EXTERNAL, op_fetch_external);\n    interpretAddFunc(OPCODE_STORE_EXTERNAL, op_store_external);\n    interpretAddFunc(OPCODE_EXPORT_VARIABLE, op_export_var);\n    interpretAddFunc(OPCODE_EXPORT_PROCEDURE, op_export_proc);\n    interpretAddFunc(OPCODE_SWAP, op_swap);\n    interpretAddFunc(OPCODE_SWAPA, op_swapa);\n    interpretAddFunc(OPCODE_POP, op_pop);\n    interpretAddFunc(OPCODE_DUP, op_dup);\n    interpretAddFunc(OPCODE_POP_RETURN, op_pop_return);\n    interpretAddFunc(OPCODE_POP_EXIT, op_pop_exit);\n    interpretAddFunc(OPCODE_POP_ADDRESS, op_pop_address);\n    interpretAddFunc(OPCODE_POP_FLAGS, op_pop_flags);\n    interpretAddFunc(OPCODE_POP_FLAGS_RETURN, op_pop_flags_return);\n    interpretAddFunc(OPCODE_POP_FLAGS_EXIT, op_pop_flags_exit);\n    interpretAddFunc(OPCODE_POP_FLAGS_RETURN_EXTERN, op_pop_flags_return_extern);\n    interpretAddFunc(OPCODE_POP_FLAGS_EXIT_EXTERN, op_pop_flags_exit_extern);\n    interpretAddFunc(OPCODE_POP_FLAGS_RETURN_VAL_EXTERN, op_pop_flags_return_val_extern);\n    interpretAddFunc(OPCODE_POP_FLAGS_RETURN_VAL_EXIT, op_pop_flags_return_val_exit);\n    interpretAddFunc(OPCODE_POP_FLAGS_RETURN_VAL_EXIT_EXTERN, op_pop_flags_return_val_exit_extern);\n    interpretAddFunc(OPCODE_CHECK_PROCEDURE_ARGUMENT_COUNT, op_check_arg_count);\n    interpretAddFunc(OPCODE_LOOKUP_PROCEDURE_BY_NAME, op_lookup_string_proc);\n    interpretAddFunc(OPCODE_POP_BASE, op_pop_base);\n    interpretAddFunc(OPCODE_POP_TO_BASE, op_pop_to_base);\n    interpretAddFunc(OPCODE_PUSH_BASE, op_push_base);\n    interpretAddFunc(OPCODE_SET_GLOBAL, op_set_global);\n    interpretAddFunc(OPCODE_FETCH_PROCEDURE_ADDRESS, op_fetch_proc_address);\n    interpretAddFunc(OPCODE_DUMP, op_dump);\n    interpretAddFunc(OPCODE_IF, op_if);\n    interpretAddFunc(OPCODE_WHILE, op_while);\n    interpretAddFunc(OPCODE_STORE, op_store);\n    interpretAddFunc(OPCODE_FETCH, op_fetch);\n    interpretAddFunc(OPCODE_EQUAL, op_equal);\n    interpretAddFunc(OPCODE_NOT_EQUAL, op_not_equal);\n    interpretAddFunc(OPCODE_LESS_THAN_EQUAL, op_less_equal);\n    interpretAddFunc(OPCODE_GREATER_THAN_EQUAL, op_greater_equal);\n    interpretAddFunc(OPCODE_LESS_THAN, op_less);\n    interpretAddFunc(OPCODE_GREATER_THAN, op_greater);\n    interpretAddFunc(OPCODE_ADD, op_add);\n    interpretAddFunc(OPCODE_SUB, op_sub);\n    interpretAddFunc(OPCODE_MUL, op_mul);\n    interpretAddFunc(OPCODE_DIV, op_div);\n    interpretAddFunc(OPCODE_MOD, op_mod);\n    interpretAddFunc(OPCODE_AND, op_and);\n    interpretAddFunc(OPCODE_OR, op_or);\n    interpretAddFunc(OPCODE_BITWISE_AND, op_bwand);\n    interpretAddFunc(OPCODE_BITWISE_OR, op_bwor);\n    interpretAddFunc(OPCODE_BITWISE_XOR, op_bwxor);\n    interpretAddFunc(OPCODE_BITWISE_NOT, op_bwnot);\n    interpretAddFunc(OPCODE_FLOOR, op_floor);\n    interpretAddFunc(OPCODE_NOT, op_not);\n    interpretAddFunc(OPCODE_NEGATE, op_negate);\n    interpretAddFunc(OPCODE_WAIT, op_wait);\n    interpretAddFunc(OPCODE_CANCEL, op_cancel);\n    interpretAddFunc(OPCODE_CANCEL_ALL, op_cancelall);\n    interpretAddFunc(OPCODE_START_CRITICAL, op_critical_start);\n    interpretAddFunc(OPCODE_END_CRITICAL, op_critical_done);\n\n    initIntlib();\n    initExport();\n}\n\n// 0x46CC68\nvoid interpretClose()\n{\n    exportClose();\n    intlibClose();\n}\n\n// NOTE: Unused.\n//\n// 0x46CC74\nvoid interpretEnableInterpreter(int value)\n{\n    enabled = value;\n\n    if (value) {\n        // NOTE: Uninline.\n        interpretResumeEvents();\n    } else {\n        // NOTE: Uninline.\n        interpretSuspendEvents();\n    }\n}\n\n// 0x46CCA4\nvoid interpret(Program* program, int a2)\n{\n    // 0x59E798\n    static int busy;\n\n    char err[260];\n\n    Program* oldCurrentProgram = currentProgram;\n\n    if (!enabled) {\n        return;\n    }\n\n    if (busy) {\n        return;\n    }\n\n    if (program->exited || (program->flags & PROGRAM_FLAG_0x20) != 0 || (program->flags & PROGRAM_FLAG_0x0100) != 0) {\n        return;\n    }\n\n    if (program->field_78 == -1) {\n        program->field_78 = 1000 * timerFunc() / timerTick;\n    }\n\n    currentProgram = program;\n\n    if (setjmp(program->env)) {\n        currentProgram = oldCurrentProgram;\n        program->flags |= PROGRAM_FLAG_EXITED | PROGRAM_FLAG_0x04;\n        return;\n    }\n\n    if ((program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0 && a2 < 3) {\n        a2 = 3;\n    }\n\n    while ((program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0 || --a2 != -1) {\n        if ((program->flags & (PROGRAM_FLAG_EXITED | PROGRAM_FLAG_0x04 | PROGRAM_FLAG_STOPPED | PROGRAM_FLAG_0x20 | PROGRAM_FLAG_0x40 | PROGRAM_FLAG_0x0100)) != 0) {\n            break;\n        }\n\n        if (program->exited) {\n            break;\n        }\n\n        if ((program->flags & PROGRAM_IS_WAITING) != 0) {\n            busy = 1;\n\n            if (program->checkWaitFunc != NULL) {\n                if (!program->checkWaitFunc(program)) {\n                    busy = 0;\n                    continue;\n                }\n            }\n\n            busy = 0;\n            program->checkWaitFunc = NULL;\n            program->flags &= ~PROGRAM_IS_WAITING;\n        }\n\n        // NOTE: Uninline.\n        opcode_t opcode = getOp(program);\n\n        // TODO: Replace with field_82 and field_80?\n        program->flags &= 0xFFFF;\n        program->flags |= (opcode << 16);\n\n        if (!((opcode >> 8) & 0x80)) {\n            sprintf(err, \"Bad opcode %x %c %d.\", opcode, opcode, opcode);\n            interpretError(err);\n        }\n\n        unsigned int opcodeIndex = opcode & 0x3FF;\n        OpcodeHandler* handler = opTable[opcodeIndex];\n        if (handler == NULL) {\n            sprintf(err, \"Undefined opcode %x.\", opcode);\n            interpretError(err);\n        }\n\n        handler(program);\n    }\n\n    if ((program->flags & PROGRAM_FLAG_EXITED) != 0) {\n        if (program->parent != NULL) {\n            if (program->parent->flags & PROGRAM_FLAG_0x20) {\n                program->parent->flags &= ~PROGRAM_FLAG_0x20;\n                program->parent->child = NULL;\n                program->parent = NULL;\n            }\n        }\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x40;\n    currentProgram = oldCurrentProgram;\n\n    checkProgramStrings(program);\n}\n\n// Prepares program stacks for executing proc at [address].\n//\n// 0x46CED0\nstatic void setupCallWithReturnVal(Program* program, int address, int returnAddress)\n{\n    // Save current instruction pointer\n    pushLongStack(program->returnStack, &(program->returnStackPointer), program->instructionPointer);\n    rPushShort(program, VALUE_TYPE_INT);\n\n    // Save return address\n    pushLongStack(program->returnStack, &(program->returnStackPointer), returnAddress);\n    rPushShort(program, VALUE_TYPE_INT);\n\n    // Save program flags\n    pushLongStack(program->stack, &(program->stackPointer), program->flags & 0xFFFF);\n    interpretPushShort(program, VALUE_TYPE_INT);\n\n    pushLongStack(program->stack, &(program->stackPointer), (intptr_t)program->checkWaitFunc);\n    interpretPushShort(program, VALUE_TYPE_INT);\n\n    pushLongStack(program->stack, &(program->stackPointer), program->windowId);\n    interpretPushShort(program, VALUE_TYPE_INT);\n\n    program->flags &= ~0xFFFF;\n    program->instructionPointer = address;\n}\n\n// NOTE: Inlined.\n//\n// 0x46CF78\nstatic void setupCall(Program* program, int address, int returnAddress)\n{\n    setupCallWithReturnVal(program, address, returnAddress);\n    interpretPushLong(program, 0);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x46CF9C\nstatic void setupExternalCallWithReturnVal(Program* program1, Program* program2, int address, int a4)\n{\n    pushLongStack(program2->returnStack, &(program2->returnStackPointer), program2->instructionPointer);\n    rPushShort(program2, VALUE_TYPE_INT);\n\n    pushLongStack(program2->returnStack, &(program2->returnStackPointer), program1->flags & 0xFFFF);\n    rPushShort(program2, VALUE_TYPE_INT);\n\n    pushLongStack(program2->returnStack, &(program2->returnStackPointer), (intptr_t)program1->checkWaitFunc);\n    rPushShort(program2, VALUE_TYPE_INT);\n\n    pushLongStack(program2->returnStack, &(program2->returnStackPointer), (intptr_t)program1);\n    rPushShort(program2, VALUE_TYPE_INT);\n\n    pushLongStack(program2->returnStack, &(program2->returnStackPointer), a4);\n    rPushShort(program2, VALUE_TYPE_INT);\n\n    pushLongStack(program2->stack, &(program2->stackPointer), program2->flags & 0xFFFF);\n    rPushShort(program2, VALUE_TYPE_INT);\n\n    pushLongStack(program2->stack, &(program2->stackPointer), (intptr_t)program2->checkWaitFunc);\n    rPushShort(program2, VALUE_TYPE_INT);\n\n    pushLongStack(program2->stack, &(program2->stackPointer), program2->windowId);\n    rPushShort(program2, VALUE_TYPE_INT);\n\n    program2->flags &= ~0xFFFF;\n    program2->instructionPointer = address;\n    program2->windowId = program1->windowId;\n\n    program1->flags |= PROGRAM_FLAG_0x20;\n}\n\n// 0x46D0B0\nstatic void setupExternalCall(Program* program1, Program* program2, int address, int a4)\n{\n    setupExternalCallWithReturnVal(program1, program2, address, a4);\n    interpretPushLong(program2, 0);\n    interpretPushShort(program2, VALUE_TYPE_INT);\n}\n\n// 0x46DB58\nvoid executeProc(Program* program, int procedureIndex)\n{\n    unsigned char* procedurePtr;\n    char* procedureIdentifier;\n    int procedureAddress;\n    Program* externalProgram;\n    int externalProcedureAddress;\n    int externalProcedureArgumentCount;\n    int procedureFlags;\n    char err[256];\n\n    procedurePtr = program->procedures + 4 + sizeof(Procedure) * procedureIndex;\n    procedureFlags = fetchLong(procedurePtr, 4);\n    if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) {\n        procedureIdentifier = interpretGetName(program, fetchLong(procedurePtr, 0));\n        externalProgram = exportFindProcedure(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount);\n        if (externalProgram != NULL) {\n            if (externalProcedureArgumentCount == 0) {\n            } else {\n                sprintf(err, \"External procedure cannot take arguments in interrupt context\");\n                interpretOutput(err);\n            }\n        } else {\n            sprintf(err, \"External procedure %s not found\\n\", procedureIdentifier);\n            interpretOutput(err);\n        }\n\n        // NOTE: Uninline.\n        setupExternalCall(program, externalProgram, externalProcedureAddress, 28);\n\n        procedurePtr = externalProgram->procedures + 4 + sizeof(Procedure) * procedureIndex;\n        procedureFlags = fetchLong(procedurePtr, 4);\n\n        if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0) {\n            // NOTE: Uninline.\n            op_critical_start(externalProgram);\n            interpret(externalProgram, 0);\n        }\n    } else {\n        procedureAddress = fetchLong(procedurePtr, 16);\n\n        // NOTE: Uninline.\n        setupCall(program, procedureAddress, 20);\n\n        if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0) {\n            // NOTE: Uninline.\n            op_critical_start(program);\n            interpret(program, 0);\n        }\n    }\n}\n\n// Returns index of the procedure with specified name or -1 if no such\n// procedure exists.\n//\n// 0x46DCD0\nint interpretFindProcedure(Program* program, const char* name)\n{\n    int procedureCount = fetchLong(program->procedures, 0);\n\n    unsigned char* ptr = program->procedures + 4;\n    for (int index = 0; index < procedureCount; index++) {\n        int identifierOffset = fetchLong(ptr, offsetof(Procedure, field_0));\n        if (stricmp((char*)(program->identifiers + identifierOffset), name) == 0) {\n            return index;\n        }\n\n        ptr += sizeof(Procedure);\n    }\n\n    return -1;\n}\n\n// 0x46DD2C\nvoid executeProcedure(Program* program, int procedureIndex)\n{\n    unsigned char* procedurePtr;\n    char* procedureIdentifier;\n    int procedureAddress;\n    Program* externalProgram;\n    int externalProcedureAddress;\n    int externalProcedureArgumentCount;\n    int procedureFlags;\n    char err[256];\n    jmp_buf env;\n\n    procedurePtr = program->procedures + 4 + sizeof(Procedure) * procedureIndex;\n    procedureFlags = fetchLong(procedurePtr, 4);\n\n    if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) {\n        procedureIdentifier = interpretGetName(program, fetchLong(procedurePtr, 0));\n        externalProgram = exportFindProcedure(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount);\n        if (externalProgram != NULL) {\n            if (externalProcedureArgumentCount == 0) {\n                // NOTE: Uninline.\n                setupExternalCall(program, externalProgram, externalProcedureAddress, 32);\n                memcpy(env, program->env, sizeof(env));\n                interpret(externalProgram, -1);\n                memcpy(externalProgram->env, env, sizeof(env));\n            } else {\n                sprintf(err, \"External procedure cannot take arguments in interrupt context\");\n                interpretOutput(err);\n            }\n        } else {\n            sprintf(err, \"External procedure %s not found\\n\", procedureIdentifier);\n            interpretOutput(err);\n        }\n    } else {\n        procedureAddress = fetchLong(procedurePtr, 16);\n\n        // NOTE: Uninline.\n        setupCall(program, procedureAddress, 24);\n        memcpy(env, program->env, sizeof(env));\n        interpret(program, -1);\n        memcpy(program->env, env, sizeof(env));\n    }\n}\n\n// 0x46DEE4\nstatic void doEvents()\n{\n    ProgramListNode* programListNode;\n    unsigned int time;\n    int procedureCount;\n    int procedureIndex;\n    unsigned char* procedurePtr;\n    int procedureFlags;\n    int oldProgramFlags;\n    int oldInstructionPointer;\n    opcode_t opcode;\n    int data;\n    jmp_buf env;\n\n    if (suspendEvents) {\n        return;\n    }\n\n    programListNode = head;\n    time = 1000 * timerFunc() / timerTick;\n\n    while (programListNode != NULL) {\n        procedureCount = fetchLong(programListNode->program->procedures, 0);\n\n        procedurePtr = programListNode->program->procedures + 4;\n        for (procedureIndex = 0; procedureIndex < procedureCount; procedureIndex++) {\n            procedureFlags = fetchLong(procedurePtr, 4);\n            if ((procedureFlags & PROCEDURE_FLAG_CONDITIONAL) != 0) {\n                memcpy(env, programListNode->program, sizeof(env));\n                oldProgramFlags = programListNode->program->flags;\n                oldInstructionPointer = programListNode->program->instructionPointer;\n\n                programListNode->program->flags = 0;\n                programListNode->program->instructionPointer = fetchLong(procedurePtr, 12);\n                interpret(programListNode->program, -1);\n\n                if ((programListNode->program->flags & PROGRAM_FLAG_0x04) == 0) {\n                    opcode = interpretPopShort(programListNode->program);\n                    data = interpretPopLong(programListNode->program);\n                    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n                        interpretDecStringRef(programListNode->program, opcode, data);\n                    }\n\n                    programListNode->program->flags = oldProgramFlags;\n                    programListNode->program->instructionPointer = oldInstructionPointer;\n\n                    if (data != 0) {\n                        // NOTE: Uninline.\n                        storeLong(0, procedurePtr, 4);\n                        executeProc(programListNode->program, procedureIndex);\n                    }\n                }\n\n                memcpy(programListNode->program, env, sizeof(env));\n            } else if ((procedureFlags & PROCEDURE_FLAG_TIMED) != 0) {\n                if ((unsigned int)fetchLong(procedurePtr, 8) < time) {\n                    // NOTE: Uninline.\n                    storeLong(0, procedurePtr, 4);\n                    executeProc(programListNode->program, procedureIndex);\n                }\n            }\n            procedurePtr += sizeof(Procedure);\n        }\n\n        programListNode = programListNode->next;\n    }\n}\n\n// 0x46E10C\nstatic void removeProgList(ProgramListNode* programListNode)\n{\n    ProgramListNode* tmp;\n\n    tmp = programListNode->next;\n    if (tmp != NULL) {\n        tmp->prev = programListNode->prev;\n    }\n\n    tmp = programListNode->prev;\n    if (tmp != NULL) {\n        tmp->next = programListNode->next;\n    } else {\n        head = programListNode->next;\n    }\n\n    interpretFreeProgram(programListNode->program);\n    myfree(programListNode, __FILE__, __LINE__); // \"..\\\\int\\\\INTRPRET.C\", 2923\n}\n\n// 0x46E154\nstatic void insertProgram(Program* program)\n{\n    ProgramListNode* programListNode = (ProgramListNode*)mymalloc(sizeof(*programListNode), __FILE__, __LINE__); // .\\\\int\\\\INTRPRET.C, 2907\n    programListNode->program = program;\n    programListNode->next = head;\n    programListNode->prev = NULL;\n\n    if (head != NULL) {\n        head->prev = programListNode;\n    }\n\n    head = programListNode;\n}\n\n// NOTE: Inlined.\n//\n// 0x46E15C\nvoid runProgram(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x02;\n    insertProgram(program);\n}\n\n// NOTE: Inlined.\n//\n// 0x46E19C\nProgram* runScript(char* name)\n{\n    Program* program;\n\n    // NOTE: Uninline.\n    program = allocateProgram(interpretMangleName(name));\n    if (program != NULL) {\n        // NOTE: Uninline.\n        runProgram(program);\n        interpret(program, 24);\n    }\n\n    return program;\n}\n\n// NOTE: Unused.\n//\n// 0x46E1DC\nvoid interpretSetCPUBurstSize(int value)\n{\n    if (value < 1) {\n        value = 1;\n    }\n\n    cpuBurstSize = value;\n}\n\n// 0x46E1EC\nvoid updatePrograms()\n{\n    ProgramListNode* curr = head;\n    while (curr != NULL) {\n        ProgramListNode* next = curr->next;\n        if (curr->program != NULL) {\n            interpret(curr->program, cpuBurstSize);\n        }\n        if (curr->program->exited) {\n            removeProgList(curr);\n        }\n        curr = next;\n    }\n    doEvents();\n    updateIntLib();\n}\n\n// 0x46E238\nvoid clearPrograms()\n{\n    ProgramListNode* curr = head;\n    while (curr != NULL) {\n        ProgramListNode* next = curr->next;\n        removeProgList(curr);\n        curr = next;\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x46E254\nvoid clearTopProgram()\n{\n    ProgramListNode* next;\n\n    next = head->next;\n    removeProgList(head);\n    head = next;\n}\n\n// NOTE: Unused.\n//\n// 0x46E26C\nchar** getProgramList(int* programListLengthPtr)\n{\n    char** programList;\n    int programListLength;\n    int index;\n    int it;\n    ProgramListNode* programListNode;\n\n    index = 0;\n    programListLength = 0;\n\n    for (it = 1; it <= 2; it++) {\n        programListNode = head;\n        while (programListNode != NULL) {\n            if (it == 1) {\n                programListLength++;\n            } else if (it == 2) {\n                if (index < programListLength) {\n                    programList[index++] = mystrdup(programListNode->program->name, __FILE__, __LINE__); // \"..\\int\\INTRPRET.C\", 3014\n                }\n            }\n            programListNode = programListNode->next;\n        }\n\n        if (it == 1) {\n            programList = (char**)mymalloc(sizeof(*programList) * programListLength, __FILE__, __LINE__); // \"..\\int\\INTRPRET.C\", 3021\n        }\n    }\n\n    if (programListLengthPtr != NULL) {\n        *programListLengthPtr = programListLength;\n    }\n\n    return programList;\n}\n\n// NOTE: Unused.\n//\n// 0x46E31C\nvoid freeProgramList(char** programList, int programListLength)\n{\n    int index;\n\n    if (programList != NULL) {\n        for (index = 0; index < programListLength; index++) {\n            if (programList[index] != NULL) {\n                myfree(programList[index], __FILE__, __LINE__); // \"..\\int\\INTRPRET.C\", 3035\n            }\n        }\n    }\n\n    myfree(programList, __FILE__, __LINE__); // \"..\\int\\INTRPRET.C\", 3038\n}\n\n// 0x46E368\nvoid interpretAddFunc(int opcode, OpcodeHandler* handler)\n{\n    int index = opcode & 0x3FFF;\n    if (index >= OPCODE_MAX_COUNT) {\n        printf(\"Too many opcodes!\\n\");\n        exit(1);\n    }\n\n    opTable[index] = handler;\n}\n\n// NOTE: Unused.\n//\n// 0x46E398\nvoid interpretSetFilenameFunc(InterpretMangleFunc* func)\n{\n    filenameFunc = func;\n}\n\n// NOTE: Unused.\n//\n// 0x46E3A0\nvoid interpretSuspendEvents()\n{\n    suspendEvents++;\n    if (suspendEvents == 1) {\n        suspendTime = timerFunc();\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x46E3C0\nvoid interpretResumeEvents()\n{\n    int counter;\n    ProgramListNode* programListNode;\n    unsigned int time;\n    int procedureCount;\n    int procedureIndex;\n    unsigned char* procedurePtr;\n\n    counter = suspendEvents;\n    if (suspendEvents != 0) {\n        suspendEvents--;\n        if (counter == 1) {\n            programListNode = head;\n            time = 1000 * (timerFunc() - suspendTime) / timerTick;\n            while (programListNode != NULL) {\n                procedureCount = fetchLong(programListNode->program->procedures, 0);\n                procedurePtr = programListNode->program->procedures + 4;\n                for (procedureIndex = 0; procedureIndex < procedureCount; procedureIndex++) {\n                    if ((fetchLong(procedurePtr, 4) & PROCEDURE_FLAG_TIMED) != 0) {\n                        storeLong(fetchLong(procedurePtr, 8) + time, procedurePtr, 8);\n                    }\n                }\n                programListNode = programListNode->next;\n                procedurePtr += sizeof(Procedure);\n            }\n        }\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x46E4AC\nint interpretSaveProgramState()\n{\n    return 0;\n}\n\n// 0x46E5EC\nvoid interpretDumpStringHeap()\n{\n    ProgramListNode* programListNode = head;\n    while (programListNode != NULL) {\n        Program* program = programListNode->program;\n        if (program != NULL) {\n            int total = 0;\n\n            if (program->dynamicStrings != NULL) {\n                debug_printf(\"Program %s\\n\");\n\n                unsigned char* heap = program->dynamicStrings + sizeof(int);\n                while (*(unsigned short*)heap != 0x8000) {\n                    int size = *(short*)heap;\n                    if (size >= 0) {\n                        int refcount = *(short*)(heap + sizeof(short));\n                        debug_printf(\"Size: %d, ref: %d, string %s\\n\", size, refcount, (char*)(heap + sizeof(short) + sizeof(short)));\n                    } else {\n                        debug_printf(\"Free space, length %d\\n\", -size);\n                    }\n\n                    // TODO: Not sure about total, probably calculated wrong, check.\n                    heap += sizeof(short) + sizeof(short) + size;\n                    total += sizeof(short) + sizeof(short) + size;\n                }\n\n                debug_printf(\"Total length of heap %d, stored length %d\\n\", total, *(int*)(program->dynamicStrings));\n            } else {\n                debug_printf(\"No string heap for program %s\\n\", program->name);\n            }\n        }\n\n        programListNode = programListNode->next;\n    }\n}\n"
  },
  {
    "path": "src/int/intrpret.h",
    "content": "#ifndef FALLOUT_INT_INTRPRET_H_\n#define FALLOUT_INT_INTRPRET_H_\n\n#include <setjmp.h>\n#include <stdbool.h>\n\ntypedef enum Opcode {\n    OPCODE_NOOP = 0x8000,\n    OPCODE_PUSH = 0x8001,\n    OPCODE_ENTER_CRITICAL_SECTION = 0x8002,\n    OPCODE_LEAVE_CRITICAL_SECTION = 0x8003,\n    OPCODE_JUMP = 0x8004,\n    OPCODE_CALL = 0x8005,\n    OPCODE_CALL_AT = 0x8006,\n    OPCODE_CALL_WHEN = 0x8007,\n    OPCODE_CALLSTART = 0x8008,\n    OPCODE_EXEC = 0x8009,\n    OPCODE_SPAWN = 0x800A,\n    OPCODE_FORK = 0x800B,\n    OPCODE_A_TO_D = 0x800C,\n    OPCODE_D_TO_A = 0x800D,\n    OPCODE_EXIT = 0x800E,\n    OPCODE_DETACH = 0x800F,\n    OPCODE_EXIT_PROGRAM = 0x8010,\n    OPCODE_STOP_PROGRAM = 0x8011,\n    OPCODE_FETCH_GLOBAL = 0x8012,\n    OPCODE_STORE_GLOBAL = 0x8013,\n    OPCODE_FETCH_EXTERNAL = 0x8014,\n    OPCODE_STORE_EXTERNAL = 0x8015,\n    OPCODE_EXPORT_VARIABLE = 0x8016,\n    OPCODE_EXPORT_PROCEDURE = 0x8017,\n    OPCODE_SWAP = 0x8018,\n    OPCODE_SWAPA = 0x8019,\n    OPCODE_POP = 0x801A,\n    OPCODE_DUP = 0x801B,\n    OPCODE_POP_RETURN = 0x801C,\n    OPCODE_POP_EXIT = 0x801D,\n    OPCODE_POP_ADDRESS = 0x801E,\n    OPCODE_POP_FLAGS = 0x801F,\n    OPCODE_POP_FLAGS_RETURN = 0x8020,\n    OPCODE_POP_FLAGS_EXIT = 0x8021,\n    OPCODE_POP_FLAGS_RETURN_EXTERN = 0x8022,\n    OPCODE_POP_FLAGS_EXIT_EXTERN = 0x8023,\n    OPCODE_POP_FLAGS_RETURN_VAL_EXTERN = 0x8024,\n    OPCODE_POP_FLAGS_RETURN_VAL_EXIT = 0x8025,\n    OPCODE_POP_FLAGS_RETURN_VAL_EXIT_EXTERN = 0x8026,\n    OPCODE_CHECK_PROCEDURE_ARGUMENT_COUNT = 0x8027,\n    OPCODE_LOOKUP_PROCEDURE_BY_NAME = 0x8028,\n    OPCODE_POP_BASE = 0x8029,\n    OPCODE_POP_TO_BASE = 0x802A,\n    OPCODE_PUSH_BASE = 0x802B,\n    OPCODE_SET_GLOBAL = 0x802C,\n    OPCODE_FETCH_PROCEDURE_ADDRESS = 0x802D,\n    OPCODE_DUMP = 0x802E,\n    OPCODE_IF = 0x802F,\n    OPCODE_WHILE = 0x8030,\n    OPCODE_STORE = 0x8031,\n    OPCODE_FETCH = 0x8032,\n    OPCODE_EQUAL = 0x8033,\n    OPCODE_NOT_EQUAL = 0x8034,\n    OPCODE_LESS_THAN_EQUAL = 0x8035,\n    OPCODE_GREATER_THAN_EQUAL = 0x8036,\n    OPCODE_LESS_THAN = 0x8037,\n    OPCODE_GREATER_THAN = 0x8038,\n    OPCODE_ADD = 0x8039,\n    OPCODE_SUB = 0x803A,\n    OPCODE_MUL = 0x803B,\n    OPCODE_DIV = 0x803C,\n    OPCODE_MOD = 0x803D,\n    OPCODE_AND = 0x803E,\n    OPCODE_OR = 0x803F,\n    OPCODE_BITWISE_AND = 0x8040,\n    OPCODE_BITWISE_OR = 0x8041,\n    OPCODE_BITWISE_XOR = 0x8042,\n    OPCODE_BITWISE_NOT = 0x8043,\n    OPCODE_FLOOR = 0x8044,\n    OPCODE_NOT = 0x8045,\n    OPCODE_NEGATE = 0x8046,\n    OPCODE_WAIT = 0x8047,\n    OPCODE_CANCEL = 0x8048,\n    OPCODE_CANCEL_ALL = 0x8049,\n    OPCODE_START_CRITICAL = 0x804A,\n    OPCODE_END_CRITICAL = 0x804B,\n} Opcode;\n\ntypedef enum ProcedureFlags {\n    PROCEDURE_FLAG_TIMED = 0x01,\n    PROCEDURE_FLAG_CONDITIONAL = 0x02,\n    PROCEDURE_FLAG_IMPORTED = 0x04,\n    PROCEDURE_FLAG_EXPORTED = 0x08,\n    PROCEDURE_FLAG_CRITICAL = 0x10,\n} ProcedureFlags;\n\ntypedef enum ProgramFlags {\n    PROGRAM_FLAG_EXITED = 0x01,\n    PROGRAM_FLAG_0x02 = 0x02,\n    PROGRAM_FLAG_0x04 = 0x04,\n    PROGRAM_FLAG_STOPPED = 0x08,\n\n    // Program is in waiting state with `checkWaitFunc` set.\n    PROGRAM_IS_WAITING = 0x10,\n    PROGRAM_FLAG_0x20 = 0x20,\n    PROGRAM_FLAG_0x40 = 0x40,\n    PROGRAM_FLAG_CRITICAL_SECTION = 0x80,\n    PROGRAM_FLAG_0x0100 = 0x0100,\n} ProgramFlags;\n\nenum RawValueType {\n    RAW_VALUE_TYPE_OPCODE = 0x8000,\n    RAW_VALUE_TYPE_INT = 0x4000,\n    RAW_VALUE_TYPE_FLOAT = 0x2000,\n    RAW_VALUE_TYPE_STATIC_STRING = 0x1000,\n    RAW_VALUE_TYPE_DYNAMIC_STRING = 0x0800,\n};\n\n#define VALUE_TYPE_MASK 0xF7FF\n\n#define VALUE_TYPE_INT 0xC001\n#define VALUE_TYPE_FLOAT 0xA001\n#define VALUE_TYPE_STRING 0x9001\n#define VALUE_TYPE_DYNAMIC_STRING 0x9801\n\ntypedef unsigned short opcode_t;\n\ntypedef struct Procedure {\n    int field_0;\n    int field_4;\n    int field_8;\n    int field_C;\n    int field_10;\n    int field_14;\n} Procedure;\n\ntypedef struct Program Program;\ntypedef int(InterpretCheckWaitFunc)(Program* program);\n\n// It's size in original code is 144 (0x8C) bytes due to the different\n// size of `jmp_buf`.\ntypedef struct Program {\n    char* name;\n    unsigned char* data;\n    struct Program* parent;\n    struct Program* child;\n    int instructionPointer; // current pos in data\n    int framePointer; // saved stack 1 pos - probably beginning of local variables - probably called base\n    int basePointer; // saved stack 1 pos - probably beginning of global variables\n    unsigned char* stack; // stack 1 (4096 bytes)\n    unsigned char* returnStack; // stack 2 (4096 bytes)\n    int stackPointer; // stack pointer 1\n    int returnStackPointer; // stack pointer 2\n    unsigned char* staticStrings; // static strings table\n    unsigned char* dynamicStrings; // dynamic strings table\n    unsigned char* identifiers;\n    unsigned char* procedures;\n    jmp_buf env;\n    unsigned int waitEnd; // end time of timer (field_74 + wait time)\n    unsigned int waitStart; // time when wait was called\n    int field_78; // time when program begin execution (for the first time)?, -1 - program never executed\n    InterpretCheckWaitFunc* checkWaitFunc;\n    int flags; // flags\n    int windowId;\n    bool exited;\n} Program;\n\ntypedef char*(InterpretMangleFunc)(char* fileName);\ntypedef int(InterpretOutputFunc)(char* string);\ntypedef unsigned int(InterpretTimerFunc)();\ntypedef void(OpcodeHandler)(Program* program);\n\nvoid interpretSetTimeFunc(InterpretTimerFunc* timerFunc, int timerTick);\nchar* interpretMangleName(char* fileName);\nvoid interpretOutputFunc(InterpretOutputFunc* func);\nint interpretOutput(const char* format, ...);\nvoid interpretError(const char* format, ...);\nvoid interpretDecStringRef(Program* program, opcode_t a2, int a3);\nvoid interpretPushShort(Program* program, int value);\nvoid interpretPushLong(Program* program, int value);\nopcode_t interpretPopShort(Program* program);\nint interpretPopLong(Program* program);\nvoid interpretFreeProgram(Program* program);\nProgram* allocateProgram(const char* path);\nchar* interpretGetString(Program* program, opcode_t opcode, int offset);\nchar* interpretGetName(Program* program, int offset);\nint interpretAddString(Program* program, char* string);\nvoid initInterpreter();\nvoid interpretClose();\nvoid interpretEnableInterpreter(int enabled);\nvoid interpret(Program* program, int a2);\nvoid executeProc(Program* program, int procedureIndex);\nint interpretFindProcedure(Program* prg, const char* name);\nvoid executeProcedure(Program* program, int procedureIndex);\nvoid runProgram(Program* program);\nProgram* runScript(char* name);\nvoid interpretSetCPUBurstSize(int value);\nvoid updatePrograms();\nvoid clearPrograms();\nvoid clearTopProgram();\nchar** getProgramList(int* programListLengthPtr);\nvoid freeProgramList(char** programList, int programListLength);\nvoid interpretAddFunc(int opcode, OpcodeHandler* handler);\nvoid interpretSetFilenameFunc(InterpretMangleFunc* func);\nvoid interpretSuspendEvents();\nvoid interpretResumeEvents();\nint interpretSaveProgramState();\nvoid interpretDumpStringHeap();\n\n#endif /* FALLOUT_INT_INTRPRET_H_ */\n"
  },
  {
    "path": "src/int/memdbg.c",
    "content": "#include \"int/memdbg.h\"\n\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nstatic void defaultOutput(const char* string);\nstatic int debug_printf(const char* format, ...);\nstatic void error(const char* func, size_t size, const char* file, int line);\nstatic void* defaultMalloc(size_t size);\nstatic void* defaultRealloc(void* ptr, size_t size);\nstatic void defaultFree(void* ptr);\n\n// 0x519588\nstatic MemoryManagerPrintErrorProc* outputFunc = defaultOutput;\n\n// 0x51958C\nstatic MallocProc* mallocPtr = defaultMalloc;\n\n// 0x519590\nstatic ReallocProc* reallocPtr = defaultRealloc;\n\n// 0x519594\nstatic FreeProc* freePtr = defaultFree;\n\n// 0x4845B0\nstatic void defaultOutput(const char* string)\n{\n    printf(\"%s\", string);\n}\n\n// NOTE: Unused.\n//\n// 0x4845C0\nvoid memoryRegisterDebug(MemoryManagerPrintErrorProc* func)\n{\n    outputFunc = func;\n}\n\n// 0x4845C8\nstatic int debug_printf(const char* format, ...)\n{\n    // 0x631F7C\n    static char buf[256];\n\n    int length = 0;\n\n    if (outputFunc != NULL) {\n        va_list args;\n        va_start(args, format);\n        length = vsprintf(buf, format, args);\n        va_end(args);\n\n        outputFunc(buf);\n    }\n\n    return length;\n}\n\n// 0x484610\nstatic void error(const char* func, size_t size, const char* file, int line)\n{\n    debug_printf(\"%s: Error allocating block of size %ld (%x), %s %d\\n\", func, size, size, file, line);\n    exit(1);\n}\n\n// 0x48462C\nstatic void* defaultMalloc(size_t size)\n{\n    return malloc(size);\n}\n\n// 0x484634\nstatic void* defaultRealloc(void* ptr, size_t size)\n{\n    return realloc(ptr, size);\n}\n\n// 0x48463C\nstatic void defaultFree(void* ptr)\n{\n    free(ptr);\n}\n\n// 0x484644\nvoid memoryRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc)\n{\n    mallocPtr = mallocProc;\n    reallocPtr = reallocProc;\n    freePtr = freeProc;\n}\n\n// 0x484660\nvoid* mymalloc(size_t size, const char* file, int line)\n{\n    void* ptr = mallocPtr(size);\n    if (ptr == NULL) {\n        error(\"malloc\", size, file, line);\n    }\n\n    return ptr;\n}\n\n// 0x4846B4\nvoid* myrealloc(void* ptr, size_t size, const char* file, int line)\n{\n    ptr = reallocPtr(ptr, size);\n    if (ptr == NULL) {\n        error(\"realloc\", size, file, line);\n    }\n\n    return ptr;\n}\n\n// 0x484688\nvoid myfree(void* ptr, const char* file, int line)\n{\n    if (ptr == NULL) {\n        debug_printf(\"free: free of a null ptr, %s %d\\n\", file, line);\n        exit(1);\n    }\n\n    freePtr(ptr);\n}\n\n// 0x4846D8\nvoid* mycalloc(int count, int size, const char* file, int line)\n{\n    void* ptr = mallocPtr(count * size);\n    if (ptr == NULL) {\n        error(\"calloc\", size, file, line);\n    }\n\n    memset(ptr, 0, count * size);\n\n    return ptr;\n}\n\n// 0x484710\nchar* mystrdup(const char* string, const char* file, int line)\n{\n    size_t size = strlen(string) + 1;\n    char* copy = (char*)mallocPtr(size);\n    if (copy == NULL) {\n        error(\"strdup\", size, file, line);\n    }\n\n    strcpy(copy, string);\n\n    return copy;\n}\n"
  },
  {
    "path": "src/int/memdbg.h",
    "content": "#ifndef FALLOUT_INT_MEMDBG_H_\n#define FALLOUT_INT_MEMDBG_H_\n\n#include \"memory_defs.h\"\n\ntypedef void(MemoryManagerPrintErrorProc)(const char* string);\n\nvoid memoryRegisterDebug(MemoryManagerPrintErrorProc* func);\nvoid memoryRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc);\nvoid* mymalloc(size_t size, const char* file, int line);\nvoid* myrealloc(void* ptr, size_t size, const char* file, int line);\nvoid myfree(void* ptr, const char* file, int line);\nvoid* mycalloc(int count, int size, const char* file, int line);\nchar* mystrdup(const char* string, const char* file, int line);\n\n#endif /* FALLOUT_INT_MEMDBG_H_ */\n"
  },
  {
    "path": "src/int/mousemgr.c",
    "content": "#include \"int/mousemgr.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/input.h\"\n#include \"int/datafile.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/memdbg.h\"\n\n#define MOUSE_MGR_CACHE_CAPACITY 32\n\ntypedef enum MouseManagerMouseType {\n    MOUSE_MANAGER_MOUSE_TYPE_NONE,\n    MOUSE_MANAGER_MOUSE_TYPE_STATIC,\n    MOUSE_MANAGER_MOUSE_TYPE_ANIMATED,\n} MouseManagerMouseType;\n\ntypedef struct MouseManagerStaticData {\n    unsigned char* data;\n    int field_4;\n    int field_8;\n    int width;\n    int height;\n} MouseManagerStaticData;\n\ntypedef struct MouseManagerAnimatedData {\n    unsigned char** field_0;\n    unsigned char** field_4;\n    int* field_8;\n    int* field_C;\n    int width;\n    int height;\n    float field_18;\n    int field_1C;\n    int field_20;\n    signed char field_24;\n    signed char frameCount;\n    signed char field_26;\n} MouseManagerAnimatedData;\n\ntypedef struct MouseManagerCacheEntry {\n    union {\n        void* data;\n        MouseManagerStaticData* staticData;\n        MouseManagerAnimatedData* animatedData;\n    };\n    int type;\n    unsigned char palette[256 * 3];\n    int ref;\n    char fileName[32];\n    char field_32C[32];\n} MouseManagerCacheEntry;\n\nstatic char* defaultNameMangler(char* a1);\nstatic int defaultRateCallback();\nstatic int defaultTimeCallback();\nstatic void setShape(unsigned char* buf, int width, int length, int full, int hotx, int hoty, char trans);\nstatic void freeCacheEntry(MouseManagerCacheEntry* entry);\nstatic int cacheInsert(void** data, int type, unsigned char* palette, const char* fileName);\nstatic void cacheFlush();\nstatic MouseManagerCacheEntry* cacheFind(const char* fileName, unsigned char** palettePtr, int* a3, int* a4, int* widthPtr, int* heightPtr, int* typePtr);\n\n// 0x5195A8\nstatic MouseManagerNameMangler* mouseNameMangler = defaultNameMangler;\n\n// 0x5195AC\nstatic MouseManagerRateProvider* rateCallback = defaultRateCallback;\n\n// 0x5195B0\nstatic MouseManagerTimeProvider* currentTimeCallback = defaultTimeCallback;\n\n// 0x5195B4\nstatic int curref = 1;\n\n// 0x63247C\nstatic MouseManagerCacheEntry Cache[MOUSE_MGR_CACHE_CAPACITY];\n\n// 0x638DFC\nstatic bool animating;\n\n// 0x638E00\nstatic unsigned char* curPal;\n\n// 0x638E04\nstatic MouseManagerAnimatedData* curAnim;\n\n// 0x638E08\nstatic unsigned char* curMouseBuf;\n\n// 0x638E0C\nstatic int lastMouseIndex;\n\n// 0x485250\nstatic char* defaultNameMangler(char* a1)\n{\n    return a1;\n}\n\n// 0x485254\nstatic int defaultRateCallback()\n{\n    return 1000;\n}\n\n// 0x48525C\nstatic int defaultTimeCallback()\n{\n    return get_time();\n}\n\n// NOTE: Inlined.\n//\n// 0x485264\nstatic void setShape(unsigned char* buf, int width, int length, int full, int hotx, int hoty, char trans)\n{\n    mouse_set_shape(buf, width, length, full, hotx, hoty, trans);\n}\n\n// 0x485288\nvoid mousemgrSetNameMangler(MouseManagerNameMangler* func)\n{\n    mouseNameMangler = func;\n}\n\n// NOTE: Unused.\n//\n// 0x485290\nvoid mousemgrSetTimeCallback(MouseManagerRateProvider* rateFunc, MouseManagerTimeProvider* currentTimeFunc)\n{\n    if (rateFunc != NULL) {\n        rateCallback = rateFunc;\n    } else {\n        rateCallback = defaultRateCallback;\n    }\n\n    if (currentTimeFunc != NULL) {\n        currentTimeCallback = currentTimeFunc;\n    } else {\n        currentTimeCallback = defaultTimeCallback;\n    }\n}\n\n// 0x4852B8\nstatic void freeCacheEntry(MouseManagerCacheEntry* entry)\n{\n    switch (entry->type) {\n    case MOUSE_MANAGER_MOUSE_TYPE_STATIC:\n        if (entry->staticData != NULL) {\n            if (entry->staticData->data != NULL) {\n                myfree(entry->staticData->data, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 120\n                entry->staticData->data = NULL;\n            }\n            myfree(entry->staticData, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 123\n            entry->staticData = NULL;\n        }\n        break;\n    case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED:\n        if (entry->animatedData != NULL) {\n            if (entry->animatedData->field_0 != NULL) {\n                for (int index = 0; index < entry->animatedData->frameCount; index++) {\n                    myfree(entry->animatedData->field_0[index], __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 134\n                    myfree(entry->animatedData->field_4[index], __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 135\n                }\n                myfree(entry->animatedData->field_0, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 137\n                myfree(entry->animatedData->field_4, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 138\n                myfree(entry->animatedData->field_8, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 139\n                myfree(entry->animatedData->field_C, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 140\n            }\n            myfree(entry->animatedData, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 143\n            entry->animatedData = NULL;\n        }\n        break;\n    }\n\n    entry->type = 0;\n    entry->fileName[0] = '\\0';\n}\n\n// 0x4853F8\nstatic int cacheInsert(void** data, int type, unsigned char* palette, const char* fileName)\n{\n    int foundIndex = -1;\n    int index;\n    for (index = 0; index < MOUSE_MGR_CACHE_CAPACITY; index++) {\n        MouseManagerCacheEntry* cacheEntry = &(Cache[index]);\n        if (cacheEntry->type == MOUSE_MANAGER_MOUSE_TYPE_NONE && foundIndex == -1) {\n            foundIndex = index;\n        }\n\n        if (stricmp(fileName, cacheEntry->fileName) == 0) {\n            freeCacheEntry(cacheEntry);\n            foundIndex = index;\n            break;\n        }\n    }\n\n    if (foundIndex != -1) {\n        index = foundIndex;\n    }\n\n    if (index == MOUSE_MGR_CACHE_CAPACITY) {\n        int v2 = -1;\n        int v1 = curref;\n        for (int index = 0; index < MOUSE_MGR_CACHE_CAPACITY; index++) {\n            MouseManagerCacheEntry* cacheEntry = &(Cache[index]);\n            if (v1 > cacheEntry->ref) {\n                v1 = cacheEntry->ref;\n                v2 = index;\n            }\n        }\n\n        if (v2 == -1) {\n            debug_printf(\"Mouse cache overflow!!!!\\n\");\n            exit(1);\n        }\n\n        index = v2;\n        freeCacheEntry(&(Cache[index]));\n    }\n\n    MouseManagerCacheEntry* cacheEntry = &(Cache[index]);\n    cacheEntry->type = type;\n    memcpy(cacheEntry->palette, palette, sizeof(cacheEntry->palette));\n    cacheEntry->ref = curref++;\n    strncpy(cacheEntry->fileName, fileName, sizeof(cacheEntry->fileName) - 1);\n    cacheEntry->field_32C[0] = '\\0';\n    cacheEntry->data = *data;\n\n    return index;\n}\n\n// NOTE: Inlined.\n//\n// 0x4853D4\nstatic void cacheFlush()\n{\n    for (int index = 0; index < MOUSE_MGR_CACHE_CAPACITY; index++) {\n        freeCacheEntry(&(Cache[index]));\n    }\n}\n\n// 0x48554C\nstatic MouseManagerCacheEntry* cacheFind(const char* fileName, unsigned char** palettePtr, int* a3, int* a4, int* widthPtr, int* heightPtr, int* typePtr)\n{\n    for (int index = 0; index < MOUSE_MGR_CACHE_CAPACITY; index++) {\n        MouseManagerCacheEntry* cacheEntry = &(Cache[index]);\n        if (strnicmp(cacheEntry->fileName, fileName, 31) == 0 || strnicmp(cacheEntry->field_32C, fileName, 31) == 0) {\n            *palettePtr = cacheEntry->palette;\n            *typePtr = cacheEntry->type;\n\n            lastMouseIndex = index;\n\n            switch (cacheEntry->type) {\n            case MOUSE_MANAGER_MOUSE_TYPE_STATIC:\n                *a3 = cacheEntry->staticData->field_4;\n                *a4 = cacheEntry->staticData->field_8;\n                *widthPtr = cacheEntry->staticData->width;\n                *heightPtr = cacheEntry->staticData->height;\n                break;\n            case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED:\n                *widthPtr = cacheEntry->animatedData->width;\n                *heightPtr = cacheEntry->animatedData->height;\n                *a3 = cacheEntry->animatedData->field_8[cacheEntry->animatedData->field_26];\n                *a4 = cacheEntry->animatedData->field_C[cacheEntry->animatedData->field_26];\n                break;\n            }\n\n            return cacheEntry;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x48568C\nvoid initMousemgr()\n{\n    mouse_set_sensitivity(1.0);\n}\n\n// 0x48569C\nvoid mousemgrClose()\n{\n    setShape(NULL, 0, 0, 0, 0, 0, 0);\n\n    if (curMouseBuf != NULL) {\n        myfree(curMouseBuf, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 243\n        curMouseBuf = NULL;\n    }\n\n    // NOTE: Uninline.\n    cacheFlush();\n\n    curPal = NULL;\n    curAnim = 0;\n}\n\n// 0x485704\nvoid mousemgrUpdate()\n{\n    if (!animating) {\n        return;\n    }\n\n    if (curAnim == NULL) {\n        debug_printf(\"Animating == 1 but curAnim == 0\\n\");\n    }\n\n    if (currentTimeCallback() >= curAnim->field_1C) {\n        curAnim->field_1C = (int)(curAnim->field_18 / curAnim->frameCount * rateCallback() + currentTimeCallback());\n        if (curAnim->field_24 != curAnim->field_26) {\n            int v1 = curAnim->field_26 + curAnim->field_20;\n            if (v1 < 0) {\n                v1 = curAnim->frameCount - 1;\n            } else if (v1 >= curAnim->frameCount) {\n                v1 = 0;\n            }\n\n            curAnim->field_26 = v1;\n            memcpy(curAnim->field_0[curAnim->field_26],\n                curAnim->field_4[curAnim->field_26],\n                curAnim->width * curAnim->height);\n\n            datafileConvertData(curAnim->field_0[curAnim->field_26],\n                curPal,\n                curAnim->width,\n                curAnim->height);\n\n            setShape(curAnim->field_0[v1],\n                curAnim->width,\n                curAnim->height,\n                curAnim->width,\n                curAnim->field_8[v1],\n                curAnim->field_C[v1],\n                0);\n        }\n    }\n}\n\n// 0x485868\nint mouseSetFrame(char* fileName, int a2)\n{\n    char* mangledFileName = mouseNameMangler(fileName);\n\n    unsigned char* palette;\n    int temp;\n    int type;\n    MouseManagerCacheEntry* cacheEntry = cacheFind(fileName, &palette, &temp, &temp, &temp, &temp, &type);\n    if (cacheEntry != NULL) {\n        if (type == MOUSE_MANAGER_MOUSE_TYPE_ANIMATED) {\n            cacheEntry->animatedData->field_24 = a2;\n            if (cacheEntry->animatedData->field_24 >= cacheEntry->animatedData->field_26) {\n                int v1 = cacheEntry->animatedData->field_24 - cacheEntry->animatedData->field_26;\n                int v2 = cacheEntry->animatedData->frameCount + cacheEntry->animatedData->field_26 - cacheEntry->animatedData->field_24;\n                if (v1 >= v2) {\n                    cacheEntry->animatedData->field_20 = -1;\n                } else {\n                    cacheEntry->animatedData->field_20 = 1;\n                }\n            } else {\n                int v1 = cacheEntry->animatedData->field_26 - cacheEntry->animatedData->field_24;\n                int v2 = cacheEntry->animatedData->frameCount + cacheEntry->animatedData->field_24 - cacheEntry->animatedData->field_26;\n                if (v1 < v2) {\n                    cacheEntry->animatedData->field_20 = -1;\n                } else {\n                    cacheEntry->animatedData->field_20 = 1;\n                }\n            }\n\n            if (!animating || curAnim != cacheEntry->animatedData) {\n                memcpy(cacheEntry->animatedData->field_0[cacheEntry->animatedData->field_26],\n                    cacheEntry->animatedData->field_4[cacheEntry->animatedData->field_26],\n                    cacheEntry->animatedData->width * cacheEntry->animatedData->height);\n\n                setShape(cacheEntry->animatedData->field_0[cacheEntry->animatedData->field_26],\n                    cacheEntry->animatedData->width,\n                    cacheEntry->animatedData->height,\n                    cacheEntry->animatedData->width,\n                    cacheEntry->animatedData->field_8[cacheEntry->animatedData->field_26],\n                    cacheEntry->animatedData->field_C[cacheEntry->animatedData->field_26],\n                    0);\n\n                animating = true;\n            }\n\n            curAnim = cacheEntry->animatedData;\n            curPal = palette;\n            curAnim->field_1C = currentTimeCallback();\n            return true;\n        }\n\n        mouseSetMousePointer(fileName);\n        return true;\n    }\n\n    if (animating) {\n        curPal = 0;\n        animating = 0;\n        curAnim = 0;\n    } else {\n        if (curMouseBuf != NULL) {\n            myfree(curMouseBuf, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 337\n            curMouseBuf = NULL;\n        }\n    }\n\n    File* stream = db_fopen(mangledFileName, \"r\");\n    if (stream == NULL) {\n        debug_printf(\"mouseSetFrame: couldn't find %s\\n\", mangledFileName);\n        return false;\n    }\n\n    char string[80];\n    db_fgets(string, sizeof(string), stream);\n    if (strnicmp(string, \"anim\", 4) != 0) {\n        db_fclose(stream);\n        mouseSetMousePointer(fileName);\n        return true;\n    }\n\n    // NOTE: Uninline.\n    char* sep = strchr(string, ' ');\n    if (sep == NULL) {\n        // FIXME: Leaks stream.\n        return false;\n    }\n\n    int v3;\n    float v4;\n    sscanf(sep + 1, \"%d %f\", &v3, &v4);\n\n    MouseManagerAnimatedData* animatedData = (MouseManagerAnimatedData*)mymalloc(sizeof(*animatedData), __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 359\n    animatedData->field_0 = (unsigned char**)mymalloc(sizeof(*animatedData->field_0) * v3, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 360\n    animatedData->field_4 = (unsigned char**)mymalloc(sizeof(*animatedData->field_4) * v3, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 361\n    animatedData->field_8 = (int*)mymalloc(sizeof(*animatedData->field_8) * v3, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 362\n    animatedData->field_C = (int*)mymalloc(sizeof(*animatedData->field_8) * v3, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 363\n    animatedData->field_18 = v4;\n    animatedData->field_1C = currentTimeCallback();\n    animatedData->field_26 = 0;\n    animatedData->field_24 = a2;\n    animatedData->frameCount = v3;\n    if (animatedData->frameCount / 2 <= a2) {\n        animatedData->field_20 = -1;\n    } else {\n        animatedData->field_20 = 1;\n    }\n\n    int width;\n    int height;\n    for (int index = 0; index < v3; index++) {\n        string[0] = '\\0';\n        db_fgets(string, sizeof(string), stream);\n        if (string[0] == '\\0') {\n            debug_printf(\"Not enough frames in %s, got %d, needed %d\", mangledFileName, index, v3);\n            break;\n        }\n\n        // NOTE: Uninline.\n        char* sep = strchr(string, ' ');\n        if (sep == NULL) {\n            debug_printf(\"Bad line %s in %s\\n\", string, fileName);\n            // FIXME: Leaking stream.\n            return false;\n        }\n\n        *sep = '\\0';\n\n        int v5;\n        int v6;\n        sscanf(sep + 1, \"%d %d\", &v5, &v6);\n\n        animatedData->field_4[index] = loadRawDataFile(mouseNameMangler(string), &width, &height);\n        animatedData->field_0[index] = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 390\n        memcpy(animatedData->field_0[index], animatedData->field_4[index], width * height);\n        datafileConvertData(animatedData->field_0[index], datafileGetPalette(), width, height);\n        animatedData->field_8[index] = v5;\n        animatedData->field_C[index] = v6;\n    }\n\n    db_fclose(stream);\n\n    animatedData->width = width;\n    animatedData->height = height;\n\n    lastMouseIndex = cacheInsert(&animatedData, MOUSE_MANAGER_MOUSE_TYPE_ANIMATED, datafileGetPalette(), fileName);\n    strncpy(Cache[lastMouseIndex].field_32C, fileName, 31);\n\n    curAnim = animatedData;\n    curPal = Cache[lastMouseIndex].palette;\n    animating = true;\n\n    setShape(animatedData->field_0[0],\n        animatedData->width,\n        animatedData->height,\n        animatedData->width,\n        animatedData->field_8[0],\n        animatedData->field_C[0],\n        0);\n\n    return true;\n}\n\n// 0x485E58\nbool mouseSetMouseShape(char* fileName, int a2, int a3)\n{\n    unsigned char* palette;\n    int temp;\n    int width;\n    int height;\n    int type;\n    MouseManagerCacheEntry* cacheEntry = cacheFind(fileName, &palette, &temp, &temp, &width, &height, &type);\n    char* mangledFileName = mouseNameMangler(fileName);\n\n    if (cacheEntry == NULL) {\n        MouseManagerStaticData* staticData;\n        staticData = (MouseManagerStaticData*)mymalloc(sizeof(*staticData), __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 430\n        staticData->data = loadRawDataFile(mangledFileName, &width, &height);\n        staticData->field_4 = a2;\n        staticData->field_8 = a3;\n        staticData->width = width;\n        staticData->height = height;\n        lastMouseIndex = cacheInsert(&staticData, MOUSE_MANAGER_MOUSE_TYPE_STATIC, datafileGetPalette(), fileName);\n\n        // NOTE: Original code is slightly different. It obtains address of\n        // `staticData` and sets it's it into `cacheEntry`, which is a bit\n        // awkward. Maybe there is more level on indirection was used. Any way\n        // in order to make code path below unaltered take entire cache entry.\n        cacheEntry = &(Cache[lastMouseIndex]);\n\n        type = MOUSE_MANAGER_MOUSE_TYPE_STATIC;\n        palette = Cache[lastMouseIndex].palette;\n    }\n\n    switch (type) {\n    case MOUSE_MANAGER_MOUSE_TYPE_STATIC:\n        if (curMouseBuf != NULL) {\n            myfree(curMouseBuf, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 446\n        }\n\n        curMouseBuf = mymalloc(width * height, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 448\n        memcpy(curMouseBuf, cacheEntry->staticData->data, width * height);\n        datafileConvertData(curMouseBuf, palette, width, height);\n        setShape(curMouseBuf, width, height, width, a2, a3, 0);\n        animating = false;\n        break;\n    case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED:\n        curAnim = cacheEntry->animatedData;\n        animating = true;\n        curPal = palette;\n        break;\n    }\n\n    return true;\n}\n\n// 0x486010\nbool mouseSetMousePointer(char* fileName)\n{\n    unsigned char* palette;\n    int v1;\n    int v2;\n    int width;\n    int height;\n    int type;\n    MouseManagerCacheEntry* cacheEntry = cacheFind(fileName, &palette, &v1, &v2, &width, &height, &type);\n    if (cacheEntry != NULL) {\n        if (curMouseBuf != NULL) {\n            myfree(curMouseBuf, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 482\n            curMouseBuf = NULL;\n        }\n\n        curPal = NULL;\n        animating = false;\n        curAnim = 0;\n\n        switch (type) {\n        case MOUSE_MANAGER_MOUSE_TYPE_STATIC:\n            curMouseBuf = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 492\n            memcpy(curMouseBuf, cacheEntry->staticData->data, width * height);\n            datafileConvertData(curMouseBuf, palette, width, height);\n            setShape(curMouseBuf, width, height, width, v1, v2, 0);\n            animating = false;\n            break;\n        case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED:\n            curAnim = cacheEntry->animatedData;\n            curPal = palette;\n            curAnim->field_26 = 0;\n            curAnim->field_24 = 0;\n            setShape(curAnim->field_0[0],\n                curAnim->width,\n                curAnim->height,\n                curAnim->width,\n                curAnim->field_8[0],\n                curAnim->field_C[0],\n                0);\n            animating = true;\n            break;\n        }\n        return true;\n    }\n\n    char* dot = strrchr(fileName, '.');\n    if (dot != NULL && stricmp(dot + 1, \"mou\") == 0) {\n        return mouseSetMouseShape(fileName, 0, 0);\n    }\n\n    char* mangledFileName = mouseNameMangler(fileName);\n    File* stream = db_fopen(mangledFileName, \"r\");\n    if (stream == NULL) {\n        debug_printf(\"Can't find %s\\n\", mangledFileName);\n        return false;\n    }\n\n    char string[80];\n    string[0] = '\\0';\n    db_fgets(string, sizeof(string) - 1, stream);\n    if (string[0] == '\\0') {\n        return false;\n    }\n\n    bool rc;\n    if (strnicmp(string, \"anim\", 4) == 0) {\n        db_fclose(stream);\n        rc = mouseSetFrame(fileName, 0);\n    } else {\n        // NOTE: Uninline.\n        char* sep = strchr(string, ' ');\n        if (sep != NULL) {\n            return 0;\n        }\n\n        *sep = '\\0';\n\n        int v3;\n        int v4;\n        sscanf(sep + 1, \"%d %d\", &v3, &v4);\n\n        db_fclose(stream);\n\n        rc = mouseSetMouseShape(string, v3, v4);\n    }\n\n    strncpy(Cache[lastMouseIndex].field_32C, fileName, 31);\n\n    return rc;\n}\n\n// 0x4862AC\nvoid mousemgrResetMouse()\n{\n    MouseManagerCacheEntry* entry = &(Cache[lastMouseIndex]);\n\n    int imageWidth;\n    int imageHeight;\n    switch (entry->type) {\n    case MOUSE_MANAGER_MOUSE_TYPE_STATIC:\n        imageWidth = entry->staticData->width;\n        imageHeight = entry->staticData->height;\n        break;\n    case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED:\n        imageWidth = entry->animatedData->width;\n        imageHeight = entry->animatedData->height;\n        break;\n    }\n\n    switch (entry->type) {\n    case MOUSE_MANAGER_MOUSE_TYPE_STATIC:\n        if (curMouseBuf != NULL) {\n            if (curMouseBuf != NULL) {\n                myfree(curMouseBuf, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 572\n            }\n\n            curMouseBuf = (unsigned char*)mymalloc(imageWidth * imageHeight, __FILE__, __LINE__); // \"..\\\\int\\\\MOUSEMGR.C\", 574\n            memcpy(curMouseBuf, entry->staticData->data, imageWidth * imageHeight);\n            datafileConvertData(curMouseBuf, entry->palette, imageWidth, imageHeight);\n\n            setShape(curMouseBuf,\n                imageWidth,\n                imageHeight,\n                imageWidth,\n                entry->staticData->field_4,\n                entry->staticData->field_8,\n                0);\n        } else {\n            debug_printf(\"Hm, current mouse type is M_STATIC, but no current mouse pointer\\n\");\n        }\n        break;\n    case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED:\n        if (curAnim != NULL) {\n            for (int index = 0; index < curAnim->frameCount; index++) {\n                memcpy(curAnim->field_0[index], curAnim->field_4[index], imageWidth * imageHeight);\n                datafileConvertData(curAnim->field_0[index], entry->palette, imageWidth, imageHeight);\n            }\n\n            setShape(curAnim->field_0[curAnim->field_26],\n                imageWidth,\n                imageHeight,\n                imageWidth,\n                curAnim->field_8[curAnim->field_26],\n                curAnim->field_C[curAnim->field_26],\n                0);\n        } else {\n            debug_printf(\"Hm, current mouse type is M_ANIMATED, but no current mouse pointer\\n\");\n        }\n    }\n}\n\n// 0x4865C4\nvoid mouseHide()\n{\n    mouse_hide();\n}\n\n// 0x4865CC\nvoid mouseShow()\n{\n    mouse_show();\n}\n"
  },
  {
    "path": "src/int/mousemgr.h",
    "content": "#ifndef FALLOUT_INT_MOUSEMGR_H_\n#define FALLOUT_INT_MOUSEMGR_H_\n\n#include <stdbool.h>\n\ntypedef char*(MouseManagerNameMangler)(char* fileName);\ntypedef int(MouseManagerRateProvider)();\ntypedef int(MouseManagerTimeProvider)();\n\nvoid mousemgrSetNameMangler(MouseManagerNameMangler* func);\nvoid mousemgrSetTimeCallback(MouseManagerRateProvider* rateFunc, MouseManagerTimeProvider* currentTimeFunc);\nvoid initMousemgr();\nvoid mousemgrClose();\nvoid mousemgrUpdate();\nint mouseSetFrame(char* fileName, int a2);\nbool mouseSetMouseShape(char* fileName, int a2, int a3);\nbool mouseSetMousePointer(char* fileName);\nvoid mousemgrResetMouse();\nvoid mouseHide();\nvoid mouseShow();\n\n#endif /* FALLOUT_INT_MOUSEMGR_H_ */\n"
  },
  {
    "path": "src/int/movie.c",
    "content": "#include \"int/movie.h\"\n\n#include <string.h>\n\n#include \"int/window.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/gconfig.h\"\n#include \"int/memdbg.h\"\n#include \"game/moviefx.h\"\n#include \"movie_lib.h\"\n#include \"int/sound.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/svga.h\"\n#include \"plib/gnw/winmain.h\"\n\n\ntypedef void(MovieCallback)();\ntypedef int(MovieBlitFunc)(int win, unsigned char* data, int width, int height, int pitch);\n\ntypedef struct MovieSubtitleListNode {\n    int num;\n    char* text;\n    struct MovieSubtitleListNode* next;\n} MovieSubtitleListNode;\n\nstatic void* movieMalloc(size_t size);\nstatic void movieFree(void* ptr);\nstatic bool movieRead(int fileHandle, void* buf, int count);\nstatic void movie_MVE_ShowFrame(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9);\nstatic void movieShowFrame(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9);\nstatic int movieScaleSubRect(int win, unsigned char* data, int width, int height, int pitch);\nstatic int movieScaleWindowAlpha(int win, unsigned char* data, int width, int height, int pitch);\nstatic int movieScaleSubRectAlpha(int win, unsigned char* data, int width, int height, int pitch);\nstatic int blitAlpha(int win, unsigned char* data, int width, int height, int pitch);\nstatic int movieScaleWindow(int win, unsigned char* data, int width, int height, int pitch);\nstatic int blitNormal(int win, unsigned char* data, int width, int height, int pitch);\nstatic void movieSetPalette(unsigned char* palette, int start, int end);\nstatic int noop();\nstatic void cleanupMovie(int a1);\nstatic void cleanupLast();\nstatic File* openFile(char* filePath);\nstatic void openSubtitle(char* filePath);\nstatic void doSubtitle();\nstatic int movieStart(int win, char* filePath, int (*a3)());\nstatic bool localMovieCallback();\nstatic int stepMovie();\n\n// 0x5195B8\nstatic int GNWWin = -1;\n\n// 0x5195BC\nstatic int subtitleFont = -1;\n\n// 0x5195C0\nstatic MovieBlitFunc* showFrameFuncs[2][2][2] = {\n    {\n        {\n            blitNormal,\n            blitNormal,\n        },\n        {\n            movieScaleWindow,\n            movieScaleSubRect,\n        },\n    },\n    {\n        {\n            blitAlpha,\n            blitAlpha,\n        },\n        {\n            movieScaleSubRectAlpha,\n            movieScaleWindowAlpha,\n        },\n    },\n};\n\n// 0x5195E0\nstatic MoviePaletteFunc* paletteFunc = setSystemPaletteEntries;\n\n// 0x5195E4\nstatic int subtitleR = 31;\n\n// 0x5195E8\nstatic int subtitleG = 31;\n\n// 0x5195EC\nstatic int subtitleB = 31;\n\n// 0x638E10\nstatic Rect winRect;\n\n// 0x638E20\nstatic Rect movieRect;\n\n// 0x638E30\nstatic MovieCallback* movieCallback;\n\n// 0x638E34\nstatic MovieEndFunc* endMovieFunc;\n\n// 0x638E38\nstatic MovieUpdateCallbackProc* updateCallbackFunc;\n\n// 0x638E3C\nstatic MovieFailedOpenFunc* failedOpenFunc;\n\n// 0x638E40\nstatic MovieSubtitleFunc* subtitleFilenameFunc;\n\n// 0x638E44\nstatic MovieStartFunc* startMovieFunc;\n\n// 0x638E48\nstatic int subtitleW;\n\n// 0x638E4C\nstatic int lastMovieBH;\n\n// 0x638E50\nstatic int lastMovieBW;\n\n// 0x638E54\nstatic int lastMovieSX;\n\n// 0x638E58\nstatic int lastMovieSY;\n\n// 0x638E5C\nstatic int movieScaleFlag;\n\n// 0x638E60\nstatic MoviePreDrawFunc* moviePreDrawFunc;\n\n// 0x638E64\nstatic int lastMovieH;\n\n// 0x638E68\nstatic int lastMovieW;\n\n// 0x638E6C\nstatic int lastMovieX;\n\n// 0x638E70\nstatic int lastMovieY;\n\n// 0x638E74\nstatic MovieSubtitleListNode* subtitleList;\n\n// 0x638E78\nstatic unsigned int movieFlags;\n\n// 0x638E7C\nstatic int movieAlphaFlag;\n\n// 0x638E80\nstatic bool movieSubRectFlag;\n\n// 0x638E84\nstatic int movieH;\n\n// 0x638E88\nstatic int movieOffset;\n\n// 0x638E8C\nstatic MovieCaptureFrameProc* movieCaptureFrameFunc;\n\n// 0x638E90\nstatic unsigned char* lastMovieBuffer;\n\n// 0x638E94\nstatic int movieW;\n\n// 0x638E98\nstatic MovieFrameGrabProc* movieFrameGrabFunc;\n\n// 0x638E9C\nstatic LPDIRECTDRAWSURFACE MVE_lastBuffer;\n\n// 0x638EA0\nstatic int subtitleH;\n\n// 0x638EA4\nstatic int running;\n\n// 0x638EA8\nstatic File* handle;\n\n// 0x638EAC\nstatic unsigned char* alphaWindowBuf;\n\n// 0x638EB0\nstatic int movieX;\n\n// 0x638EB4\nstatic int movieY;\n\n// 0x638EB8\nstatic bool soundEnabled;\n\n// 0x638EBC\nstatic File* alphaHandle;\n\n// 0x638EC0\nstatic unsigned char* alphaBuf;\n\n// NOTE: Unused.\n//\n// 0x4865E0\nvoid movieSetPreDrawFunc(MoviePreDrawFunc* func)\n{\n    moviePreDrawFunc = func;\n}\n\n// NOTE: Unused.\n//\n// 0x4865E8\nvoid movieSetFailedOpenFunc(MovieFailedOpenFunc* func)\n{\n    failedOpenFunc = func;\n}\n\n// NOTE: Unused.\n//\n// 0x4865F0\nvoid movieSetFunc(MovieStartFunc* startFunc, MovieEndFunc* endFunc)\n{\n    startMovieFunc = startFunc;\n    endMovieFunc = endFunc;\n}\n\n// 0x4865FC\nstatic void* movieMalloc(size_t size)\n{\n    return mymalloc(size, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 209\n}\n\n// 0x486614\nstatic void movieFree(void* ptr)\n{\n    myfree(ptr, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 213\n}\n\n// 0x48662C\nstatic bool movieRead(int fileHandle, void* buf, int count)\n{\n    return db_fread(buf, 1, count, (File*)fileHandle) == count;\n}\n\n// 0x486654\nstatic void movie_MVE_ShowFrame(LPDIRECTDRAWSURFACE surface, int srcWidth, int srcHeight, int srcX, int srcY, int destWidth, int destHeight, int a8, int a9)\n{\n    int v14;\n    int v15;\n\n    DDSURFACEDESC ddsd;\n    memset(&ddsd, 0, sizeof(DDSURFACEDESC));\n    ddsd.dwSize = sizeof(DDSURFACEDESC);\n\n    RECT srcRect;\n    srcRect.left = srcX;\n    srcRect.top = srcY;\n    srcRect.right = srcWidth + srcX;\n    srcRect.bottom = srcHeight + srcY;\n\n    v14 = winRect.lrx - winRect.ulx;\n    v15 = winRect.lrx - winRect.ulx + 1;\n\n    RECT destRect;\n\n    if (movieScaleFlag) {\n        if ((movieFlags & MOVIE_EXTENDED_FLAG_0x08) != 0) {\n            destRect.top = (winRect.lry - winRect.uly + 1 - destHeight) / 2;\n            destRect.left = (v15 - 4 * srcWidth / 3) / 2;\n        } else {\n            destRect.top = movieY + winRect.uly;\n            destRect.left = winRect.ulx + movieX;\n        }\n\n        destRect.right = 4 * srcWidth / 3 + destRect.left;\n        destRect.bottom = destHeight + destRect.top;\n    } else {\n        if ((movieFlags & MOVIE_EXTENDED_FLAG_0x08) != 0) {\n            destRect.top = (winRect.lry - winRect.uly + 1 - destHeight) / 2;\n            destRect.left = (v15 - destWidth) / 2;\n        } else {\n            destRect.top = movieY + winRect.uly;\n            destRect.left = winRect.ulx + movieX;\n        }\n        destRect.right = destWidth + destRect.left;\n        destRect.bottom = destHeight + destRect.top;\n    }\n\n    lastMovieSX = srcX;\n    lastMovieSY = srcY;\n    lastMovieX = destRect.left;\n    lastMovieY = destRect.top;\n    lastMovieBH = srcHeight;\n    lastMovieW = destRect.right - destRect.left;\n    MVE_lastBuffer = surface;\n    lastMovieBW = srcWidth;\n    lastMovieH = destRect.bottom - destRect.top;\n\n    HRESULT hr;\n    do {\n        if (movieCaptureFrameFunc != NULL) {\n            if (IDirectDrawSurface_Lock(surface, NULL, &ddsd, 1, NULL) == DD_OK) {\n                unsigned char* data = (unsigned char*)ddsd.lpSurface + ddsd.lPitch * srcY + srcX;\n                movieCaptureFrameFunc(data,\n                    srcWidth,\n                    srcHeight,\n                    ddsd.lPitch,\n                    destRect.left,\n                    destRect.top,\n                    destRect.right - destRect.left,\n                    destRect.bottom - destRect.top);\n                IDirectDrawSurface_Unlock(surface, ddsd.lpSurface);\n            }\n        }\n\n        hr = IDirectDrawSurface_Blt(GNW95_DDPrimarySurface, &destRect, surface, &srcRect, 0, NULL);\n    } while (hr != DD_OK && hr != DDERR_SURFACELOST && hr == DDERR_WASSTILLDRAWING);\n}\n\n// 0x486900\nstatic void movieShowFrame(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)\n{\n    if (GNWWin == -1) {\n        return;\n    }\n\n    lastMovieBW = a2;\n    MVE_lastBuffer = a1;\n    lastMovieBH = a2;\n    lastMovieW = a6;\n    lastMovieH = a7;\n    lastMovieX = a4;\n    lastMovieY = a5;\n    lastMovieSX = a4;\n    lastMovieSY = a5;\n\n    DDSURFACEDESC ddsd;\n    ddsd.dwSize = sizeof(DDSURFACEDESC);\n\n    if (IDirectDrawSurface_Lock(a1, NULL, &ddsd, 1, NULL) != DD_OK) {\n        return;\n    }\n\n    unsigned char* data = (unsigned char*)ddsd.lpSurface + ddsd.lPitch * a5 + a4;\n\n    if (movieCaptureFrameFunc != NULL) {\n        // FIXME: Looks wrong as it ignores lPitch (as seen in movie_MVE_ShowFrame).\n        movieCaptureFrameFunc(data, a2, a3, a2, movieRect.ulx, movieRect.uly, a6, a7);\n    }\n\n    if (movieFrameGrabFunc != NULL) {\n        movieFrameGrabFunc(data, a2, a3, ddsd.lPitch);\n    } else {\n        MovieBlitFunc* func = showFrameFuncs[movieAlphaFlag][movieScaleFlag][movieSubRectFlag];\n        if (func(GNWWin, data, a2, a3, ddsd.lPitch) != 0) {\n            if (moviePreDrawFunc != NULL) {\n                moviePreDrawFunc(GNWWin, &movieRect);\n            }\n\n            win_draw_rect(GNWWin, &movieRect);\n        }\n    }\n\n    IDirectDrawSurface_Unlock(a1, ddsd.lpSurface);\n}\n\n// NOTE: Unused.\n//\n// 0x486A98\nvoid movieSetFrameGrabFunc(MovieFrameGrabProc* func)\n{\n    movieFrameGrabFunc = func;\n}\n\n// NOTE: Unused.\n//\n// 0x486AA0\nvoid movieSetCaptureFrameFunc(MovieCaptureFrameProc* func)\n{\n    movieCaptureFrameFunc = func;\n}\n\n// 0x486B68\nstatic int movieScaleSubRect(int win, unsigned char* data, int width, int height, int pitch)\n{\n    int windowWidth = win_width(win);\n    unsigned char* windowBuffer = win_get_buf(win) + windowWidth * movieY + movieX;\n    if (width * 4 / 3 > movieW) {\n        movieFlags |= 0x01;\n        return 0;\n    }\n\n    int v1 = width / 3;\n    for (int y = 0; y < height; y++) {\n        int x;\n        for (x = 0; x < v1; x++) {\n            unsigned int value = data[0];\n            value |= data[1] << 8;\n            value |= data[2] << 16;\n            value |= data[2] << 24;\n\n            *(unsigned int*)windowBuffer = value;\n\n            windowBuffer += 4;\n            data += 3;\n        }\n\n        for (x = x * 3; x < width; x++) {\n            *windowBuffer++ = *data++;\n        }\n\n        data += pitch - width;\n        windowBuffer += windowWidth - movieW;\n    }\n\n    return 1;\n}\n\n// 0x486C74\nstatic int movieScaleWindowAlpha(int win, unsigned char* data, int width, int height, int pitch)\n{\n    movieFlags |= 1;\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x486C74.\nstatic int movieScaleSubRectAlpha(int win, unsigned char* data, int width, int height, int pitch)\n{\n    movieFlags |= 1;\n    return 0;\n}\n\n// 0x486C80\nstatic int blitAlpha(int win, unsigned char* data, int width, int height, int pitch)\n{\n    int windowWidth = win_width(win);\n    unsigned char* windowBuffer = win_get_buf(win);\n    alphaBltBuf(data, width, height, pitch, alphaWindowBuf, alphaBuf, windowBuffer + windowWidth * movieY + movieX, windowWidth);\n    return 1;\n}\n\n// 0x486CD4\nstatic int movieScaleWindow(int win, unsigned char* data, int width, int height, int pitch)\n{\n    int windowWidth = win_width(win);\n    if (width != 3 * windowWidth / 4) {\n        movieFlags |= 1;\n        return 0;\n    }\n\n    unsigned char* windowBuffer = win_get_buf(win);\n    for (int y = 0; y < height; y++) {\n        int scaledWidth = width / 3;\n        for (int x = 0; x < scaledWidth; x++) {\n            unsigned int value = data[0];\n            value |= data[1] << 8;\n            value |= data[2] << 16;\n            value |= data[3] << 24;\n\n            *(unsigned int*)windowBuffer = value;\n\n            windowBuffer += 4;\n            data += 3;\n        }\n        data += pitch - width;\n    }\n\n    return 1;\n}\n\n// 0x486D84\nstatic int blitNormal(int win, unsigned char* data, int width, int height, int pitch)\n{\n    int windowWidth = win_width(win);\n    unsigned char* windowBuffer = win_get_buf(win);\n    drawScaled(windowBuffer + windowWidth * movieY + movieX, movieW, movieH, windowWidth, data, width, height, pitch);\n    return 1;\n}\n\n// 0x486DDC\nstatic void movieSetPalette(unsigned char* palette, int start, int end)\n{\n    if (end != 0) {\n        paletteFunc(palette + start * 3, start, end + start - 1);\n    }\n}\n\n// 0x486E08\nstatic int noop()\n{\n    return 0;\n}\n\n// initMovie\n// 0x486E0C\nvoid initMovie()\n{\n    movieLibSetMemoryProcs(movieMalloc, movieFree);\n    movieLibSetDirectSound(soundDSObject);\n    soundEnabled = (soundDSObject != NULL);\n    movieLibSetDirectDraw(GNW95_DDObject);\n    movieLibSetPaletteEntriesProc(movieSetPalette);\n    _MVE_sfSVGA(640, 480, 480, 0, 0, 0, 0, 0, 0);\n    movieLibSetReadProc(movieRead);\n}\n\n// 0x486E98\nstatic void cleanupMovie(int a1)\n{\n    if (!running) {\n        return;\n    }\n\n    if (endMovieFunc != NULL) {\n        endMovieFunc(GNWWin, movieX, movieY, movieW, movieH);\n    }\n\n    int frame;\n    int dropped;\n    _MVE_rmFrameCounts(&frame, &dropped);\n    debug_printf(\"Frames %d, dropped %d\\n\", frame, dropped);\n\n    if (lastMovieBuffer != NULL) {\n        myfree(lastMovieBuffer, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 787\n        lastMovieBuffer = NULL;\n    }\n\n    if (MVE_lastBuffer != NULL) {\n        DDSURFACEDESC ddsd;\n        ddsd.dwSize = sizeof(DDSURFACEDESC);\n        if (IDirectDrawSurface_Lock(MVE_lastBuffer, 0, &ddsd, 1, NULL) == DD_OK) {\n            lastMovieBuffer = (unsigned char*)mymalloc(lastMovieBH * lastMovieBW, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 802\n            buf_to_buf((unsigned char*)ddsd.lpSurface + ddsd.lPitch * lastMovieSX + lastMovieSY, lastMovieBW, lastMovieBH, ddsd.lPitch, lastMovieBuffer, lastMovieBW);\n            IDirectDrawSurface_Unlock(MVE_lastBuffer, ddsd.lpSurface);\n        } else {\n            debug_printf(\"Couldn't lock movie surface\\n\");\n        }\n\n        MVE_lastBuffer = NULL;\n    }\n\n    if (a1) {\n        _MVE_rmEndMovie();\n    }\n\n    _MVE_ReleaseMem();\n\n    db_fclose(handle);\n\n    if (alphaWindowBuf != NULL) {\n        buf_to_buf(alphaWindowBuf, movieW, movieH, movieW, win_get_buf(GNWWin) + movieY * win_width(GNWWin) + movieX, win_width(GNWWin));\n        win_draw_rect(GNWWin, &movieRect);\n    }\n\n    if (alphaHandle != NULL) {\n        db_fclose(alphaHandle);\n        alphaHandle = NULL;\n    }\n\n    if (alphaBuf != NULL) {\n        myfree(alphaBuf, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 840\n        alphaBuf = NULL;\n    }\n\n    if (alphaWindowBuf != NULL) {\n        myfree(alphaWindowBuf, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 845\n        alphaWindowBuf = NULL;\n    }\n\n    while (subtitleList != NULL) {\n        MovieSubtitleListNode* next = subtitleList->next;\n        myfree(subtitleList->text, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 851\n        myfree(subtitleList, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 852\n        subtitleList = next;\n    }\n\n    running = 0;\n    movieSubRectFlag = 0;\n    movieScaleFlag = 0;\n    movieAlphaFlag = 0;\n    movieFlags = 0;\n    GNWWin = -1;\n}\n\n// 0x48711C\nvoid movieClose()\n{\n    cleanupMovie(1);\n\n    if (lastMovieBuffer) {\n        myfree(lastMovieBuffer, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 869\n        lastMovieBuffer = NULL;\n    }\n}\n\n// 0x487150\nvoid movieStop()\n{\n    if (running) {\n        movieFlags |= MOVIE_EXTENDED_FLAG_0x02;\n    }\n}\n\n// 0x487164\nint movieSetFlags(int flags)\n{\n    if ((flags & MOVIE_FLAG_0x04) != 0) {\n        movieFlags |= MOVIE_EXTENDED_FLAG_0x04 | MOVIE_EXTENDED_FLAG_0x08;\n    } else {\n        movieFlags &= ~MOVIE_EXTENDED_FLAG_0x08;\n        if ((flags & MOVIE_FLAG_0x02) != 0) {\n            movieFlags |= MOVIE_EXTENDED_FLAG_0x04;\n        } else {\n            movieFlags &= ~MOVIE_EXTENDED_FLAG_0x04;\n        }\n    }\n\n    if ((flags & MOVIE_FLAG_0x01) != 0) {\n        movieScaleFlag = 1;\n\n        if ((movieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) {\n            _sub_4F4BB(3);\n        }\n    } else {\n        movieScaleFlag = 0;\n\n        if ((movieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) {\n            _sub_4F4BB(4);\n        } else {\n            movieFlags &= ~MOVIE_EXTENDED_FLAG_0x08;\n        }\n    }\n\n    if ((flags & MOVIE_FLAG_0x08) != 0) {\n        movieFlags |= MOVIE_EXTENDED_FLAG_0x10;\n    } else {\n        movieFlags &= ~MOVIE_EXTENDED_FLAG_0x10;\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x48720C\nvoid movieSetSubtitleFont(int font)\n{\n    subtitleFont = font;\n}\n\n// NOTE: Unused.\n//\n// 0x487214\nvoid movieSetSubtitleColor(float r, float g, float b)\n{\n    subtitleR = (int)(r * 31.0f);\n    subtitleG = (int)(g * 31.0f);\n    subtitleB = (int)(b * 31.0f);\n}\n\n// 0x48725C\nvoid movieSetPaletteFunc(MoviePaletteFunc* func)\n{\n    paletteFunc = func != NULL ? func : setSystemPaletteEntries;\n}\n\n// 0x487274\nvoid movieSetCallback(MovieUpdateCallbackProc* func)\n{\n    updateCallbackFunc = func;\n}\n\n// 0x4872E8\nstatic void cleanupLast()\n{\n    if (lastMovieBuffer != NULL) {\n        myfree(lastMovieBuffer, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 981\n        lastMovieBuffer = NULL;\n    }\n\n    MVE_lastBuffer = NULL;\n}\n\n// 0x48731C\nstatic File* openFile(char* filePath)\n{\n    handle = db_fopen(filePath, \"rb\");\n    if (handle == NULL) {\n        if (failedOpenFunc == NULL) {\n            debug_printf(\"Couldn't find movie file %s\\n\", filePath);\n            return 0;\n        }\n\n        while (handle == NULL && failedOpenFunc(filePath) != 0) {\n            handle = db_fopen(filePath, \"rb\");\n        }\n    }\n    return handle;\n}\n\n// 0x487380\nstatic void openSubtitle(char* filePath)\n{\n    subtitleW = windowGetXres();\n    subtitleH = text_height() + 4;\n\n    if (subtitleFilenameFunc != NULL) {\n        filePath = subtitleFilenameFunc(filePath);\n    }\n\n    char path[MAX_PATH];\n    strcpy(path, filePath);\n\n    debug_printf(\"Opening subtitle file %s\\n\", path);\n    File* stream = db_fopen(path, \"r\");\n    if (stream == NULL) {\n        debug_printf(\"Couldn't open subtitle file %s\\n\", path);\n        movieFlags &= ~MOVIE_EXTENDED_FLAG_0x10;\n        return;\n    }\n\n    MovieSubtitleListNode* prev = NULL;\n    int subtitleCount = 0;\n    while (!db_feof(stream)) {\n        char string[260];\n        string[0] = '\\0';\n        db_fgets(string, 259, stream);\n        if (*string == '\\0') {\n            break;\n        }\n\n        MovieSubtitleListNode* subtitle = (MovieSubtitleListNode*)mymalloc(sizeof(*subtitle), __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 1050\n        subtitle->next = NULL;\n\n        subtitleCount++;\n\n        char* pch;\n\n        pch = strchr(string, '\\n');\n        if (pch != NULL) {\n            *pch = '\\0';\n        }\n\n        pch = strchr(string, '\\r');\n        if (pch != NULL) {\n            *pch = '\\0';\n        }\n\n        pch = strchr(string, ':');\n        if (pch != NULL) {\n            *pch = '\\0';\n            subtitle->num = atoi(string);\n            subtitle->text = mystrdup(pch + 1, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 1058\n\n            if (prev != NULL) {\n                prev->next = subtitle;\n            } else {\n                subtitleList = subtitle;\n            }\n\n            prev = subtitle;\n        } else {\n            debug_printf(\"subtitle: couldn't parse %s\\n\", string);\n        }\n    }\n\n    db_fclose(stream);\n\n    debug_printf(\"Read %d subtitles\\n\", subtitleCount);\n}\n\n// 0x48755C\nstatic void doSubtitle()\n{\n    if (subtitleList == NULL) {\n        return;\n    }\n\n    if ((movieFlags & MOVIE_EXTENDED_FLAG_0x10) == 0) {\n        return;\n    }\n\n    int v1 = text_height();\n    int v2 = (480 - lastMovieH - lastMovieY - v1) / 2 + lastMovieH + lastMovieY;\n\n    if (subtitleH + v2 > windowGetYres()) {\n        subtitleH = windowGetYres() - v2;\n    }\n\n    int frame;\n    int dropped;\n    _MVE_rmFrameCounts(&frame, &dropped);\n\n    while (subtitleList != NULL) {\n        if (frame < subtitleList->num) {\n            break;\n        }\n\n        MovieSubtitleListNode* next = subtitleList->next;\n\n        win_fill(GNWWin, 0, v2, subtitleW, subtitleH, 0);\n\n        int oldFont;\n        if (subtitleFont != -1) {\n            oldFont = text_curr();\n            text_font(subtitleFont);\n        }\n\n        int colorIndex = (subtitleR << 10) | (subtitleG << 5) | subtitleB;\n        windowWrapLine(GNWWin, subtitleList->text, subtitleW, subtitleH, 0, v2, colorTable[colorIndex] | 0x2000000, TEXT_ALIGNMENT_CENTER);\n\n        Rect rect;\n        rect.lrx = subtitleW;\n        rect.uly = v2;\n        rect.lry = v2 + subtitleH;\n        rect.ulx = 0;\n        win_draw_rect(GNWWin, &rect);\n\n        myfree(subtitleList->text, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 1108\n        myfree(subtitleList, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 1109\n\n        subtitleList = next;\n\n        if (subtitleFont != -1) {\n            text_font(oldFont);\n        }\n    }\n}\n\n// 0x487710\nstatic int movieStart(int win, char* filePath, int (*a3)())\n{\n    int v15;\n    int v16;\n    int v17;\n\n    if (running) {\n        return 1;\n    }\n\n    cleanupLast();\n\n    handle = openFile(filePath);\n    if (handle == NULL) {\n        return 1;\n    }\n\n    GNWWin = win;\n    running = 1;\n    movieFlags &= ~MOVIE_EXTENDED_FLAG_0x01;\n\n    if ((movieFlags & MOVIE_EXTENDED_FLAG_0x10) != 0) {\n        openSubtitle(filePath);\n    }\n\n    if ((movieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) {\n        debug_printf(\"Direct \");\n        win_get_rect(GNWWin, &winRect);\n        debug_printf(\"Playing at (%d, %d)  \", movieX + winRect.ulx, movieY + winRect.uly);\n        _MVE_rmCallbacks(a3);\n        _MVE_sfCallbacks(movie_MVE_ShowFrame);\n\n        v17 = 0;\n        v16 = movieY + winRect.uly;\n        v15 = movieX + winRect.ulx;\n    } else {\n        debug_printf(\"Buffered \");\n        _MVE_rmCallbacks(a3);\n        _MVE_sfCallbacks(movieShowFrame);\n        v17 = 0;\n        v16 = 0;\n        v15 = 0;\n    }\n\n    _MVE_rmPrepMovie((int)handle, v15, v16, v17);\n\n    if (movieScaleFlag) {\n        debug_printf(\"scaled\\n\");\n    } else {\n        debug_printf(\"not scaled\\n\");\n    }\n\n    if (startMovieFunc != NULL) {\n        startMovieFunc(GNWWin);\n    }\n\n    if (alphaHandle != NULL) {\n        unsigned long size;\n        db_freadLong(alphaHandle, &size);\n\n        short tmp;\n        db_freadShort(alphaHandle, &tmp);\n        db_freadShort(alphaHandle, &tmp);\n\n        alphaBuf = (unsigned char*)mymalloc(size, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 1178\n        alphaWindowBuf = (unsigned char*)mymalloc(movieH * movieW, __FILE__, __LINE__); // \"..\\\\int\\\\MOVIE.C\", 1179\n\n        unsigned char* windowBuffer = win_get_buf(GNWWin);\n        buf_to_buf(windowBuffer + win_width(GNWWin) * movieY + movieX,\n            movieW,\n            movieH,\n            win_width(GNWWin),\n            alphaWindowBuf,\n            movieW);\n    }\n\n    movieRect.ulx = movieX;\n    movieRect.uly = movieY;\n    movieRect.lrx = movieW + movieX;\n    movieRect.lry = movieH + movieY;\n\n    return 0;\n}\n\n// 0x487964\nstatic bool localMovieCallback()\n{\n    doSubtitle();\n\n    if (movieCallback != NULL) {\n        movieCallback();\n    }\n\n    return get_input() != -1;\n}\n\n// 0x487AC8\nint movieRun(int win, char* filePath)\n{\n    if (running) {\n        return 1;\n    }\n\n    movieX = 0;\n    movieY = 0;\n    movieOffset = 0;\n    movieW = win_width(win);\n    movieH = win_height(win);\n    movieSubRectFlag = 0;\n    return movieStart(win, filePath, noop);\n}\n\n// 0x487B1C\nint movieRunRect(int win, char* filePath, int a3, int a4, int a5, int a6)\n{\n    if (running) {\n        return 1;\n    }\n\n    movieX = a3;\n    movieY = a4;\n    movieOffset = a3 + a4 * win_width(win);\n    movieW = a5;\n    movieH = a6;\n    movieSubRectFlag = 1;\n\n    return movieStart(win, filePath, noop);\n}\n\n// 0x487B7C\nstatic int stepMovie()\n{\n    if (alphaHandle != NULL) {\n        unsigned long size;\n        db_freadLong(alphaHandle, &size);\n        db_fread(alphaBuf, 1, size, alphaHandle);\n    }\n\n    int v1 = _MVE_rmStepMovie();\n    if (v1 != -1) {\n        doSubtitle();\n    }\n\n    return v1;\n}\n\n// 0x487BC8\nvoid movieSetSubtitleFunc(MovieSubtitleFunc* func)\n{\n    subtitleFilenameFunc = func;\n}\n\n// 0x487BD0\nvoid movieSetVolume(int volume)\n{\n    if (soundEnabled) {\n        int normalizedVolume = soundVolumeHMItoDirectSound(volume);\n        movieLibSetVolume(normalizedVolume);\n    }\n}\n\n// 0x487BEC\nvoid movieUpdate()\n{\n    if (!running) {\n        return;\n    }\n\n    if ((movieFlags & MOVIE_EXTENDED_FLAG_0x02) != 0) {\n        debug_printf(\"Movie aborted\\n\");\n        cleanupMovie(1);\n        return;\n    }\n\n    if ((movieFlags & MOVIE_EXTENDED_FLAG_0x01) != 0) {\n        debug_printf(\"Movie error\\n\");\n        cleanupMovie(1);\n        return;\n    }\n\n    if (stepMovie() == -1) {\n        cleanupMovie(1);\n        return;\n    }\n\n    if (updateCallbackFunc != NULL) {\n        int frame;\n        int dropped;\n        _MVE_rmFrameCounts(&frame, &dropped);\n        updateCallbackFunc(frame);\n    }\n}\n\n// 0x487C88\nint moviePlaying()\n{\n    return running;\n}\n"
  },
  {
    "path": "src/int/movie.h",
    "content": "#ifndef FALLOUT_INT_MOVIE_H_\n#define FALLOUT_INT_MOVIE_H_\n\n#include \"plib/gnw/rect.h\"\n\ntypedef enum MovieFlags {\n    MOVIE_FLAG_0x01 = 0x01,\n    MOVIE_FLAG_0x02 = 0x02,\n    MOVIE_FLAG_0x04 = 0x04,\n    MOVIE_FLAG_0x08 = 0x08,\n} MovieFlags;\n\ntypedef enum MovieExtendedFlags {\n    MOVIE_EXTENDED_FLAG_0x01 = 0x01,\n    MOVIE_EXTENDED_FLAG_0x02 = 0x02,\n    MOVIE_EXTENDED_FLAG_0x04 = 0x04,\n    MOVIE_EXTENDED_FLAG_0x08 = 0x08,\n    MOVIE_EXTENDED_FLAG_0x10 = 0x10,\n} MovieExtendedFlags;\n\ntypedef char*(MovieSubtitleFunc)(char* movieFilePath);\ntypedef void(MoviePaletteFunc)(unsigned char* palette, int start, int end);\ntypedef void(MovieUpdateCallbackProc)(int frame);\ntypedef void(MovieFrameGrabProc)(unsigned char* data, int width, int height, int pitch);\ntypedef void(MovieCaptureFrameProc)(unsigned char* data, int width, int height, int pitch, int movieX, int movieY, int movieWidth, int movieHeight);\ntypedef void(MoviePreDrawFunc)(int win, Rect* rect);\ntypedef void(MovieStartFunc)(int win);\ntypedef void(MovieEndFunc)(int win, int x, int y, int width, int height);\ntypedef int(MovieFailedOpenFunc)(char* path);\n\nvoid movieSetPreDrawFunc(MoviePreDrawFunc* func);\nvoid movieSetFailedOpenFunc(MovieFailedOpenFunc* func);\nvoid movieSetFunc(MovieStartFunc* startFunc, MovieEndFunc* endFunc);\nvoid movieSetFrameGrabFunc(MovieFrameGrabProc* func);\nvoid movieSetCaptureFrameFunc(MovieCaptureFrameProc* func);\nvoid initMovie();\nvoid movieClose();\nvoid movieStop();\nint movieSetFlags(int a1);\nvoid movieSetSubtitleFont(int font);\nvoid movieSetSubtitleColor(float r, float g, float b);\nvoid movieSetPaletteFunc(MoviePaletteFunc* func);\nvoid movieSetCallback(MovieUpdateCallbackProc* func);\nint movieRun(int win, char* filePath);\nint movieRunRect(int win, char* filePath, int a3, int a4, int a5, int a6);\nvoid movieSetSubtitleFunc(MovieSubtitleFunc* proc);\nvoid movieSetVolume(int volume);\nvoid movieUpdate();\nint moviePlaying();\n\n#endif /* FALLOUT_INT_MOVIE_H_ */\n"
  },
  {
    "path": "src/int/nevs.c",
    "content": "#include \"int/nevs.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"int/intlib.h\"\n#include \"int/memdbg.h\"\n\n#define NEVS_COUNT 40\n\ntypedef struct Nevs {\n    bool used;\n    char name[32];\n    Program* program;\n    int proc;\n    int type;\n    int hits;\n    bool busy;\n    NevsCallback* callback;\n} Nevs;\n\nstatic_assert(sizeof(Nevs) == 60, \"wrong size\");\n\nstatic Nevs* nevs_alloc();\nstatic void nevs_free(Nevs* nevs);\nstatic void nevs_removeprogramreferences(Program* program);\nstatic Nevs* nevs_find(const char* name);\n\n// 0x6391C8\nstatic Nevs* nevs;\n\n// 0x6391CC\nstatic int anyhits;\n\n// 0x488340\nstatic Nevs* nevs_alloc()\n{\n    int index;\n    Nevs* entry;\n\n    if (nevs == NULL) {\n        debug_printf(\"nevs_alloc(): nevs_initonce() not called!\");\n        exit(99);\n    }\n\n    for (index = 0; index < NEVS_COUNT; index++) {\n        entry = &(nevs[index]);\n        if (!entry->used) {\n            // NOTE: Uninline.\n            nevs_free(entry);\n            return entry;\n        }\n    }\n\n    return NULL;\n}\n\n// NOTE: Inlined.\n//\n// 0x488394\nstatic void nevs_free(Nevs* entry)\n{\n    entry->used = false;\n    memset(entry, 0, sizeof(*entry));\n}\n\n// 0x4883AC\nvoid nevs_close()\n{\n    if (nevs != NULL) {\n        myfree(nevs, __FILE__, __LINE__); // \"..\\\\int\\\\NEVS.C\", 97\n        nevs = NULL;\n    }\n}\n\n// 0x4883D4\nstatic void nevs_removeprogramreferences(Program* program)\n{\n    int index;\n    Nevs* entry;\n\n    if (nevs != NULL) {\n        for (index = 0; index < NEVS_COUNT; index++) {\n            entry = &(nevs[index]);\n            if (entry->used && entry->program == program) {\n                // NOTE: Uninline.\n                nevs_free(entry);\n            }\n        }\n    }\n}\n\n// 0x488418\nvoid nevs_initonce()\n{\n    interpretRegisterProgramDeleteCallback(nevs_removeprogramreferences);\n\n    if (nevs == NULL) {\n        nevs = (Nevs*)mycalloc(sizeof(Nevs), NEVS_COUNT, __FILE__, __LINE__); // \"..\\\\int\\\\NEVS.C\", 131\n        if (nevs == NULL) {\n            debug_printf(\"nevs_initonce(): out of memory\");\n            exit(99);\n        }\n    }\n}\n\n// 0x48846C\nstatic Nevs* nevs_find(const char* name)\n{\n    int index;\n    Nevs* entry;\n\n    if (nevs == NULL) {\n        debug_printf(\"nevs_find(): nevs_initonce() not called!\");\n        exit(99);\n    }\n\n    for (index = 0; index < NEVS_COUNT; index++) {\n        entry = &(nevs[index]);\n        if (entry->used && stricmp(entry->name, name) == 0) {\n            return entry;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x4884C8\nint nevs_addevent(const char* name, Program* program, int proc, int type)\n{\n    Nevs* entry;\n\n    entry = nevs_find(name);\n    if (entry == NULL) {\n        entry = nevs_alloc();\n    }\n\n    if (entry == NULL) {\n        return 1;\n    }\n\n    entry->used = true;\n    strcpy(entry->name, name);\n    entry->program = program;\n    entry->proc = proc;\n    entry->type = type;\n    entry->callback = NULL;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x488528\nint nevs_addCevent(const char* name, NevsCallback* callback, int type)\n{\n    Nevs* entry;\n\n    debug_printf(\"nevs_addCevent( '%s', %p);\\n\", name, callback);\n\n    entry = nevs_find(name);\n    if (entry == NULL) {\n        entry = nevs_alloc();\n    }\n\n    if (entry == NULL) {\n        return 1;\n    }\n\n    entry->used = true;\n    strcpy(entry->name, name);\n    entry->program = NULL;\n    entry->proc = 0;\n    entry->type = type;\n    entry->callback = NULL;\n\n    return 0;\n}\n\n// 0x48859C\nint nevs_clearevent(const char* a1)\n{\n    Nevs* entry;\n\n    debug_printf(\"nevs_clearevent( '%s');\\n\", a1);\n\n    entry = nevs_find(a1);\n    if (entry != NULL) {\n        // NOTE: Uninline.\n        nevs_free(entry);\n        return 0;\n    }\n\n    return 1;\n}\n\n// 0x48862C\nint nevs_signal(const char* name)\n{\n    Nevs* entry;\n\n    debug_printf(\"nevs_signal( '%s');\\n\", name);\n\n    entry = nevs_find(name);\n    if (entry == NULL) {\n        return 1;\n    }\n\n    debug_printf(\"nep: %p,  used = %u, prog = %p, proc = %d\", entry, entry->used, entry->program, entry->proc);\n\n    if (entry->used\n        && ((entry->program != NULL && entry->proc != 0) || entry->callback != NULL)\n        && !entry->busy) {\n        entry->hits++;\n        anyhits++;\n        return 0;\n    }\n\n    return 1;\n}\n\n// 0x4886AC\nvoid nevs_update()\n{\n    int index;\n    Nevs* entry;\n\n    if (anyhits == 0) {\n        return;\n    }\n\n    debug_printf(\"nevs_update(): we have anyhits = %u\\n\", anyhits);\n\n    anyhits = 0;\n\n    for (index = 0; index < NEVS_COUNT; index++) {\n        entry = &(nevs[index]);\n        if (entry->used\n            && ((entry->program != NULL && entry->proc != 0) || entry->callback != NULL)\n            && !entry->busy) {\n            if (entry->hits > 0) {\n                entry->busy = true;\n\n                entry->hits -= 1;\n                anyhits += entry->hits;\n\n                if (entry->callback == NULL) {\n                    executeProc(entry->program, entry->proc);\n                } else {\n                    entry->callback(entry->name);\n                }\n\n                entry->busy = false;\n\n                if (entry->type == NEVS_TYPE_EVENT) {\n                    // NOTE: Uninline.\n                    nevs_free(entry);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/int/nevs.h",
    "content": "#ifndef FALLOUT_INT_NEVS_H_\n#define FALLOUT_INT_NEVS_H_\n\n#include <stdbool.h>\n\n#include \"int/intrpret.h\"\n\ntypedef void(NevsCallback)(const char* name);\n\ntypedef enum NevsType {\n    NEVS_TYPE_EVENT = 0,\n    NEVS_TYPE_HANDLER = 1,\n} NevsType;\n\nvoid nevs_close();\nvoid nevs_initonce();\nint nevs_addevent(const char* name, Program* program, int proc, int type);\nint nevs_addCevent(const char* name, NevsCallback* callback, int type);\nint nevs_clearevent(const char* name);\nint nevs_signal(const char* name);\nvoid nevs_update();\n\n#endif /* FALLOUT_INT_NEVS_H_ */\n"
  },
  {
    "path": "src/int/pcx.c",
    "content": "#include \"int/pcx.h\"\n\n#include \"plib/db/db.h\"\n#include \"int/memdbg.h\"\n\ntypedef struct PcxHeader {\n    unsigned char identifier;\n    unsigned char version;\n    unsigned char encoding;\n    unsigned char bitsPerPixel;\n    short minX;\n    short minY;\n    short maxX;\n    short maxY;\n    short horizontalResolution;\n    short verticalResolution;\n    unsigned char palette[48];\n    unsigned char reserved1;\n    unsigned char planeCount;\n    short bytesPerLine;\n    short paletteType;\n    short horizontalScreenSize;\n    short verticalScreenSize;\n    unsigned char reserved2[54];\n} PcxHeader;\n\nstatic short getWord(File* stream);\nstatic void readPcxHeader(PcxHeader* pcxHeader, File* stream);\nstatic int pcxDecodeScanline(unsigned char* data, int size, File* stream);\nstatic int readPcxVgaPalette(PcxHeader* pcxHeader, unsigned char* palette, File* stream);\n\n// 0x519DC8\nstatic unsigned char runcount = 0;\n\n// 0x519DC9\nstatic unsigned char runvalue = 0;\n\n// NOTE: Inlined.\n//\n// 0x4961B0\nstatic short getWord(File* stream)\n{\n    short value;\n    db_fread(&value, sizeof(value), 1, stream);\n    return value;\n}\n\n// 0x4961D4\nstatic void readPcxHeader(PcxHeader* pcxHeader, File* stream)\n{\n    pcxHeader->identifier = db_fgetc(stream);\n    pcxHeader->version = db_fgetc(stream);\n    pcxHeader->encoding = db_fgetc(stream);\n    pcxHeader->bitsPerPixel = db_fgetc(stream);\n    pcxHeader->minX = getWord(stream);\n    pcxHeader->minY = getWord(stream);\n    pcxHeader->maxX = getWord(stream);\n    pcxHeader->maxY = getWord(stream);\n    pcxHeader->horizontalResolution = getWord(stream);\n    pcxHeader->verticalResolution = getWord(stream);\n\n    for (int index = 0; index < 48; index++) {\n        pcxHeader->palette[index] = db_fgetc(stream);\n    }\n\n    pcxHeader->reserved1 = db_fgetc(stream);\n    pcxHeader->planeCount = db_fgetc(stream);\n    pcxHeader->bytesPerLine = getWord(stream);\n    pcxHeader->paletteType = getWord(stream);\n    pcxHeader->horizontalScreenSize = getWord(stream);\n    pcxHeader->verticalScreenSize = getWord(stream);\n\n    for (int index = 0; index < 54; index++) {\n        pcxHeader->reserved2[index] = db_fgetc(stream);\n    }\n}\n\n// 0x49636C\nstatic int pcxDecodeScanline(unsigned char* data, int size, File* stream)\n{\n    unsigned char runLength = runcount;\n    unsigned char value = runvalue;\n\n    int uncompressedSize = 0;\n    int index = 0;\n    do {\n        uncompressedSize += runLength;\n        while (runLength > 0 && index < size) {\n            data[index] = value;\n            runLength--;\n            index++;\n        }\n\n        runcount = runLength;\n        runvalue = value;\n\n        if (runLength != 0) {\n            uncompressedSize -= runLength;\n            break;\n        }\n\n        value = db_fgetc(stream);\n        if ((value & 0xC0) == 0xC0) {\n            runcount = value & 0x3F;\n            value = db_fgetc(stream);\n            runLength = runcount;\n        } else {\n            runLength = 1;\n        }\n    } while (index < size);\n\n    runcount = runLength;\n    runvalue = value;\n\n    return uncompressedSize;\n}\n\n// 0x49641C\nstatic int readPcxVgaPalette(PcxHeader* pcxHeader, unsigned char* palette, File* stream)\n{\n    if (pcxHeader->version != 5) {\n        return 0;\n    }\n\n    long pos = db_ftell(stream);\n    long size = db_filelength(stream);\n    db_fseek(stream, size - 769, SEEK_SET);\n    if (db_fgetc(stream) != 12) {\n        db_fseek(stream, pos, SEEK_SET);\n        return 0;\n    }\n\n    for (int index = 0; index < 768; index++) {\n        palette[index] = db_fgetc(stream);\n    }\n\n    db_fseek(stream, pos, SEEK_SET);\n\n    return 1;\n}\n\n// 0x496494\nunsigned char* loadPCX(const char* path, int* widthPtr, int* heightPtr, unsigned char* palette)\n{\n    File* stream = db_fopen(path, \"rb\");\n    if (stream == NULL) {\n        return NULL;\n    }\n\n    PcxHeader pcxHeader;\n    readPcxHeader(&pcxHeader, stream);\n\n    int width = pcxHeader.maxX - pcxHeader.minX + 1;\n    int height = pcxHeader.maxY - pcxHeader.minY + 1;\n\n    *widthPtr = width;\n    *heightPtr = height;\n\n    int bytesPerLine = pcxHeader.planeCount * pcxHeader.bytesPerLine;\n    unsigned char* data = mymalloc(bytesPerLine * height, __FILE__, __LINE__); // \"..\\\\int\\\\PCX.C\", 195\n    if (data == NULL) {\n        // NOTE: This code is unreachable, internal_malloc_safe never fails.\n        db_fclose(stream);\n        return NULL;\n    }\n\n    runcount = 0;\n    runvalue = 0;\n\n    unsigned char* ptr = data;\n    for (int y = 0; y < height; y++) {\n        pcxDecodeScanline(ptr, bytesPerLine, stream);\n        ptr += width;\n    }\n\n    readPcxVgaPalette(&pcxHeader, palette, stream);\n\n    db_fclose(stream);\n\n    return data;\n}\n"
  },
  {
    "path": "src/int/pcx.h",
    "content": "#ifndef FALLOUT_INT_PCX_H_\n#define FALLOUT_INT_PCX_H_\n\nunsigned char* loadPCX(const char* path, int* widthPtr, int* heightPtr, unsigned char* palette);\n\n#endif /* FALLOUT_INT_PCX_H_ */\n"
  },
  {
    "path": "src/int/region.c",
    "content": "#include \"int/region.h\"\n\n#include <limits.h>\n#include <string.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"int/memdbg.h\"\n\nstatic_assert(sizeof(Region) == 140, \"wrong size\");\n\n// 0x4A2B50\nvoid regionSetBound(Region* region)\n{\n    int minX = INT_MAX;\n    int maxX = INT_MIN;\n    int minY = INT_MAX;\n    int maxY = INT_MIN;\n    int numPoints = 0;\n    int totalX = 0;\n    int totalY = 0;\n\n    for (int index = 0; index < region->pointsLength; index++) {\n        Point* point = &(region->points[index]);\n        if (minX >= point->x) minX = point->x;\n        if (minY >= point->y) minY = point->y;\n        if (maxX <= point->x) maxX = point->x;\n        if (maxY <= point->y) maxY = point->y;\n        totalX += point->x;\n        totalY += point->y;\n        numPoints++;\n    }\n\n    region->minY = minY;\n    region->maxX = maxX;\n    region->maxY = maxY;\n    region->minX = minX;\n\n    if (numPoints != 0) {\n        region->centerX = totalX / numPoints;\n        region->centerY = totalY / numPoints;\n    }\n}\n\n// 0x4A2C14\nbool pointInRegion(Region* region, int x, int y)\n{\n    if (region == NULL) {\n        return false;\n    }\n\n    if (x < region->minX || x > region->maxX || y < region->minY || y > region->maxY) {\n        return false;\n    }\n\n    int v1;\n\n    Point* prev = &(region->points[0]);\n    if (x >= prev->x) {\n        if (y >= prev->y) {\n            v1 = 2;\n        } else {\n            v1 = 1;\n        }\n    } else {\n        if (y >= prev->y) {\n            v1 = 3;\n        } else {\n            v1 = 0;\n        }\n    }\n\n    int v4 = 0;\n    for (int index = 0; index < region->pointsLength; index++) {\n        int v2;\n\n        Point* point = &(region->points[index + 1]);\n        if (x >= point->x) {\n            if (y >= point->y) {\n                v2 = 2;\n            } else {\n                v2 = 1;\n            }\n        } else {\n            if (y >= point->y) {\n                v2 = 3;\n            } else {\n                v2 = 0;\n            }\n        }\n\n        int v3 = v2 - v1;\n        switch (v3) {\n        case -3:\n            v3 = 1;\n            break;\n        case -2:\n        case 2:\n            if ((double)x < ((double)point->x - (double)(prev->x - point->x) / (double)(prev->y - point->y) * (double)(point->y - y))) {\n                v3 = -v3;\n            }\n            break;\n        case 3:\n            v3 = -1;\n            break;\n        }\n\n        prev = point;\n        v1 = v2;\n\n        v4 += v3;\n    }\n\n    if (v4 == 4 || v4 == -4) {\n        return true;\n    }\n\n    return false;\n}\n\n// 0x4A2D78\nRegion* allocateRegion(int initialCapacity)\n{\n    Region* region = (Region*)mymalloc(sizeof(*region), __FILE__, __LINE__); // \"..\\int\\REGION.C\", 142\n    memset(region, 0, sizeof(*region));\n\n    if (initialCapacity != 0) {\n        region->points = (Point*)mymalloc(sizeof(*region->points) * (initialCapacity + 1), __FILE__, __LINE__); // \"..\\int\\REGION.C\", 147\n        region->pointsCapacity = initialCapacity + 1;\n    } else {\n        region->points = NULL;\n        region->pointsCapacity = 0;\n    }\n\n    region->name[0] = '\\0';\n    region->flags = 0;\n    region->minY = INT_MIN;\n    region->maxY = INT_MAX;\n    region->procs[3] = 0;\n    region->rightProcs[1] = 0;\n    region->rightProcs[3] = 0;\n    region->field_68 = 0;\n    region->rightProcs[0] = 0;\n    region->field_70 = 0;\n    region->rightProcs[2] = 0;\n    region->mouseEventCallback = NULL;\n    region->rightMouseEventCallback = NULL;\n    region->mouseEventCallbackUserData = 0;\n    region->rightMouseEventCallbackUserData = 0;\n    region->pointsLength = 0;\n    region->minX = region->minY;\n    region->maxX = region->maxY;\n    region->procs[2] = 0;\n    region->procs[1] = 0;\n    region->procs[0] = 0;\n    region->rightProcs[0] = 0;\n\n    return region;\n}\n\n// 0x4A2E68\nvoid regionAddPoint(Region* region, int x, int y)\n{\n    if (region == NULL) {\n        debug_printf(\"regionAddPoint(): null region ptr\\n\");\n        return;\n    }\n\n    if (region->points != NULL) {\n        if (region->pointsCapacity - 1 == region->pointsLength) {\n            region->points = (Point*)myrealloc(region->points, sizeof(*region->points) * (region->pointsCapacity + 1), __FILE__, __LINE__); // \"..\\int\\REGION.C\", 190\n            region->pointsCapacity++;\n        }\n    } else {\n        region->pointsCapacity = 2;\n        region->pointsLength = 0;\n        region->points = (Point*)mymalloc(sizeof(*region->points) * 2, __FILE__, __LINE__); // \"..\\int\\REGION.C\", 185\n    }\n\n    int pointIndex = region->pointsLength;\n    region->pointsLength++;\n\n    Point* point = &(region->points[pointIndex]);\n    point->x = x;\n    point->y = y;\n\n    Point* end = &(region->points[pointIndex + 1]);\n    end->x = region->points->x;\n    end->y = region->points->y;\n}\n\n// 0x4A2F0C\nvoid regionDelete(Region* region)\n{\n    if (region == NULL) {\n        debug_printf(\"regionDelete(): null region ptr\\n\");\n        return;\n    }\n\n    if (region->points != NULL) {\n        myfree(region->points, __FILE__, __LINE__); // \"..\\int\\REGION.C\", 206\n    }\n\n    myfree(region, __FILE__, __LINE__); // \"..\\int\\REGION.C\", 207\n}\n\n// 0x4A2F54\nvoid regionAddName(Region* region, const char* name)\n{\n    if (region == NULL) {\n        debug_printf(\"regionAddName(): null region ptr\\n\");\n        return;\n    }\n\n    if (name == NULL) {\n        region->name[0] = '\\0';\n        return;\n    }\n\n    strncpy(region->name, name, REGION_NAME_LENGTH - 1);\n}\n\n// 0x4A2F80\nconst char* regionGetName(Region* region)\n{\n    if (region == NULL) {\n        debug_printf(\"regionGetName(): null region ptr\\n\");\n        return \"<null>\";\n    }\n\n    return region->name;\n}\n\n// 0x4A2F98\nvoid* regionGetUserData(Region* region)\n{\n    if (region == NULL) {\n        debug_printf(\"regionGetUserData(): null region ptr\\n\");\n        return NULL;\n    }\n\n    return region->userData;\n}\n\n// 0x4A2FB4\nvoid regionSetUserData(Region* region, void* data)\n{\n    if (region == NULL) {\n        debug_printf(\"regionSetUserData(): null region ptr\\n\");\n        return;\n    }\n\n    region->userData = data;\n}\n\n// 0x4A2FD0\nvoid regionSetFlag(Region* region, int value)\n{\n    region->flags |= value;\n}\n\n// NOTE: Unused.\n//\n// 0x4A2FD4\nint regionGetFlag(Region* region)\n{\n    return region->flags;\n}\n"
  },
  {
    "path": "src/int/region.h",
    "content": "#ifndef FALLOUT_INT_REGION_H_\n#define FALLOUT_INT_REGION_H_\n\n#include \"plib/gnw/rect.h\"\n#include \"int/intrpret.h\"\n\n#define REGION_NAME_LENGTH 32\n\ntypedef struct Region Region;\n\ntypedef void RegionMouseEventCallback(Region* region, void* userData, int event);\n\ntypedef struct Region {\n    char name[REGION_NAME_LENGTH];\n    Point* points;\n    int minX;\n    int minY;\n    int maxX;\n    int maxY;\n    int centerX;\n    int centerY;\n    int pointsLength;\n    int pointsCapacity;\n    Program* program;\n    int procs[4];\n    int rightProcs[4];\n    int field_68;\n    int field_6C;\n    int field_70;\n    int flags;\n    RegionMouseEventCallback* mouseEventCallback;\n    RegionMouseEventCallback* rightMouseEventCallback;\n    void* mouseEventCallbackUserData;\n    void* rightMouseEventCallbackUserData;\n    void* userData;\n} Region;\n\nvoid regionSetBound(Region* region);\nbool pointInRegion(Region* region, int x, int y);\nRegion* allocateRegion(int initialCapacity);\nvoid regionAddPoint(Region* region, int x, int y);\nvoid regionDelete(Region* region);\nvoid regionAddName(Region* region, const char* src);\nconst char* regionGetName(Region* region);\nvoid* regionGetUserData(Region* region);\nvoid regionSetUserData(Region* region, void* data);\nvoid regionSetFlag(Region* region, int value);\nint regionGetFlag(Region* region);\n\n#endif /* FALLOUT_INT_REGION_H_ */\n"
  },
  {
    "path": "src/int/share1.c",
    "content": "#include \"int/share1.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/db/db.h\"\n\nstatic int compare(const void* a1, const void* a2);\n\n// 0x4AA250\nstatic int compare(const void* a1, const void* a2)\n{\n    const char* v1 = *(const char**)a1;\n    const char* v2 = *(const char**)a2;\n    return strcmp(v1, v2);\n}\n\n// 0x4AA2A4\nchar** getFileList(const char* pattern, int* fileNameListLengthPtr)\n{\n    char** fileNameList;\n    int fileNameListLength = db_get_file_list(pattern, &fileNameList, 0, 0);\n    *fileNameListLengthPtr = fileNameListLength;\n    if (fileNameListLength == 0) {\n        return NULL;\n    }\n\n    qsort(fileNameList, fileNameListLength, sizeof(*fileNameList), compare);\n\n    return fileNameList;\n}\n\n// 0x4AA2DC\nvoid freeFileList(char** fileList)\n{\n    db_free_file_list(&fileList, 0);\n}\n"
  },
  {
    "path": "src/int/share1.h",
    "content": "#ifndef FALLOUT_INT_SHARE1_H_\n#define FALLOUT_INT_SHARE1_H_\n\nchar** getFileList(const char* pattern, int* fileNameListLengthPtr);\nvoid freeFileList(char** fileList);\n\n#endif /* FALLOUT_INT_SHARE1_H_ */\n"
  },
  {
    "path": "src/int/sound.c",
    "content": "#include \"int/sound.h\"\n\n#include <io.h>\n#include <limits.h>\n#include <math.h>\n#include <mmsystem.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/memory.h\"\n#include \"plib/gnw/winmain.h\"\n\ntypedef struct FadeSound {\n    Sound* sound;\n    int deltaVolume;\n    int targetVolume;\n    int initialVolume;\n    int currentVolume;\n    int field_14;\n    struct FadeSound* prev;\n    struct FadeSound* next;\n} FadeSound;\n\nstatic_assert(sizeof(Sound) == 156, \"wrong size\");\n\nstatic void* defaultMalloc(size_t size);\nstatic void* defaultRealloc(void* ptr, size_t size);\nstatic void defaultFree(void* ptr);\nstatic long soundFileSize(int fileHandle);\nstatic long soundTellData(int fileHandle);\nstatic int soundWriteData(int fileHandle, const void* buf, unsigned int size);\nstatic int soundReadData(int fileHandle, void* buf, unsigned int size);\nstatic int soundOpenData(const char* filePath, int flags, ...);\nstatic int soundSeekData(int fileHandle, long offset, int origin);\nstatic int soundCloseData(int fileHandle);\nstatic char* defaultMangler(char* fname);\nstatic void refreshSoundBuffers(Sound* sound);\nstatic int preloadBuffers(Sound* sound);\nstatic int addSoundData(Sound* sound, unsigned char* buf, int size);\nstatic void CALLBACK doTimerEvent(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2);\nstatic void removeTimedEvent(unsigned int* timerId);\nstatic void removeFadeSound(FadeSound* fadeSound);\nstatic void fadeSounds();\nstatic int internalSoundFade(Sound* sound, int duration, int targetVolume, int a4);\n\n// 0x51D478\nstatic FadeSound* fadeHead = NULL;\n\n// 0x51D47C\nstatic FadeSound* fadeFreeList = NULL;\n\n// 0x51D480\nstatic unsigned int fadeEventHandle = UINT_MAX;\n\n// 0x51D488\nstatic MallocProc* mallocPtr = defaultMalloc;\n\n// 0x51D48C\nstatic ReallocProc* reallocPtr = defaultRealloc;\n\n// 0x51D490\nstatic FreeProc* freePtr = defaultFree;\n\n// 0x51D494\nstatic SoundFileIO defaultStream = {\n    soundOpenData,\n    soundCloseData,\n    soundReadData,\n    soundWriteData,\n    soundSeekData,\n    soundTellData,\n    soundFileSize,\n    -1,\n};\n\n// 0x51D4B4\nstatic SoundFileNameMangler* nameMangler = defaultMangler;\n\n// 0x51D4B8\nstatic const char* errorMsgs[SOUND_ERR_COUNT] = {\n    \"sound.c: No error\",\n    \"sound.c: SOS driver not loaded\",\n    \"sound.c: SOS invalid pointer\",\n    \"sound.c: SOS detect initialized\",\n    \"sound.c: SOS fail on file open\",\n    \"sound.c: SOS memory fail\",\n    \"sound.c: SOS invalid driver ID\",\n    \"sound.c: SOS no driver found\",\n    \"sound.c: SOS detection failure\",\n    \"sound.c: SOS driver loaded\",\n    \"sound.c: SOS invalid handle\",\n    \"sound.c: SOS no handles\",\n    \"sound.c: SOS paused\",\n    \"sound.c: SOS not paused\",\n    \"sound.c: SOS invalid data\",\n    \"sound.c: SOS drv file fail\",\n    \"sound.c: SOS invalid port\",\n    \"sound.c: SOS invalid IRQ\",\n    \"sound.c: SOS invalid DMA\",\n    \"sound.c: SOS invalid DMA IRQ\",\n    \"sound.c: no device\",\n    \"sound.c: not initialized\",\n    \"sound.c: no sound\",\n    \"sound.c: function not supported\",\n    \"sound.c: no buffers available\",\n    \"sound.c: file not found\",\n    \"sound.c: already playing\",\n    \"sound.c: not playing\",\n    \"sound.c: already paused\",\n    \"sound.c: not paused\",\n    \"sound.c: invalid handle\",\n    \"sound.c: no memory available\",\n    \"sound.c: unknown error\",\n};\n\n// 0x668150\nstatic int soundErrorno;\n\n// 0x668154\nstatic int masterVol;\n\n// 0x668158\nLPDIRECTSOUNDBUFFER primaryDSBuffer;\n\n// 0x66815C\nstatic int sampleRate;\n\n// Number of sounds currently playing.\n//\n// 0x668160\nstatic int numSounds;\n\n// 0x668164\nstatic int deviceInit;\n\n// 0x668168\nstatic int dataSize;\n\n// 0x66816C\nstatic int numBuffers;\n\n// 0x668170\nstatic bool driverInit;\n\n// 0x668174\nstatic Sound* soundMgrList;\n\n// 0x668178\nLPDIRECTSOUND soundDSObject;\n\n// 0x4AC6F0\nstatic void* defaultMalloc(size_t size)\n{\n    return malloc(size);\n}\n\n// 0x4AC6F8\nstatic void* defaultRealloc(void* ptr, size_t size)\n{\n    return realloc(ptr, size);\n}\n\n// 0x4AC700\nstatic void defaultFree(void* ptr)\n{\n    free(ptr);\n}\n\n// 0x4AC708\nvoid soundRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc)\n{\n    mallocPtr = mallocProc;\n    reallocPtr = reallocProc;\n    freePtr = freeProc;\n}\n\n// 0x4AC71C\nstatic long soundFileSize(int fileHandle)\n{\n    long pos;\n    long size;\n\n    pos = tell(fileHandle);\n    size = lseek(fileHandle, 0, SEEK_END);\n    lseek(fileHandle, pos, SEEK_SET);\n\n    return size;\n}\n\n// 0x4AC750\nstatic long soundTellData(int fileHandle)\n{\n    return tell(fileHandle);\n}\n\n// 0x4AC758\nstatic int soundWriteData(int fileHandle, const void* buf, unsigned int size)\n{\n    return write(fileHandle, buf, size);\n}\n\n// 0x4AC760\nstatic int soundReadData(int fileHandle, void* buf, unsigned int size)\n{\n    return read(fileHandle, buf, size);\n}\n\n// 0x4AC768\nstatic int soundOpenData(const char* filePath, int flags, ...)\n{\n    return open(filePath, flags);\n}\n\n// 0x4AC774\nstatic int soundSeekData(int fileHandle, long offset, int origin)\n{\n    return lseek(fileHandle, offset, origin);\n}\n\n// 0x4AC77C\nstatic int soundCloseData(int fileHandle)\n{\n    return close(fileHandle);\n}\n\n// 0x4AC78C\nstatic char* defaultMangler(char* fname)\n{\n    return fname;\n}\n\n// 0x4AC790\nconst char* soundError(int err)\n{\n    if (err == -1) {\n        err = soundErrorno;\n    }\n\n    if (err < 0 || err > SOUND_UNKNOWN_ERROR) {\n        err = SOUND_UNKNOWN_ERROR;\n    }\n\n    return errorMsgs[err];\n}\n\n// 0x4AC7B0\nstatic void refreshSoundBuffers(Sound* sound)\n{\n    if (sound->field_3C & 0x80) {\n        return;\n    }\n\n    DWORD readPos;\n    DWORD writePos;\n    HRESULT hr = IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &readPos, &writePos);\n    if (hr != DS_OK) {\n        return;\n    }\n\n    if (readPos < sound->field_74) {\n        sound->field_64 += readPos + sound->field_78 * sound->field_7C - sound->field_74;\n    } else {\n        sound->field_64 += readPos - sound->field_74;\n    }\n\n    if (sound->field_3C & 0x0100) {\n        if (sound->field_44 & 0x20) {\n            if (sound->field_3C & 0x0200) {\n                sound->field_3C |= 0x80;\n            }\n        } else {\n            if (sound->field_60 <= sound->field_64) {\n                sound->field_3C |= 0x0280;\n            }\n        }\n    }\n    sound->field_74 = readPos;\n\n    if (sound->field_60 < sound->field_64) {\n        int v3;\n        do {\n            v3 = sound->field_64 - sound->field_60;\n            sound->field_64 = v3;\n        } while (v3 > sound->field_60);\n    }\n\n    int v6 = readPos / sound->field_7C;\n    if (sound->field_70 == v6) {\n        return;\n    }\n\n    int v53;\n    if (sound->field_70 > v6) {\n        v53 = v6 + sound->field_78 - sound->field_70;\n    } else {\n        v53 = v6 - sound->field_70;\n    }\n\n    if (sound->field_7C * v53 >= sound->readLimit) {\n        v53 = (sound->readLimit + sound->field_7C - 1) / sound->field_7C;\n    }\n\n    if (v53 < sound->field_5C) {\n        return;\n    }\n\n    VOID* audioPtr1;\n    VOID* audioPtr2;\n    DWORD audioBytes1;\n    DWORD audioBytes2;\n    hr = IDirectSoundBuffer_Lock(sound->directSoundBuffer, sound->field_7C * sound->field_70, sound->field_7C * v53, &audioPtr1, &audioBytes1, &audioPtr2, &audioBytes2, 0);\n    if (hr == DSERR_BUFFERLOST) {\n        IDirectSoundBuffer_Restore(sound->directSoundBuffer);\n        hr = IDirectSoundBuffer_Lock(sound->directSoundBuffer, sound->field_7C * sound->field_70, sound->field_7C * v53, &audioPtr1, &audioBytes1, &audioPtr2, &audioBytes2, 0);\n    }\n\n    if (hr != DS_OK) {\n        return;\n    }\n\n    if (audioBytes1 + audioBytes2 != sound->field_7C * v53) {\n        debug_printf(\"locked memory region not big enough, wanted %d (%d * %d), got %d (%d + %d)\\n\", sound->field_7C * v53, v53, sound->field_7C, audioBytes1 + audioBytes2, audioBytes1, audioBytes2);\n        debug_printf(\"Resetting readBuffers from %d to %d\\n\", v53, (audioBytes1 + audioBytes2) / sound->field_7C);\n\n        v53 = (audioBytes1 + audioBytes2) / sound->field_7C;\n        if (v53 < sound->field_5C) {\n            debug_printf(\"No longer above read buffer size, returning\\n\");\n            return;\n        }\n    }\n    unsigned char* audioPtr = (unsigned char*)audioPtr1;\n    int audioBytes = audioBytes1;\n    while (--v53 != -1) {\n        int bytesRead;\n        if (sound->field_3C & 0x0200) {\n            bytesRead = sound->field_7C;\n            memset(sound->field_20, 0, bytesRead);\n        } else {\n            int bytesToRead = sound->field_7C;\n            if (sound->field_58 != -1) {\n                int pos = sound->io.tell(sound->io.fd);\n                if (bytesToRead + pos > sound->field_58) {\n                    bytesToRead = sound->field_58 - pos;\n                }\n            }\n\n            bytesRead = sound->io.read(sound->io.fd, sound->field_20, bytesToRead);\n            if (bytesRead < sound->field_7C) {\n                if (!(sound->field_3C & 0x20) || (sound->field_3C & 0x0100)) {\n                    memset(sound->field_20 + bytesRead, 0, sound->field_7C - bytesRead);\n                    sound->field_3C |= 0x0200;\n                    bytesRead = sound->field_7C;\n                } else {\n                    while (bytesRead < sound->field_7C) {\n                        if (sound->field_50 == -1) {\n                            sound->io.seek(sound->io.fd, sound->field_54, SEEK_SET);\n                            if (sound->callback != NULL) {\n                                sound->callback(sound->callbackUserData, 0x0400);\n                            }\n                        } else {\n                            if (sound->field_50 <= 0) {\n                                sound->field_58 = -1;\n                                sound->field_54 = 0;\n                                sound->field_50 = 0;\n                                sound->field_3C &= ~0x20;\n                                bytesRead += sound->io.read(sound->io.fd, sound->field_20 + bytesRead, sound->field_7C - bytesRead);\n                                break;\n                            }\n\n                            sound->field_50--;\n                            sound->io.seek(sound->io.fd, sound->field_54, SEEK_SET);\n\n                            if (sound->callback != NULL) {\n                                sound->callback(sound->callbackUserData, 0x400);\n                            }\n                        }\n\n                        if (sound->field_58 == -1) {\n                            bytesToRead = sound->field_7C - bytesRead;\n                        } else {\n                            int pos = sound->io.tell(sound->io.fd);\n                            if (sound->field_7C + bytesRead + pos <= sound->field_58) {\n                                bytesToRead = sound->field_7C - bytesRead;\n                            } else {\n                                bytesToRead = sound->field_58 - bytesRead - pos;\n                            }\n                        }\n\n                        int v20 = sound->io.read(sound->io.fd, sound->field_20 + bytesRead, bytesToRead);\n                        bytesRead += v20;\n                        if (v20 < bytesToRead) {\n                            break;\n                        }\n                    }\n                }\n            }\n        }\n\n        if (bytesRead > audioBytes) {\n            if (audioBytes != 0) {\n                memcpy(audioPtr, sound->field_20, audioBytes);\n            }\n\n            if (audioPtr2 != NULL) {\n                memcpy(audioPtr2, sound->field_20 + audioBytes, bytesRead - audioBytes);\n                audioPtr = (unsigned char*)audioPtr2 + bytesRead - audioBytes;\n                audioBytes = audioBytes2 - bytesRead;\n            } else {\n                debug_printf(\"Hm, no second write pointer, but buffer not big enough, this shouldn't happen\\n\");\n            }\n        } else {\n            memcpy(audioPtr, sound->field_20, bytesRead);\n            audioPtr += bytesRead;\n            audioBytes -= bytesRead;\n        }\n    }\n\n    IDirectSoundBuffer_Unlock(sound->directSoundBuffer, audioPtr1, audioBytes1, audioPtr2, audioBytes2);\n\n    sound->field_70 = v6;\n\n    return;\n}\n\n// 0x4ACC58\nint soundInit(int a1, int a2, int a3, int a4, int rate)\n{\n    HRESULT hr;\n    DWORD v24;\n\n    if (GNW95_DirectSoundCreate(0, &soundDSObject, 0) != DS_OK) {\n        soundDSObject = NULL;\n        soundErrorno = SOUND_SOS_DETECTION_FAILURE;\n        return soundErrorno;\n    }\n\n    if (IDirectSound_SetCooperativeLevel(soundDSObject, GNW95_hwnd, DSSCL_EXCLUSIVE) != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    sampleRate = rate;\n    dataSize = a4;\n    numBuffers = a2;\n    driverInit = true;\n    deviceInit = 1;\n\n    DSBUFFERDESC dsbdesc;\n    memset(&dsbdesc, 0, sizeof(dsbdesc));\n    dsbdesc.dwSize = sizeof(dsbdesc);\n    dsbdesc.dwFlags = DSCAPS_PRIMARYMONO;\n    dsbdesc.dwBufferBytes = 0;\n\n    hr = IDirectSound_CreateSoundBuffer(soundDSObject, &dsbdesc, &primaryDSBuffer, NULL);\n    if (hr != DS_OK) {\n        switch (hr) {\n        case DSERR_ALLOCATED:\n            debug_printf(\"%s:%s\\n\", \"CreateSoundBuffer\", \"DSERR_ALLOCATED\");\n            break;\n        case DSERR_BADFORMAT:\n            debug_printf(\"%s:%s\\n\", \"CreateSoundBuffer\", \"DSERR_BADFORMAT\");\n            break;\n        case DSERR_INVALIDPARAM:\n            debug_printf(\"%s:%s\\n\", \"CreateSoundBuffer\", \"DSERR_INVALIDPARAM\");\n            break;\n        case DSERR_NOAGGREGATION:\n            debug_printf(\"%s:%s\\n\", \"CreateSoundBuffer\", \"DSERR_NOAGGREGATION\");\n            break;\n        case DSERR_OUTOFMEMORY:\n            debug_printf(\"%s:%s\\n\", \"CreateSoundBuffer\", \"DSERR_OUTOFMEMORY\");\n            break;\n        case DSERR_UNINITIALIZED:\n            debug_printf(\"%s:%s\\n\", \"CreateSoundBuffer\", \"DSERR_UNINITIALIZED\");\n            break;\n        case DSERR_UNSUPPORTED:\n            debug_printf(\"%s:%s\\n\", \"CreateSoundBuffer\", \"DSERR_UNSUPPORTED\");\n            break;\n        }\n\n        exit(1);\n    }\n\n    WAVEFORMATEX pcmwf;\n    memset(&pcmwf, 0, sizeof(pcmwf));\n\n    DSCAPS dscaps;\n    memset(&dscaps, 0, sizeof(dscaps));\n    dscaps.dwSize = sizeof(dscaps);\n\n    hr = IDirectSound_GetCaps(soundDSObject, &dscaps);\n    if (hr != DS_OK) {\n        debug_printf(\"soundInit: Error getting primary buffer parameters\\n\");\n        goto out;\n    }\n\n    pcmwf.nSamplesPerSec = rate;\n    pcmwf.wFormatTag = WAVE_FORMAT_PCM;\n\n    if (dscaps.dwFlags & DSCAPS_PRIMARY16BIT) {\n        pcmwf.wBitsPerSample = 16;\n    } else {\n        pcmwf.wBitsPerSample = 8;\n    }\n\n    pcmwf.nChannels = (dscaps.dwFlags & DSCAPS_PRIMARYSTEREO) ? 2 : 1;\n    pcmwf.nBlockAlign = pcmwf.wBitsPerSample * pcmwf.nChannels / 8;\n    pcmwf.nSamplesPerSec = rate;\n    pcmwf.cbSize = 0;\n    pcmwf.nAvgBytesPerSec = pcmwf.nBlockAlign * rate;\n\n    debug_printf(\"soundInit: Setting primary buffer to: %d bit, %d channels, %d rate\\n\", pcmwf.wBitsPerSample, pcmwf.nChannels, rate);\n    hr = IDirectSoundBuffer_SetFormat(primaryDSBuffer, &pcmwf);\n    if (hr != DS_OK) {\n        debug_printf(\"soundInit: Couldn't change rate to %d\\n\", rate);\n\n        switch (hr) {\n        case DSERR_BADFORMAT:\n            debug_printf(\"%s:%s\\n\", \"SetFormat\", \"DSERR_BADFORMAT\");\n            break;\n        case DSERR_INVALIDCALL:\n            debug_printf(\"%s:%s\\n\", \"SetFormat\", \"DSERR_INVALIDCALL\");\n            break;\n        case DSERR_INVALIDPARAM:\n            debug_printf(\"%s:%s\\n\", \"SetFormat\", \"DSERR_INVALIDPARAM\");\n            break;\n        case DSERR_OUTOFMEMORY:\n            debug_printf(\"%s:%s\\n\", \"SetFormat\", \"DSERR_OUTOFMEMORY\");\n            break;\n        case DSERR_PRIOLEVELNEEDED:\n            debug_printf(\"%s:%s\\n\", \"SetFormat\", \"DSERR_PRIOLEVELNEEDED\");\n            break;\n        case DSERR_UNSUPPORTED:\n            debug_printf(\"%s:%s\\n\", \"SetFormat\", \"DSERR_UNSUPPORTED\");\n            break;\n        }\n\n        goto out;\n    }\n\n    hr = IDirectSoundBuffer_GetFormat(primaryDSBuffer, &pcmwf, sizeof(WAVEFORMATEX), &v24);\n    if (hr != DS_OK) {\n        debug_printf(\"soundInit: Couldn't read new settings\\n\");\n        goto out;\n    }\n\n    debug_printf(\"soundInit: Primary buffer settings set to: %d bit, %d channels, %d rate\\n\", pcmwf.wBitsPerSample, pcmwf.nChannels, pcmwf.nSamplesPerSec);\n\n    if (dscaps.dwFlags & DSCAPS_EMULDRIVER) {\n        debug_printf(\"soundInit: using DirectSound emulated drivers\\n\");\n    }\n\nout:\n\n    soundSetMasterVolume(VOLUME_MAX);\n    soundErrorno = SOUND_NO_ERROR;\n\n    return 0;\n}\n\n// 0x4AD04C\nvoid soundClose()\n{\n    while (soundMgrList != NULL) {\n        Sound* next = soundMgrList->next;\n        soundDelete(soundMgrList);\n        soundMgrList = next;\n    }\n\n    if (fadeEventHandle != -1) {\n        removeTimedEvent(&fadeEventHandle);\n    }\n\n    while (fadeFreeList != NULL) {\n        FadeSound* next = fadeFreeList->next;\n        freePtr(fadeFreeList);\n        fadeFreeList = next;\n    }\n\n    if (primaryDSBuffer != NULL) {\n        IDirectSoundBuffer_Release(primaryDSBuffer);\n        primaryDSBuffer = NULL;\n    }\n\n    if (soundDSObject != NULL) {\n        IDirectSound_Release(soundDSObject);\n        soundDSObject = NULL;\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    driverInit = false;\n}\n\n// 0x4AD0FC\nSound* soundAllocate(int a1, int a2)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return NULL;\n    }\n\n    Sound* sound = (Sound*)mallocPtr(sizeof(*sound));\n    memset(sound, 0, sizeof(*sound));\n\n    memcpy(&(sound->io), &defaultStream, sizeof(defaultStream));\n\n    WAVEFORMATEX* wfxFormat = (WAVEFORMATEX*)mallocPtr(sizeof(*wfxFormat));\n    memset(wfxFormat, 0, sizeof(*wfxFormat));\n\n    wfxFormat->wFormatTag = 1;\n    wfxFormat->nChannels = 1;\n\n    if (a2 & 0x08) {\n        wfxFormat->wBitsPerSample = 16;\n    } else {\n        wfxFormat->wBitsPerSample = 8;\n    }\n\n    if (!(a2 & 0x02)) {\n        a2 |= 0x02;\n    }\n\n    wfxFormat->nSamplesPerSec = sampleRate;\n    wfxFormat->nBlockAlign = wfxFormat->nChannels * (wfxFormat->wBitsPerSample / 8);\n    wfxFormat->cbSize = 0;\n    wfxFormat->nAvgBytesPerSec = wfxFormat->nBlockAlign * wfxFormat->nSamplesPerSec;\n\n    sound->field_3C = a2;\n    sound->field_44 = a1;\n    sound->field_7C = dataSize;\n    sound->field_64 = 0;\n    sound->directSoundBuffer = 0;\n    sound->field_40 = 0;\n    sound->directSoundBufferDescription.dwSize = sizeof(DSBUFFERDESC);\n    sound->directSoundBufferDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2;\n    sound->field_78 = numBuffers;\n    sound->readLimit = sound->field_7C * numBuffers;\n\n    if (a2 & 0x2) {\n        sound->directSoundBufferDescription.dwFlags |= DSBCAPS_CTRLVOLUME;\n    }\n\n    if (a2 & 0x4) {\n        sound->directSoundBufferDescription.dwFlags |= DSBCAPS_CTRLPAN;\n    }\n\n    if (a2 & 0x40) {\n        sound->directSoundBufferDescription.dwFlags |= DSBCAPS_CTRLFREQUENCY;\n    }\n\n    sound->directSoundBufferDescription.lpwfxFormat = wfxFormat;\n\n    if (a1 & 0x10) {\n        sound->field_50 = -1;\n        sound->field_3C |= 0x20;\n    }\n\n    sound->field_58 = -1;\n    sound->field_5C = 1;\n    sound->volume = VOLUME_MAX;\n    sound->prev = NULL;\n    sound->field_54 = 0;\n    sound->next = soundMgrList;\n\n    if (soundMgrList != NULL) {\n        soundMgrList->prev = sound;\n    }\n\n    soundMgrList = sound;\n\n    return sound;\n}\n\n// 0x4AD308\nstatic int preloadBuffers(Sound* sound)\n{\n    unsigned char* buf;\n    int bytes_read;\n    int result;\n    int v15;\n    unsigned char* v14;\n    int size;\n\n    size = sound->io.filelength(sound->io.fd);\n    sound->field_60 = size;\n\n    if (sound->field_44 & 0x02) {\n        if (!(sound->field_3C & 0x20)) {\n            sound->field_3C |= 0x0120;\n        }\n\n        if (sound->field_78 * sound->field_7C >= size) {\n            if (size / sound->field_7C * sound->field_7C != size) {\n                size = (size / sound->field_7C + 1) * sound->field_7C;\n            }\n        } else {\n            size = sound->field_78 * sound->field_7C;\n        }\n    } else {\n        sound->field_44 &= ~(0x03);\n        sound->field_44 |= 0x01;\n    }\n\n    buf = (unsigned char*)mallocPtr(size);\n    bytes_read = sound->io.read(sound->io.fd, buf, size);\n    if (bytes_read != size) {\n        if (!(sound->field_3C & 0x20) || (sound->field_3C & (0x01 << 8))) {\n            memset(buf + bytes_read, 0, size - bytes_read);\n        } else {\n            v14 = buf + bytes_read;\n            v15 = bytes_read;\n            while (size - v15 > bytes_read) {\n                memcpy(v14, buf, bytes_read);\n                v15 += bytes_read;\n                v14 += bytes_read;\n            }\n\n            if (v15 < size) {\n                memcpy(v14, buf, size - v15);\n            }\n        }\n    }\n\n    result = soundSetData(sound, buf, size);\n    freePtr(buf);\n\n    if (sound->field_44 & 0x01) {\n        sound->io.close(sound->io.fd);\n        sound->io.fd = -1;\n    } else {\n        if (sound->field_20 == NULL) {\n            sound->field_20 = (unsigned char*)mallocPtr(sound->field_7C);\n        }\n    }\n\n    return result;\n}\n\n// 0x4AD498\nint soundLoad(Sound* sound, char* filePath)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    sound->io.fd = sound->io.open(nameMangler(filePath), 0x0200);\n    if (sound->io.fd == -1) {\n        soundErrorno = SOUND_FILE_NOT_FOUND;\n        return soundErrorno;\n    }\n\n    return preloadBuffers(sound);\n}\n\n// 0x4AD504\nint soundRewind(Sound* sound)\n{\n    HRESULT hr;\n\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (sound->field_44 & 0x02) {\n        sound->io.seek(sound->io.fd, 0, SEEK_SET);\n        sound->field_70 = 0;\n        sound->field_74 = 0;\n        sound->field_64 = 0;\n        sound->field_3C &= 0xFD7F;\n        hr = IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, 0);\n        preloadBuffers(sound);\n    } else {\n        hr = IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, 0);\n    }\n\n    if (hr != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    sound->field_40 &= ~SOUND_FLAG_SOUND_IS_DONE;\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AD5C8\nstatic int addSoundData(Sound* sound, unsigned char* buf, int size)\n{\n    HRESULT hr;\n    void* audio_ptr_1;\n    DWORD audio_bytes_1;\n    void* audio_ptr_2;\n    DWORD audio_bytes_2;\n\n    hr = IDirectSoundBuffer_Lock(sound->directSoundBuffer, 0, size, &audio_ptr_1, &audio_bytes_1, &audio_ptr_2, &audio_bytes_2, DSBLOCK_FROMWRITECURSOR);\n    if (hr == DSERR_BUFFERLOST) {\n        IDirectSoundBuffer_Restore(sound->directSoundBuffer);\n        hr = IDirectSoundBuffer_Lock(sound->directSoundBuffer, 0, size, &audio_ptr_1, &audio_bytes_1, &audio_ptr_2, &audio_bytes_2, DSBLOCK_FROMWRITECURSOR);\n    }\n\n    if (hr != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    memcpy(audio_ptr_1, buf, audio_bytes_1);\n\n    if (audio_ptr_2 != NULL) {\n        memcpy(audio_ptr_2, buf + audio_bytes_1, audio_bytes_2);\n    }\n\n    hr = IDirectSoundBuffer_Unlock(sound->directSoundBuffer, audio_ptr_1, audio_bytes_1, audio_ptr_2, audio_bytes_2);\n    if (hr != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AD6C0\nint soundSetData(Sound* sound, unsigned char* buf, int size)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (sound->directSoundBuffer == NULL) {\n        sound->directSoundBufferDescription.dwBufferBytes = size;\n\n        if (IDirectSound_CreateSoundBuffer(soundDSObject, &(sound->directSoundBufferDescription), &(sound->directSoundBuffer), NULL) != DS_OK) {\n            soundErrorno = SOUND_UNKNOWN_ERROR;\n            return soundErrorno;\n        }\n    }\n\n    return addSoundData(sound, buf, size);\n}\n\n// 0x4AD73C\nint soundPlay(Sound* sound)\n{\n    HRESULT hr;\n    DWORD readPos;\n    DWORD writePos;\n\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    // TODO: Check.\n    if (sound->field_40 & SOUND_FLAG_SOUND_IS_DONE) {\n        soundRewind(sound);\n    }\n\n    soundVolume(sound, sound->volume);\n\n    hr = IDirectSoundBuffer_Play(sound->directSoundBuffer, 0, 0, sound->field_3C & 0x20 ? DSBPLAY_LOOPING : 0);\n\n    IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &readPos, &writePos);\n    sound->field_70 = readPos / sound->field_7C;\n\n    if (hr != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    sound->field_40 |= SOUND_FLAG_SOUND_IS_PLAYING;\n\n    ++numSounds;\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AD828\nint soundStop(Sound* sound)\n{\n    HRESULT hr;\n\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING)) {\n        soundErrorno = SOUND_NOT_PLAYING;\n        return soundErrorno;\n    }\n\n    hr = IDirectSoundBuffer_Stop(sound->directSoundBuffer);\n    if (hr != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    sound->field_40 &= ~SOUND_FLAG_SOUND_IS_PLAYING;\n    numSounds--;\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AD8DC\nint soundDelete(Sound* sample)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sample == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (sample->io.fd != -1) {\n        sample->io.close(sample->io.fd);\n        sample->io.fd = -1;\n    }\n\n    soundMgrDelete(sample);\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AD948\nint soundContinue(Sound* sound)\n{\n    HRESULT hr;\n    DWORD status;\n\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) || (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED)) {\n        soundErrorno = SOUND_NOT_PLAYING;\n        return soundErrorno;\n    }\n\n    if (sound->field_40 & SOUND_FLAG_SOUND_IS_DONE) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    hr = IDirectSoundBuffer_GetStatus(sound->directSoundBuffer, &status);\n    if (hr != DS_OK) {\n        debug_printf(\"Error in soundContinue, %x\\n\", hr);\n\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    if (!(sound->field_3C & 0x80) && (status & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))) {\n        if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED) && (sound->field_44 & 0x02)) {\n            refreshSoundBuffers(sound);\n        }\n    } else if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED)) {\n        if (sound->callback != NULL) {\n            sound->callback(sound->callbackUserData, 1);\n            sound->callback = NULL;\n        }\n\n        if (sound->field_44 & 0x04) {\n            sound->callback = NULL;\n            soundDelete(sound);\n        } else {\n            sound->field_40 |= SOUND_FLAG_SOUND_IS_DONE;\n\n            if (sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) {\n                --numSounds;\n            }\n\n            soundStop(sound);\n\n            sound->field_40 &= ~(SOUND_FLAG_SOUND_IS_DONE | SOUND_FLAG_SOUND_IS_PLAYING);\n        }\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4ADA84\nbool soundPlaying(Sound* sound)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return false;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == 0) {\n        soundErrorno = SOUND_NO_SOUND;\n        return false;\n    }\n\n    return (sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) != 0;\n}\n\n// 0x4ADAC4\nbool soundDone(Sound* sound)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return false;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == 0) {\n        soundErrorno = SOUND_NO_SOUND;\n        return false;\n    }\n\n    return sound->field_40 & 1;\n}\n\n// 0x4ADB44\nbool soundPaused(Sound* sound)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return false;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return false;\n    }\n\n    return (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED) != 0;\n}\n\n// 0x4ADBC4\nint soundType(Sound* sound, int a2)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return 0;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return 0;\n    }\n\n    return sound->field_44 & a2;\n}\n\n// 0x4ADC04\nint soundLength(Sound* sound)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    int bytesPerSec = sound->directSoundBufferDescription.lpwfxFormat->nAvgBytesPerSec;\n    int v3 = sound->field_60;\n    int v4 = v3 % bytesPerSec;\n    int result = v3 / bytesPerSec;\n    if (v4 != 0) {\n        result += 1;\n    }\n\n    return result;\n}\n\n// 0x4ADD00\nint soundLoop(Sound* sound, int a2)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (a2) {\n        sound->field_3C |= 0x20;\n        sound->field_50 = a2;\n    } else {\n        sound->field_50 = 0;\n        sound->field_58 = -1;\n        sound->field_54 = 0;\n        sound->field_3C &= ~(0x20);\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// Normalize volume?\n//\n// 0x4ADD68\nint soundVolumeHMItoDirectSound(int volume)\n{\n    double normalizedVolume;\n\n    if (volume > VOLUME_MAX) {\n        volume = VOLUME_MAX;\n    }\n\n    if (volume != 0) {\n        normalizedVolume = -1000.0 * log2(32767.0 / volume);\n        normalizedVolume = max(min(normalizedVolume, 0.0), -10000.0);\n    } else {\n        normalizedVolume = -10000.0;\n    }\n\n    return (int)normalizedVolume;\n}\n\n// 0x4ADE0C\nint soundVolume(Sound* sound, int volume)\n{\n    int normalizedVolume;\n    HRESULT hr;\n\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    sound->volume = volume;\n\n    if (sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_ERROR;\n        return soundErrorno;\n    }\n\n    normalizedVolume = soundVolumeHMItoDirectSound(masterVol * volume / VOLUME_MAX);\n\n    hr = IDirectSoundBuffer_SetVolume(sound->directSoundBuffer, normalizedVolume);\n    if (hr != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4ADE80\nint soundGetVolume(Sound* sound)\n{\n    long volume;\n    int v13;\n    int v8;\n    int diff;\n\n    if (!deviceInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    IDirectSoundBuffer_GetVolume(sound->directSoundBuffer, &volume);\n\n    if (volume == -10000) {\n        v13 = 0;\n    } else {\n        // TODO: Check.\n        volume = -volume;\n        v13 = (int)(32767.0 / pow(2.0, (volume * 0.001)));\n    }\n\n    v8 = VOLUME_MAX * v13 / masterVol;\n    diff = abs(v8 - sound->volume);\n    if (diff > 20) {\n        debug_printf(\"Actual sound volume differs significantly from noted volume actual %x stored %x, diff %d.\", v8, sound->volume, diff);\n    }\n\n    return sound->volume;\n}\n\n// 0x4ADFF0\nint soundSetCallback(Sound* sound, SoundCallback* callback, void* userData)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    sound->callback = callback;\n    sound->callbackUserData = userData;\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AE02C\nint soundSetChannel(Sound* sound, int channels)\n{\n    LPWAVEFORMATEX format;\n\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (channels == 3) {\n        format = sound->directSoundBufferDescription.lpwfxFormat;\n\n        format->nBlockAlign = (2 * format->wBitsPerSample) / 8;\n        format->nChannels = 2;\n        format->nAvgBytesPerSec = format->nBlockAlign * sampleRate;\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AE0B0\nint soundSetReadLimit(Sound* sound, int readLimit)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_DEVICE;\n        return soundErrorno;\n    }\n\n    sound->readLimit = readLimit;\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// TODO: Check, looks like it uses couple of inlined functions.\n//\n// 0x4AE0E4\nint soundPause(Sound* sound)\n{\n    HRESULT hr;\n    DWORD readPos;\n    DWORD writePos;\n\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING)) {\n        soundErrorno = SOUND_NOT_PLAYING;\n        return soundErrorno;\n    }\n\n    if (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED) {\n        soundErrorno = SOUND_ALREADY_PAUSED;\n        return soundErrorno;\n    }\n\n    hr = IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &readPos, &writePos);\n    if (hr != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    sound->field_48 = readPos;\n    sound->field_40 |= SOUND_FLAG_SOUND_IS_PAUSED;\n\n    return soundStop(sound);\n}\n\n// TODO: Check, looks like it uses couple of inlined functions.\n//\n// 0x4AE1F0\nint soundUnpause(Sound* sound)\n{\n    HRESULT hr;\n\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL || sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if ((sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) != 0) {\n        soundErrorno = SOUND_NOT_PAUSED;\n        return soundErrorno;\n    }\n\n    if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED)) {\n        soundErrorno = SOUND_NOT_PAUSED;\n        return soundErrorno;\n    }\n\n    hr = IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, sound->field_48);\n    if (hr != DS_OK) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    sound->field_40 &= ~SOUND_FLAG_SOUND_IS_PAUSED;\n    sound->field_48 = 0;\n\n    return soundPlay(sound);\n}\n\n// 0x4AE2FC\nint soundSetFileIO(Sound* sound, SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (openProc != NULL) {\n        sound->io.open = openProc;\n    }\n\n    if (closeProc != NULL) {\n        sound->io.close = closeProc;\n    }\n\n    if (readProc != NULL) {\n        sound->io.read = readProc;\n    }\n\n    if (writeProc != NULL) {\n        sound->io.write = writeProc;\n    }\n\n    if (seekProc != NULL) {\n        sound->io.seek = seekProc;\n    }\n\n    if (tellProc != NULL) {\n        sound->io.tell = tellProc;\n    }\n\n    if (fileLengthProc != NULL) {\n        sound->io.filelength = fileLengthProc;\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AE378\nvoid soundMgrDelete(Sound* sound)\n{\n    FadeSound* curr;\n    Sound* v10;\n    Sound* v11;\n\n    if (sound->field_40 & SOUND_FLAG_SOUND_IS_FADING) {\n        curr = fadeHead;\n\n        while (curr != NULL) {\n            if (sound == curr->sound) {\n                break;\n            }\n\n            curr = curr->next;\n        }\n\n        removeFadeSound(curr);\n    }\n\n    if (sound->directSoundBuffer != NULL) {\n        // NOTE: Uninline.\n        if (!soundPlaying(sound)) {\n            soundStop(sound);\n        }\n\n        if (sound->callback != NULL) {\n            sound->callback(sound->callbackUserData, 1);\n        }\n\n        IDirectSoundBuffer_Release(sound->directSoundBuffer);\n        sound->directSoundBuffer = NULL;\n    }\n\n    if (sound->field_90 != NULL) {\n        sound->field_90(sound->field_8C);\n    }\n\n    if (sound->field_20 != NULL) {\n        freePtr(sound->field_20);\n        sound->field_20 = NULL;\n    }\n\n    if (sound->directSoundBufferDescription.lpwfxFormat != NULL) {\n        freePtr(sound->directSoundBufferDescription.lpwfxFormat);\n    }\n\n    v10 = sound->next;\n    if (v10 != NULL) {\n        v10->prev = sound->prev;\n    }\n\n    v11 = sound->prev;\n    if (v11 != NULL) {\n        v11->next = sound->next;\n    } else {\n        soundMgrList = sound->next;\n    }\n\n    freePtr(sound);\n}\n\n// 0x4AE578\nint soundSetMasterVolume(int volume)\n{\n    if (volume < VOLUME_MIN || volume > VOLUME_MAX) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    masterVol = volume;\n\n    Sound* curr = soundMgrList;\n    while (curr != NULL) {\n        soundVolume(curr, curr->volume);\n        curr = curr->next;\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AE5C8\nstatic void CALLBACK doTimerEvent(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)\n{\n    void (*fn)();\n\n    if (dwUser != 0) {\n        fn = (void (*)())dwUser;\n        fn();\n    }\n}\n\n// 0x4AE614\nstatic void removeTimedEvent(unsigned int* timerId)\n{\n    if (*timerId != -1) {\n        timeKillEvent(*timerId);\n        *timerId = -1;\n    }\n}\n\n// 0x4AE634\nint soundGetPosition(Sound* sound)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    DWORD playPos;\n    DWORD writePos;\n    IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &playPos, &writePos);\n\n    if ((sound->field_44 & 0x02) != 0) {\n        if (playPos < sound->field_74) {\n            playPos += sound->field_64 + sound->field_78 * sound->field_7C - sound->field_74;\n        } else {\n            playPos -= sound->field_74 + sound->field_64;\n        }\n    }\n\n    return playPos;\n}\n\n// 0x4AE6CC\nint soundSetPosition(Sound* sound, int a2)\n{\n    if (!driverInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (sound->directSoundBuffer == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    if (sound->field_44 & 0x02) {\n        int v6 = a2 / sound->field_7C % sound->field_78;\n\n        IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, v6 * sound->field_7C + a2 % sound->field_7C);\n\n        sound->io.seek(sound->io.fd, v6 * sound->field_7C, SEEK_SET);\n        int bytes_read = sound->io.read(sound->io.fd, sound->field_20, sound->field_7C);\n        if (bytes_read < sound->field_7C) {\n            if (sound->field_44 & 0x02) {\n                sound->io.seek(sound->io.fd, 0, SEEK_SET);\n                sound->io.read(sound->io.fd, sound->field_20 + bytes_read, sound->field_7C - bytes_read);\n            } else {\n                memset(sound->field_20 + bytes_read, 0, sound->field_7C - bytes_read);\n            }\n        }\n\n        int v17 = v6 + 1;\n        sound->field_64 = a2;\n\n        if (v17 < sound->field_78) {\n            sound->field_70 = v17;\n        } else {\n            sound->field_70 = 0;\n        }\n\n        soundContinue(sound);\n    } else {\n        IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, a2);\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AE830\nstatic void removeFadeSound(FadeSound* fadeSound)\n{\n    FadeSound* prev;\n    FadeSound* next;\n    FadeSound* tmp;\n\n    if (fadeSound == NULL) {\n        return;\n    }\n\n    if (fadeSound->sound == NULL) {\n        return;\n    }\n\n    if (!(fadeSound->sound->field_40 & SOUND_FLAG_SOUND_IS_FADING)) {\n        return;\n    }\n\n    prev = fadeSound->prev;\n    if (prev != NULL) {\n        prev->next = fadeSound->next;\n    } else {\n        fadeHead = fadeSound->next;\n    }\n\n    next = fadeSound->next;\n    if (next != NULL) {\n        next->prev = fadeSound->prev;\n    }\n\n    fadeSound->sound->field_40 &= ~SOUND_FLAG_SOUND_IS_FADING;\n    fadeSound->sound = NULL;\n\n    tmp = fadeFreeList;\n    fadeFreeList = fadeSound;\n    fadeSound->next = tmp;\n}\n\n// 0x4AE8B0\nstatic void fadeSounds()\n{\n    FadeSound* ptr;\n\n    ptr = fadeHead;\n    while (ptr != NULL) {\n        if ((ptr->currentVolume > ptr->targetVolume || ptr->currentVolume + ptr->deltaVolume < ptr->targetVolume) && (ptr->currentVolume < ptr->targetVolume || ptr->currentVolume + ptr->deltaVolume > ptr->targetVolume)) {\n            ptr->currentVolume += ptr->deltaVolume;\n            soundVolume(ptr->sound, ptr->currentVolume);\n        } else {\n            if (ptr->targetVolume == 0) {\n                if (ptr->field_14) {\n                    soundPause(ptr->sound);\n                    soundVolume(ptr->sound, ptr->initialVolume);\n                } else {\n                    if (ptr->sound->field_44 & 0x04) {\n                        soundDelete(ptr->sound);\n                    } else {\n                        soundStop(ptr->sound);\n\n                        ptr->initialVolume = ptr->targetVolume;\n                        ptr->currentVolume = ptr->targetVolume;\n                        ptr->deltaVolume = 0;\n\n                        soundVolume(ptr->sound, ptr->targetVolume);\n                    }\n                }\n            }\n\n            removeFadeSound(ptr);\n        }\n    }\n\n    if (fadeHead == NULL) {\n        // NOTE: Uninline.\n        removeTimedEvent(&fadeEventHandle);\n    }\n}\n\n// 0x4AE988\nstatic int internalSoundFade(Sound* sound, int duration, int targetVolume, int a4)\n{\n    FadeSound* ptr;\n\n    if (!deviceInit) {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        return soundErrorno;\n    }\n\n    if (sound == NULL) {\n        soundErrorno = SOUND_NO_SOUND;\n        return soundErrorno;\n    }\n\n    ptr = NULL;\n    if (sound->field_40 & SOUND_FLAG_SOUND_IS_FADING) {\n        ptr = fadeHead;\n        while (ptr != NULL) {\n            if (ptr->sound == sound) {\n                break;\n            }\n\n            ptr = ptr->next;\n        }\n    }\n\n    if (ptr == NULL) {\n        if (fadeFreeList != NULL) {\n            ptr = fadeFreeList;\n            fadeFreeList = fadeFreeList->next;\n        } else {\n            ptr = (FadeSound*)mallocPtr(sizeof(FadeSound));\n        }\n\n        if (ptr != NULL) {\n            if (fadeHead != NULL) {\n                fadeHead->prev = ptr;\n            }\n\n            ptr->sound = sound;\n            ptr->prev = NULL;\n            ptr->next = fadeHead;\n            fadeHead = ptr;\n        }\n    }\n\n    if (ptr == NULL) {\n        soundErrorno = SOUND_NO_MEMORY_AVAILABLE;\n        return soundErrorno;\n    }\n\n    ptr->targetVolume = targetVolume;\n    ptr->initialVolume = soundGetVolume(sound);\n    ptr->currentVolume = ptr->initialVolume;\n    ptr->field_14 = a4;\n    // TODO: Check.\n    ptr->deltaVolume = 8 * (125 * (targetVolume - ptr->initialVolume)) / (40 * duration);\n\n    sound->field_40 |= SOUND_FLAG_SOUND_IS_FADING;\n\n    bool v14;\n    if (driverInit) {\n        if (sound->directSoundBuffer != NULL) {\n            v14 = (sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) == 0;\n        } else {\n            soundErrorno = SOUND_NO_SOUND;\n            v14 = true;\n        }\n    } else {\n        soundErrorno = SOUND_NOT_INITIALIZED;\n        v14 = true;\n    }\n\n    if (v14) {\n        soundPlay(sound);\n    }\n\n    if (fadeEventHandle != -1) {\n        soundErrorno = SOUND_NO_ERROR;\n        return soundErrorno;\n    }\n\n    fadeEventHandle = timeSetEvent(40, 10, doTimerEvent, (DWORD_PTR)fadeSounds, 1);\n    if (fadeEventHandle == 0) {\n        soundErrorno = SOUND_UNKNOWN_ERROR;\n        return soundErrorno;\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n\n// 0x4AEB0C\nint soundFade(Sound* sound, int duration, int targetVolume)\n{\n    return internalSoundFade(sound, duration, targetVolume, 0);\n}\n\n// 0x4AEB54\nvoid soundFlushAllSounds()\n{\n    while (soundMgrList != NULL) {\n        soundDelete(soundMgrList);\n    }\n}\n\n// 0x4AEBE0\nvoid soundContinueAll()\n{\n    Sound* curr = soundMgrList;\n    while (curr != NULL) {\n        // Sound can be deallocated in `soundContinue`.\n        Sound* next = curr->next;\n        soundContinue(curr);\n        curr = next;\n    }\n}\n\n// 0x4AEC00\nint soundSetDefaultFileIO(SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc)\n{\n    if (openProc != NULL) {\n        defaultStream.open = openProc;\n    }\n\n    if (closeProc != NULL) {\n        defaultStream.close = closeProc;\n    }\n\n    if (readProc != NULL) {\n        defaultStream.read = readProc;\n    }\n\n    if (writeProc != NULL) {\n        defaultStream.write = writeProc;\n    }\n\n    if (seekProc != NULL) {\n        defaultStream.seek = seekProc;\n    }\n\n    if (tellProc != NULL) {\n        defaultStream.tell = tellProc;\n    }\n\n    if (fileLengthProc != NULL) {\n        defaultStream.filelength = fileLengthProc;\n    }\n\n    soundErrorno = SOUND_NO_ERROR;\n    return soundErrorno;\n}\n"
  },
  {
    "path": "src/int/sound.h",
    "content": "#ifndef FALLOUT_INT_SOUND_H_\n#define FALLOUT_INT_SOUND_H_\n\n#include <stdbool.h>\n\n#include \"memory_defs.h\"\n#include \"plib/gnw/gnw95dx.h\"\n\n#define SOUND_FLAG_SOUND_IS_DONE 0x01\n#define SOUND_FLAG_SOUND_IS_PLAYING 0x02\n#define SOUND_FLAG_SOUND_IS_FADING 0x04\n#define SOUND_FLAG_SOUND_IS_PAUSED 0x08\n\n#define VOLUME_MIN 0\n#define VOLUME_MAX 0x7FFF\n\ntypedef enum SoundError {\n    SOUND_NO_ERROR = 0,\n    SOUND_SOS_DRIVER_NOT_LOADED = 1,\n    SOUND_SOS_INVALID_POINTER = 2,\n    SOUND_SOS_DETECT_INITIALIZED = 3,\n    SOUND_SOS_FAIL_ON_FILE_OPEN = 4,\n    SOUND_SOS_MEMORY_FAIL = 5,\n    SOUND_SOS_INVALID_DRIVER_ID = 6,\n    SOUND_SOS_NO_DRIVER_FOUND = 7,\n    SOUND_SOS_DETECTION_FAILURE = 8,\n    SOUND_SOS_DRIVER_LOADED = 9,\n    SOUND_SOS_INVALID_HANDLE = 10,\n    SOUND_SOS_NO_HANDLES = 11,\n    SOUND_SOS_PAUSED = 12,\n    SOUND_SOS_NO_PAUSED = 13,\n    SOUND_SOS_INVALID_DATA = 14,\n    SOUND_SOS_DRV_FILE_FAIL = 15,\n    SOUND_SOS_INVALID_PORT = 16,\n    SOUND_SOS_INVALID_IRQ = 17,\n    SOUND_SOS_INVALID_DMA = 18,\n    SOUND_SOS_INVALID_DMA_IRQ = 19,\n    SOUND_NO_DEVICE = 20,\n    SOUND_NOT_INITIALIZED = 21,\n    SOUND_NO_SOUND = 22,\n    SOUND_FUNCTION_NOT_SUPPORTED = 23,\n    SOUND_NO_BUFFERS_AVAILABLE = 24,\n    SOUND_FILE_NOT_FOUND = 25,\n    SOUND_ALREADY_PLAYING = 26,\n    SOUND_NOT_PLAYING = 27,\n    SOUND_ALREADY_PAUSED = 28,\n    SOUND_NOT_PAUSED = 29,\n    SOUND_INVALID_HANDLE = 30,\n    SOUND_NO_MEMORY_AVAILABLE = 31,\n    SOUND_UNKNOWN_ERROR = 32,\n    SOUND_ERR_COUNT,\n} SoundError;\n\ntypedef char*(SoundFileNameMangler)(char*);\ntypedef int SoundOpenProc(const char* filePath, int flags, ...);\ntypedef int SoundCloseProc(int fileHandle);\ntypedef int SoundReadProc(int fileHandle, void* buf, unsigned int size);\ntypedef int SoundWriteProc(int fileHandle, const void* buf, unsigned int size);\ntypedef long SoundSeekProc(int fileHandle, long offset, int origin);\ntypedef long SoundTellProc(int fileHandle);\ntypedef long SoundFileLengthProc(int fileHandle);\n\ntypedef struct SoundFileIO {\n    SoundOpenProc* open;\n    SoundCloseProc* close;\n    SoundReadProc* read;\n    SoundWriteProc* write;\n    SoundSeekProc* seek;\n    SoundTellProc* tell;\n    SoundFileLengthProc* filelength;\n    int fd;\n} SoundFileIO;\n\ntypedef void SoundCallback(void* userData, int a2);\n\ntypedef struct Sound {\n    SoundFileIO io;\n    unsigned char* field_20;\n    LPDIRECTSOUNDBUFFER directSoundBuffer;\n    DSBUFFERDESC directSoundBufferDescription;\n    int field_3C;\n    // flags\n    int field_40;\n    int field_44;\n    // pause pos\n    int field_48;\n    int volume;\n    int field_50;\n    int field_54;\n    int field_58;\n    int field_5C;\n    // file size\n    int field_60;\n    int field_64;\n    int field_68;\n    int readLimit;\n    int field_70;\n    DWORD field_74;\n    int field_78;\n    int field_7C;\n    int field_80;\n    // callback data\n    void* callbackUserData;\n    SoundCallback* callback;\n    int field_8C;\n    void (*field_90)(int);\n    struct Sound* next;\n    struct Sound* prev;\n} Sound;\n\nextern LPDIRECTSOUNDBUFFER primaryDSBuffer;\nextern LPDIRECTSOUND soundDSObject;\n\nvoid soundRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc);\nconst char* soundError(int err);\nint soundInit(int a1, int a2, int a3, int a4, int rate);\nvoid soundClose();\nSound* soundAllocate(int a1, int a2);\nint soundLoad(Sound* sound, char* filePath);\nint soundRewind(Sound* sound);\nint soundSetData(Sound* sound, unsigned char* buf, int size);\nint soundPlay(Sound* sound);\nint soundStop(Sound* sound);\nint soundDelete(Sound* sound);\nint soundContinue(Sound* sound);\nbool soundPlaying(Sound* sound);\nbool soundDone(Sound* sound);\nbool soundPaused(Sound* sound);\nint soundType(Sound* sound, int a2);\nint soundLength(Sound* sound);\nint soundLoop(Sound* sound, int a2);\nint soundVolumeHMItoDirectSound(int a1);\nint soundVolume(Sound* sound, int volume);\nint soundGetVolume(Sound* sound);\nint soundSetCallback(Sound* sound, SoundCallback* callback, void* userData);\nint soundSetChannel(Sound* sound, int channels);\nint soundSetReadLimit(Sound* sound, int readLimit);\nint soundPause(Sound* sound);\nint soundUnpause(Sound* sound);\nint soundSetFileIO(Sound* sound, SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc);\nvoid soundMgrDelete(Sound* sound);\nint soundSetMasterVolume(int value);\nint soundGetPosition(Sound* sound);\nint soundSetPosition(Sound* sound, int a2);\nint soundFade(Sound* sound, int duration, int targetVolume);\nvoid soundFlushAllSounds();\nvoid soundContinueAll();\nint soundSetDefaultFileIO(SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc);\n\n#endif /* FALLOUT_INT_SOUND_H_ */\n"
  },
  {
    "path": "src/int/support/intextra.c",
    "content": "#include \"int/support/intextra.h\"\n\n#include <limits.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"game/actions.h\"\n#include \"game/anim.h\"\n#include \"plib/color/color.h\"\n#include \"game/combat.h\"\n#include \"game/combatai.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/critter.h\"\n#include \"plib/gnw/debug.h\"\n#include \"int/dialog.h\"\n#include \"game/display.h\"\n#include \"game/endgame.h\"\n#include \"game/game.h\"\n#include \"game/gconfig.h\"\n#include \"game/gdialog.h\"\n#include \"game/gmovie.h\"\n#include \"game/gsound.h\"\n#include \"plib/gnw/rect.h\"\n#include \"game/intface.h\"\n#include \"game/item.h\"\n#include \"game/light.h\"\n#include \"game/loadsave.h\"\n#include \"game/map.h\"\n#include \"game/object.h\"\n#include \"game/palette.h\"\n#include \"game/perk.h\"\n#include \"game/proto.h\"\n#include \"game/protinst.h\"\n#include \"game/queue.h\"\n#include \"game/roll.h\"\n#include \"game/reaction.h\"\n#include \"game/scripts.h\"\n#include \"game/skill.h\"\n#include \"game/stat.h\"\n#include \"game/textobj.h\"\n#include \"game/tile.h\"\n#include \"game/trait.h\"\n#include \"plib/gnw/vcr.h\"\n#include \"game/worldmap.h\"\n\ntypedef enum Metarule {\n    METARULE_SIGNAL_END_GAME = 13,\n    METARULE_FIRST_RUN = 14,\n    METARULE_ELEVATOR = 15,\n    METARULE_PARTY_COUNT = 16,\n    METARULE_AREA_KNOWN = 17,\n    METARULE_WHO_ON_DRUGS = 18,\n    METARULE_MAP_KNOWN = 19,\n    METARULE_IS_LOADGAME = 22,\n    METARULE_CAR_CURRENT_TOWN = 30,\n    METARULE_GIVE_CAR_TO_PARTY = 31,\n    METARULE_GIVE_CAR_GAS = 32,\n    METARULE_SKILL_CHECK_TAG = 40,\n    METARULE_DROP_ALL_INVEN = 42,\n    METARULE_INVEN_UNWIELD_WHO = 43,\n    METARULE_GET_WORLDMAP_XPOS = 44,\n    METARULE_GET_WORLDMAP_YPOS = 45,\n    METARULE_CURRENT_TOWN = 46,\n    METARULE_LANGUAGE_FILTER = 47,\n    METARULE_VIOLENCE_FILTER = 48,\n    METARULE_WEAPON_DAMAGE_TYPE = 49,\n    METARULE_CRITTER_BARTERS = 50,\n    METARULE_CRITTER_KILL_TYPE = 51,\n    METARULE_SET_CAR_CARRY_AMOUNT = 52,\n    METARULE_GET_CAR_CARRY_AMOUNT = 53,\n} Metarule;\n\ntypedef enum Metarule3 {\n    METARULE3_CLR_FIXED_TIMED_EVENTS = 100,\n    METARULE3_MARK_SUBTILE = 101,\n    METARULE3_SET_WM_MUSIC = 102,\n    METARULE3_GET_KILL_COUNT = 103,\n    METARULE3_MARK_MAP_ENTRANCE = 104,\n    METARULE3_WM_SUBTILE_STATE = 105,\n    METARULE3_TILE_GET_NEXT_CRITTER = 106,\n    METARULE3_ART_SET_BASE_FID_NUM = 107,\n    METARULE3_TILE_SET_CENTER = 108,\n    // chem use preference\n    METARULE3_109 = 109,\n    // probably true if car is out of fuel\n    METARULE3_110 = 110,\n    // probably returns city index\n    METARULE3_111 = 111,\n} Metarule3;\n\ntypedef enum CritterTrait {\n    CRITTER_TRAIT_PERK = 0,\n    CRITTER_TRAIT_OBJECT = 1,\n    CRITTER_TRAIT_TRAIT = 2,\n} CritterTrait;\n\ntypedef enum CritterTraitObject {\n    CRITTER_TRAIT_OBJECT_AI_PACKET = 5,\n    CRITTER_TRAIT_OBJECT_TEAM = 6,\n    CRITTER_TRAIT_OBJECT_ROTATION = 10,\n    CRITTER_TRAIT_OBJECT_IS_INVISIBLE = 666,\n    CRITTER_TRAIT_OBJECT_GET_INVENTORY_WEIGHT = 669,\n} CritterTraitObject;\n\n// See [op_critter_state].\ntypedef enum CritterState {\n    CRITTER_STATE_NORMAL = 0x00,\n    CRITTER_STATE_DEAD = 0x01,\n    CRITTER_STATE_PRONE = 0x02,\n} CritterState;\n\nenum {\n    INVEN_TYPE_WORN = 0,\n    INVEN_TYPE_RIGHT_HAND = 1,\n    INVEN_TYPE_LEFT_HAND = 2,\n    INVEN_TYPE_INV_COUNT = -2,\n};\n\ntypedef enum FloatingMessageType {\n    FLOATING_MESSAGE_TYPE_WARNING = -2,\n    FLOATING_MESSAGE_TYPE_COLOR_SEQUENCE = -1,\n    FLOATING_MESSAGE_TYPE_NORMAL = 0,\n    FLOATING_MESSAGE_TYPE_BLACK,\n    FLOATING_MESSAGE_TYPE_RED,\n    FLOATING_MESSAGE_TYPE_GREEN,\n    FLOATING_MESSAGE_TYPE_BLUE,\n    FLOATING_MESSAGE_TYPE_PURPLE,\n    FLOATING_MESSAGE_TYPE_NEAR_WHITE,\n    FLOATING_MESSAGE_TYPE_LIGHT_RED,\n    FLOATING_MESSAGE_TYPE_YELLOW,\n    FLOATING_MESSAGE_TYPE_WHITE,\n    FLOATING_MESSAGE_TYPE_GREY,\n    FLOATING_MESSAGE_TYPE_DARK_GREY,\n    FLOATING_MESSAGE_TYPE_LIGHT_GREY,\n    FLOATING_MESSAGE_TYPE_COUNT,\n} FloatingMessageType;\n\ntypedef enum OpRegAnimFunc {\n    OP_REG_ANIM_FUNC_BEGIN = 1,\n    OP_REG_ANIM_FUNC_CLEAR = 2,\n    OP_REG_ANIM_FUNC_END = 3,\n} OpRegAnimFunc;\n\nstatic void int_debug(const char* format, ...);\nstatic int scripts_tile_is_visible(int tile);\nstatic int correctFidForRemovedItem(Object* a1, Object* a2, int a3);\nstatic void op_give_exp_points(Program* program);\nstatic void op_scr_return(Program* program);\nstatic void op_play_sfx(Program* program);\nstatic void op_set_map_start(Program* program);\nstatic void op_override_map_start(Program* program);\nstatic void op_has_skill(Program* program);\nstatic void op_using_skill(Program* program);\nstatic void op_roll_vs_skill(Program* program);\nstatic void op_skill_contest(Program* program);\nstatic void op_do_check(Program* program);\nstatic void op_is_success(Program* program);\nstatic void op_is_critical(Program* program);\nstatic void op_how_much(Program* program);\nstatic void op_mark_area_known(Program* program);\nstatic void op_reaction_influence(Program* program);\nstatic void op_random(Program* program);\nstatic void op_roll_dice(Program* program);\nstatic void op_move_to(Program* program);\nstatic void op_create_object_sid(Program* program);\nstatic void op_destroy_object(Program* program);\nstatic void op_display_msg(Program* program);\nstatic void op_script_overrides(Program* program);\nstatic void op_obj_is_carrying_obj_pid(Program* program);\nstatic void op_tile_contains_obj_pid(Program* program);\nstatic void op_self_obj(Program* program);\nstatic void op_source_obj(Program* program);\nstatic void op_target_obj(Program* program);\nstatic void op_dude_obj(Program* program);\nstatic void op_obj_being_used_with(Program* program);\nstatic void op_local_var(Program* program);\nstatic void op_set_local_var(Program* program);\nstatic void op_map_var(Program* program);\nstatic void op_set_map_var(Program* program);\nstatic void op_global_var(Program* program);\nstatic void op_set_global_var(Program* program);\nstatic void op_script_action(Program* program);\nstatic void op_obj_type(Program* program);\nstatic void op_obj_item_subtype(Program* program);\nstatic void op_get_critter_stat(Program* program);\nstatic void op_set_critter_stat(Program* program);\nstatic void op_animate_stand_obj(Program* program);\nstatic void op_animate_stand_reverse_obj(Program* program);\nstatic void op_animate_move_obj_to_tile(Program* program);\nstatic void op_tile_in_tile_rect(Program* program);\nstatic void op_make_daytime(Program* program);\nstatic void op_tile_distance(Program* program);\nstatic void op_tile_distance_objs(Program* program);\nstatic void op_tile_num(Program* program);\nstatic void op_tile_num_in_direction(Program* program);\nstatic void op_pickup_obj(Program* program);\nstatic void op_drop_obj(Program* program);\nstatic void op_add_obj_to_inven(Program* program);\nstatic void op_rm_obj_from_inven(Program* program);\nstatic void op_wield_obj_critter(Program* program);\nstatic void op_use_obj(Program* program);\nstatic void op_obj_can_see_obj(Program* program);\nstatic void op_attack(Program* program);\nstatic void op_start_gdialog(Program* program);\nstatic void op_end_dialogue(Program* program);\nstatic void op_dialogue_reaction(Program* program);\nstatic void op_metarule3(Program* program);\nstatic void op_set_map_music(Program* program);\nstatic void op_set_obj_visibility(Program* program);\nstatic void op_load_map(Program* program);\nstatic void op_wm_area_set_pos(Program* program);\nstatic void op_set_exit_grids(Program* program);\nstatic void op_anim_busy(Program* program);\nstatic void op_critter_heal(Program* program);\nstatic void op_set_light_level(Program* program);\nstatic void op_game_time(Program* program);\nstatic void op_game_time_in_seconds(Program* program);\nstatic void op_elevation(Program* program);\nstatic void op_kill_critter(Program* program);\nstatic void op_kill_critter_type(Program* program);\nstatic void op_critter_damage(Program* program);\nstatic void op_add_timer_event(Program* program);\nstatic void op_rm_timer_event(Program* program);\nstatic void op_game_ticks(Program* program);\nstatic void op_has_trait(Program* program);\nstatic void op_obj_can_hear_obj(Program* program);\nstatic void op_game_time_hour(Program* program);\nstatic void op_fixed_param(Program* program);\nstatic void op_tile_is_visible(Program* program);\nstatic void op_dialogue_system_enter(Program* program);\nstatic void op_action_being_used(Program* program);\nstatic void op_critter_state(Program* program);\nstatic void op_game_time_advance(Program* program);\nstatic void op_radiation_inc(Program* program);\nstatic void op_radiation_dec(Program* program);\nstatic void op_critter_attempt_placement(Program* program);\nstatic void op_obj_pid(Program* program);\nstatic void op_cur_map_index(Program* program);\nstatic void op_critter_add_trait(Program* program);\nstatic void op_critter_rm_trait(Program* program);\nstatic void op_proto_data(Program* program);\nstatic void op_message_str(Program* program);\nstatic void op_critter_inven_obj(Program* program);\nstatic void op_obj_set_light_level(Program* program);\nstatic void op_world_map(Program* program);\nstatic void op_inven_cmds(Program* program);\nstatic void op_float_msg(Program* program);\nstatic void op_metarule(Program* program);\nstatic void op_anim(Program* program);\nstatic void op_obj_carrying_pid_obj(Program* program);\nstatic void op_reg_anim_func(Program* program);\nstatic void op_reg_anim_animate(Program* program);\nstatic void op_reg_anim_animate_reverse(Program* program);\nstatic void op_reg_anim_obj_move_to_obj(Program* program);\nstatic void op_reg_anim_obj_run_to_obj(Program* program);\nstatic void op_reg_anim_obj_move_to_tile(Program* program);\nstatic void op_reg_anim_obj_run_to_tile(Program* program);\nstatic void op_play_gmovie(Program* program);\nstatic void op_add_mult_objs_to_inven(Program* program);\nstatic void op_rm_mult_objs_from_inven(Program* program);\nstatic void op_get_month(Program* program);\nstatic void op_get_day(Program* program);\nstatic void op_explosion(Program* program);\nstatic void op_days_since_visited(Program* program);\nstatic void op_gsay_start(Program* program);\nstatic void op_gsay_end(Program* program);\nstatic void op_gsay_reply(Program* program);\nstatic void op_gsay_option(Program* program);\nstatic void op_gsay_message(Program* program);\nstatic void op_giq_option(Program* program);\nstatic void op_poison(Program* program);\nstatic void op_get_poison(Program* program);\nstatic void op_party_add(Program* program);\nstatic void op_party_remove(Program* program);\nstatic void op_reg_anim_animate_forever(Program* program);\nstatic void op_critter_injure(Program* program);\nstatic void op_combat_is_initialized(Program* program);\nstatic void op_gdialog_barter(Program* program);\nstatic void op_difficulty_level(Program* program);\nstatic void op_running_burning_guy(Program* program);\nstatic void op_inven_unwield(Program* program);\nstatic void op_obj_is_locked(Program* program);\nstatic void op_obj_lock(Program* program);\nstatic void op_obj_unlock(Program* program);\nstatic void op_obj_is_open(Program* program);\nstatic void op_obj_open(Program* program);\nstatic void op_obj_close(Program* program);\nstatic void op_game_ui_disable(Program* program);\nstatic void op_game_ui_enable(Program* program);\nstatic void op_game_ui_is_disabled(Program* program);\nstatic void op_gfade_out(Program* program);\nstatic void op_gfade_in(Program* program);\nstatic void op_item_caps_total(Program* program);\nstatic void op_item_caps_adjust(Program* program);\nstatic void op_anim_action_frame(Program* program);\nstatic void op_reg_anim_play_sfx(Program* program);\nstatic void op_critter_mod_skill(Program* program);\nstatic void op_sfx_build_char_name(Program* program);\nstatic void op_sfx_build_ambient_name(Program* program);\nstatic void op_sfx_build_interface_name(Program* program);\nstatic void op_sfx_build_item_name(Program* program);\nstatic void op_sfx_build_weapon_name(Program* program);\nstatic void op_sfx_build_scenery_name(Program* program);\nstatic void op_sfx_build_open_name(Program* program);\nstatic void op_attack_setup(Program* program);\nstatic void op_destroy_mult_objs(Program* program);\nstatic void op_use_obj_on_obj(Program* program);\nstatic void op_endgame_slideshow(Program* program);\nstatic void op_move_obj_inven_to_obj(Program* program);\nstatic void op_endgame_movie(Program* program);\nstatic void op_obj_art_fid(Program* program);\nstatic void op_art_anim(Program* program);\nstatic void op_party_member_obj(Program* program);\nstatic void op_rotation_to_tile(Program* program);\nstatic void op_jam_lock(Program* program);\nstatic void op_gdialog_set_barter_mod(Program* program);\nstatic void op_combat_difficulty(Program* program);\nstatic void op_obj_on_screen(Program* program);\nstatic void op_critter_is_fleeing(Program* program);\nstatic void op_critter_set_flee_state(Program* program);\nstatic void op_terminate_combat(Program* program);\nstatic void op_debug_msg(Program* program);\nstatic void op_critter_stop_attacking(Program* program);\nstatic void op_tile_contains_pid_obj(Program* program);\nstatic void op_obj_name(Program* program);\nstatic void op_get_pc_stat(Program* program);\n\n// TODO: Remove.\n// 0x504B0C\nchar _aCritter[] = \"<Critter>\";\n\n// NOTE: This value is a little bit odd. It's used to handle 2 operations:\n// [op_start_gdialog] and [op_dialogue_reaction]. It's not used outside those\n// functions.\n//\n// When used inside [op_start_gdialog] this value stores [Fidget] constant\n// (1 - Good, 4 - Neutral, 7 - Bad).\n//\n// When used inside [op_dialogue_reaction] this value contains specified\n// reaction (-1 - Good, 0 - Neutral, 1 - Bad).\n//\n// 0x5970D0\nstatic int dialogue_mood;\n\n// 0x453FD0\nvoid dbg_error(Program* program, const char* name, int error)\n{\n    // 0x518EC0\n    static const char* dbg_error_strs[SCRIPT_ERROR_COUNT] = {\n        \"unimped\",\n        \"obj is NULL\",\n        \"can't match program to sid\",\n        \"follows\",\n    };\n\n    char string[260];\n\n    sprintf(string, \"Script Error: %s: op_%s: %s\", program->name, name, dbg_error_strs[error]);\n\n    debug_printf(string);\n}\n\n// 0x45400C\nstatic void int_debug(const char* format, ...)\n{\n    char string[260];\n\n    va_list argptr;\n    va_start(argptr, format);\n    vsprintf(string, format, argptr);\n    va_end(argptr);\n\n    debug_printf(string);\n}\n\n// 0x45404C\nstatic int scripts_tile_is_visible(int tile)\n{\n    if (abs(tile_center_tile - tile) % 200 < 5) {\n        return 1;\n    }\n\n    if (abs(tile_center_tile - tile) / 200 < 5) {\n        return 1;\n    }\n\n    return 0;\n}\n\n// 0x45409C\nstatic int correctFidForRemovedItem(Object* a1, Object* a2, int flags)\n{\n    if (a1 == obj_dude) {\n        bool animated = !game_ui_is_disabled();\n        intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n    }\n\n    int fid = a1->fid;\n    int v8 = (fid & 0xF000) >> 12;\n    int newFid = -1;\n\n    if ((flags & 0x03000000) != 0) {\n        if (a1 == obj_dude) {\n            if (intface_is_item_right_hand()) {\n                if ((flags & 0x02000000) != 0) {\n                    v8 = 0;\n                }\n            } else {\n                if ((flags & 0x01000000) != 0) {\n                    v8 = 0;\n                }\n            }\n        } else {\n            if ((flags & 0x02000000) != 0) {\n                v8 = 0;\n            }\n        }\n\n        if (v8 == 0) {\n            newFid = art_id(FID_TYPE(fid), fid & 0xFFF, FID_ANIM_TYPE(fid), 0, (fid & 0x70000000) >> 28);\n        }\n    } else {\n        if (a1 == obj_dude) {\n            newFid = art_id(FID_TYPE(fid), art_vault_guy_num, FID_ANIM_TYPE(fid), v8, (fid & 0x70000000) >> 28);\n        }\n\n        adjust_ac(a1, a2, NULL);\n    }\n\n    if (newFid != -1) {\n        Rect rect;\n        obj_change_fid(a1, newFid, &rect);\n        tile_refresh_rect(&rect, map_elevation);\n    }\n\n    return 0;\n}\n\n// 0x4541C8\nstatic void op_give_exp_points(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to give_exp_points\", program->name);\n    }\n\n    if (stat_pc_add_experience(data) != 0) {\n        int_debug(\"\\nScript Error: %s: op_give_exp_points: stat_pc_set failed\");\n    }\n}\n\n// 0x454238\nstatic void op_scr_return(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to scr_return\", program->name);\n    }\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        script->field_28 = data;\n    }\n}\n\n// 0x4542AC\nstatic void op_play_sfx(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"script error: %s: invalid arg to play_sfx\", program->name);\n    }\n\n    char* name = interpretGetString(program, opcode, data);\n    gsound_play_sfx_file(name);\n}\n\n// 0x454314\nstatic void op_set_map_start(Program* program)\n{\n    opcode_t opcode[4];\n    int data[4];\n\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to set_map_start\", program->name, arg);\n        }\n    }\n\n    int x = data[3];\n    int y = data[2];\n    int elevation = data[1];\n    int rotation = data[0];\n\n    if (map_set_elevation(elevation) != 0) {\n        int_debug(\"\\nScript Error: %s: op_set_map_start: map_set_elevation failed\", program->name);\n        return;\n    }\n\n    int tile = 200 * y + x;\n    if (tile_set_center(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) != 0) {\n        int_debug(\"\\nScript Error: %s: op_set_map_start: tile_set_center failed\", program->name);\n        return;\n    }\n\n    map_set_entrance_hex(tile, elevation, rotation);\n}\n\n// 0x4543F4\nstatic void op_override_map_start(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[4];\n    int data[4];\n\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to override_map_start\", program->name, arg);\n        }\n    }\n\n    int x = data[3];\n    int y = data[2];\n    int elevation = data[1];\n    int rotation = data[0];\n\n    char text[60];\n    sprintf(text, \"OVERRIDE_MAP_START: x: %d, y: %d\", x, y);\n    debug_printf(text);\n\n    int tile = 200 * y + x;\n    int previousTile = tile_center_tile;\n    if (tile != -1) {\n        if (obj_set_rotation(obj_dude, rotation, NULL) != 0) {\n            int_debug(\"\\nError: %s: obj_set_rotation failed in override_map_start!\", program->name);\n        }\n\n        if (obj_move_to_tile(obj_dude, tile, elevation, NULL) != 0) {\n            int_debug(\"\\nError: %s: obj_move_to_tile failed in override_map_start!\", program->name);\n\n            if (obj_move_to_tile(obj_dude, previousTile, elevation, NULL) != 0) {\n                int_debug(\"\\nError: %s: obj_move_to_tile RECOVERY Also failed!\");\n                exit(1);\n            }\n        }\n\n        tile_set_center(tile, TILE_SET_CENTER_REFRESH_WINDOW);\n        tile_refresh_display();\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x454568\nstatic void op_has_skill(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to has_skill\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int skill = data[0];\n\n    int result = 0;\n    if (object != NULL) {\n        if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n            result = skill_level(object, skill);\n        }\n    } else {\n        dbg_error(program, \"has_skill\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x454634\nstatic void op_using_skill(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to using_skill\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int skill = data[0];\n\n    // NOTE: In the original source code this value is left uninitialized, that\n    // explains why garbage is returned when using something else than dude and\n    // SKILL_SNEAK as arguments.\n    int result = 0;\n\n    if (skill == SKILL_SNEAK && object == obj_dude) {\n        result = is_pc_flag(DUDE_STATE_SNEAKING);\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4546E8\nstatic void op_roll_vs_skill(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to roll_vs_skill\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int skill = data[1];\n    int modifier = data[0];\n\n    int roll = ROLL_CRITICAL_FAILURE;\n    if (object != NULL) {\n        if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n            int sid = scr_find_sid_from_program(program);\n\n            Script* script;\n            if (scr_ptr(sid, &script) != -1) {\n                roll = skill_result(object, skill, modifier, &(script->howMuch));\n            }\n        }\n    } else {\n        dbg_error(program, \"roll_vs_skill\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, roll);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4547D4\nstatic void op_skill_contest(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to skill_contest\", program->name, arg);\n        }\n    }\n\n    dbg_error(program, \"skill_contest\", SCRIPT_ERROR_NOT_IMPLEMENTED);\n    interpretPushLong(program, 0);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x454890\nstatic void op_do_check(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to do_check\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int stat = data[1];\n    int mod = data[0];\n\n    int roll = 0;\n    if (object != NULL) {\n        int sid = scr_find_sid_from_program(program);\n\n        Script* script;\n        if (scr_ptr(sid, &script) != -1) {\n            switch (stat) {\n            case STAT_STRENGTH:\n            case STAT_PERCEPTION:\n            case STAT_ENDURANCE:\n            case STAT_CHARISMA:\n            case STAT_INTELLIGENCE:\n            case STAT_AGILITY:\n            case STAT_LUCK:\n                roll = stat_result(object, stat, mod, &(script->howMuch));\n                break;\n            default:\n                int_debug(\"\\nScript Error: %s: op_do_check: Stat out of range\", program->name);\n                break;\n            }\n        }\n    } else {\n        dbg_error(program, \"do_check\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, roll);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// success\n// 0x4549A8\nstatic void op_is_success(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to success\", program->name);\n    }\n\n    int result = -1;\n\n    switch (data) {\n    case ROLL_CRITICAL_FAILURE:\n    case ROLL_FAILURE:\n        result = 0;\n        break;\n    case ROLL_SUCCESS:\n    case ROLL_CRITICAL_SUCCESS:\n        result = 1;\n        break;\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// critical\n// 0x454A44\nstatic void op_is_critical(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to critical\", program->name);\n    }\n\n    int result = -1;\n\n    switch (data) {\n    case ROLL_CRITICAL_FAILURE:\n    case ROLL_CRITICAL_SUCCESS:\n        result = 1;\n        break;\n    case ROLL_FAILURE:\n    case ROLL_SUCCESS:\n        result = 0;\n        break;\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x454AD0\nstatic void op_how_much(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to how_much\", program->name);\n    }\n\n    int result = 0;\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        result = script->howMuch;\n    } else {\n        dbg_error(program, \"how_much\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x454B6C\nstatic void op_mark_area_known(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to mark_area_known\", program->name, arg);\n        }\n    }\n\n    // TODO: Provide meaningful names.\n    if (data[2] == 0) {\n        if (data[0] == CITY_STATE_INVISIBLE) {\n            wmAreaSetVisibleState(data[1], 0, 1);\n        } else {\n            wmAreaSetVisibleState(data[1], 1, 1);\n            wmAreaMarkVisitedState(data[1], data[0]);\n        }\n    } else if (data[2] == 1) {\n        wmMapMarkVisited(data[1]);\n    }\n}\n\n// 0x454C34\nstatic void op_reaction_influence(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reaction_influence\", program->name, arg);\n        }\n    }\n\n    int result = reaction_influence();\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x454CD4\nstatic void op_random(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to random\", program->name, arg);\n        }\n    }\n\n    int result;\n    if (vcr_status() == VCR_STATE_TURNED_OFF) {\n        result = roll_random(data[1], data[0]);\n    } else {\n        result = (data[0] - data[1]) / 2;\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x454D88\nstatic void op_roll_dice(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to roll_dice\", program->name, arg);\n        }\n    }\n\n    dbg_error(program, \"roll_dice\", SCRIPT_ERROR_NOT_IMPLEMENTED);\n\n    interpretPushLong(program, 0);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x454E28\nstatic void op_move_to(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to move_to\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int tile = data[1];\n    int elevation = data[0];\n\n    int newTile;\n\n    if (object != NULL) {\n        if (object == obj_dude) {\n            bool tileLimitingEnabled = tile_get_scroll_limiting();\n            bool tileBlockingEnabled = tile_get_scroll_blocking();\n\n            if (tileLimitingEnabled) {\n                tile_disable_scroll_limiting();\n            }\n\n            if (tileBlockingEnabled) {\n                tile_disable_scroll_blocking();\n            }\n\n            Rect rect;\n            newTile = obj_move_to_tile(object, tile, elevation, &rect);\n            if (newTile != -1) {\n                tile_set_center(object->tile, TILE_SET_CENTER_REFRESH_WINDOW);\n            }\n\n            if (tileLimitingEnabled) {\n                tile_enable_scroll_limiting();\n            }\n\n            if (tileBlockingEnabled) {\n                tile_enable_scroll_blocking();\n            }\n        } else {\n            Rect before;\n            obj_bound(object, &before);\n\n            if (object->elevation != elevation && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n                combat_delete_critter(object);\n            }\n\n            Rect after;\n            newTile = obj_move_to_tile(object, tile, elevation, &after);\n            if (newTile != -1) {\n                rect_min_bound(&before, &after, &before);\n                tile_refresh_rect(&before, map_elevation);\n            }\n        }\n    } else {\n        dbg_error(program, \"move_to\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        newTile = -1;\n    }\n\n    interpretPushLong(program, newTile);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x454FA8\nstatic void op_create_object_sid(Program* program)\n{\n    opcode_t opcode[4];\n    int data[4];\n\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to create_object\", program->name, arg);\n        }\n    }\n\n    int pid = data[3];\n    int tile = data[2];\n    int elevation = data[1];\n    int sid = data[0];\n\n    Object* object = NULL;\n\n    if (isLoadingGame() != 0) {\n        debug_printf(\"\\nError: attempt to Create critter in load/save-game: %s!\", program->name);\n        goto out;\n    }\n\n    if (pid == 0) {\n        debug_printf(\"\\nError: attempt to Create critter With PID of 0: %s!\", program->name);\n        goto out;\n    }\n\n    Proto* proto;\n    if (proto_ptr(pid, &proto) != -1) {\n        if (obj_new(&object, proto->fid, pid) != -1) {\n            if (tile == -1) {\n                tile = 0;\n            }\n\n            Rect rect;\n            if (obj_move_to_tile(object, tile, elevation, &rect) != -1) {\n                tile_refresh_rect(&rect, object->elevation);\n            }\n        }\n    }\n\n    if (sid != -1) {\n        int scriptType = 0;\n        switch (PID_TYPE(object->pid)) {\n        case OBJ_TYPE_CRITTER:\n            scriptType = SCRIPT_TYPE_CRITTER;\n            break;\n        case OBJ_TYPE_ITEM:\n        case OBJ_TYPE_SCENERY:\n            scriptType = SCRIPT_TYPE_ITEM;\n            break;\n        }\n\n        if (object->sid != -1) {\n            scr_remove(object->sid);\n            object->sid = -1;\n        }\n\n        if (scr_new(&(object->sid), scriptType) == -1) {\n            goto out;\n        }\n\n        Script* script;\n        if (scr_ptr(object->sid, &script) == -1) {\n            goto out;\n        }\n\n        script->field_14 = sid - 1;\n\n        if (scriptType == SCRIPT_TYPE_SPATIAL) {\n            script->sp.built_tile = builtTileCreate(object->tile, object->elevation);\n            script->sp.radius = 3;\n        }\n\n        object->id = new_obj_id();\n        script->field_1C = object->id;\n        script->owner = object;\n        scr_find_str_run_info(sid - 1, &(script->field_50), object->sid);\n    };\n\nout:\n\n    interpretPushLong(program, (int)object);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4551E4\nstatic void op_destroy_object(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to destroy_object\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object == NULL) {\n        dbg_error(program, \"destroy_object\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n        if (isLoadingGame()) {\n            debug_printf(\"\\nError: attempt to destroy critter in load/save-game: %s!\", program->name);\n            program->flags &= ~PROGRAM_FLAG_0x20;\n            return;\n        }\n    }\n\n    bool isSelf = object == scr_find_obj_from_program(program);\n\n    if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n        combat_delete_critter(object);\n    }\n\n    Object* owner = obj_top_environment(object);\n    if (owner != NULL) {\n        int quantity = item_count(owner, object);\n        item_remove_mult(owner, object, quantity);\n\n        if (owner == obj_dude) {\n            bool animated = !game_ui_is_disabled();\n            intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n        }\n\n        obj_connect(object, 1, 0, NULL);\n\n        if (isSelf) {\n            object->sid = -1;\n            object->flags |= (OBJECT_HIDDEN | OBJECT_TEMPORARY);\n        } else {\n            register_clear(object);\n            obj_erase_object(object, NULL);\n        }\n    } else {\n        register_clear(object);\n\n        Rect rect;\n        obj_erase_object(object, &rect);\n        tile_refresh_rect(&rect, map_elevation);\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n\n    if (isSelf) {\n        program->flags |= PROGRAM_FLAG_0x0100;\n    }\n}\n\n// 0x455388\nstatic void op_display_msg(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"script error: %s: invalid arg to display_msg\", program->name);\n    }\n\n    char* string = interpretGetString(program, opcode, data);\n    display_print(string);\n\n    bool showScriptMessages = false;\n    configGetBool(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages);\n\n    if (showScriptMessages) {\n        debug_printf(\"\\n\");\n        debug_printf(string);\n    }\n}\n\n// 0x455430\nstatic void op_script_overrides(Program* program)\n{\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        script->scriptOverrides = 1;\n    } else {\n        dbg_error(program, \"script_overrides\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n    }\n}\n\n// 0x455470\nstatic void op_obj_is_carrying_obj_pid(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to obj_is_carrying_obj\", program->name, arg);\n        }\n    }\n\n    Object* obj = (Object*)data[1];\n    int pid = data[0];\n\n    int result = 0;\n    if (obj != NULL) {\n        result = inven_pid_quantity_carried(obj, pid);\n    } else {\n        dbg_error(program, \"obj_is_carrying_obj_pid\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455534\nstatic void op_tile_contains_obj_pid(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to tile_contains_obj_pid\", program->name, arg);\n        }\n    }\n\n    int tile = data[2];\n    int elevation = data[1];\n    int pid = data[0];\n\n    int result = 0;\n\n    Object* object = obj_find_first_at_tile(elevation, tile);\n    while (object) {\n        if (object->pid == pid) {\n            result = 1;\n            break;\n        }\n        object = obj_find_next_at_tile();\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455600\nstatic void op_self_obj(Program* program)\n{\n    Object* object = scr_find_obj_from_program(program);\n    interpretPushLong(program, (int)object);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455624\nstatic void op_source_obj(Program* program)\n{\n    Object* object = NULL;\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        object = script->source;\n    } else {\n        dbg_error(program, \"source_obj\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n    }\n\n    interpretPushLong(program, (int)object);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455678\nstatic void op_target_obj(Program* program)\n{\n    Object* object = NULL;\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        object = script->target;\n    } else {\n        dbg_error(program, \"target_obj\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n    }\n\n    interpretPushLong(program, (int)object);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4556CC\nstatic void op_dude_obj(Program* program)\n{\n    interpretPushLong(program, (int)obj_dude);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// NOTE: The implementation is the same as in [op_target_obj].\n//\n// 0x4556EC\nstatic void op_obj_being_used_with(Program* program)\n{\n    Object* object = NULL;\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        object = script->target;\n    } else {\n        dbg_error(program, \"obj_being_used_with\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n    }\n\n    interpretPushLong(program, (int)object);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455740\nstatic void op_local_var(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        // FIXME: The error message is wrong.\n        interpretError(\"script error: %s: invalid arg to op_global_var\", program->name);\n    }\n\n    int value = -1;\n\n    int sid = scr_find_sid_from_program(program);\n    scr_get_local_var(sid, data, &value);\n\n    interpretPushLong(program, value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4557C8\nstatic void op_set_local_var(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to set_local_var\", program->name, arg);\n        }\n    }\n\n    int variable = data[1];\n    int value = data[0];\n\n    int sid = scr_find_sid_from_program(program);\n    scr_set_local_var(sid, variable, value);\n}\n\n// 0x455858\nstatic void op_map_var(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to op_map_var\", program->name);\n    }\n\n    int value = map_get_global_var(data);\n\n    interpretPushLong(program, value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4558C8\nstatic void op_set_map_var(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to set_map_var\", program->name, arg);\n        }\n    }\n\n    int variable = data[1];\n    int value = data[0];\n\n    map_set_global_var(variable, value);\n}\n\n// 0x455950\nstatic void op_global_var(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to op_global_var\", program->name);\n    }\n\n    int value = -1;\n    if (num_game_global_vars != 0) {\n        value = game_get_global_var(data);\n    } else {\n        int_debug(\"\\nScript Error: %s: op_global_var: no global vars found!\", program->name);\n    }\n\n    interpretPushLong(program, value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4559EC\nstatic void op_set_global_var(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to set_global_var\", program->name, arg);\n        }\n    }\n\n    int variable = data[1];\n    int value = data[0];\n\n    if (num_game_global_vars != 0) {\n        game_set_global_var(variable, value);\n    } else {\n        int_debug(\"\\nScript Error: %s: op_set_global_var: no global vars found!\", program->name);\n    }\n}\n\n// 0x455A90\nstatic void op_script_action(Program* program)\n{\n    int action = 0;\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        action = script->action;\n    } else {\n        dbg_error(program, \"script_action\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n    }\n\n    interpretPushLong(program, action);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455AE4\nstatic void op_obj_type(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to op_obj_type\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    int objectType = -1;\n    if (object != NULL) {\n        objectType = FID_TYPE(object->fid);\n    }\n\n    interpretPushLong(program, objectType);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455B6C\nstatic void op_obj_item_subtype(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to op_item_subtype\", program->name);\n    }\n\n    Object* obj = (Object*)data;\n\n    int itemType = -1;\n    if (obj != NULL) {\n        if (PID_TYPE(obj->pid) == OBJ_TYPE_ITEM) {\n            Proto* proto;\n            if (proto_ptr(obj->pid, &proto) != -1) {\n                itemType = item_get_type(obj);\n            }\n        }\n    }\n\n    interpretPushLong(program, itemType);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455C10\nstatic void op_get_critter_stat(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to get_critter_stat\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int stat = data[0];\n\n    int value = -1;\n    if (object != NULL) {\n        value = critterGetStat(object, stat);\n    } else {\n        dbg_error(program, \"get_critter_stat\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// NOTE: Despite it's name it does not actually \"set\" stat, but \"adjust\". So\n// it's last argument is amount of adjustment, not it's final value.\n//\n// 0x455CCC\nstatic void op_set_critter_stat(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to set_critter_stat\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int stat = data[1];\n    int value = data[0];\n\n    int result = 0;\n    if (object != NULL) {\n        if (object == obj_dude) {\n            int currentValue = stat_get_base(object, stat);\n            stat_set_base(object, stat, currentValue + value);\n        } else {\n            dbg_error(program, \"set_critter_stat\", SCRIPT_ERROR_FOLLOWS);\n            debug_printf(\" Can't modify anyone except obj_dude!\");\n            result = -1;\n        }\n    } else {\n        dbg_error(program, \"set_critter_stat\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        result = -1;\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x455DC8\nstatic void op_animate_stand_obj(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to animate_stand_obj\", program->name);\n    }\n\n    Object* object = (Object*)data;\n    if (object == NULL) {\n        int sid = scr_find_sid_from_program(program);\n\n        Script* script;\n        if (scr_ptr(sid, &script) == -1) {\n            dbg_error(program, \"animate_stand_obj\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n            return;\n        }\n\n        object = scr_find_obj_from_program(program);\n    }\n\n    if (!isInCombat()) {\n        register_begin(ANIMATION_REQUEST_UNRESERVED);\n        register_object_animate(object, ANIM_STAND, 0);\n        register_end();\n    }\n}\n\n// 0x455E7C\nstatic void op_animate_stand_reverse_obj(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        // FIXME: typo in message, should be animate_stand_reverse_obj.\n        interpretError(\"script error: %s: invalid arg to animate_stand_obj\", program->name);\n    }\n\n    Object* object = (Object*)data;\n    if (object == NULL) {\n        int sid = scr_find_sid_from_program(program);\n\n        Script* script;\n        if (scr_ptr(sid, &script) == -1) {\n            dbg_error(program, \"animate_stand_reverse_obj\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n            return;\n        }\n\n        object = scr_find_obj_from_program(program);\n    }\n\n    if (!isInCombat()) {\n        register_begin(ANIMATION_REQUEST_UNRESERVED);\n        register_object_animate_reverse(object, ANIM_STAND, 0);\n        register_end();\n    }\n}\n\n// 0x455F30\nstatic void op_animate_move_obj_to_tile(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to animate_move_obj_to_tile\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int tile = data[1];\n    int flags = data[0];\n\n    if (object == NULL) {\n        dbg_error(program, \"animate_move_obj_to_tile\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (tile <= -1) {\n        return;\n    }\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        dbg_error(program, \"animate_move_obj_to_tile\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n        return;\n    }\n\n    if (!critter_is_active(object)) {\n        return;\n    }\n\n    if (isInCombat()) {\n        return;\n    }\n\n    if ((flags & 0x10) != 0) {\n        register_clear(object);\n        flags &= ~0x10;\n    }\n\n    register_begin(ANIMATION_REQUEST_UNRESERVED);\n\n    if (flags == 0) {\n        register_object_move_to_tile(object, tile, object->elevation, -1, 0);\n    } else {\n        register_object_run_to_tile(object, tile, object->elevation, -1, 0);\n    }\n\n    register_end();\n}\n\n// 0x45607C\nstatic void op_tile_in_tile_rect(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n    Point points[5];\n\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to tile_in_tile_rect\", program->name, arg);\n        }\n\n        points[arg].x = data[arg] % 200;\n        points[arg].y = data[arg] / 200;\n    }\n\n    int x = points[0].x;\n    int y = points[0].y;\n\n    int minX = points[1].x;\n    int maxX = points[4].x;\n\n    int minY = points[4].y;\n    int maxY = points[1].y;\n\n    int result = 0;\n    if (x >= minX && x <= maxX && y >= minY && y <= maxY) {\n        result = 1;\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x456170\nstatic void op_make_daytime(Program* program)\n{\n}\n\n// 0x456174\nstatic void op_tile_distance(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to tile_distance\", program->name, arg);\n        }\n    }\n\n    int tile1 = data[1];\n    int tile2 = data[0];\n\n    int distance;\n\n    if (tile1 != -1 && tile2 != -1) {\n        distance = tile_dist(tile1, tile2);\n    } else {\n        distance = 9999;\n    }\n\n    interpretPushLong(program, distance);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x456228\nstatic void op_tile_distance_objs(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to tile_distance_objs\", program->name, arg);\n        }\n    }\n\n    Object* object1 = (Object*)data[1];\n    Object* object2 = (Object*)data[0];\n\n    int distance = 9999;\n    if (object1 != NULL && object2 != NULL) {\n        if ((unsigned int)data[1] >= HEX_GRID_SIZE && (unsigned int)data[0] >= HEX_GRID_SIZE) {\n            if (object1->elevation == object2->elevation) {\n                if (object1->tile != -1 && object2->tile != -1) {\n                    distance = tile_dist(object1->tile, object2->tile);\n                }\n            }\n        } else {\n            dbg_error(program, \"tile_distance_objs\", SCRIPT_ERROR_FOLLOWS);\n            debug_printf(\" Passed a tile # instead of an object!!!BADBADBAD!\");\n        }\n    }\n\n    interpretPushLong(program, distance);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x456324\nstatic void op_tile_num(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to tile_num\", program->name);\n    }\n\n    Object* obj = (Object*)data;\n\n    int tile = -1;\n    if (obj != NULL) {\n        tile = obj->tile;\n    } else {\n        dbg_error(program, \"tile_num\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, tile);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4563B4\nstatic void op_tile_num_in_direction(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to tile_num_in_direction\", program->name, arg);\n        }\n    }\n\n    int origin = data[2];\n    int rotation = data[1];\n    int distance = data[0];\n\n    int tile = -1;\n\n    if (origin != -1) {\n        if (rotation < ROTATION_COUNT) {\n            if (distance != 0) {\n                tile = tile_num_in_direction(origin, rotation, distance);\n                if (tile < -1) {\n                    debug_printf(\"\\nError: %s: op_tile_num_in_direction got #: %d\", program->name, tile);\n                    tile = -1;\n                }\n            }\n        } else {\n            dbg_error(program, \"tile_num_in_direction\", SCRIPT_ERROR_FOLLOWS);\n            debug_printf(\" rotation out of Range!\");\n        }\n    } else {\n        dbg_error(program, \"tile_num_in_direction\", SCRIPT_ERROR_FOLLOWS);\n        debug_printf(\" tileNum is -1!\");\n    }\n\n    interpretPushLong(program, tile);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4564D4\nstatic void op_pickup_obj(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to pickup_obj\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object == NULL) {\n        return;\n    }\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) == 1) {\n        dbg_error(program, \"pickup_obj\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n        return;\n    }\n\n    if (script->target == NULL) {\n        dbg_error(program, \"pickup_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    action_get_an_object(script->target, object);\n}\n\n// 0x456580\nstatic void op_drop_obj(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to drop_obj\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object == NULL) {\n        return;\n    }\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID.\n        dbg_error(program, \"drop_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (script->target == NULL) {\n        // FIXME: Should be SCRIPT_ERROR_OBJECT_IS_NULL.\n        dbg_error(program, \"drop_obj\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n        return;\n    }\n\n    obj_drop(script->target, object);\n}\n\n// 0x45662C\nstatic void op_add_obj_to_inven(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to add_obj_to_inven\", program->name, arg);\n        }\n    }\n\n    Object* owner = (Object*)data[1];\n    Object* item = (Object*)data[0];\n\n    if (owner == NULL || item == NULL) {\n        return;\n    }\n\n    if (item->owner == NULL) {\n        if (item_add_force(owner, item, 1) == 0) {\n            Rect rect;\n            obj_disconnect(item, &rect);\n            tile_refresh_rect(&rect, item->elevation);\n        }\n    } else {\n        dbg_error(program, \"add_obj_to_inven\", SCRIPT_ERROR_FOLLOWS);\n        debug_printf(\" Item was already attached to something else!\");\n    }\n}\n\n// 0x456708\nstatic void op_rm_obj_from_inven(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to rm_obj_from_inven\", program->name, arg);\n        }\n    }\n\n    Object* owner = (Object*)data[1];\n    Object* item = (Object*)data[0];\n\n    if (owner == NULL || item == NULL) {\n        return;\n    }\n\n    bool updateFlags = false;\n    int flags = 0;\n\n    if ((item->flags & OBJECT_EQUIPPED) != 0) {\n        if ((item->flags & OBJECT_IN_LEFT_HAND) != 0) {\n            flags |= OBJECT_IN_LEFT_HAND;\n        }\n\n        if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) {\n            flags |= OBJECT_IN_RIGHT_HAND;\n        }\n\n        if ((item->flags & OBJECT_WORN) != 0) {\n            flags |= OBJECT_WORN;\n        }\n\n        updateFlags = true;\n    }\n\n    if (item_remove_mult(owner, item, 1) == 0) {\n        Rect rect;\n        obj_connect(item, 1, 0, &rect);\n        tile_refresh_rect(&rect, item->elevation);\n\n        if (updateFlags) {\n            correctFidForRemovedItem(owner, item, flags);\n        }\n    }\n}\n\n// 0x45681C\nstatic void op_wield_obj_critter(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to wield_obj_critter\", program->name, arg);\n        }\n    }\n\n    Object* critter = (Object*)data[1];\n    Object* item = (Object*)data[0];\n\n    if (critter == NULL) {\n        dbg_error(program, \"wield_obj_critter\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (item == NULL) {\n        dbg_error(program, \"wield_obj_critter\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) {\n        dbg_error(program, \"wield_obj_critter\", SCRIPT_ERROR_FOLLOWS);\n        debug_printf(\" Only works for critters!  ERROR ERROR ERROR!\");\n        return;\n    }\n\n    int hand = HAND_RIGHT;\n\n    bool shouldAdjustArmorClass = false;\n    Object* oldArmor = NULL;\n    Object* newArmor = NULL;\n    if (critter == obj_dude) {\n        if (intface_is_item_right_hand() == HAND_LEFT) {\n            hand = HAND_LEFT;\n        }\n\n        if (item_get_type(item) == ITEM_TYPE_ARMOR) {\n            oldArmor = inven_worn(obj_dude);\n        }\n\n        shouldAdjustArmorClass = true;\n        newArmor = item;\n    }\n\n    if (inven_wield(critter, item, hand) == -1) {\n        dbg_error(program, \"wield_obj_critter\", SCRIPT_ERROR_FOLLOWS);\n        debug_printf(\" inven_wield failed!  ERROR ERROR ERROR!\");\n        return;\n    }\n\n    if (critter == obj_dude) {\n        if (shouldAdjustArmorClass) {\n            adjust_ac(critter, oldArmor, newArmor);\n        }\n\n        bool animated = !game_ui_is_disabled();\n        intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n    }\n}\n\n// 0x4569D0\nstatic void op_use_obj(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to use_obj\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object == NULL) {\n        dbg_error(program, \"use_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID.\n        dbg_error(program, \"use_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (script->target == NULL) {\n        dbg_error(program, \"use_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    Object* self = scr_find_obj_from_program(program);\n    if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) {\n        action_use_an_object(script->target, object);\n    } else {\n        obj_use(self, object);\n    }\n}\n\n// 0x456AC4\nstatic void op_obj_can_see_obj(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to obj_can_see_obj\", program->name, arg);\n        }\n    }\n\n    Object* object1 = (Object*)data[1];\n    Object* object2 = (Object*)data[0];\n\n    int result = 0;\n\n    if (object1 != NULL && object2 != NULL) {\n        if (object2->tile != -1) {\n            // NOTE: Looks like dead code, I guess these checks were incorporated\n            // into higher level functions, but this code left intact.\n            if (object2 == obj_dude) {\n                is_pc_flag(0);\n            }\n\n            critterGetStat(object1, STAT_PERCEPTION);\n\n            if (is_within_perception(object1, object2)) {\n                Object* a5;\n                make_straight_path(object1, object1->tile, object2->tile, NULL, &a5, 16);\n                if (a5 == object2) {\n                    result = 1;\n                }\n            }\n        }\n    } else {\n        dbg_error(program, \"obj_can_see_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x456C00\nstatic void op_attack(Program* program)\n{\n    opcode_t opcode[8];\n    int data[8];\n\n    for (int arg = 0; arg < 8; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to attack\", program->name, arg);\n        }\n    }\n\n    Object* target = (Object*)data[7];\n    if (target == NULL) {\n        dbg_error(program, \"attack\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    Object* self = scr_find_obj_from_program(program);\n    if (self == NULL) {\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if (!critter_is_active(self) || (self->flags & OBJECT_HIDDEN) != 0) {\n        debug_printf(\"\\n   But is already Inactive (Dead/Stunned/Invisible)\");\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if (!critter_is_active(target) || (target->flags & OBJECT_HIDDEN) != 0) {\n        debug_printf(\"\\n   But target is already dead or invisible\");\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if ((target->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) {\n        debug_printf(\"\\n   But target is AFRAID\");\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if (gdialogActive()) {\n        // TODO: Might be an error, program flag is not removed.\n        return;\n    }\n\n    if (isInCombat()) {\n        CritterCombatData* combatData = &(self->data.critter.combat);\n        if ((combatData->maneuver & CRITTER_MANEUVER_0x01) == 0) {\n            combatData->maneuver |= CRITTER_MANEUVER_0x01;\n            combatData->whoHitMe = target;\n        }\n    } else {\n        STRUCT_664980 attack;\n        attack.attacker = self;\n        attack.defender = target;\n        attack.actionPointsBonus = 0;\n        attack.accuracyBonus = data[4];\n        attack.damageBonus = 0;\n        attack.minDamage = data[3];\n        attack.maxDamage = data[2];\n\n        // TODO: Something is probably broken here, why it wants\n        // flags to be the same? Maybe because both of them\n        // are applied to defender because of the bug in 0x422F3C?\n        if (data[1] == data[0]) {\n            attack.field_1C = 1;\n            attack.field_24 = data[0];\n            attack.field_20 = data[1];\n        } else {\n            attack.field_1C = 0;\n        }\n\n        scripts_request_combat(&attack);\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x456DF0\nstatic void op_start_gdialog(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to start_gdialog\", program->name, arg);\n        }\n    }\n\n    Object* obj = (Object*)data[3];\n    int reactionLevel = data[2];\n    int headId = data[1];\n    int backgroundId = data[0];\n\n    if (isInCombat()) {\n        return;\n    }\n\n    if (obj == NULL) {\n        dbg_error(program, \"start_gdialog\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    dialogue_head = -1;\n    if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n        Proto* proto;\n        if (proto_ptr(obj->pid, &proto) == -1) {\n            return;\n        }\n    }\n\n    if (headId != -1) {\n        dialogue_head = art_id(OBJ_TYPE_HEAD, headId, 0, 0, 0);\n    }\n\n    gdialogSetBackground(backgroundId);\n    dialogue_mood = reactionLevel;\n\n    if (dialogue_head != -1) {\n        int npcReactionValue = reaction_get(dialog_target);\n        int npcReactionType = reaction_lookup_internal(npcReactionValue);\n        switch (npcReactionType) {\n        case NPC_REACTION_BAD:\n            dialogue_mood = FIDGET_BAD;\n            break;\n        case NPC_REACTION_NEUTRAL:\n            dialogue_mood = FIDGET_NEUTRAL;\n            break;\n        case NPC_REACTION_GOOD:\n            dialogue_mood = FIDGET_GOOD;\n            break;\n        }\n    }\n\n    dialogue_scr_id = scr_find_sid_from_program(program);\n    dialog_target = scr_find_obj_from_program(program);\n    gdialogInitFromScript(dialogue_head, dialogue_mood);\n}\n\n// 0x456F80\nstatic void op_end_dialogue(Program* program)\n{\n    if (gdialogExitFromScript() != -1) {\n        dialog_target = NULL;\n        dialogue_scr_id = -1;\n    }\n}\n\n// 0x456FA4\nstatic void op_dialogue_reaction(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int value = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, value);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to dialogue_reaction\", program->name);\n    }\n\n    dialogue_mood = value;\n    talk_to_critter_reacts(value);\n}\n\n// 0x457110\nstatic void op_metarule3(Program* program)\n{\n    opcode_t opcode[4];\n    int data[4];\n\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to metarule3\", program->name, arg);\n        }\n    }\n\n    int rule = data[3];\n    int result = 0;\n\n    switch (rule) {\n    case METARULE3_CLR_FIXED_TIMED_EVENTS:\n        if (1) {\n            scrSetQueueTestVals((Object*)data[2], data[1]);\n            queue_clear_type(EVENT_TYPE_SCRIPT, scrQueueRemoveFixed);\n        }\n        break;\n    case METARULE3_MARK_SUBTILE:\n        result = wmSubTileMarkRadiusVisited(data[2], data[1], data[0]);\n        break;\n    case METARULE3_GET_KILL_COUNT:\n        result = critter_kill_count(data[2]);\n        break;\n    case METARULE3_MARK_MAP_ENTRANCE:\n        result = wmMapMarkMapEntranceState(data[2], data[1], data[0]);\n        break;\n    case METARULE3_WM_SUBTILE_STATE:\n        if (1) {\n            int state;\n            if (wmSubTileGetVisitedState(data[2], data[1], &state) == 0) {\n                result = state;\n            }\n        }\n        break;\n    case METARULE3_TILE_GET_NEXT_CRITTER:\n        if (1) {\n            int tile = data[2];\n            int elevation = data[1];\n            Object* previousCritter = (Object*)data[0];\n\n            bool critterFound = previousCritter == NULL;\n\n            Object* object = obj_find_first_at_tile(elevation, tile);\n            while (object != NULL) {\n                if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n                    if (critterFound) {\n                        result = (int)object;\n                        break;\n                    }\n                }\n\n                if (object == previousCritter) {\n                    critterFound = true;\n                }\n\n                object = obj_find_next_at_tile();\n            }\n        }\n        break;\n    case METARULE3_ART_SET_BASE_FID_NUM:\n        if (1) {\n            Object* obj = (Object*)data[2];\n            int frmId = data[1];\n\n            int fid = art_id(FID_TYPE(obj->fid),\n                frmId,\n                FID_ANIM_TYPE(obj->fid),\n                (obj->fid & 0xF000) >> 12,\n                (obj->fid & 0x70000000) >> 28);\n\n            Rect updatedRect;\n            obj_change_fid(obj, fid, &updatedRect);\n            tile_refresh_rect(&updatedRect, map_elevation);\n        }\n        break;\n    case METARULE3_TILE_SET_CENTER:\n        result = tile_set_center(data[2], TILE_SET_CENTER_REFRESH_WINDOW);\n        break;\n    case METARULE3_109:\n        result = ai_get_chem_use_value((Object*)data[2]);\n        break;\n    case METARULE3_110:\n        result = wmCarIsOutOfGas() ? 1 : 0;\n        break;\n    case METARULE3_111:\n        result = map_target_load_area();\n        break;\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45734C\nstatic void op_set_map_music(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        // FIXME: argument is wrong, should be 1.\n        interpretError(\"script error: %s: invalid arg %d to set_map_music\", program->name, 2);\n    }\n\n    int mapIndex = data[1];\n\n    char* string = NULL;\n    if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        string = interpretGetString(program, opcode[0], data[0]);\n    } else {\n        // FIXME: argument is wrong, should be 0.\n        interpretError(\"script error: %s: invalid arg %d to set_map_music\", program->name, 2);\n    }\n\n    debug_printf(\"\\nset_map_music: %d, %s\", mapIndex, string);\n    wmSetMapMusic(mapIndex, string);\n}\n\n// NOTE: Function name is a bit misleading. Last parameter is a boolean value\n// where 1 or true makes object invisible, and value 0 (false) makes it visible\n// again. So a better name for this function is opSetObjectInvisible.\n//\n//\n// 0x45741C\nstatic void op_set_obj_visibility(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to set_obj_visibility\", program->name, arg);\n        }\n    }\n\n    Object* obj = (Object*)data[1];\n    int invisible = data[0];\n\n    if (obj == NULL) {\n        dbg_error(program, \"set_obj_visibility\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (isLoadingGame()) {\n        debug_printf(\"Error: attempt to set_obj_visibility in load/save-game: %s!\", program->name);\n        return;\n    }\n\n    if (invisible != 0) {\n        if ((obj->flags & OBJECT_HIDDEN) == 0) {\n            if (isInCombat()) {\n                obj_turn_off_outline(obj, NULL);\n                obj_remove_outline(obj, NULL);\n            }\n\n            Rect rect;\n            if (obj_turn_off(obj, &rect) != -1) {\n                if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n                    obj->flags |= OBJECT_NO_BLOCK;\n                }\n\n                tile_refresh_rect(&rect, obj->elevation);\n            }\n        }\n    } else {\n        if ((obj->flags & OBJECT_HIDDEN) != 0) {\n            if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n                obj->flags &= ~OBJECT_NO_BLOCK;\n            }\n\n            Rect rect;\n            if (obj_turn_on(obj, &rect) != -1) {\n                tile_refresh_rect(&rect, obj->elevation);\n            }\n        }\n    }\n}\n\n// 0x45755C\nstatic void op_load_map(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    opcode[0] = interpretPopShort(program);\n    data[0] = interpretPopLong(program);\n\n    if (opcode[0] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[0], data[0]);\n    }\n\n    if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg 0 to load_map\", program->name);\n    }\n\n    opcode[1] = interpretPopShort(program);\n    data[1] = interpretPopLong(program);\n\n    if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode[1], data[1]);\n    }\n\n    int param = data[0];\n    int mapIndexOrName = data[1];\n\n    char* mapName = NULL;\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n            mapName = interpretGetString(program, opcode[1], mapIndexOrName);\n        } else {\n            interpretError(\"script error: %s: invalid arg 1 to load_map\", program->name);\n        }\n    }\n\n    int mapIndex = -1;\n\n    if (mapName != NULL) {\n        game_global_vars[GVAR_LOAD_MAP_INDEX] = param;\n        mapIndex = wmMapMatchNameToIdx(mapName);\n    } else {\n        if (mapIndexOrName >= 0) {\n            game_global_vars[GVAR_LOAD_MAP_INDEX] = param;\n            mapIndex = mapIndexOrName;\n        }\n    }\n\n    if (mapIndex != -1) {\n        MapTransition transition;\n        transition.map = mapIndex;\n        transition.elevation = -1;\n        transition.tile = -1;\n        transition.rotation = -1;\n        map_leave_map(&transition);\n    }\n}\n\n// 0x457680\nstatic void op_wm_area_set_pos(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to wm_area_set_pos\", program->name, arg);\n        }\n    }\n\n    int city = data[2];\n    int x = data[1];\n    int y = data[0];\n\n    if (wmAreaSetWorldPos(city, x, y) == -1) {\n        dbg_error(program, \"wm_area_set_pos\", SCRIPT_ERROR_FOLLOWS);\n        debug_printf(\"Invalid Parameter!\");\n    }\n}\n\n// 0x457730\nstatic void op_set_exit_grids(Program* program)\n{\n    opcode_t opcode[5];\n    int data[5];\n\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to set_exit_grids\", program->name, arg);\n        }\n    }\n\n    int elevation = data[4];\n    int destinationMap = data[3];\n    int destinationElevation = data[2];\n    int destinationTile = data[1];\n    int destinationRotation = data[0];\n\n    Object* object = obj_find_first_at(elevation);\n    while (object != NULL) {\n        if (object->pid >= PROTO_ID_0x5000010 && object->pid <= PROTO_ID_0x5000017) {\n            object->data.misc.map = destinationMap;\n            object->data.misc.tile = destinationTile;\n            object->data.misc.elevation = destinationElevation;\n        }\n        object = obj_find_next_at();\n    }\n}\n\n// 0x4577EC\nstatic void op_anim_busy(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to anim_busy\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    int rc = 0;\n    if (object != NULL) {\n        rc = anim_busy(object);\n    } else {\n        dbg_error(program, \"anim_busy\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, rc);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x457880\nstatic void op_critter_heal(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_heal\", program->name, arg);\n        }\n    }\n\n    Object* critter = (Object*)data[1];\n    int amount = data[0];\n\n    int rc = critter_adjust_hits(critter, amount);\n\n    if (critter == obj_dude) {\n        intface_update_hit_points(true);\n    }\n\n    interpretPushLong(program, rc);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x457934\nstatic void op_set_light_level(Program* program)\n{\n    // Maps light level to light intensity.\n    //\n    // Middle value is mapped one-to-one which corresponds to 50% light level\n    // (cavern lighting). Light levels above (51-100%) and below (0-49) is\n    // calculated as percentage from two adjacent light values.\n    //\n    // 0x453F90\n    static const int dword_453F90[3] = {\n        0x4000,\n        0xA000,\n        0x10000,\n    };\n\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to set_light_level\", program->name);\n    }\n\n    int lightLevel = data;\n\n    if (data == 50) {\n        light_set_ambient(dword_453F90[1], true);\n        return;\n    }\n\n    int lightIntensity;\n    if (data > 50) {\n        lightIntensity = dword_453F90[1] + data * (dword_453F90[2] - dword_453F90[1]) / 100;\n    } else {\n        lightIntensity = dword_453F90[0] + data * (dword_453F90[1] - dword_453F90[0]) / 100;\n    }\n\n    light_set_ambient(lightIntensity, true);\n}\n\n// 0x4579F4\nstatic void op_game_time(Program* program)\n{\n    int time = game_time();\n    interpretPushLong(program, time);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x457A18\nstatic void op_game_time_in_seconds(Program* program)\n{\n    int time = game_time();\n    interpretPushLong(program, time / 10);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x457A44\nstatic void op_elevation(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to elevation\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    int elevation = 0;\n    if (object != NULL) {\n        elevation = object->elevation;\n    } else {\n        dbg_error(program, \"elevation\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, elevation);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x457AD4\nstatic void op_kill_critter(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to kill_critter\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int deathFrame = data[0];\n\n    if (object == NULL) {\n        dbg_error(program, \"kill_critter\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (isLoadingGame()) {\n        debug_printf(\"\\nError: attempt to destroy critter in load/save-game: %s!\", program->name);\n    }\n\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    Object* self = scr_find_obj_from_program(program);\n    bool isSelf = self == object;\n\n    register_clear(object);\n    combat_delete_critter(object);\n    critter_kill(object, deathFrame, 1);\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n\n    if (isSelf) {\n        program->flags |= PROGRAM_FLAG_0x0100;\n    }\n}\n\n// [forceBack] is to force fall back animation, otherwise it's fall front if it's present\nint correctDeath(Object* critter, int anim, bool forceBack)\n{\n    if (anim >= ANIM_BIG_HOLE_SF && anim <= ANIM_FALL_FRONT_BLOOD_SF) {\n        int violenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD;\n        config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel);\n\n        bool useStandardDeath = false;\n        if (violenceLevel < VIOLENCE_LEVEL_MAXIMUM_BLOOD) {\n            useStandardDeath = true;\n        } else {\n            int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1);\n            if (!art_exists(fid)) {\n                useStandardDeath = true;\n            }\n        }\n\n        if (useStandardDeath) {\n            if (forceBack) {\n                anim = ANIM_FALL_BACK;\n            } else {\n                int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_FRONT, (critter->fid & 0xF000) >> 12, critter->rotation + 1);\n                if (art_exists(fid)) {\n                    anim = ANIM_FALL_FRONT;\n                } else {\n                    anim = ANIM_FALL_BACK;\n                }\n            }\n        }\n    }\n\n    return anim;\n}\n\n// 0x457CB4\nstatic void op_kill_critter_type(Program* program)\n{\n    // 0x518ED0\n    static int ftList[11] = {\n        ANIM_FALL_BACK_BLOOD_SF,\n        ANIM_BIG_HOLE_SF,\n        ANIM_CHARRED_BODY_SF,\n        ANIM_CHUNKS_OF_FLESH_SF,\n        ANIM_FALL_FRONT_BLOOD_SF,\n        ANIM_FALL_BACK_BLOOD_SF,\n        ANIM_DANCING_AUTOFIRE_SF,\n        ANIM_SLICED_IN_HALF_SF,\n        ANIM_EXPLODED_TO_NOTHING_SF,\n        ANIM_FALL_BACK_BLOOD_SF,\n        ANIM_FALL_FRONT_BLOOD_SF,\n    };\n\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to kill_critter\", program->name, arg);\n        }\n    }\n\n    int pid = data[1];\n    int deathFrame = data[0];\n\n    if (isLoadingGame()) {\n        debug_printf(\"\\nError: attempt to destroy critter in load/save-game: %s!\", program->name);\n        return;\n    }\n\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    Object* previousObj = NULL;\n    int count = 0;\n    int v3 = 0;\n\n    Object* obj = obj_find_first();\n    while (obj != NULL) {\n        if (FID_ANIM_TYPE(obj->fid) >= ANIM_FALL_BACK_SF) {\n            obj = obj_find_next();\n            continue;\n        }\n\n        if ((obj->flags & OBJECT_HIDDEN) == 0 && obj->pid == pid && !critter_is_dead(obj)) {\n            if (obj == previousObj || count > 200) {\n                dbg_error(program, \"kill_critter_type\", SCRIPT_ERROR_FOLLOWS);\n                debug_printf(\" Infinite loop destroying critters!\");\n                program->flags &= ~PROGRAM_FLAG_0x20;\n                return;\n            }\n\n            register_clear(obj);\n\n            if (deathFrame != 0) {\n                combat_delete_critter(obj);\n                if (deathFrame == 1) {\n                    int anim = correctDeath(obj, ftList[v3], 1);\n                    critter_kill(obj, anim, 1);\n                    v3 += 1;\n                    if (v3 >= 11) {\n                        v3 = 0;\n                    }\n                } else {\n                    critter_kill(obj, ANIM_FALL_BACK_SF, 1);\n                }\n            } else {\n                register_clear(obj);\n\n                Rect rect;\n                obj_erase_object(obj, &rect);\n                tile_refresh_rect(&rect, map_elevation);\n            }\n\n            previousObj = obj;\n            count += 1;\n\n            obj_find_first();\n\n            map_data.lastVisitTime = game_time();\n        }\n\n        obj = obj_find_next();\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// critter_dmg\n// 0x457EB4\nstatic void op_critter_damage(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_damage\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int amount = data[1];\n    int damageTypeWithFlags = data[0];\n\n    if (object == NULL) {\n        dbg_error(program, \"critter_damage\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) {\n        dbg_error(program, \"critter_damage\", SCRIPT_ERROR_FOLLOWS);\n        debug_printf(\" Can't call on non-critters!\");\n        return;\n    }\n\n    Object* self = scr_find_obj_from_program(program);\n    if (object->data.critter.combat.whoHitMeCid == -1) {\n        object->data.critter.combat.whoHitMe = NULL;\n    }\n\n    bool animate = (damageTypeWithFlags & 0x200) == 0;\n    bool bypassArmor = (damageTypeWithFlags & 0x100) != 0;\n    int damageType = damageTypeWithFlags & ~(0x100 | 0x200);\n    action_dmg(object->tile, object->elevation, amount, amount, damageType, animate, bypassArmor);\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n\n    if (self == object) {\n        program->flags |= PROGRAM_FLAG_0x0100;\n    }\n}\n\n// 0x457FF0\nstatic void op_add_timer_event(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to add_timer_event\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int delay = data[1];\n    int param = data[0];\n\n    if (object == NULL) {\n        int_debug(\"\\nScript Error: %s: op_add_timer_event: pobj is NULL!\", program->name);\n        return;\n    }\n\n    script_q_add(object->sid, delay, param);\n}\n\n// 0x458094\nstatic void op_rm_timer_event(Program* program)\n{\n    int elevation;\n\n    elevation = 0;\n\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to rm_timer_event\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object == NULL) {\n        // FIXME: Should be op_rm_timer_event.\n        int_debug(\"\\nScript Error: %s: op_add_timer_event: pobj is NULL!\");\n        return;\n    }\n\n    queue_remove(object);\n}\n\n// Converts seconds into game ticks.\n//\n// 0x458108\nstatic void op_game_ticks(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to game_ticks\", program->name);\n    }\n\n    int ticks = data;\n\n    if (ticks < 0) {\n        ticks = 0;\n    }\n\n    interpretPushLong(program, ticks * 10);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// NOTE: The name of this function is misleading. It has (almost) nothing to do\n// with player's \"Traits\" as a feature. Instead it's used to query many\n// information of the critters using passed parameters. It's like \"metarule\" but\n// for critters.\n//\n// 0x458180\n// has_trait\nstatic void op_has_trait(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to has_trait\", program->name, arg);\n        }\n    }\n\n    int type = data[2];\n    Object* object = (Object*)data[1];\n    int param = data[0];\n\n    int result = 0;\n\n    if (object != NULL) {\n        switch (type) {\n        case CRITTER_TRAIT_PERK:\n            if (param < PERK_COUNT) {\n                result = perk_level(object, param);\n            } else {\n                int_debug(\"\\nScript Error: %s: op_has_trait: Perk out of range\", program->name);\n            }\n            break;\n        case CRITTER_TRAIT_OBJECT:\n            switch (param) {\n            case CRITTER_TRAIT_OBJECT_AI_PACKET:\n                if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n                    result = object->data.critter.combat.aiPacket;\n                }\n                break;\n            case CRITTER_TRAIT_OBJECT_TEAM:\n                if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n                    result = object->data.critter.combat.team;\n                }\n                break;\n            case CRITTER_TRAIT_OBJECT_ROTATION:\n                result = object->rotation;\n                break;\n            case CRITTER_TRAIT_OBJECT_IS_INVISIBLE:\n                result = (object->flags & OBJECT_HIDDEN) == 0;\n                break;\n            case CRITTER_TRAIT_OBJECT_GET_INVENTORY_WEIGHT:\n                result = item_total_weight(object);\n                break;\n            }\n            break;\n        case CRITTER_TRAIT_TRAIT:\n            if (param < TRAIT_COUNT) {\n                result = trait_level(param);\n            } else {\n                int_debug(\"\\nScript Error: %s: op_has_trait: Trait out of range\", program->name);\n            }\n            break;\n        default:\n            int_debug(\"\\nScript Error: %s: op_has_trait: Trait out of range\", program->name);\n            break;\n        }\n    } else {\n        dbg_error(program, \"has_trait\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45835C\nstatic void op_obj_can_hear_obj(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d, to obj_can_hear_obj\", program->name, arg);\n        }\n    }\n\n    Object* object1 = (Object*)data[1];\n    Object* object2 = (Object*)data[0];\n\n    bool canHear = false;\n\n    // FIXME: This is clearly an error. If any of the object is NULL\n    // dereferencing will crash the game.\n    if (object2 == NULL || object1 == NULL) {\n        if (object2->elevation == object1->elevation) {\n            if (object2->tile != -1 && object1->tile != -1) {\n                if (is_within_perception(object2, object1)) {\n                    canHear = true;\n                }\n            }\n        }\n    }\n\n    interpretPushLong(program, canHear);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x458438\nstatic void op_game_time_hour(Program* program)\n{\n    int value = game_time_hour();\n    interpretPushLong(program, value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45845C\nstatic void op_fixed_param(Program* program)\n{\n    int fixedParam = 0;\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        fixedParam = script->fixedParam;\n    } else {\n        dbg_error(program, \"fixed_param\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n    }\n\n    interpretPushLong(program, fixedParam);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4584B0\nstatic void op_tile_is_visible(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to tile_is_visible\", program->name);\n    }\n\n    int isVisible = 0;\n    if (scripts_tile_is_visible(data)) {\n        isVisible = 1;\n    }\n\n    interpretPushLong(program, isVisible);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x458534\nstatic void op_dialogue_system_enter(Program* program)\n{\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) == -1) {\n        return;\n    }\n\n    Object* self = scr_find_obj_from_program(program);\n    if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) {\n        if (!critter_is_active(self)) {\n            return;\n        }\n    }\n\n    if (isInCombat()) {\n        return;\n    }\n\n    if (game_state_request(GAME_STATE_4) == -1) {\n        return;\n    }\n\n    dialog_target = scr_find_obj_from_program(program);\n}\n\n// 0x458594\nstatic void op_action_being_used(Program* program)\n{\n    int action = -1;\n\n    int sid = scr_find_sid_from_program(program);\n\n    Script* script;\n    if (scr_ptr(sid, &script) != -1) {\n        action = script->actionBeingUsed;\n    } else {\n        dbg_error(program, \"action_being_used\", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID);\n    }\n\n    interpretPushLong(program, action);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4585E8\nstatic void op_critter_state(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to critter_state\", program->name);\n    }\n\n    Object* critter = (Object*)data;\n\n    int state = CRITTER_STATE_DEAD;\n    if (critter != NULL && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) {\n        if (critter_is_active(critter)) {\n            state = CRITTER_STATE_NORMAL;\n\n            int anim = FID_ANIM_TYPE(critter->fid);\n            if (anim >= ANIM_FALL_BACK_SF && anim <= ANIM_FALL_FRONT_SF) {\n                state = CRITTER_STATE_PRONE;\n            }\n\n            state |= (critter->data.critter.combat.results & DAM_CRIP);\n        } else {\n            if (!critter_is_dead(critter)) {\n                state = CRITTER_STATE_PRONE;\n            }\n        }\n    } else {\n        dbg_error(program, \"critter_state\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, state);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4586C8\nstatic void op_game_time_advance(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to game_time_advance\", program->name);\n    }\n\n    int days = data / GAME_TIME_TICKS_PER_DAY;\n    int remainder = data % GAME_TIME_TICKS_PER_DAY;\n\n    for (int day = 0; day < days; day++) {\n        inc_game_time(GAME_TIME_TICKS_PER_DAY);\n        queue_process();\n    }\n\n    inc_game_time(remainder);\n    queue_process();\n}\n\n// 0x458760\nstatic void op_radiation_inc(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to radiation_inc\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int amount = data[0];\n\n    if (object == NULL) {\n        dbg_error(program, \"radiation_inc\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    critter_adjust_rads(object, amount);\n}\n\n// 0x458800\nstatic void op_radiation_dec(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to radiation_dec\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int amount = data[0];\n\n    if (object == NULL) {\n        dbg_error(program, \"radiation_dec\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    int radiation = critter_get_rads(object);\n    int adjustment = radiation >= 0 ? -amount : 0;\n\n    critter_adjust_rads(object, adjustment);\n}\n\n// 0x4588B4\nstatic void op_critter_attempt_placement(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_attempt_placement\", program->name, arg);\n        }\n    }\n\n    Object* critter = (Object*)data[2];\n    int tile = data[1];\n    int elevation = data[0];\n\n    if (critter == NULL) {\n        dbg_error(program, \"critter_attempt_placement\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (elevation != critter->elevation && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) {\n        combat_delete_critter(critter);\n    }\n\n    obj_move_to_tile(critter, 0, elevation, NULL);\n\n    int rc = obj_attempt_placement(critter, tile, elevation, 1);\n    interpretPushLong(program, rc);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4589A0\nstatic void op_obj_pid(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_pid\", program->name);\n    }\n\n    Object* obj = (Object*)data;\n\n    int pid = -1;\n    if (obj) {\n        pid = obj->pid;\n    } else {\n        dbg_error(program, \"obj_pid\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, pid);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x458A30\nstatic void op_cur_map_index(Program* program)\n{\n    int mapIndex = map_get_index_number();\n    interpretPushLong(program, mapIndex);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x458A54\nstatic void op_critter_add_trait(Program* program)\n{\n    opcode_t opcode[4];\n    int data[4];\n\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_add_trait\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[3];\n    int kind = data[2];\n    int param = data[1];\n    int value = data[0];\n\n    if (object != NULL) {\n        if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n            switch (kind) {\n            case CRITTER_TRAIT_PERK:\n                if (1) {\n                    char* critterName = critter_name(object);\n                    char* perkName = perk_name(param);\n                    debug_printf(\"\\nintextra::critter_add_trait: Adding Perk %s to %s\", perkName, critterName);\n\n                    if (value > 0) {\n                        if (perk_add_force(object, param) != 0) {\n                            int_debug(\"\\nScript Error: %s: op_critter_add_trait: perk_add_force failed\", program->name);\n                            debug_printf(\"Perk: %d\", param);\n                        }\n                    } else {\n                        if (perk_sub(object, param) != 0) {\n                            // FIXME: typo in debug message, should be perk_sub\n                            int_debug(\"\\nScript Error: %s: op_critter_add_trait: per_sub failed\", program->name);\n                            debug_printf(\"Perk: %d\", param);\n                        }\n                    }\n\n                    if (object == obj_dude) {\n                        intface_update_hit_points(true);\n                    }\n                }\n                break;\n            case CRITTER_TRAIT_OBJECT:\n                switch (param) {\n                case CRITTER_TRAIT_OBJECT_AI_PACKET:\n                    combat_ai_set_ai_packet(object, value);\n                    break;\n                case CRITTER_TRAIT_OBJECT_TEAM:\n                    if (isPartyMember(object)) {\n                        break;\n                    }\n\n                    if (object->data.critter.combat.team == value) {\n                        break;\n                    }\n\n                    if (isLoadingGame()) {\n                        break;\n                    }\n\n                    combatai_switch_team(object, value);\n                    break;\n                }\n                break;\n            default:\n                int_debug(\"\\nScript Error: %s: op_critter_add_trait: Trait out of range\", program->name);\n                break;\n            }\n        }\n    } else {\n        dbg_error(program, \"critter_add_trait\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, -1);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x458C2C\nstatic void op_critter_rm_trait(Program* program)\n{\n    opcode_t opcode[4];\n    int data[4];\n\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_rm_trait\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[3];\n    int kind = data[2];\n    int param = data[1];\n    int value = data[0];\n\n    if (object == NULL) {\n        dbg_error(program, \"critter_rm_trait\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        // FIXME: Ruins stack.\n        return;\n    }\n\n    if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n        switch (kind) {\n        case CRITTER_TRAIT_PERK:\n            while (perk_level(object, param) > 0) {\n                if (perk_sub(object, param) != 0) {\n                    int_debug(\"\\nScript Error: op_critter_rm_trait: perk_sub failed\");\n                }\n            }\n            break;\n        default:\n            int_debug(\"\\nScript Error: %s: op_critter_rm_trait: Trait out of range\", program->name);\n            break;\n        }\n    }\n\n    interpretPushLong(program, -1);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x458D38\nstatic void op_proto_data(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to proto_data\", program->name, arg);\n        }\n    }\n\n    int pid = data[1];\n    int member = data[0];\n\n    ProtoDataMemberValue value;\n    value.integerValue = 0;\n    int valueType = proto_data_member(pid, member, &value);\n    switch (valueType) {\n    case PROTO_DATA_MEMBER_TYPE_INT:\n        interpretPushLong(program, value.integerValue);\n        interpretPushShort(program, VALUE_TYPE_INT);\n        break;\n    case PROTO_DATA_MEMBER_TYPE_STRING:\n        interpretPushLong(program, interpretAddString(program, value.stringValue));\n        interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n        break;\n    default:\n        interpretPushLong(program, 0);\n        interpretPushShort(program, VALUE_TYPE_INT);\n        break;\n    }\n}\n\n// 0x458E10\nstatic void op_message_str(Program* program)\n{\n    // 0x518EFC\n    static char errStr[] = \"Error\";\n\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to message_str\", program->name, arg);\n        }\n    }\n\n    int messageListIndex = data[1];\n    int messageIndex = data[0];\n\n    char* string;\n    if (messageIndex >= 1) {\n        string = scr_get_msg_str_speech(messageListIndex, messageIndex, 1);\n        if (string == NULL) {\n            debug_printf(\"\\nError: No message file EXISTS!: index %d, line %d\", messageListIndex, messageIndex);\n            string = errStr;\n        }\n    } else {\n        string = errStr;\n    }\n\n    interpretPushLong(program, interpretAddString(program, string));\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x458F00\nstatic void op_critter_inven_obj(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_inven_obj\", program->name, arg);\n        }\n    }\n\n    Object* critter = (Object*)data[1];\n    int type = data[0];\n\n    int result = 0;\n\n    if (PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) {\n        switch (type) {\n        case INVEN_TYPE_WORN:\n            result = (int)inven_worn(critter);\n            break;\n        case INVEN_TYPE_RIGHT_HAND:\n            if (critter == obj_dude) {\n                if (intface_is_item_right_hand() != HAND_LEFT) {\n                    result = (int)inven_right_hand(critter);\n                }\n            } else {\n                result = (int)inven_right_hand(critter);\n            }\n            break;\n        case INVEN_TYPE_LEFT_HAND:\n            if (critter == obj_dude) {\n                if (intface_is_item_right_hand() == HAND_LEFT) {\n                    result = (int)inven_left_hand(critter);\n                }\n            } else {\n                result = (int)inven_left_hand(critter);\n            }\n            break;\n        case INVEN_TYPE_INV_COUNT:\n            result = critter->data.inventory.length;\n            break;\n        default:\n            int_debug(\"script error: %s: Error in critter_inven_obj -- wrong type!\", program->name);\n            break;\n        }\n    } else {\n        dbg_error(program, \"critter_inven_obj\", SCRIPT_ERROR_FOLLOWS);\n        debug_printf(\"  Not a critter!\");\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x459088\nstatic void op_obj_set_light_level(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to obj_set_light_level\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int lightIntensity = data[1];\n    int lightDistance = data[0];\n\n    if (object == NULL) {\n        dbg_error(program, \"obj_set_light_level\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    Rect rect;\n    if (lightIntensity != 0) {\n        if (obj_set_light(object, lightDistance, (lightIntensity * 65636) / 100, &rect) == -1) {\n            return;\n        }\n    } else {\n        if (obj_set_light(object, lightDistance, 0, &rect) == -1) {\n            return;\n        }\n    }\n    tile_refresh_rect(&rect, object->elevation);\n}\n\n// 0x459170\nstatic void op_world_map(Program* program)\n{\n    scripts_request_worldmap();\n}\n\n// 0x459178\nstatic void op_inven_cmds(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to inven_cmds\", program->name, arg);\n        }\n    }\n\n    Object* obj = (Object*)data[2];\n    int cmd = data[1];\n    int index = data[0];\n\n    Object* item = NULL;\n\n    if (obj != NULL) {\n        switch (cmd) {\n        case 13:\n            item = inven_index_ptr(obj, index);\n            break;\n        }\n    } else {\n        // FIXME: Should be inven_cmds.\n        dbg_error(program, \"anim\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, (int)item);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x459280\nstatic void op_float_msg(Program* program)\n{\n    // 0x518F00\n    static int last_color = 1;\n\n    opcode_t opcode[3];\n    int data[3];\n\n    char* string = NULL;\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if (arg == 1) {\n            if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n                string = interpretGetString(program, opcode[arg], data[arg]);\n            }\n        } else {\n            if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n                interpretError(\"script error: %s: invalid arg %d to float_msg\", program->name, arg);\n            }\n        }\n    }\n\n    Object* obj = (Object*)data[2];\n    int floatingMessageType = data[0];\n\n    int color = colorTable[32747];\n    int a5 = colorTable[0];\n    int font = 101;\n\n    if (obj == NULL) {\n        dbg_error(program, \"float_msg\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (string == NULL || *string == '\\0') {\n        text_object_remove(obj);\n        tile_refresh_display();\n        return;\n    }\n\n    if (obj->elevation != map_elevation) {\n        return;\n    }\n\n    if (floatingMessageType == FLOATING_MESSAGE_TYPE_COLOR_SEQUENCE) {\n        floatingMessageType = last_color + 1;\n        if (floatingMessageType >= FLOATING_MESSAGE_TYPE_COUNT) {\n            floatingMessageType = FLOATING_MESSAGE_TYPE_BLACK;\n        }\n        last_color = floatingMessageType;\n    }\n\n    switch (floatingMessageType) {\n    case FLOATING_MESSAGE_TYPE_WARNING:\n        color = colorTable[31744];\n        a5 = colorTable[0];\n        font = 103;\n        tile_set_center(obj_dude->tile, TILE_SET_CENTER_REFRESH_WINDOW);\n        break;\n    case FLOATING_MESSAGE_TYPE_NORMAL:\n    case FLOATING_MESSAGE_TYPE_YELLOW:\n        color = colorTable[32747];\n        break;\n    case FLOATING_MESSAGE_TYPE_BLACK:\n    case FLOATING_MESSAGE_TYPE_PURPLE:\n    case FLOATING_MESSAGE_TYPE_GREY:\n        color = colorTable[10570];\n        break;\n    case FLOATING_MESSAGE_TYPE_RED:\n        color = colorTable[31744];\n        break;\n    case FLOATING_MESSAGE_TYPE_GREEN:\n        color = colorTable[992];\n        break;\n    case FLOATING_MESSAGE_TYPE_BLUE:\n        color = colorTable[31];\n        break;\n    case FLOATING_MESSAGE_TYPE_NEAR_WHITE:\n        color = colorTable[21140];\n        break;\n    case FLOATING_MESSAGE_TYPE_LIGHT_RED:\n        color = colorTable[32074];\n        break;\n    case FLOATING_MESSAGE_TYPE_WHITE:\n        color = colorTable[32767];\n        break;\n    case FLOATING_MESSAGE_TYPE_DARK_GREY:\n        color = colorTable[8456];\n        break;\n    case FLOATING_MESSAGE_TYPE_LIGHT_GREY:\n        color = colorTable[15855];\n        break;\n    }\n\n    Rect rect;\n    if (text_object_create(obj, string, font, color, a5, &rect) != -1) {\n        tile_refresh_rect(&rect, obj->elevation);\n    }\n}\n\n// 0x4594A0\nstatic void op_metarule(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to metarule\", program->name, arg);\n        }\n    }\n\n    int rule = data[1];\n    int param = data[0];\n\n    int result = 0;\n\n    switch (rule) {\n    case METARULE_SIGNAL_END_GAME:\n        result = 0;\n        game_user_wants_to_quit = 2;\n        break;\n    case METARULE_FIRST_RUN:\n        result = (map_data.flags & MAP_SAVED) == 0;\n        break;\n    case METARULE_ELEVATOR:\n        scripts_request_elevator(scr_find_obj_from_program(program), param);\n        result = 0;\n        break;\n    case METARULE_PARTY_COUNT:\n        result = getPartyMemberCount();\n        break;\n    case METARULE_AREA_KNOWN:\n        result = wmAreaVisitedState(param);\n        break;\n    case METARULE_WHO_ON_DRUGS:\n        result = queue_find((Object*)param, EVENT_TYPE_DRUG);\n        break;\n    case METARULE_MAP_KNOWN:\n        result = wmMapIsKnown(param);\n        break;\n    case METARULE_IS_LOADGAME:\n        result = isLoadingGame();\n        break;\n    case METARULE_CAR_CURRENT_TOWN:\n        result = wmCarCurrentArea();\n        break;\n    case METARULE_GIVE_CAR_TO_PARTY:\n        result = wmCarGiveToParty();\n        break;\n    case METARULE_GIVE_CAR_GAS:\n        result = wmCarFillGas(param);\n        break;\n    case METARULE_SKILL_CHECK_TAG:\n        result = skill_is_tagged(param);\n        break;\n    case METARULE_DROP_ALL_INVEN:\n        if (1) {\n            Object* object = (Object*)param;\n            result = item_drop_all(object, object->tile);\n            if (obj_dude == object) {\n                intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n                intface_update_ac(false);\n            }\n        }\n        break;\n    case METARULE_INVEN_UNWIELD_WHO:\n        if (1) {\n            Object* object = (Object*)param;\n\n            int hand = HAND_RIGHT;\n            if (object == obj_dude) {\n                if (intface_is_item_right_hand() == HAND_LEFT) {\n                    hand = HAND_LEFT;\n                }\n            }\n\n            result = invenUnwieldFunc(object, hand, 0);\n\n            if (object == obj_dude) {\n                bool animated = !game_ui_is_disabled();\n                intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n            } else {\n                Object* item = inven_left_hand(object);\n                if (item_get_type(item) == ITEM_TYPE_WEAPON) {\n                    item->flags &= ~OBJECT_IN_LEFT_HAND;\n                }\n            }\n        }\n        break;\n    case METARULE_GET_WORLDMAP_XPOS:\n        wmGetPartyWorldPos(&result, NULL);\n        break;\n    case METARULE_GET_WORLDMAP_YPOS:\n        wmGetPartyWorldPos(NULL, &result);\n        break;\n    case METARULE_CURRENT_TOWN:\n        if (wmGetPartyCurArea(&result) == -1) {\n            debug_printf(\"\\nIntextra: Error: metarule: current_town\");\n        }\n        break;\n    case METARULE_LANGUAGE_FILTER:\n        config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &result);\n        break;\n    case METARULE_VIOLENCE_FILTER:\n        config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &result);\n        break;\n    case METARULE_WEAPON_DAMAGE_TYPE:\n        if (1) {\n            Object* object = (Object*)param;\n            if (PID_TYPE(object->pid) == OBJ_TYPE_ITEM) {\n                if (item_get_type(object) == ITEM_TYPE_WEAPON) {\n                    result = item_w_damage_type(NULL, object);\n                    break;\n                }\n            } else {\n                if (art_id(OBJ_TYPE_MISC, 10, 0, 0, 0) == object->fid) {\n                    result = DAMAGE_TYPE_EXPLOSION;\n                    break;\n                }\n            }\n\n            dbg_error(program, \"metarule:w_damage_type\", SCRIPT_ERROR_FOLLOWS);\n            debug_printf(\"Not a weapon!\");\n        }\n        break;\n    case METARULE_CRITTER_BARTERS:\n        if (1) {\n            Object* object = (Object*)param;\n            if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n                Proto* proto;\n                proto_ptr(object->pid, &proto);\n                if ((proto->critter.data.flags & CRITTER_BARTER) != 0) {\n                    result = 1;\n                }\n            }\n        }\n        break;\n    case METARULE_CRITTER_KILL_TYPE:\n        result = critterGetKillType((Object*)param);\n        break;\n    case METARULE_SET_CAR_CARRY_AMOUNT:\n        if (1) {\n            Proto* proto;\n            if (proto_ptr(PROTO_ID_CAR_TRUNK, &proto) != -1) {\n                proto->item.data.container.maxSize = param;\n                result = 1;\n            }\n        }\n        break;\n    case METARULE_GET_CAR_CARRY_AMOUNT:\n        if (1) {\n            Proto* proto;\n            if (proto_ptr(PROTO_ID_CAR_TRUNK, &proto) != -1) {\n                result = proto->item.data.container.maxSize;\n            }\n        }\n        break;\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x4598BC\nstatic void op_anim(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to anim\", program->name, arg);\n        }\n    }\n\n    Object* obj = (Object*)data[2];\n    int anim = data[1];\n    int frame = data[0];\n\n    if (obj == NULL) {\n        dbg_error(program, \"anim\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (anim < ANIM_COUNT) {\n        CritterCombatData* combatData = NULL;\n        if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n            combatData = &(obj->data.critter.combat);\n        }\n\n        anim = correctDeath(obj, anim, true);\n\n        register_begin(ANIMATION_REQUEST_UNRESERVED);\n\n        // TODO: Not sure about the purpose, why it handles knock down flag?\n        if (frame == 0) {\n            register_object_animate(obj, anim, 0);\n            if (anim >= ANIM_FALL_BACK && anim <= ANIM_FALL_FRONT_BLOOD) {\n                int fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim + 28, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 28);\n                register_object_change_fid(obj, fid, -1);\n            }\n\n            if (combatData != NULL) {\n                combatData->results &= DAM_KNOCKED_DOWN;\n            }\n        } else {\n            int fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24);\n            register_object_animate_reverse(obj, anim, 0);\n\n            if (anim == ANIM_PRONE_TO_STANDING) {\n                fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_FALL_FRONT_SF, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24);\n            } else if (anim == ANIM_BACK_TO_STANDING) {\n                fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_FALL_BACK_SF, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24);\n            }\n\n            if (combatData != NULL) {\n                combatData->results |= DAM_KNOCKED_DOWN;\n            }\n\n            register_object_change_fid(obj, fid, -1);\n        }\n\n        register_end();\n    } else if (anim == 1000) {\n        if (frame < ROTATION_COUNT) {\n            Rect rect;\n            obj_set_rotation(obj, frame, &rect);\n            tile_refresh_rect(&rect, map_elevation);\n        }\n    } else if (anim == 1010) {\n        Rect rect;\n        obj_set_frame(obj, frame, &rect);\n        tile_refresh_rect(&rect, map_elevation);\n    } else {\n        int_debug(\"\\nScript Error: %s: op_anim: anim out of range\", program->name);\n    }\n}\n\n// 0x459B5C\nstatic void op_obj_carrying_pid_obj(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to obj_carrying_pid_obj\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int pid = data[0];\n\n    Object* result = NULL;\n    if (object != NULL) {\n        result = inven_pid_is_carried(object, pid);\n    } else {\n        dbg_error(program, \"obj_carrying_pid_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, (int)result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x459C20\nstatic void op_reg_anim_func(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reg_anim_func\", program->name, arg);\n        }\n    }\n\n    int cmd = data[1];\n    int param = data[0];\n\n    if (!isInCombat()) {\n        switch (cmd) {\n        case OP_REG_ANIM_FUNC_BEGIN:\n            register_begin(param);\n            break;\n        case OP_REG_ANIM_FUNC_CLEAR:\n            register_clear((Object*)param);\n            break;\n        case OP_REG_ANIM_FUNC_END:\n            register_end();\n            break;\n        }\n    }\n}\n\n// 0x459CD4\nstatic void op_reg_anim_animate(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reg_anim_animate\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int anim = data[1];\n    int delay = data[0];\n\n    if (!isInCombat()) {\n        int violenceLevel = VIOLENCE_LEVEL_NONE;\n        if (anim != 20 || object == NULL || object->pid != 0x100002F || (config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel) && violenceLevel >= 2)) {\n            if (object != NULL) {\n                register_object_animate(object, anim, delay);\n            } else {\n                dbg_error(program, \"reg_anim_animate\", SCRIPT_ERROR_OBJECT_IS_NULL);\n            }\n        }\n    }\n}\n\n// 0x459DC4\nstatic void op_reg_anim_animate_reverse(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reg_anim_animate_reverse\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int anim = data[1];\n    int delay = data[0];\n\n    if (!isInCombat()) {\n        if (object != NULL) {\n            register_object_animate_reverse(object, anim, delay);\n        } else {\n            dbg_error(program, \"reg_anim_animate_reverse\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        }\n    }\n}\n\n// 0x459E74\nstatic void op_reg_anim_obj_move_to_obj(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reg_anim_obj_move_to_obj\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    Object* dest = (Object*)data[1];\n    int delay = data[0];\n\n    if (!isInCombat()) {\n        if (object != NULL) {\n            register_object_move_to_object(object, dest, -1, delay);\n        } else {\n            dbg_error(program, \"reg_anim_obj_move_to_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        }\n    }\n}\n\n// 0x459F28\nstatic void op_reg_anim_obj_run_to_obj(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reg_anim_obj_run_to_obj\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    Object* dest = (Object*)data[1];\n    int delay = data[0];\n\n    if (!isInCombat()) {\n        if (object != NULL) {\n            register_object_run_to_object(object, dest, -1, delay);\n        } else {\n            dbg_error(program, \"reg_anim_obj_run_to_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        }\n    }\n}\n\n// 0x459FDC\nstatic void op_reg_anim_obj_move_to_tile(Program* prg)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(prg);\n        data[arg] = interpretPopLong(prg);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(prg, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reg_anim_obj_move_to_tile\", prg->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int tile = data[1];\n    int delay = data[0];\n\n    if (!isInCombat()) {\n        if (object != NULL) {\n            register_object_move_to_tile(object, tile, object->elevation, -1, delay);\n        } else {\n            dbg_error(prg, \"reg_anim_obj_move_to_tile\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        }\n    }\n}\n\n// 0x45A094\nstatic void op_reg_anim_obj_run_to_tile(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reg_anim_obj_run_to_tile\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    int tile = data[1];\n    int delay = data[0];\n\n    if (!isInCombat()) {\n        if (object != NULL) {\n            register_object_run_to_tile(object, tile, object->elevation, -1, delay);\n        } else {\n            dbg_error(program, \"reg_anim_obj_run_to_tile\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        }\n    }\n}\n\n// 0x45A14C\nstatic void op_play_gmovie(Program* program)\n{\n    // 0x453F9C\n    static const unsigned short word_453F9C[MOVIE_COUNT] = {\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n        GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC,\n    };\n\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to play_gmovie\", program->name);\n    }\n\n    gdialogDisableBK();\n\n    if (gmovie_play(data, word_453F9C[data]) == -1) {\n        debug_printf(\"\\nError playing movie %d!\", data);\n    }\n\n    gdialogEnableBK();\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x45A200\nstatic void op_add_mult_objs_to_inven(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to add_mult_objs_to_inven\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[2];\n    Object* item = (Object*)data[1];\n    int quantity = data[0];\n\n    if (object == NULL || item == NULL) {\n        return;\n    }\n\n    if (quantity < 0) {\n        quantity = 1;\n    } else if (quantity > 99999) {\n        quantity = 500;\n    }\n\n    if (item_add_force(object, item, quantity) == 0) {\n        Rect rect;\n        obj_disconnect(item, &rect);\n        tile_refresh_rect(&rect, item->elevation);\n    }\n}\n\n// 0x45A2D4\nstatic void op_rm_mult_objs_from_inven(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to rm_mult_objs_from_inven\", program->name, arg);\n        }\n    }\n\n    Object* owner = (Object*)data[2];\n    Object* item = (Object*)data[1];\n    int quantityToRemove = data[0];\n\n    if (owner == NULL || item == NULL) {\n        // FIXME: Ruined stack.\n        return;\n    }\n\n    bool itemWasEquipped = (item->flags & OBJECT_EQUIPPED) != 0;\n\n    int quantity = item_count(owner, item);\n    if (quantity > quantityToRemove) {\n        quantity = quantityToRemove;\n    }\n\n    if (quantity != 0) {\n        if (item_remove_mult(owner, item, quantity) == 0) {\n            Rect updatedRect;\n            obj_connect(item, 1, 0, &updatedRect);\n            if (itemWasEquipped) {\n                if (owner == obj_dude) {\n                    bool animated = !game_ui_is_disabled();\n                    intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n                }\n            }\n        }\n    }\n\n    interpretPushLong(program, quantity);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45A40C\nstatic void op_get_month(Program* program)\n{\n    int month;\n    game_time_date(&month, NULL, NULL);\n\n    interpretPushLong(program, month);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45A43C\nstatic void op_get_day(Program* program)\n{\n    int day;\n    game_time_date(NULL, &day, NULL);\n\n    interpretPushLong(program, day);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45A46C\nstatic void op_explosion(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to explosion\", program->name, arg);\n        }\n    }\n\n    int tile = data[2];\n    int elevation = data[1];\n    int maxDamage = data[0];\n\n    if (tile == -1) {\n        debug_printf(\"\\nError: explosion: bad tile_num!\");\n        return;\n    }\n\n    int minDamage = 1;\n    if (maxDamage == 0) {\n        minDamage = 0;\n    }\n\n    scripts_request_explosion(tile, elevation, minDamage, maxDamage);\n}\n\n// 0x45A528\nstatic void op_days_since_visited(Program* program)\n{\n    int days;\n\n    if (map_data.lastVisitTime != 0) {\n        days = (game_time() - map_data.lastVisitTime) / GAME_TIME_TICKS_PER_DAY;\n    } else {\n        days = -1;\n    }\n\n    interpretPushLong(program, days);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45A56C\nstatic void op_gsay_start(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    if (gdialogStart() != 0) {\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        interpretError(\"Error starting dialog.\");\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x45A5B0\nstatic void op_gsay_end(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n    gdialogGo();\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x45A5D4\nstatic void op_gsay_reply(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[2];\n    int data[2];\n\n    char* string = NULL;\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            if (arg == 0) {\n                if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n                    string = interpretGetString(program, opcode[arg], data[arg]);\n                } else {\n                    interpretError(\"script error: %s: invalid arg %d to gsay_reply\", program->name, arg);\n                }\n            } else {\n                interpretError(\"script error: %s: invalid arg %d to gsay_reply\", program->name, arg);\n            }\n        }\n    }\n\n    int messageListId = data[1];\n    int messageId = data[0];\n\n    if (string != NULL) {\n        gdialogReplyStr(program, messageListId, string);\n    } else {\n        gdialogReply(program, messageListId, messageId);\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x45A6C4\nstatic void op_gsay_option(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[4];\n    int data[4];\n\n    // TODO: Original code is slightly different, does not use loop for first\n    // two args, but uses loop for two last args.\n    char* string = NULL;\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            if (arg == 2) {\n                if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n                    string = interpretGetString(program, opcode[arg], data[arg]);\n                } else {\n                    interpretError(\"script error: %s: invalid arg %d to gsay_option\", program->name, arg);\n                }\n            } else {\n                interpretError(\"script error: %s: invalid arg %d to gsay_option\", program->name, arg);\n            }\n        }\n    }\n\n    int messageListId = data[3];\n    int messageId = data[2];\n    int proc = data[1];\n    int reaction = data[0];\n\n    // TODO: Not sure about this, needs testing.\n    if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        char* procName = interpretGetString(program, opcode[1], data[1]);\n        if (string != NULL) {\n            gdialogOptionStr(data[3], string, procName, reaction);\n        } else {\n            gdialogOption(data[3], data[2], procName, reaction);\n        }\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 3 to sayOption\");\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if (string != NULL) {\n        gdialogOptionProcStr(data[3], string, proc, reaction);\n        program->flags &= ~PROGRAM_FLAG_0x20;\n    } else {\n        gdialogOptionProc(data[3], data[2], proc, reaction);\n        program->flags &= ~PROGRAM_FLAG_0x20;\n    }\n}\n\n// 0x45A8AC\nstatic void op_gsay_message(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[3];\n    int data[3];\n\n    char* string = NULL;\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            if (arg == 1) {\n                if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n                    string = interpretGetString(program, opcode[arg], data[arg]);\n                } else {\n                    interpretError(\"script error: %s: invalid arg %d to gsay_message\", program->name, arg);\n                }\n            } else {\n                interpretError(\"script error: %s: invalid arg %d to gsay_message\", program->name, arg);\n            }\n        }\n    }\n\n    int messageListId = data[2];\n    int messageId = data[1];\n    int reaction = data[0];\n\n    if (string != NULL) {\n        gdialogReplyStr(program, messageListId, string);\n    } else {\n        gdialogReply(program, messageListId, messageId);\n    }\n\n    gdialogOption(-2, -2, NULL, 50);\n    gdialogSayMessage();\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x45A9B4\nstatic void op_giq_option(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[5];\n    int data[5];\n\n    char* string = NULL;\n\n    for (int arg = 0; arg < 5; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            if (arg == 2) {\n                if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n                    string = interpretGetString(program, opcode[arg], data[arg]);\n                } else {\n                    interpretError(\"script error: %s: invalid arg %d to giq_option\", program->name, arg);\n                }\n            } else {\n                interpretError(\"script error: %s: invalid arg %d to giq_option\", program->name, arg);\n            }\n        }\n    }\n\n    int iq = data[4];\n    int messageListId = data[3];\n    int messageId = data[2];\n    int proc = data[1];\n    int reaction = data[0];\n\n    int intelligence = critterGetStat(obj_dude, STAT_INTELLIGENCE);\n    intelligence += perk_level(obj_dude, PERK_SMOOTH_TALKER);\n\n    if (iq < 0) {\n        if (-intelligence < iq) {\n            program->flags &= ~PROGRAM_FLAG_0x20;\n            return;\n        }\n    } else {\n        if (intelligence < iq) {\n            program->flags &= ~PROGRAM_FLAG_0x20;\n            return;\n        }\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) {\n        char* procName = interpretGetString(program, opcode[1], data[1]);\n        if (string != NULL) {\n            gdialogOptionStr(messageListId, string, procName, reaction);\n        } else {\n            gdialogOption(messageListId, messageId, procName, reaction);\n        }\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"Invalid arg 4 to sayOption\");\n        program->flags &= ~PROGRAM_FLAG_0x20;\n        return;\n    }\n\n    if (string != NULL) {\n        gdialogOptionProcStr(messageListId, string, proc, reaction);\n        program->flags &= ~PROGRAM_FLAG_0x20;\n    } else {\n        gdialogOptionProc(messageListId, messageId, proc, reaction);\n        program->flags &= ~PROGRAM_FLAG_0x20;\n    }\n}\n\n// 0x45AB90\nstatic void op_poison(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to poison\", program->name, arg);\n        }\n    }\n\n    Object* obj = (Object*)data[1];\n    int amount = data[0];\n\n    if (obj == NULL) {\n        dbg_error(program, \"poison\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (critter_adjust_poison(obj, amount) != 0) {\n        debug_printf(\"\\nScript Error: poison: adjust failed!\");\n    }\n}\n\n// 0x45AC44\nstatic void op_get_poison(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to get_poison\", program->name);\n    }\n\n    Object* obj = (Object*)data;\n\n    int poison = 0;\n    if (obj != NULL) {\n        if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) {\n            poison = critter_get_poison(obj);\n        } else {\n            debug_printf(\"\\nScript Error: get_poison: who is not a critter!\");\n        }\n    } else {\n        dbg_error(program, \"get_poison\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, poison);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45ACF4\nstatic void op_party_add(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to party_add\", program->name);\n    }\n\n    Object* object = (Object*)data;\n    if (object == NULL) {\n        dbg_error(program, \"party_add\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    partyMemberAdd(object);\n}\n\n// 0x45AD68\nstatic void op_party_remove(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to party_remove\", program->name);\n    }\n\n    Object* object = (Object*)data;\n    if (object == NULL) {\n        dbg_error(program, \"party_remove\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    partyMemberRemove(object);\n}\n\n// 0x45ADDC\nstatic void op_reg_anim_animate_forever(Program* prg)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(prg);\n        data[arg] = interpretPopLong(prg);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(prg, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to reg_anim_animate_forever\", prg->name, arg);\n        }\n    }\n\n    Object* obj = (Object*)data[1];\n    int anim = data[0];\n\n    if (!isInCombat()) {\n        if (obj != NULL) {\n            register_object_animate_forever(obj, anim, -1);\n        } else {\n            dbg_error(prg, \"reg_anim_animate_forever\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        }\n    }\n}\n\n// 0x45AE8C\nstatic void op_critter_injure(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_injure\", program->name, arg);\n        }\n    }\n\n    Object* critter = (Object*)data[1];\n    int flags = data[0];\n\n    if (critter == NULL) {\n        dbg_error(program, \"critter_injure\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    bool reverse = (flags & DAM_PERFORM_REVERSE) != 0;\n\n    flags &= DAM_CRIP;\n\n    if (reverse) {\n        critter->data.critter.combat.results &= ~flags;\n    } else {\n        critter->data.critter.combat.results |= flags;\n    }\n\n    if (critter == obj_dude) {\n        if ((flags & DAM_CRIP_ARM_ANY) != 0) {\n            int leftItemAction;\n            int rightItemAction;\n            intface_get_item_states(&leftItemAction, &rightItemAction);\n            intface_update_items(true, leftItemAction, rightItemAction);\n        }\n    }\n}\n\n// 0x45AF7C\nstatic void op_combat_is_initialized(Program* program)\n{\n    interpretPushLong(program, isInCombat());\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45AFA0\nstatic void op_gdialog_barter(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to gdialog_barter\", program->name);\n    }\n\n    if (gdActivateBarter(data) == -1) {\n        debug_printf(\"\\nScript Error: gdialog_barter: failed\");\n    }\n}\n\n// 0x45B010\nstatic void op_difficulty_level(Program* program)\n{\n    int gameDifficulty;\n    if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty)) {\n        gameDifficulty = GAME_DIFFICULTY_NORMAL;\n    }\n\n    interpretPushLong(program, gameDifficulty);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45B05C\nstatic void op_running_burning_guy(Program* program)\n{\n    int runningBurningGuy;\n    if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_BURNING_GUY_KEY, &runningBurningGuy)) {\n        runningBurningGuy = 1;\n    }\n\n    interpretPushLong(program, runningBurningGuy);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45B0A8\nstatic void op_inven_unwield(Program* program)\n{\n    Object* obj;\n    int v1;\n\n    obj = scr_find_obj_from_program(program);\n    v1 = 1;\n\n    if (obj == obj_dude && !intface_is_item_right_hand()) {\n        v1 = 0;\n    }\n\n    inven_unwield(obj, v1);\n}\n\n// 0x45B0D8\nstatic void op_obj_is_locked(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_is_locked\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    bool locked = false;\n    if (object != NULL) {\n        locked = obj_is_locked(object);\n    } else {\n        dbg_error(program, \"obj_is_locked\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, locked);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45B16C\nstatic void op_obj_lock(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_lock\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object != NULL) {\n        obj_lock(object);\n    } else {\n        dbg_error(program, \"obj_lock\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45B1E0\nstatic void op_obj_unlock(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_unlock\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object != NULL) {\n        obj_unlock(object);\n    } else {\n        dbg_error(program, \"obj_unlock\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45B254\nstatic void op_obj_is_open(Program* s)\n{\n    opcode_t opcode = interpretPopShort(s);\n    int data = interpretPopLong(s);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(s, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_is_open\", s->name);\n    }\n\n    Object* object = (Object*)data;\n\n    bool isOpen = false;\n    if (object != NULL) {\n        isOpen = obj_is_open(object);\n    } else {\n        dbg_error(s, \"obj_is_open\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(s, isOpen);\n    interpretPushShort(s, VALUE_TYPE_INT);\n}\n\n// 0x45B2E8\nstatic void op_obj_open(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_open\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object != NULL) {\n        obj_open(object);\n    } else {\n        dbg_error(program, \"obj_open\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45B35C\nstatic void op_obj_close(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_close\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    if (object != NULL) {\n        obj_close(object);\n    } else {\n        dbg_error(program, \"obj_close\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45B3D0\nstatic void op_game_ui_disable(Program* program)\n{\n    game_ui_disable(0);\n}\n\n// 0x45B3D8\nstatic void op_game_ui_enable(Program* program)\n{\n    game_ui_enable();\n}\n\n// 0x45B3E0\nstatic void op_game_ui_is_disabled(Program* program)\n{\n    interpretPushLong(program, game_ui_is_disabled());\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45B404\nstatic void op_gfade_out(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to gfade_out\", program->name);\n    }\n\n    if (data != 0) {\n        palette_fade_to(black_palette);\n    } else {\n        dbg_error(program, \"gfade_out\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45B47C\nstatic void op_gfade_in(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to gfade_in\", program->name);\n    }\n\n    if (data != 0) {\n        palette_fade_to(cmap);\n    } else {\n        dbg_error(program, \"gfade_in\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45B4F4\nstatic void op_item_caps_total(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to item_caps_total\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    int amount = 0;\n    if (object != NULL) {\n        amount = item_caps_total(object);\n    } else {\n        dbg_error(program, \"item_caps_total\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, amount);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45B588\nstatic void op_item_caps_adjust(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to item_caps_adjust\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int amount = data[0];\n\n    int rc = -1;\n\n    if (object != NULL) {\n        rc = item_caps_adjust(object, amount);\n    } else {\n        dbg_error(program, \"item_caps_adjust\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, rc);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45B64C\nstatic void op_anim_action_frame(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to anim_action_frame\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int anim = data[0];\n\n    int actionFrame = 0;\n\n    if (object != NULL) {\n        int fid = art_id(FID_TYPE(object->fid), object->fid & 0xFFF, anim, 0, object->rotation);\n        CacheEntry* frmHandle;\n        Art* frm = art_ptr_lock(fid, &frmHandle);\n        if (frm != NULL) {\n            actionFrame = art_frame_action_frame(frm);\n            art_ptr_unlock(frmHandle);\n        }\n    } else {\n        dbg_error(program, \"anim_action_frame\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, actionFrame);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45B740\nstatic void op_reg_anim_play_sfx(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if (arg == 1) {\n            if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n                interpretError(\"script error: %s: invalid arg %d to reg_anim_play_sfx\", program->name, arg);\n            }\n        } else {\n            if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n                interpretError(\"script error: %s: invalid arg %d to reg_anim_play_sfx\", program->name, arg);\n            }\n        }\n    }\n\n    Object* obj = (Object*)data[2];\n    int name = data[1];\n    int delay = data[0];\n\n    char* soundEffectName = interpretGetString(program, opcode[1], name);\n    if (soundEffectName == NULL) {\n        dbg_error(program, \"reg_anim_play_sfx\", SCRIPT_ERROR_FOLLOWS);\n        debug_printf(\" Can't match string!\");\n    }\n\n    if (obj != NULL) {\n        register_object_play_sfx(obj, soundEffectName, delay);\n    } else {\n        dbg_error(program, \"reg_anim_play_sfx\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45B840\nstatic void op_critter_mod_skill(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_mod_skill\", program->name, arg);\n        }\n    }\n\n    Object* critter = (Object*)data[2];\n    int skill = data[1];\n    int points = data[0];\n\n    if (critter != NULL && points != 0) {\n        if (PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) {\n            if (critter == obj_dude) {\n                int normalizedPoints = abs(points);\n                if (skill_is_tagged(skill)) {\n                    // Halve number of skill points. Increment/decrement skill\n                    // points routines handle that.\n                    normalizedPoints /= 2;\n                }\n\n                if (points > 0) {\n                    // Increment skill points one by one.\n                    for (int it = 0; it < normalizedPoints; it++) {\n                        skill_inc_point_force(obj_dude, skill);\n                    }\n                } else {\n                    // Decrement skill points one by one.\n                    for (int it = 0; it < normalizedPoints; it++) {\n                        skill_dec_point_force(obj_dude, skill);\n                    }\n                }\n\n                // TODO: Checking for critter is dude twice probably means this\n                // is inlined function.\n                if (critter == obj_dude) {\n                    int leftItemAction;\n                    int rightItemAction;\n                    intface_get_item_states(&leftItemAction, &rightItemAction);\n                    intface_update_items(false, leftItemAction, rightItemAction);\n                }\n            } else {\n                dbg_error(program, \"critter_mod_skill\", SCRIPT_ERROR_FOLLOWS);\n                debug_printf(\" Can't modify anyone except obj_dude!\");\n            }\n        }\n    } else {\n        dbg_error(program, \"critter_mod_skill\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, 0);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45B9C4\nstatic void op_sfx_build_char_name(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to sfx_build_char_name\", program->name, arg);\n        }\n    }\n\n    Object* obj = (Object*)data[2];\n    int anim = data[1];\n    int extra = data[0];\n\n    int stringOffset = 0;\n\n    if (obj != NULL) {\n        char soundEffectName[16];\n        strcpy(soundEffectName, gsnd_build_character_sfx_name(obj, anim, extra));\n        stringOffset = interpretAddString(program, soundEffectName);\n    } else {\n        dbg_error(program, \"sfx_build_char_name\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, stringOffset);\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x45BAA8\nstatic void op_sfx_build_ambient_name(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to sfx_build_ambient_name\", program->name);\n    }\n\n    char* baseName = interpretGetString(program, opcode, data);\n\n    char soundEffectName[16];\n    strcpy(soundEffectName, gsnd_build_ambient_sfx_name(baseName));\n\n    int stringOffset = interpretAddString(program, soundEffectName);\n\n    interpretPushLong(program, stringOffset);\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x45BB54\nstatic void op_sfx_build_interface_name(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to sfx_build_interface_name\", program->name);\n    }\n\n    char* baseName = interpretGetString(program, opcode, data);\n\n    char soundEffectName[16];\n    strcpy(soundEffectName, gsnd_build_interface_sfx_name(baseName));\n\n    int stringOffset = interpretAddString(program, soundEffectName);\n\n    interpretPushLong(program, stringOffset);\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x45BC00\nstatic void op_sfx_build_item_name(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to sfx_build_item_name\", program->name);\n    }\n\n    const char* baseName = interpretGetString(program, opcode, data);\n\n    char soundEffectName[16];\n    strcpy(soundEffectName, gsnd_build_interface_sfx_name(baseName));\n\n    int stringOffset = interpretAddString(program, soundEffectName);\n\n    interpretPushLong(program, stringOffset);\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x45BCAC\nstatic void op_sfx_build_weapon_name(Program* program)\n{\n    opcode_t opcode[4];\n    int data[4];\n\n    for (int arg = 0; arg < 4; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to sfx_build_weapon_name\", program->name, arg);\n        }\n    }\n\n    int weaponSfxType = data[3];\n    Object* weapon = (Object*)data[2];\n    int hitMode = data[1];\n    Object* target = (Object*)data[0];\n\n    char soundEffectName[16];\n    strcpy(soundEffectName, gsnd_build_weapon_sfx_name(weaponSfxType, weapon, hitMode, target));\n\n    int stringOffset = interpretAddString(program, soundEffectName);\n\n    interpretPushLong(program, stringOffset);\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x45BD7C\nstatic void op_sfx_build_scenery_name(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to sfx_build_scenery_name\", program->name, arg);\n        }\n    }\n\n    int action = data[1];\n    int actionType = data[0];\n\n    char* baseName = interpretGetString(program, opcode[2], data[2]);\n\n    char soundEffectName[16];\n    strcpy(soundEffectName, gsnd_build_scenery_sfx_name(actionType, action, baseName));\n\n    int stringOffset = interpretAddString(program, soundEffectName);\n\n    interpretPushLong(program, stringOffset);\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x45BE58\nstatic void op_sfx_build_open_name(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to sfx_build_open_name\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int action = data[0];\n\n    int stringOffset = 0;\n\n    if (object != NULL) {\n        char soundEffectName[16];\n        strcpy(soundEffectName, gsnd_build_open_sfx_name(object, action));\n\n        stringOffset = interpretAddString(program, soundEffectName);\n    } else {\n        dbg_error(program, \"sfx_build_open_name\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, stringOffset);\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x45BF38\nstatic void op_attack_setup(Program* program)\n{\n    opcode_t opcodes[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcodes[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcodes[arg], data[arg]);\n        }\n\n        if ((opcodes[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to attack_setup\", program->name, arg);\n        }\n    }\n\n    Object* attacker = (Object*)data[1];\n    Object* defender = (Object*)data[0];\n\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    if (attacker != NULL) {\n        if (!critter_is_active(attacker) || (attacker->flags & OBJECT_HIDDEN) != 0) {\n            debug_printf(\"\\n   But is already dead or invisible\");\n            program->flags &= ~PROGRAM_FLAG_0x20;\n            return;\n        }\n\n        if (!critter_is_active(defender) || (defender->flags & OBJECT_HIDDEN) != 0) {\n            debug_printf(\"\\n   But target is already dead or invisible\");\n            program->flags &= ~PROGRAM_FLAG_0x20;\n            return;\n        }\n\n        if ((defender->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) {\n            debug_printf(\"\\n   But target is AFRAID\");\n            program->flags &= ~PROGRAM_FLAG_0x20;\n            return;\n        }\n\n        if (isInCombat()) {\n            if ((attacker->data.critter.combat.maneuver & CRITTER_MANEUVER_0x01) == 0) {\n                attacker->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01;\n                attacker->data.critter.combat.whoHitMe = defender;\n            }\n        } else {\n            STRUCT_664980 attack;\n            attack.attacker = attacker;\n            attack.defender = defender;\n            attack.actionPointsBonus = 0;\n            attack.accuracyBonus = 0;\n            attack.damageBonus = 0;\n            attack.minDamage = 0;\n            attack.maxDamage = INT_MAX;\n\n            // FIXME: Something bad here, when attacker and defender are\n            // the same object, these objects are used as flags, which\n            // are later used in 0x422F3C as flags of defender.\n            if (data[1] == data[0]) {\n                attack.field_1C = 1;\n                attack.field_20 = data[1];\n                attack.field_24 = data[0];\n            } else {\n                attack.field_1C = 0;\n            }\n\n            scripts_request_combat(&attack);\n        }\n    }\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x45C0E8\nstatic void op_destroy_mult_objs(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to destroy_mult_objs\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int quantity = data[0];\n\n    Object* self = scr_find_obj_from_program(program);\n    bool isSelf = self == object;\n\n    int result = 0;\n\n    if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) {\n        combat_delete_critter(object);\n    }\n\n    Object* owner = obj_top_environment(object);\n    if (owner != NULL) {\n        int quantityToDestroy = item_count(owner, object);\n        if (quantityToDestroy > quantity) {\n            quantityToDestroy = quantity;\n        }\n\n        item_remove_mult(owner, object, quantityToDestroy);\n\n        if (owner == obj_dude) {\n            bool animated = !game_ui_is_disabled();\n            intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n        }\n\n        obj_connect(object, 1, 0, NULL);\n\n        if (isSelf) {\n            object->sid = -1;\n            object->flags |= (OBJECT_HIDDEN | OBJECT_TEMPORARY);\n        } else {\n            register_clear(object);\n            obj_erase_object(object, NULL);\n        }\n\n        result = quantityToDestroy;\n    } else {\n        register_clear(object);\n\n        Rect rect;\n        obj_erase_object(object, &rect);\n        tile_refresh_rect(&rect, map_elevation);\n    }\n\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n\n    program->flags &= ~PROGRAM_FLAG_0x20;\n\n    if (isSelf) {\n        program->flags |= PROGRAM_FLAG_0x0100;\n    }\n}\n\n// 0x45C290\nstatic void op_use_obj_on_obj(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to use_obj_on_obj\", program->name, arg);\n        }\n    }\n\n    Object* item = (Object*)data[1];\n    Object* target = (Object*)data[0];\n\n    if (item == NULL) {\n        dbg_error(program, \"use_obj_on_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (target == NULL) {\n        dbg_error(program, \"use_obj_on_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    Script* script;\n    int sid = scr_find_sid_from_program(program);\n    if (scr_ptr(sid, &script) == -1) {\n        // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID.\n        dbg_error(program, \"use_obj_on_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    Object* self = scr_find_obj_from_program(program);\n    if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) {\n        action_use_an_item_on_object(self, target, item);\n    } else {\n        obj_use_item_on(self, target, item);\n    }\n}\n\n// 0x45C3B0\nstatic void op_endgame_slideshow(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n    scripts_request_endgame_slideshow();\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x45C3D0\nstatic void op_move_obj_inven_to_obj(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to move_obj_inven_to_obj\", program->name, arg);\n        }\n    }\n\n    Object* object1 = (Object*)data[1];\n    Object* object2 = (Object*)data[0];\n\n    if (object1 == NULL) {\n        dbg_error(program, \"move_obj_inven_to_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    if (object2 == NULL) {\n        dbg_error(program, \"move_obj_inven_to_obj\", SCRIPT_ERROR_OBJECT_IS_NULL);\n        return;\n    }\n\n    Object* oldArmor = NULL;\n    Object* item2 = NULL;\n    if (object1 == obj_dude) {\n        oldArmor = inven_worn(object1);\n    } else {\n        item2 = inven_right_hand(object1);\n    }\n\n    if (object1 != obj_dude && item2 != NULL) {\n        int flags = 0;\n        if ((item2->flags & 0x01000000) != 0) {\n            flags |= 0x01000000;\n        }\n\n        if ((item2->flags & 0x02000000) != 0) {\n            flags |= 0x02000000;\n        }\n\n        correctFidForRemovedItem(object1, item2, flags);\n    }\n\n    item_move_all(object1, object2);\n\n    if (object1 == obj_dude) {\n        if (oldArmor != NULL) {\n            adjust_ac(obj_dude, oldArmor, NULL);\n        }\n\n        proto_dude_update_gender();\n\n        bool animated = !game_ui_is_disabled();\n        intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT);\n    }\n}\n\n// 0x45C54C\nstatic void op_endgame_movie(Program* program)\n{\n    program->flags |= PROGRAM_FLAG_0x20;\n    endgame_movie();\n    program->flags &= ~PROGRAM_FLAG_0x20;\n}\n\n// 0x45C56C\nstatic void op_obj_art_fid(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_art_fid\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    int fid = 0;\n    if (object != NULL) {\n        fid = object->fid;\n    } else {\n        dbg_error(program, \"obj_art_fid\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, fid);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45C5F8\nstatic void op_art_anim(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to art_anim\", program->name);\n    }\n\n    interpretPushLong(program, FID_ANIM_TYPE(data));\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45C66C\nstatic void op_party_member_obj(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to party_member_obj\", program->name);\n    }\n\n    Object* object = partyMemberFindObjFromPid(data);\n    interpretPushLong(program, (int)object);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45C6DC\nstatic void op_rotation_to_tile(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to rotation_to_tile\", program->name, arg);\n        }\n    }\n\n    int tile1 = data[1];\n    int tile2 = data[0];\n\n    int rotation = tile_dir(tile1, tile2);\n    interpretPushLong(program, rotation);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45C778\nstatic void op_jam_lock(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to jam_lock\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    obj_jam_lock(object);\n}\n\n// 0x45C7D4\nstatic void op_gdialog_set_barter_mod(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to gdialog_set_barter_mod\", program->name);\n    }\n\n    gdialogSetBarterMod(data);\n}\n\n// 0x45C830\nstatic void op_combat_difficulty(Program* program)\n{\n    int combatDifficulty;\n    if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combatDifficulty)) {\n        combatDifficulty = 0;\n    }\n\n    interpretPushLong(program, combatDifficulty);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45C878\nstatic void op_obj_on_screen(Program* program)\n{\n    // 0x453FC0\n    static Rect rect = { 0, 0, 640, 480 };\n\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_on_screen\", program->name);\n    }\n\n    Object* object = (Object*)data;\n\n    int result = 0;\n\n    if (object != NULL) {\n        if (map_elevation == object->elevation) {\n            Rect objectRect;\n            obj_bound(object, &objectRect);\n\n            if (rect_inside_bound(&objectRect, &rect, &objectRect) == 0) {\n                result = 1;\n            }\n        }\n    } else {\n        dbg_error(program, \"obj_on_screen\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    //debug_printf(\"ObjOnScreen: %d\\n\", result);\n    interpretPushLong(program, result);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45C93C\nstatic void op_critter_is_fleeing(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to critter_is_fleeing\", program->name);\n    }\n\n    Object* obj = (Object*)data;\n\n    bool fleeing = false;\n    if (obj != NULL) {\n        fleeing = (obj->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0;\n    } else {\n        dbg_error(program, \"critter_is_fleeing\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    interpretPushLong(program, fleeing);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45C9DC\nstatic void op_critter_set_flee_state(Program* program)\n{\n    opcode_t opcode[2];\n    int data[2];\n\n    for (int arg = 0; arg < 2; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to critter_set_flee_state\", program->name, arg);\n        }\n    }\n\n    Object* object = (Object*)data[1];\n    int fleeing = data[0];\n\n    if (object != NULL) {\n        if (fleeing != 0) {\n            object->data.critter.combat.maneuver |= CRITTER_MANUEVER_FLEEING;\n        } else {\n            object->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING;\n        }\n    } else {\n        dbg_error(program, \"critter_set_flee_state\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45CA84\nstatic void op_terminate_combat(Program* program)\n{\n    if (isInCombat()) {\n        game_user_wants_to_quit = 1;\n        Object* self = scr_find_obj_from_program(program);\n        if (self != NULL) {\n            if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) {\n                self->data.critter.combat.maneuver |= CRITTER_MANEUVER_STOP_ATTACKING;\n                self->data.critter.combat.whoHitMe = NULL;\n                combatAIInfoSetLastTarget(self, NULL);\n            }\n        }\n    }\n}\n\n// 0x45CAC8\nstatic void op_debug_msg(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) {\n        interpretError(\"script error: %s: invalid arg to debug_msg\", program->name);\n    }\n\n    char* string = interpretGetString(program, opcode, data);\n\n    if (string != NULL) {\n        bool showScriptMessages = false;\n        configGetBool(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages);\n        if (showScriptMessages) {\n            debug_printf(\"\\n\");\n            debug_printf(string);\n        }\n    }\n}\n\n// 0x45CB70\nstatic void op_critter_stop_attacking(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to critter_stop_attacking\", program->name);\n    }\n\n    Object* obj = (Object*)data;\n\n    if (obj != NULL) {\n        obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_STOP_ATTACKING;\n        obj->data.critter.combat.whoHitMe = NULL;\n        combatAIInfoSetLastTarget(obj, NULL);\n    } else {\n        dbg_error(program, \"critter_stop_attacking\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n}\n\n// 0x45CBF8\nstatic void op_tile_contains_pid_obj(Program* program)\n{\n    opcode_t opcode[3];\n    int data[3];\n\n    for (int arg = 0; arg < 3; arg++) {\n        opcode[arg] = interpretPopShort(program);\n        data[arg] = interpretPopLong(program);\n\n        if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) {\n            interpretDecStringRef(program, opcode[arg], data[arg]);\n        }\n\n        if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n            interpretError(\"script error: %s: invalid arg %d to tile_contains_pid_obj\", program->name, arg);\n        }\n    }\n\n    int tile = data[2];\n    int elevation = data[1];\n    int pid = data[0];\n    Object* found = NULL;\n\n    if (tile != -1) {\n        Object* object = obj_find_first_at_tile(elevation, tile);\n        while (object != NULL) {\n            if (object->pid == pid) {\n                found = object;\n                break;\n            }\n            object = obj_find_next_at_tile();\n        }\n    }\n\n    interpretPushLong(program, (int)found);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45CCC8\nstatic void op_obj_name(Program* program)\n{\n    // 0x518F04\n    static char* strName = _aCritter;\n\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to obj_name\", program->name);\n    }\n\n    Object* obj = (Object*)data;\n    if (obj != NULL) {\n        strName = object_name(obj);\n    } else {\n        dbg_error(program, \"obj_name\", SCRIPT_ERROR_OBJECT_IS_NULL);\n    }\n\n    int stringOffset = interpretAddString(program, strName);\n\n    interpretPushLong(program, stringOffset);\n    interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING);\n}\n\n// 0x45CD64\nstatic void op_get_pc_stat(Program* program)\n{\n    opcode_t opcode = interpretPopShort(program);\n    int data = interpretPopLong(program);\n\n    if (opcode == VALUE_TYPE_DYNAMIC_STRING) {\n        interpretDecStringRef(program, opcode, data);\n    }\n\n    if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) {\n        interpretError(\"script error: %s: invalid arg to get_pc_stat\", program->name);\n    }\n\n    int value = stat_pc_get(data);\n    interpretPushLong(program, value);\n    interpretPushShort(program, VALUE_TYPE_INT);\n}\n\n// 0x45CDD4\nvoid intExtraClose()\n{\n}\n\n// 0x45CDD8\nvoid initIntExtra()\n{\n    interpretAddFunc(0x80A1, op_give_exp_points);\n    interpretAddFunc(0x80A2, op_scr_return);\n    interpretAddFunc(0x80A3, op_play_sfx);\n    interpretAddFunc(0x80A4, op_obj_name);\n    interpretAddFunc(0x80A5, op_sfx_build_open_name);\n    interpretAddFunc(0x80A6, op_get_pc_stat);\n    interpretAddFunc(0x80A7, op_tile_contains_pid_obj);\n    interpretAddFunc(0x80A8, op_set_map_start);\n    interpretAddFunc(0x80A9, op_override_map_start);\n    interpretAddFunc(0x80AA, op_has_skill);\n    interpretAddFunc(0x80AB, op_using_skill);\n    interpretAddFunc(0x80AC, op_roll_vs_skill);\n    interpretAddFunc(0x80AD, op_skill_contest);\n    interpretAddFunc(0x80AE, op_do_check);\n    interpretAddFunc(0x80AF, op_is_success);\n    interpretAddFunc(0x80B0, op_is_critical);\n    interpretAddFunc(0x80B1, op_how_much);\n    interpretAddFunc(0x80B2, op_mark_area_known);\n    interpretAddFunc(0x80B3, op_reaction_influence);\n    interpretAddFunc(0x80B4, op_random);\n    interpretAddFunc(0x80B5, op_roll_dice);\n    interpretAddFunc(0x80B6, op_move_to);\n    interpretAddFunc(0x80B7, op_create_object_sid);\n    interpretAddFunc(0x80B8, op_display_msg);\n    interpretAddFunc(0x80B9, op_script_overrides);\n    interpretAddFunc(0x80BA, op_obj_is_carrying_obj_pid);\n    interpretAddFunc(0x80BB, op_tile_contains_obj_pid);\n    interpretAddFunc(0x80BC, op_self_obj);\n    interpretAddFunc(0x80BD, op_source_obj);\n    interpretAddFunc(0x80BE, op_target_obj);\n    interpretAddFunc(0x80BF, op_dude_obj);\n    interpretAddFunc(0x80C0, op_obj_being_used_with);\n    interpretAddFunc(0x80C1, op_local_var);\n    interpretAddFunc(0x80C2, op_set_local_var);\n    interpretAddFunc(0x80C3, op_map_var);\n    interpretAddFunc(0x80C4, op_set_map_var);\n    interpretAddFunc(0x80C5, op_global_var);\n    interpretAddFunc(0x80C6, op_set_global_var);\n    interpretAddFunc(0x80C7, op_script_action);\n    interpretAddFunc(0x80C8, op_obj_type);\n    interpretAddFunc(0x80C9, op_obj_item_subtype);\n    interpretAddFunc(0x80CA, op_get_critter_stat);\n    interpretAddFunc(0x80CB, op_set_critter_stat);\n    interpretAddFunc(0x80CC, op_animate_stand_obj);\n    interpretAddFunc(0x80CD, op_animate_stand_reverse_obj);\n    interpretAddFunc(0x80CE, op_animate_move_obj_to_tile);\n    interpretAddFunc(0x80CF, op_tile_in_tile_rect);\n    interpretAddFunc(0x80D0, op_attack);\n    interpretAddFunc(0x80D1, op_make_daytime);\n    interpretAddFunc(0x80D2, op_tile_distance);\n    interpretAddFunc(0x80D3, op_tile_distance_objs);\n    interpretAddFunc(0x80D4, op_tile_num);\n    interpretAddFunc(0x80D5, op_tile_num_in_direction);\n    interpretAddFunc(0x80D6, op_pickup_obj);\n    interpretAddFunc(0x80D7, op_drop_obj);\n    interpretAddFunc(0x80D8, op_add_obj_to_inven);\n    interpretAddFunc(0x80D9, op_rm_obj_from_inven);\n    interpretAddFunc(0x80DA, op_wield_obj_critter);\n    interpretAddFunc(0x80DB, op_use_obj);\n    interpretAddFunc(0x80DC, op_obj_can_see_obj);\n    interpretAddFunc(0x80DD, op_attack);\n    interpretAddFunc(0x80DE, op_start_gdialog);\n    interpretAddFunc(0x80DF, op_end_dialogue);\n    interpretAddFunc(0x80E0, op_dialogue_reaction);\n    interpretAddFunc(0x80E1, op_metarule3);\n    interpretAddFunc(0x80E2, op_set_map_music);\n    interpretAddFunc(0x80E3, op_set_obj_visibility);\n    interpretAddFunc(0x80E4, op_load_map);\n    interpretAddFunc(0x80E5, op_wm_area_set_pos);\n    interpretAddFunc(0x80E6, op_set_exit_grids);\n    interpretAddFunc(0x80E7, op_anim_busy);\n    interpretAddFunc(0x80E8, op_critter_heal);\n    interpretAddFunc(0x80E9, op_set_light_level);\n    interpretAddFunc(0x80EA, op_game_time);\n    interpretAddFunc(0x80EB, op_game_time_in_seconds);\n    interpretAddFunc(0x80EC, op_elevation);\n    interpretAddFunc(0x80ED, op_kill_critter);\n    interpretAddFunc(0x80EE, op_kill_critter_type);\n    interpretAddFunc(0x80EF, op_critter_damage);\n    interpretAddFunc(0x80F0, op_add_timer_event);\n    interpretAddFunc(0x80F1, op_rm_timer_event);\n    interpretAddFunc(0x80F2, op_game_ticks);\n    interpretAddFunc(0x80F3, op_has_trait);\n    interpretAddFunc(0x80F4, op_destroy_object);\n    interpretAddFunc(0x80F5, op_obj_can_hear_obj);\n    interpretAddFunc(0x80F6, op_game_time_hour);\n    interpretAddFunc(0x80F7, op_fixed_param);\n    interpretAddFunc(0x80F8, op_tile_is_visible);\n    interpretAddFunc(0x80F9, op_dialogue_system_enter);\n    interpretAddFunc(0x80FA, op_action_being_used);\n    interpretAddFunc(0x80FB, op_critter_state);\n    interpretAddFunc(0x80FC, op_game_time_advance);\n    interpretAddFunc(0x80FD, op_radiation_inc);\n    interpretAddFunc(0x80FE, op_radiation_dec);\n    interpretAddFunc(0x80FF, op_critter_attempt_placement);\n    interpretAddFunc(0x8100, op_obj_pid);\n    interpretAddFunc(0x8101, op_cur_map_index);\n    interpretAddFunc(0x8102, op_critter_add_trait);\n    interpretAddFunc(0x8103, op_critter_rm_trait);\n    interpretAddFunc(0x8104, op_proto_data);\n    interpretAddFunc(0x8105, op_message_str);\n    interpretAddFunc(0x8106, op_critter_inven_obj);\n    interpretAddFunc(0x8107, op_obj_set_light_level);\n    interpretAddFunc(0x8108, op_world_map);\n    interpretAddFunc(0x8109, op_inven_cmds);\n    interpretAddFunc(0x810A, op_float_msg);\n    interpretAddFunc(0x810B, op_metarule);\n    interpretAddFunc(0x810C, op_anim);\n    interpretAddFunc(0x810D, op_obj_carrying_pid_obj);\n    interpretAddFunc(0x810E, op_reg_anim_func);\n    interpretAddFunc(0x810F, op_reg_anim_animate);\n    interpretAddFunc(0x8110, op_reg_anim_animate_reverse);\n    interpretAddFunc(0x8111, op_reg_anim_obj_move_to_obj);\n    interpretAddFunc(0x8112, op_reg_anim_obj_run_to_obj);\n    interpretAddFunc(0x8113, op_reg_anim_obj_move_to_tile);\n    interpretAddFunc(0x8114, op_reg_anim_obj_run_to_tile);\n    interpretAddFunc(0x8115, op_play_gmovie);\n    interpretAddFunc(0x8116, op_add_mult_objs_to_inven);\n    interpretAddFunc(0x8117, op_rm_mult_objs_from_inven);\n    interpretAddFunc(0x8118, op_get_month);\n    interpretAddFunc(0x8119, op_get_day);\n    interpretAddFunc(0x811A, op_explosion);\n    interpretAddFunc(0x811B, op_days_since_visited);\n    interpretAddFunc(0x811C, op_gsay_start);\n    interpretAddFunc(0x811D, op_gsay_end);\n    interpretAddFunc(0x811E, op_gsay_reply);\n    interpretAddFunc(0x811F, op_gsay_option);\n    interpretAddFunc(0x8120, op_gsay_message);\n    interpretAddFunc(0x8121, op_giq_option);\n    interpretAddFunc(0x8122, op_poison);\n    interpretAddFunc(0x8123, op_get_poison);\n    interpretAddFunc(0x8124, op_party_add);\n    interpretAddFunc(0x8125, op_party_remove);\n    interpretAddFunc(0x8126, op_reg_anim_animate_forever);\n    interpretAddFunc(0x8127, op_critter_injure);\n    interpretAddFunc(0x8128, op_combat_is_initialized);\n    interpretAddFunc(0x8129, op_gdialog_barter);\n    interpretAddFunc(0x812A, op_difficulty_level);\n    interpretAddFunc(0x812B, op_running_burning_guy);\n    interpretAddFunc(0x812C, op_inven_unwield);\n    interpretAddFunc(0x812D, op_obj_is_locked);\n    interpretAddFunc(0x812E, op_obj_lock);\n    interpretAddFunc(0x812F, op_obj_unlock);\n    interpretAddFunc(0x8131, op_obj_open);\n    interpretAddFunc(0x8130, op_obj_is_open);\n    interpretAddFunc(0x8132, op_obj_close);\n    interpretAddFunc(0x8133, op_game_ui_disable);\n    interpretAddFunc(0x8134, op_game_ui_enable);\n    interpretAddFunc(0x8135, op_game_ui_is_disabled);\n    interpretAddFunc(0x8136, op_gfade_out);\n    interpretAddFunc(0x8137, op_gfade_in);\n    interpretAddFunc(0x8138, op_item_caps_total);\n    interpretAddFunc(0x8139, op_item_caps_adjust);\n    interpretAddFunc(0x813A, op_anim_action_frame);\n    interpretAddFunc(0x813B, op_reg_anim_play_sfx);\n    interpretAddFunc(0x813C, op_critter_mod_skill);\n    interpretAddFunc(0x813D, op_sfx_build_char_name);\n    interpretAddFunc(0x813E, op_sfx_build_ambient_name);\n    interpretAddFunc(0x813F, op_sfx_build_interface_name);\n    interpretAddFunc(0x8140, op_sfx_build_item_name);\n    interpretAddFunc(0x8141, op_sfx_build_weapon_name);\n    interpretAddFunc(0x8142, op_sfx_build_scenery_name);\n    interpretAddFunc(0x8143, op_attack_setup);\n    interpretAddFunc(0x8144, op_destroy_mult_objs);\n    interpretAddFunc(0x8145, op_use_obj_on_obj);\n    interpretAddFunc(0x8146, op_endgame_slideshow);\n    interpretAddFunc(0x8147, op_move_obj_inven_to_obj);\n    interpretAddFunc(0x8148, op_endgame_movie);\n    interpretAddFunc(0x8149, op_obj_art_fid);\n    interpretAddFunc(0x814A, op_art_anim);\n    interpretAddFunc(0x814B, op_party_member_obj);\n    interpretAddFunc(0x814C, op_rotation_to_tile);\n    interpretAddFunc(0x814D, op_jam_lock);\n    interpretAddFunc(0x814E, op_gdialog_set_barter_mod);\n    interpretAddFunc(0x814F, op_combat_difficulty);\n    interpretAddFunc(0x8150, op_obj_on_screen);\n    interpretAddFunc(0x8151, op_critter_is_fleeing);\n    interpretAddFunc(0x8152, op_critter_set_flee_state);\n    interpretAddFunc(0x8153, op_terminate_combat);\n    interpretAddFunc(0x8154, op_debug_msg);\n    interpretAddFunc(0x8155, op_critter_stop_attacking);\n}\n\n// NOTE: Uncollapsed 0x45D878.\n//\n// 0x45D878\nvoid updateIntExtra()\n{\n}\n\n// NOTE: Uncollapsed 0x45D878.\n//\n// 0x45D878\nvoid intExtraRemoveProgramReferences(Program* program)\n{\n}\n"
  },
  {
    "path": "src/int/support/intextra.h",
    "content": "#ifndef FALLOUT_INT_SUPPORT_INTEXTRA_H_\n#define FALLOUT_INT_SUPPORT_INTEXTRA_H_\n\n#include <stdbool.h>\n\n#include \"int/intrpret.h\"\n#include \"game/object_types.h\"\n\ntypedef enum ScriptError {\n    SCRIPT_ERROR_NOT_IMPLEMENTED,\n    SCRIPT_ERROR_OBJECT_IS_NULL,\n    SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID,\n    SCRIPT_ERROR_FOLLOWS,\n    SCRIPT_ERROR_COUNT,\n} ScriptError;\n\nvoid dbg_error(Program* program, const char* name, int error);\nint correctDeath(Object* critter, int anim, bool a3);\nvoid intExtraClose();\nvoid initIntExtra();\nvoid updateIntExtra();\nvoid intExtraRemoveProgramReferences(Program* program);\n\n#endif /* FALLOUT_INT_SUPPORT_INTEXTRA_H_ */\n"
  },
  {
    "path": "src/int/widget.c",
    "content": "#include \"int/widget.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"int/datafile.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"plib/gnw/rect.h\"\n#include \"int/memdbg.h\"\n#include \"int/sound.h\"\n#include \"plib/gnw/text.h\"\n#include \"int/window.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define WIDGET_UPDATE_REGIONS_CAPACITY 32\n\ntypedef struct StatusBar {\n    unsigned char* field_0;\n    unsigned char* field_4;\n    int win;\n    int x;\n    int y;\n    int width;\n    int height;\n    int field_1C;\n    int field_20;\n    int field_24;\n} StatusBar;\n\ntypedef struct UpdateRegion {\n    int win;\n    int x;\n    int y;\n    unsigned int type;\n    int field_10;\n    void* value;\n    UpdateRegionShowFunc* showFunc;\n    UpdateRegionDrawFunc* drawFunc;\n} UpdateRegion;\n\n\ntypedef struct TextInputRegion {\n    int textRegionId;\n    int isUsed;\n    int field_8;\n    int field_C;\n    int field_10;\n    char* text;\n    int field_18;\n    int field_1C;\n    int btn;\n    TextInputRegionDeleteFunc* deleteFunc;\n    int field_28;\n    void* deleteFuncUserData;\n} TextInputRegion;\n\ntypedef struct TextRegion {\n    int win;\n    int isUsed;\n    int x;\n    int y;\n    int width;\n    int height;\n    int textAlignment;\n    int textFlags;\n    int backgroundColor;\n    int font;\n} TextRegion;\n\nstatic void deleteChar(char* string, int pos, int length);\nstatic void insertChar(char* string, char ch, int pos, int length);\nstatic void textInputRegionDispatch(int btn, int inputEvent);\nstatic void showRegion(UpdateRegion* updateRegion);\nstatic void freeStatusBar();\nstatic void drawStatusBar();\n\n// 0x66E6A0\nstatic UpdateRegion* updateRegions[WIDGET_UPDATE_REGIONS_CAPACITY];\n\n// 0x66E720\nstatic StatusBar statusBar;\n\n// 0x66E750\nstatic TextInputRegion* textInputRegions;\n\n// 0x66E754\nstatic int numTextInputRegions;\n\n// 0x66E758\nstatic TextRegion* textRegions;\n\n// 0x66E75C\nstatic int statusBarActive;\n\n// 0x66E760\nstatic int numTextRegions;\n\n// 0x4B45A0\nstatic void deleteChar(char* string, int pos, int length)\n{\n    if (length > pos) {\n        memcpy(string + pos, string + pos + 1, length - pos);\n    }\n}\n\n// 0x4B45C8\nstatic void insertChar(char* string, char ch, int pos, int length)\n{\n    if (length >= pos) {\n        if (length > pos) {\n            memmove(string + pos + 1, string + pos, length - pos);\n        }\n        string[pos] = ch;\n    }\n}\n\n// 0x4B4788\nstatic void textInputRegionDispatch(int btn, int inputEvent)\n{\n    // TODO: Incomplete.\n}\n\n// 0x4B51D4\nint win_add_text_input_region(int textRegionId, char* text, int a3, int a4)\n{\n    int textInputRegionIndex;\n    int oldFont;\n    int btn;\n\n    if (textRegionId <= 0 || textRegionId > numTextRegions) {\n        return 0;\n    }\n\n    if (textRegions[textRegionId - 1].isUsed == 0) {\n        return 0;\n    }\n\n    for (textInputRegionIndex = 0; textInputRegionIndex < numTextInputRegions; textInputRegionIndex++) {\n        if (textInputRegions[textInputRegionIndex].isUsed == 0) {\n            break;\n        }\n    }\n\n    if (textInputRegionIndex == numTextInputRegions) {\n        if (textInputRegions == NULL) {\n            textInputRegions = (TextInputRegion*)mymalloc(sizeof(*textInputRegions), __FILE__, __LINE__);\n        } else {\n            textInputRegions = (TextInputRegion*)myrealloc(textInputRegions, sizeof(*textInputRegions) * (numTextInputRegions + 1), __FILE__, __LINE__);\n        }\n        numTextInputRegions++;\n    }\n\n    textInputRegions[textInputRegionIndex].field_28 = a4;\n    textInputRegions[textInputRegionIndex].textRegionId = textRegionId;\n    textInputRegions[textInputRegionIndex].isUsed = 1;\n    textInputRegions[textInputRegionIndex].field_8 = a3;\n    textInputRegions[textInputRegionIndex].field_C = 0;\n    textInputRegions[textInputRegionIndex].text = text;\n    textInputRegions[textInputRegionIndex].field_10 = strlen(text);\n    textInputRegions[textInputRegionIndex].deleteFunc = NULL;\n    textInputRegions[textInputRegionIndex].deleteFuncUserData = NULL;\n\n    oldFont = text_curr();\n    text_font(textRegions[textRegionId - 1].font);\n\n    btn = win_register_button(textRegions[textRegionId - 1].win,\n        textRegions[textRegionId - 1].x,\n        textRegions[textRegionId - 1].y,\n        textRegions[textRegionId - 1].width,\n        text_height(),\n        -1,\n        -1,\n        -1,\n        (textInputRegionIndex + 1) | 0x400,\n        NULL,\n        NULL,\n        NULL,\n        0);\n    win_register_button_func(btn, NULL, NULL, NULL, textInputRegionDispatch);\n\n    // NOTE: Uninline.\n    win_print_text_region(textRegionId, text);\n\n    textInputRegions[textInputRegionIndex].btn = btn;\n\n    text_font(oldFont);\n\n    return textInputRegionIndex + 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B53A8\nvoid windowSelectTextInputRegion(int textInputRegionId)\n{\n    textInputRegionDispatch(textInputRegions[textInputRegionId - 1].btn, textInputRegionId | 0x400);\n}\n\n// 0x4B53D0\nint win_delete_all_text_input_regions(int win)\n{\n    int index;\n\n    for (index = 0; index < numTextInputRegions; index++) {\n        if (textRegions[textInputRegions[index].textRegionId - 1].win == win) {\n            win_delete_text_input_region(index + 1);\n        }\n    }\n\n    return 1;\n}\n\n// 0x4B541C\nint win_delete_text_input_region(int textInputRegionId)\n{\n    int textInputRegionIndex;\n\n    textInputRegionIndex = textInputRegionId - 1;\n    if (textInputRegionIndex >= 0 && textInputRegionIndex < numTextInputRegions) {\n        if (textInputRegions[textInputRegionIndex].isUsed != 0) {\n            if (textInputRegions[textInputRegionIndex].deleteFunc != NULL) {\n                textInputRegions[textInputRegionIndex].deleteFunc(textInputRegions[textInputRegionIndex].text, textInputRegions[textInputRegionIndex].deleteFuncUserData);\n            }\n\n            // NOTE: Uninline.\n            win_delete_text_region(textInputRegions[textInputRegionIndex].textRegionId);\n\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B54C8\nint win_set_text_input_delete_func(int textInputRegionId, TextInputRegionDeleteFunc* deleteFunc, void* userData)\n{\n    int textInputRegionIndex;\n\n    textInputRegionIndex = textInputRegionId - 1;\n    if (textInputRegionIndex >= 0 && textInputRegionIndex < numTextInputRegions) {\n        if (textInputRegions[textInputRegionIndex].isUsed != 0) {\n            textInputRegions[textInputRegionIndex].deleteFunc = deleteFunc;\n            textInputRegions[textInputRegionIndex].deleteFuncUserData = userData;\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B5508\nint win_add_text_region(int win, int x, int y, int width, int font, int textAlignment, int textFlags, int backgroundColor)\n{\n    int textRegionIndex;\n    int oldFont;\n    int height;\n\n    for (textRegionIndex = 0; textRegionIndex < numTextRegions; textRegionIndex++) {\n        if (textRegions[textRegionIndex].isUsed == 0) {\n            break;\n        }\n    }\n\n    if (textRegionIndex == numTextRegions) {\n        if (textRegions == NULL) {\n            textRegions = (TextRegion*)mymalloc(sizeof(*textRegions), __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 615\n        } else {\n            textRegions = (TextRegion*)myrealloc(textRegions, sizeof(*textRegions) * (numTextRegions + 1), __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 616\n        }\n        numTextRegions++;\n    }\n\n    oldFont = text_curr();\n    text_font(font);\n\n    height = text_height();\n\n    text_font(oldFont);\n\n    if ((textFlags & FONT_SHADOW) != 0) {\n        width++;\n        height++;\n    }\n\n    textRegions[textRegionIndex].isUsed = 1;\n    textRegions[textRegionIndex].win = win;\n    textRegions[textRegionIndex].x = x;\n    textRegions[textRegionIndex].y = y;\n    textRegions[textRegionIndex].width = width;\n    textRegions[textRegionIndex].height = height;\n    textRegions[textRegionIndex].font = font;\n    textRegions[textRegionIndex].textAlignment = textAlignment;\n    textRegions[textRegionIndex].textFlags = textFlags;\n    textRegions[textRegionIndex].backgroundColor = backgroundColor;\n\n    return textRegionIndex + 1;\n}\n\n// 0x4B5634\nint win_print_text_region(int textRegionId, char* string)\n{\n    int textRegionIndex;\n    int oldFont;\n\n    textRegionIndex = textRegionId - 1;\n    if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) {\n        if (textRegions[textRegionIndex].isUsed != 0) {\n            oldFont = text_curr();\n            text_font(textRegions[textRegionIndex].font);\n\n            win_fill(textRegions[textRegionIndex].win,\n                textRegions[textRegionIndex].x,\n                textRegions[textRegionIndex].y,\n                textRegions[textRegionIndex].width,\n                textRegions[textRegionIndex].height,\n                textRegions[textRegionIndex].backgroundColor);\n\n            windowPrintBuf(textRegions[textRegionIndex].win,\n                string,\n                strlen(string),\n                textRegions[textRegionIndex].width,\n                win_height(textRegions[textRegionIndex].win),\n                textRegions[textRegionIndex].x,\n                textRegions[textRegionIndex].y,\n                textRegions[textRegionIndex].textFlags | 0x2000000,\n                textRegions[textRegionIndex].textAlignment);\n\n            text_font(oldFont);\n\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B5714\nint win_print_substr_region(int textRegionId, char* string, int stringLength)\n{\n    int textRegionIndex;\n    int oldFont;\n\n    textRegionIndex = textRegionId - 1;\n    if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) {\n        if (textRegions[textRegionIndex].isUsed != 0) {\n            oldFont = text_curr();\n            text_font(textRegions[textRegionIndex].font);\n\n            win_fill(textRegions[textRegionIndex].win,\n                textRegions[textRegionIndex].x,\n                textRegions[textRegionIndex].y,\n                textRegions[textRegionIndex].width,\n                textRegions[textRegionIndex].height,\n                textRegions[textRegionIndex].backgroundColor);\n\n            windowPrintBuf(textRegions[textRegionIndex].win,\n                string,\n                stringLength,\n                textRegions[textRegionIndex].width,\n                win_height(textRegions[textRegionIndex].win),\n                textRegions[textRegionIndex].x,\n                textRegions[textRegionIndex].y,\n                textRegions[textRegionIndex].textFlags | 0x2000000,\n                textRegions[textRegionIndex].textAlignment);\n\n            text_font(oldFont);\n\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B57E4\nint win_update_text_region(int textRegionId)\n{\n    int textRegionIndex;\n    Rect rect;\n\n    textRegionIndex = textRegionId - 1;\n    if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) {\n        if (textRegions[textRegionIndex].isUsed != 0) {\n            rect.ulx = textRegions[textRegionIndex].x;\n            rect.uly = textRegions[textRegionIndex].y;\n            rect.lrx = textRegions[textRegionIndex].x + textRegions[textRegionIndex].width;\n            rect.lry = textRegions[textRegionIndex].y + text_height();\n            win_draw_rect(textRegions[textRegionIndex].win, &rect);\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B5864\nint win_delete_text_region(int textRegionId)\n{\n    int textRegionIndex;\n\n    textRegionIndex = textRegionId - 1;\n    if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) {\n        if (textRegions[textRegionIndex].isUsed != 0) {\n            textRegions[textRegionIndex].isUsed = 0;\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B58A0\nint win_delete_all_update_regions(int win)\n{\n    int index;\n\n    for (index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) {\n        if (updateRegions[index] != NULL) {\n            if (win == updateRegions[index]->win) {\n                myfree(updateRegions[index], __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 722\n                updateRegions[index] = NULL;\n            }\n        }\n    }\n\n    return 1;\n}\n\n// 0x4B58E8\nint win_text_region_style(int textRegionId, int font, int textAlignment, int textFlags, int backgroundColor)\n{\n    int textRegionIndex;\n    int oldFont;\n    int height;\n\n    textRegionIndex = textRegionId - 1;\n    if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) {\n        if (textRegions[textRegionIndex].isUsed != 0) {\n            textRegions[textRegionIndex].font = font;\n            textRegions[textRegionIndex].textAlignment = textAlignment;\n\n            oldFont = text_curr();\n            text_font(font);\n\n            height = text_height();\n\n            text_font(oldFont);\n\n            if ((textRegions[textRegionIndex].textFlags & FONT_SHADOW) == 0\n                && (textFlags & FONT_SHADOW) != 0) {\n                height++;\n                textRegions[textRegionIndex].width++;\n            }\n\n            textRegions[textRegionIndex].height = height;\n            textRegions[textRegionIndex].textFlags = textFlags;\n            textRegions[textRegionIndex].backgroundColor = backgroundColor;\n\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B5998\nvoid win_delete_widgets(int win)\n{\n    int index;\n\n    win_delete_all_text_input_regions(win);\n\n    for (index = 0; index < numTextRegions; index++) {\n        if (textRegions[index].win == win) {\n            // NOTE: Uninline.\n            win_delete_text_region(index + 1);\n        }\n    }\n\n    win_delete_all_update_regions(win);\n}\n\n// 0x4B5A04\nint widgetDoInput()\n{\n    int index;\n\n    for (index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) {\n        if (updateRegions[index] != NULL) {\n            showRegion(updateRegions[index]);\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B5A2C\nint win_center_str(int win, char* string, int y, int a4)\n{\n    int windowWidth;\n    int stringWidth;\n\n    windowWidth = win_width(win);\n    stringWidth = text_width(string);\n    win_print(win, string, 0, (windowWidth - stringWidth) / 2, y, a4);\n\n    return 1;\n}\n\n// 0x4B5A64\nstatic void showRegion(UpdateRegion* updateRegion)\n{\n    float value;\n    char stringBuffer[80];\n\n    switch (updateRegion->type & 0xFF) {\n    case 1:\n        value = (float)(*(int*)updateRegion->value);\n        break;\n    case 2:\n        value = *(float*)updateRegion->value;\n        break;\n    case 4:\n        value = *(float*)updateRegion->value / 65636.0f;\n        break;\n    case 8:\n        win_print(updateRegion->win,\n            (char*)updateRegion->value,\n            0,\n            updateRegion->x,\n            updateRegion->y,\n            updateRegion->field_10);\n        return;\n    case 0x10:\n        break;\n    default:\n        debug_printf(\"Invalid input type given to win_register_update\\n\");\n        return;\n    }\n\n    switch (updateRegion->type & 0xFF00) {\n    case 0x100:\n        sprintf(stringBuffer, \" %d \", (int)value);\n        break;\n    case 0x200:\n        sprintf(stringBuffer, \" %f \", value);\n        break;\n    case 0x400:\n        sprintf(stringBuffer, \" %6.2f%% \", value * 100.0f);\n        break;\n    case 0x800:\n        if (updateRegion->showFunc != NULL) {\n            updateRegion->showFunc(updateRegion->value);\n        }\n        return;\n    default:\n        debug_printf(\"Invalid output type given to win_register_update\\n\");\n        return;\n    }\n\n    win_print(updateRegion->win,\n        stringBuffer,\n        0,\n        updateRegion->x,\n        updateRegion->y,\n        updateRegion->field_10 | 0x1000000);\n}\n\n// 0x4B5BE8\nint draw_widgets()\n{\n    int index;\n\n    for (index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) {\n        if (updateRegions[index] != NULL) {\n            if ((updateRegions[index]->type & 0xFF00) == 0x800) {\n                updateRegions[index]->drawFunc(updateRegions[index]->value);\n            }\n        }\n    }\n\n    return 1;\n}\n\n// 0x4B5C24\nint update_widgets()\n{\n    for (int index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) {\n        if (updateRegions[index] != NULL) {\n            showRegion(updateRegions[index]);\n        }\n    }\n\n    return 1;\n}\n\n// 0x4B5C4C\nint win_register_update(int win, int x, int y, UpdateRegionShowFunc* showFunc, UpdateRegionDrawFunc* drawFunc, void* value, unsigned int type, int a8)\n{\n    int updateRegionIndex;\n\n    for (updateRegionIndex = 0; updateRegionIndex < WIDGET_UPDATE_REGIONS_CAPACITY; updateRegionIndex++) {\n        if (updateRegions[updateRegionIndex] == NULL) {\n            break;\n        }\n    }\n\n    if (updateRegionIndex == WIDGET_UPDATE_REGIONS_CAPACITY) {\n        return -1;\n    }\n\n    updateRegions[updateRegionIndex] = (UpdateRegion*)mymalloc(sizeof(*updateRegions), __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 859\n    updateRegions[updateRegionIndex]->win = win;\n    updateRegions[updateRegionIndex]->x = x;\n    updateRegions[updateRegionIndex]->y = y;\n    updateRegions[updateRegionIndex]->type = type;\n    updateRegions[updateRegionIndex]->field_10 = a8;\n    updateRegions[updateRegionIndex]->value = value;\n    updateRegions[updateRegionIndex]->showFunc = showFunc;\n    updateRegions[updateRegionIndex]->drawFunc = drawFunc;\n\n    return updateRegionIndex;\n}\n\n// 0x4B5D0C\nint win_delete_update_region(int updateRegionIndex)\n{\n    if (updateRegionIndex >= 0 && updateRegionIndex < WIDGET_UPDATE_REGIONS_CAPACITY) {\n        if (updateRegions[updateRegionIndex] == NULL) {\n            myfree(updateRegions[updateRegionIndex], __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 875\n            updateRegions[updateRegionIndex] = NULL;\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B5D54\nvoid win_do_updateregions()\n{\n    for (int index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) {\n        if (updateRegions[index] != NULL) {\n            showRegion(updateRegions[index]);\n        }\n    }\n}\n\n// 0x4B5D78\nstatic void freeStatusBar()\n{\n    if (statusBar.field_0 != NULL) {\n        myfree(statusBar.field_0, __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 891\n        statusBar.field_0 = NULL;\n    }\n\n    if (statusBar.field_4 != NULL) {\n        myfree(statusBar.field_4, __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 892\n        statusBar.field_4 = NULL;\n    }\n\n    memset(&statusBar, 0, sizeof(statusBar));\n\n    statusBarActive = 0;\n}\n\n// 0x4B5DE4\nvoid initWidgets()\n{\n    int updateRegionIndex;\n\n    for (updateRegionIndex = 0; updateRegionIndex < WIDGET_UPDATE_REGIONS_CAPACITY; updateRegionIndex++) {\n        updateRegions[updateRegionIndex] = NULL;\n    }\n\n    textRegions = NULL;\n    numTextRegions = 0;\n\n    textInputRegions = NULL;\n    numTextInputRegions = 0;\n\n    freeStatusBar();\n}\n\n// 0x4B5E1C\nvoid widgetsClose()\n{\n    if (textRegions != NULL) {\n        myfree(textRegions, __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 908\n    }\n    textRegions = NULL;\n    numTextRegions = 0;\n\n    if (textInputRegions != NULL) {\n        myfree(textInputRegions, __FILE__, __LINE__); // \"..\\int\\WIDGET.C\", 912\n    }\n    textInputRegions = NULL;\n    numTextInputRegions = 0;\n\n    freeStatusBar();\n}\n\n// 0x4B5E7C\nstatic void drawStatusBar()\n{\n    Rect rect;\n    unsigned char* dest;\n\n    if (statusBarActive) {\n        dest = win_get_buf(statusBar.win) + statusBar.y * win_width(statusBar.win) + statusBar.x;\n\n        buf_to_buf(statusBar.field_0,\n            statusBar.width,\n            statusBar.height,\n            statusBar.width,\n            dest,\n            win_width(statusBar.win));\n\n        buf_to_buf(statusBar.field_4,\n            statusBar.field_1C,\n            statusBar.height,\n            statusBar.width,\n            dest,\n            win_width(statusBar.win));\n\n        rect.ulx = statusBar.x;\n        rect.uly = statusBar.y;\n        rect.lrx = statusBar.x + statusBar.width;\n        rect.lry = statusBar.y + statusBar.height;\n        win_draw_rect(statusBar.win, &rect);\n    }\n}\n\n// 0x4B5F5C\nvoid real_win_set_status_bar(int a1, int a2, int a3)\n{\n    if (statusBarActive) {\n        statusBar.field_1C = a2;\n        statusBar.field_20 = a2;\n        statusBar.field_24 = a3;\n        drawStatusBar();\n    }\n}\n\n// 0x4B5F80\nvoid real_win_update_status_bar(float a1, float a2)\n{\n    if (statusBarActive) {\n        statusBar.field_1C = (int)(a1 * statusBar.width);\n        statusBar.field_20 = (int)(a1 * statusBar.width);\n        statusBar.field_24 = (int)(a2 * statusBar.width);\n        drawStatusBar();\n        soundContinueAll();\n    }\n}\n\n// 0x4B5FD4\nvoid real_win_increment_status_bar(float a1)\n{\n    if (statusBarActive) {\n        statusBar.field_1C = statusBar.field_20 + (int)(a1 * (statusBar.field_24 - statusBar.field_20));\n        drawStatusBar();\n        soundContinueAll();\n    }\n}\n\n// 0x4B6020\nvoid real_win_add_status_bar(int win, int a2, char* a3, char* a4, int x, int y)\n{\n    int imageWidth1;\n    int imageHeight1;\n    int imageWidth2;\n    int imageHeight2;\n\n    freeStatusBar();\n\n    statusBar.field_0 = loadRawDataFile(a4, &imageWidth1, &imageHeight1);\n    statusBar.field_4 = loadRawDataFile(a3, &imageWidth2, &imageHeight2);\n\n    if (imageWidth2 == imageWidth1 && imageHeight2 == imageHeight1) {\n        statusBar.x = x;\n        statusBar.y = y;\n        statusBar.width = imageWidth1;\n        statusBar.height = imageHeight1;\n        statusBar.win = win;\n        real_win_set_status_bar(a2, 0, 0);\n        statusBarActive = 1;\n    } else {\n        freeStatusBar();\n        debug_printf(\"status bar dimensions not the same\\n\");\n    }\n}\n\n// 0x4B60CC\nvoid real_win_get_status_info(int a1, int* a2, int* a3, int* a4)\n{\n    if (statusBarActive) {\n        *a2 = statusBar.field_1C;\n        *a3 = statusBar.field_20;\n        *a4 = statusBar.field_24;\n    } else {\n        *a2 = -1;\n        *a3 = -1;\n        *a4 = -1;\n    }\n}\n\n// 0x4B6100\nvoid real_win_modify_status_info(int a1, int a2, int a3, int a4)\n{\n    if (statusBarActive) {\n        statusBar.field_1C = a2;\n        statusBar.field_20 = a3;\n        statusBar.field_24 = a4;\n    }\n}\n"
  },
  {
    "path": "src/int/widget.h",
    "content": "#ifndef FALLOUT_INT_WIDGET_H_\n#define FALLOUT_INT_WIDGET_H_\n\ntypedef void(UpdateRegionShowFunc)(void* value);\ntypedef void(UpdateRegionDrawFunc)(void* value);\ntypedef void(TextInputRegionDeleteFunc)(char* text, void* userData);\n\nint win_add_text_input_region(int textRegionId, char* text, int a3, int a4);\nvoid windowSelectTextInputRegion(int textInputRegionId);\nint win_delete_all_text_input_regions(int win);\nint win_delete_text_input_region(int textInputRegionId);\nint win_set_text_input_delete_func(int textInputRegionId, TextInputRegionDeleteFunc* deleteFunc, void* userData);\nint win_add_text_region(int win, int x, int y, int width, int font, int textAlignment, int textFlags, int backgroundColor);\nint win_print_text_region(int textRegionId, char* string);\nint win_print_substr_region(int textRegionId, char* string, int stringLength);\nint win_update_text_region(int textRegionId);\nint win_delete_text_region(int textRegionId);\nint win_delete_all_update_regions(int a1);\nint win_text_region_style(int textRegionId, int font, int textAlignment, int textFlags, int backgroundColor);\nvoid win_delete_widgets(int win);\nint widgetDoInput();\nint win_center_str(int win, char* string, int y, int a4);\nint draw_widgets();\nint update_widgets();\nint win_register_update(int win, int x, int y, UpdateRegionShowFunc* showFunc, UpdateRegionDrawFunc* drawFunc, void* value, unsigned int type, int a8);\nint win_delete_update_region(int updateRegionIndex);\nvoid win_do_updateregions();\nvoid initWidgets();\nvoid widgetsClose();\nvoid real_win_set_status_bar(int a1, int a2, int a3);\nvoid real_win_update_status_bar(float a1, float a2);\nvoid real_win_increment_status_bar(float a1);\nvoid real_win_add_status_bar(int win, int a2, char* a3, char* a4, int x, int y);\nvoid real_win_get_status_info(int a1, int* a2, int* a3, int* a4);\nvoid real_win_modify_status_info(int a1, int a2, int a3, int a4);\n\n#endif /* FALLOUT_INT_WIDGET_H_ */\n"
  },
  {
    "path": "src/int/window.c",
    "content": "#include \"int/window.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/db/db.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"int/datafile.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"game/game.h\"\n#include \"int/intlib.h\"\n#include \"int/memdbg.h\"\n#include \"int/mousemgr.h\"\n#include \"int/movie.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/svga.h\"\n\ntypedef enum ManagedButtonMouseEvent {\n    MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN,\n    MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP,\n    MANAGED_BUTTON_MOUSE_EVENT_ENTER,\n    MANAGED_BUTTON_MOUSE_EVENT_EXIT,\n    MANAGED_BUTTON_MOUSE_EVENT_COUNT,\n} ManagedButtonMouseEvent;\n\ntypedef enum ManagedButtonRightMouseEvent {\n    MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_DOWN,\n    MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_UP,\n    MANAGED_BUTTON_RIGHT_MOUSE_EVENT_COUNT,\n} ManagedButtonRightMouseEvent;\n\ntypedef struct ManagedButton {\n    int btn;\n    int width;\n    int height;\n    int x;\n    int y;\n    int flags;\n    int field_18;\n    char name[32];\n    Program* program;\n    unsigned char* pressed;\n    unsigned char* normal;\n    unsigned char* hover;\n    void* field_4C;\n    void* field_50;\n    int procs[MANAGED_BUTTON_MOUSE_EVENT_COUNT];\n    int rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_COUNT];\n    ManagedButtonMouseEventCallback* mouseEventCallback;\n    ManagedButtonMouseEventCallback* rightMouseEventCallback;\n    void* mouseEventCallbackUserData;\n    void* rightMouseEventCallbackUserData;\n} ManagedButton;\n\ntypedef struct ManagedWindow {\n    char name[32];\n    int window;\n    int width;\n    int height;\n    Region** regions;\n    int currentRegionIndex;\n    int regionsLength;\n    int field_38;\n    ManagedButton* buttons;\n    int buttonsLength;\n    int field_44;\n    int field_48;\n    int field_4C;\n    int field_50;\n    float field_54;\n    float field_58;\n} ManagedWindow;\n\nstatic_assert(sizeof(ManagedButton) == 0x7C, \"wrong size\");\n\nstatic bool checkRegion(int windowIndex, int mouseX, int mouseY, int mouseEvent);\nstatic bool checkAllRegions();\nstatic void doRegionRightFunc(Region* region, int a2);\nstatic void doRegionFunc(Region* region, int a2);\nstatic void doButtonOn(int btn, int keyCode);\nstatic void doButtonProc(int btn, int mouseEvent);\nstatic void doButtonOff(int btn, int keyCode);\nstatic void doButtonPress(int btn, int keyCode);\nstatic void doButtonRelease(int btn, int keyCode);\nstatic void doRightButtonPress(int btn, int keyCode);\nstatic void doRightButtonProc(int btn, int mouseEvent);\nstatic void doRightButtonRelease(int btn, int keyCode);\nstatic void setButtonGFX(int width, int height, unsigned char* normal, unsigned char* pressed, unsigned char* a5);\nstatic void redrawButton(ManagedButton* button);\nstatic void removeProgramReferences(Program* program);\n\n// 0x51DCAC\nstatic int holdTime = 250;\n\n// 0x51DCB0\nstatic int checkRegionEnable = 1;\n\n// 0x51DCB4\nstatic int winTOS = -1;\n\n// 051DCB8\nstatic int currentWindow = -1;\n\n// 0x51DCBC\nstatic VideoSystemInitProc* gfx_init[12] = {\n    init_mode_320_200,\n    init_mode_640_480,\n    init_mode_640_480_16,\n    init_mode_320_400,\n    init_mode_640_480_16,\n    init_mode_640_400,\n    init_mode_640_480_16,\n    init_mode_800_600,\n    init_mode_640_480_16,\n    init_mode_1024_768,\n    init_mode_640_480_16,\n    init_mode_1280_1024,\n};\n\n// 0x51DD1C\nstatic Size sizes[12] = {\n    { 320, 200 },\n    { 640, 480 },\n    { 640, 240 },\n    { 320, 400 },\n    { 640, 200 },\n    { 640, 400 },\n    { 800, 300 },\n    { 800, 600 },\n    { 1024, 384 },\n    { 1024, 768 },\n    { 1280, 512 },\n    { 1280, 1024 },\n};\n\n// 0x51DD7C\nstatic int numInputFunc = 0;\n\n// 0x51DD80\nint _lastWin = -1;\n\n// 0x51DD84\nint _said_quit = 1;\n\n// 0x66E770\nstatic int winStack[MANAGED_WINDOW_COUNT];\n\n// 0x66E7B0\nstatic char alphaBlendTable[64 * 256];\n\n// 0x6727B0\nstatic ManagedWindow windows[MANAGED_WINDOW_COUNT];\n\n// 0x672D70\nstatic WindowInputHandler** inputFunc;\n\n// 0x672D74\nstatic ManagedWindowCreateCallback* createWindowFunc;\n\n// 0x672D78\nstatic ManagedWindowSelectFunc* selectWindowFunc;\n\n// 0x672D7C\nstatic int xres;\n\n// 0x672D80\nstatic DisplayInWindowCallback* displayFunc;\n\n// 0x672D84\nstatic WindowDeleteCallback* deleteWindowFunc;\n\n// 0x672D88\nstatic int yres;\n\n// Highlight color (maybe r).\n//\n// 0x672D8C\nstatic int currentHighlightColorR;\n\n// 0x672D90\nstatic int currentFont;\n\n// 0x672D94\nstatic ButtonCallback* soundDisableFunc;\n\n// 0x672D98\nstatic ButtonCallback* soundPressFunc;\n\n// 0x672D9C\nstatic ButtonCallback* soundReleaseFunc;\n\n// Text color (maybe g).\n//\n// 0x672DA0\nstatic int currentTextColorG;\n\n// text color (maybe b).\n//\n// 0x672DA4\nstatic int currentTextColorB;\n\n// 0x672DA8\nstatic int currentTextFlags;\n\n// Text color (maybe r)\n//\n// 0x672DAC\nstatic int currentTextColorR;\n\n// highlight color (maybe g)\n//\n// 0x672DB0\nstatic int currentHighlightColorG;\n\n// Highlight color (maybe b).\n//\n// 0x672DB4\nstatic int currentHighlightColorB;\n\n// 0x4B6120\nint windowGetFont()\n{\n    return currentFont;\n}\n\n// 0x4B6128\nint windowSetFont(int a1)\n{\n    currentFont = a1;\n    text_font(a1);\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B6138\nvoid windowResetTextAttributes()\n{\n    // NOTE: Uninline.\n    windowSetTextColor(1.0, 1.0, 1.0);\n\n    // NOTE: Uninline.\n    windowSetTextFlags(0x2000000 | 0x10000);\n}\n\n// 0x4B6160\nint windowGetTextFlags()\n{\n    return currentTextFlags;\n}\n\n// 0x4B6168\nint windowSetTextFlags(int a1)\n{\n    currentTextFlags = a1;\n    return 1;\n}\n\n// 0x4B6174\nunsigned char windowGetTextColor()\n{\n    return colorTable[currentTextColorB | (currentTextColorG << 5) | (currentTextColorR << 10)];\n}\n\n// 0x4B6198\nunsigned char windowGetHighlightColor()\n{\n    return colorTable[currentHighlightColorB | (currentHighlightColorG << 5) | (currentHighlightColorR << 10)];\n}\n\n// 0x4B61BC\nint windowSetTextColor(float r, float g, float b)\n{\n    currentTextColorR = (int)(r * 31.0);\n    currentTextColorG = (int)(g * 31.0);\n    currentTextColorB = (int)(b * 31.0);\n\n    return 1;\n}\n\n// 0x4B6208\nint windowSetHighlightColor(float r, float g, float b)\n{\n    currentHighlightColorR = (int)(r * 31.0);\n    currentHighlightColorG = (int)(g * 31.0);\n    currentHighlightColorB = (int)(b * 31.0);\n\n    return 1;\n}\n\n// 0x4B62E4\nstatic bool checkRegion(int windowIndex, int mouseX, int mouseY, int mouseEvent)\n{\n    // TODO: Incomplete.\n    return false;\n}\n\n// 0x4B6858\nbool windowCheckRegion(int windowIndex, int mouseX, int mouseY, int mouseEvent)\n{\n    bool rc = checkRegion(windowIndex, mouseX, mouseY, mouseEvent);\n\n    ManagedWindow* managedWindow = &(windows[windowIndex]);\n    int v1 = managedWindow->field_38;\n\n    for (int index = 0; index < managedWindow->regionsLength; index++) {\n        Region* region = managedWindow->regions[index];\n        if (region != NULL) {\n            if (region->field_6C != 0) {\n                region->field_6C = 0;\n                rc = true;\n\n                if (region->mouseEventCallback != NULL) {\n                    region->mouseEventCallback(region, region->mouseEventCallbackUserData, 2);\n                    if (v1 != managedWindow->field_38) {\n                        return true;\n                    }\n                }\n\n                if (region->rightMouseEventCallback != NULL) {\n                    region->rightMouseEventCallback(region, region->rightMouseEventCallbackUserData, 2);\n                    if (v1 != managedWindow->field_38) {\n                        return true;\n                    }\n                }\n\n                if (region->program != NULL && region->procs[2] != 0) {\n                    executeProc(region->program, region->procs[2]);\n                    if (v1 != managedWindow->field_38) {\n                        return true;\n                    }\n                }\n            }\n        }\n    }\n\n    return rc;\n}\n\n// 0x4B69BC\nbool windowRefreshRegions()\n{\n    int mouseX;\n    int mouseY;\n    mouse_get_position(&mouseX, &mouseY);\n\n    int win = win_get_top_win(mouseX, mouseY);\n\n    for (int windowIndex = 0; windowIndex < MANAGED_WINDOW_COUNT; windowIndex++) {\n        ManagedWindow* managedWindow = &(windows[windowIndex]);\n        if (managedWindow->window == win) {\n            for (int regionIndex = 0; regionIndex < managedWindow->regionsLength; regionIndex++) {\n                Region* region = managedWindow->regions[regionIndex];\n                region->rightProcs[3] = 0;\n            }\n\n            int mouseEvent = mouse_get_buttons();\n            return windowCheckRegion(windowIndex, mouseX, mouseY, mouseEvent);\n        }\n    }\n\n    return false;\n}\n\n// 0x4B6A54\nstatic bool checkAllRegions()\n{\n    if (!checkRegionEnable) {\n        return false;\n    }\n\n    int mouseX;\n    int mouseY;\n    mouse_get_position(&mouseX, &mouseY);\n\n    int mouseEvent = mouse_get_buttons();\n    int win = win_get_top_win(mouseX, mouseY);\n\n    for (int windowIndex = 0; windowIndex < MANAGED_WINDOW_COUNT; windowIndex++) {\n        ManagedWindow* managedWindow = &(windows[windowIndex]);\n        if (managedWindow->window != -1 && managedWindow->window == win) {\n            if (_lastWin != -1 && _lastWin != windowIndex && windows[_lastWin].window != -1) {\n                ManagedWindow* managedWindow = &(windows[_lastWin]);\n                int v1 = managedWindow->field_38;\n\n                for (int regionIndex = 0; regionIndex < managedWindow->regionsLength; regionIndex++) {\n                    Region* region = managedWindow->regions[regionIndex];\n                    if (region != NULL && region->rightProcs[3] != 0) {\n                        region->rightProcs[3] = 0;\n                        if (region->mouseEventCallback != NULL) {\n                            region->mouseEventCallback(region, region->mouseEventCallbackUserData, 3);\n                            if (v1 != managedWindow->field_38) {\n                                return true;\n                            }\n                        }\n\n                        if (region->rightMouseEventCallback != NULL) {\n                            region->rightMouseEventCallback(region, region->rightMouseEventCallbackUserData, 3);\n                            if (v1 != managedWindow->field_38) {\n                                return true;\n                            }\n                        }\n\n                        if (region->program != NULL && region->procs[3] != 0) {\n                            executeProc(region->program, region->procs[3]);\n                            if (v1 != managedWindow->field_38) {\n                                return 1;\n                            }\n                        }\n                    }\n                }\n                _lastWin = -1;\n            } else {\n                _lastWin = windowIndex;\n            }\n\n            return windowCheckRegion(windowIndex, mouseX, mouseY, mouseEvent);\n        }\n    }\n\n    return false;\n}\n\n// 0x4B6C48\nvoid windowAddInputFunc(WindowInputHandler* handler)\n{\n    int index;\n    for (index = 0; index < numInputFunc; index++) {\n        if (inputFunc[index] == NULL) {\n            break;\n        }\n    }\n\n    if (index == numInputFunc) {\n        if (inputFunc != NULL) {\n            inputFunc = (WindowInputHandler**)myrealloc(inputFunc, sizeof(*inputFunc) * (numInputFunc + 1), __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 521\n        } else {\n            inputFunc = (WindowInputHandler**)mymalloc(sizeof(*inputFunc), __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 523\n        }\n    }\n\n    inputFunc[numInputFunc] = handler;\n    numInputFunc++;\n}\n\n// 0x4B6CE8\nstatic void doRegionRightFunc(Region* region, int a2)\n{\n    int v1 = windows[currentWindow].field_38;\n    if (region->rightMouseEventCallback != NULL) {\n        region->rightMouseEventCallback(region, region->rightMouseEventCallbackUserData, a2);\n        if (v1 != windows[currentWindow].field_38) {\n            return;\n        }\n    }\n\n    if (a2 < 4) {\n        if (region->program != NULL && region->rightProcs[a2] != 0) {\n            executeProc(region->program, region->rightProcs[a2]);\n        }\n    }\n}\n\n// 0x4B6D68\nstatic void doRegionFunc(Region* region, int a2)\n{\n    int v1 = windows[currentWindow].field_38;\n    if (region->mouseEventCallback != NULL) {\n        region->mouseEventCallback(region, region->mouseEventCallbackUserData, a2);\n        if (v1 != windows[currentWindow].field_38) {\n            return;\n        }\n    }\n\n    if (a2 < 4) {\n        if (region->program != NULL && region->rightProcs[a2] != 0) {\n            executeProc(region->program, region->rightProcs[a2]);\n        }\n    }\n}\n\n// 0x4B6DE8\nbool windowActivateRegion(const char* regionName, int a2)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n\n    if (a2 <= 4) {\n        for (int index = 0; index < managedWindow->regionsLength; index++) {\n            Region* region = managedWindow->regions[index];\n            if (stricmp(regionGetName(region), regionName) == 0) {\n                doRegionFunc(region, a2);\n                return true;\n            }\n        }\n    } else {\n        for (int index = 0; index < managedWindow->regionsLength; index++) {\n            Region* region = managedWindow->regions[index];\n            if (stricmp(regionGetName(region), regionName) == 0) {\n                doRegionRightFunc(region, a2 - 5);\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n// 0x4B6ED0\nint getInput()\n{\n    int keyCode = get_input();\n    if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) {\n        game_quit_with_confirm();\n    }\n\n    if (game_user_wants_to_quit != 0) {\n        _said_quit = 1 - _said_quit;\n        if (_said_quit) {\n            return -1;\n        }\n\n        return KEY_ESCAPE;\n    }\n\n    for (int index = 0; index < numInputFunc; index++) {\n        WindowInputHandler* handler = inputFunc[index];\n        if (handler != NULL) {\n            if (handler(keyCode) != 0) {\n                return -1;\n            }\n        }\n    }\n\n    return keyCode;\n}\n\n// 0x4B6F60\nstatic void doButtonOn(int btn, int keyCode)\n{\n    doButtonProc(btn, MANAGED_BUTTON_MOUSE_EVENT_ENTER);\n}\n\n// 0x4B6F68\nstatic void doButtonProc(int btn, int mouseEvent)\n{\n    int win = win_last_button_winID();\n    if (win == -1) {\n        return;\n    }\n\n    for (int windowIndex = 0; windowIndex < MANAGED_WINDOW_COUNT; windowIndex++) {\n        ManagedWindow* managedWindow = &(windows[windowIndex]);\n        if (managedWindow->window == win) {\n            for (int buttonIndex = 0; buttonIndex < managedWindow->buttonsLength; buttonIndex++) {\n                ManagedButton* managedButton = &(managedWindow->buttons[buttonIndex]);\n                if (managedButton->btn == btn) {\n                    if ((managedButton->flags & 0x02) != 0) {\n                        win_set_button_rest_state(managedButton->btn, 0, 0);\n                    } else {\n                        if (managedButton->program != NULL && managedButton->procs[mouseEvent] != 0) {\n                            executeProc(managedButton->program, managedButton->procs[mouseEvent]);\n                        }\n\n                        if (managedButton->mouseEventCallback != NULL) {\n                            managedButton->mouseEventCallback(managedButton->mouseEventCallbackUserData, mouseEvent);\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n// 0x4B7028\nstatic void doButtonOff(int btn, int keyCode)\n{\n    doButtonProc(btn, MANAGED_BUTTON_MOUSE_EVENT_EXIT);\n}\n\n// 0x4B7034\nstatic void doButtonPress(int btn, int keyCode)\n{\n    doButtonProc(btn, MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN);\n}\n\n// 0x4B703C\nstatic void doButtonRelease(int btn, int keyCode)\n{\n    doButtonProc(btn, MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP);\n}\n\n// NOTE: Unused.\n//\n// 0x4B7048\nstatic void doRightButtonPress(int btn, int keyCode)\n{\n    doRightButtonProc(btn, MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_DOWN);\n}\n\n// NOTE: Unused.\n//\n// 0x4B704C\nstatic void doRightButtonProc(int btn, int mouseEvent)\n{\n    int win = win_last_button_winID();\n    if (win == -1) {\n        return;\n    }\n\n    for (int windowIndex = 0; windowIndex < MANAGED_WINDOW_COUNT; windowIndex++) {\n        ManagedWindow* managedWindow = &(windows[windowIndex]);\n        if (managedWindow->window == win) {\n            for (int buttonIndex = 0; buttonIndex < managedWindow->buttonsLength; buttonIndex++) {\n                ManagedButton* managedButton = &(managedWindow->buttons[buttonIndex]);\n                if (managedButton->btn == btn) {\n                    if ((managedButton->flags & 0x02) != 0) {\n                        win_set_button_rest_state(managedButton->btn, 0, 0);\n                    } else {\n                        if (managedButton->program != NULL && managedButton->rightProcs[mouseEvent] != 0) {\n                            executeProc(managedButton->program, managedButton->rightProcs[mouseEvent]);\n                        }\n\n                        if (managedButton->rightMouseEventCallback != NULL) {\n                            managedButton->rightMouseEventCallback(managedButton->rightMouseEventCallbackUserData, mouseEvent);\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4B710C\nstatic void doRightButtonRelease(int btn, int keyCode)\n{\n    doRightButtonProc(btn, MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_UP);\n}\n\n// 0x4B7118\nstatic void setButtonGFX(int width, int height, unsigned char* normal, unsigned char* pressed, unsigned char* a5)\n{\n    if (normal != NULL) {\n        buf_fill(normal, width, height, width, colorTable[0]);\n        buf_fill(normal + width + 1, width - 2, height - 2, width, intensityColorTable[colorTable[32767]][89]);\n        draw_line(normal, width, 1, 1, width - 2, 1, colorTable[32767]);\n        draw_line(normal, width, 2, 2, width - 3, 2, colorTable[32767]);\n        draw_line(normal, width, 1, height - 2, width - 2, height - 2, intensityColorTable[colorTable[32767]][44]);\n        draw_line(normal, width, 2, height - 3, width - 3, height - 3, intensityColorTable[colorTable[32767]][44]);\n        draw_line(normal, width, width - 2, 1, width - 3, 2, intensityColorTable[colorTable[32767]][89]);\n        draw_line(normal, width, 1, 2, 1, height - 3, colorTable[32767]);\n        draw_line(normal, width, 2, 3, 2, height - 4, colorTable[32767]);\n        draw_line(normal, width, width - 2, 2, width - 2, height - 3, intensityColorTable[colorTable[32767]][44]);\n        draw_line(normal, width, width - 3, 3, width - 3, height - 4, intensityColorTable[colorTable[32767]][44]);\n        draw_line(normal, width, 1, height - 2, 2, height - 3, intensityColorTable[colorTable[32767]][89]);\n    }\n\n    if (pressed != NULL) {\n        buf_fill(pressed, width, height, width, colorTable[0]);\n        buf_fill(pressed + width + 1, width - 2, height - 2, width, intensityColorTable[colorTable[32767]][89]);\n        draw_line(pressed, width, 1, 1, width - 2, 1, colorTable[32767] + 44);\n        draw_line(pressed, width, 1, 1, 1, height - 2, colorTable[32767] + 44);\n    }\n\n    if (a5 != NULL) {\n        buf_fill(a5, width, height, width, colorTable[0]);\n        buf_fill(a5 + width + 1, width - 2, height - 2, width, intensityColorTable[colorTable[32767]][89]);\n        draw_line(a5, width, 1, 1, width - 2, 1, colorTable[32767]);\n        draw_line(a5, width, 2, 2, width - 3, 2, colorTable[32767]);\n        draw_line(a5, width, 1, height - 2, width - 2, height - 2, intensityColorTable[colorTable[32767]][44]);\n        draw_line(a5, width, 2, height - 3, width - 3, height - 3, intensityColorTable[colorTable[32767]][44]);\n        draw_line(a5, width, width - 2, 1, width - 3, 2, intensityColorTable[colorTable[32767]][89]);\n        draw_line(a5, width, 1, 2, 1, height - 3, colorTable[32767]);\n        draw_line(a5, width, 2, 3, 2, height - 4, colorTable[32767]);\n        draw_line(a5, width, width - 2, 2, width - 2, height - 3, intensityColorTable[colorTable[32767]][44]);\n        draw_line(a5, width, width - 3, 3, width - 3, height - 4, intensityColorTable[colorTable[32767]][44]);\n        draw_line(a5, width, 1, height - 2, 2, height - 3, intensityColorTable[colorTable[32767]][89]);\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4B75F4\nstatic void redrawButton(ManagedButton* button)\n{\n    win_register_button_image(button->btn, button->normal, button->pressed, button->hover, 0);\n}\n\n// NOTE: Unused.\n//\n// 0x4B7610\nint windowHide()\n{\n    if (windows[currentWindow].window == -1) {\n        return 0;\n    }\n\n    win_hide(windows[currentWindow].window);\n\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B7648\nint windowShow()\n{\n    if (windows[currentWindow].window == -1) {\n        return 0;\n    }\n\n    win_show(windows[currentWindow].window);\n\n    return 1;\n}\n\n// 0x4B7680\nint windowDraw()\n{\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->window == -1) {\n        return 0;\n    }\n\n    win_draw(managedWindow->window);\n\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B76B8\nint windowDrawRect(int left, int top, int right, int bottom)\n{\n    Rect rect;\n\n    rect.ulx = left;\n    rect.uly = top;\n    rect.lrx = right;\n    rect.lry = bottom;\n    win_draw_rect(windows[currentWindow].window, &rect);\n\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B76F8\nint windowDrawRectID(int windowId, int left, int top, int right, int bottom)\n{\n    Rect rect;\n\n    rect.ulx = left;\n    rect.uly = top;\n    rect.lrx = right;\n    rect.lry = bottom;\n    win_draw_rect(windows[windowId].window, &rect);\n\n    return 1;\n}\n\n// 0x4B7734\nint windowWidth()\n{\n    return windows[currentWindow].width;\n}\n\n// 0x4B7754\nint windowHeight()\n{\n    return windows[currentWindow].height;\n}\n\n// NOTE: Unused.\n//\n// 0x4B7774\nint windowSX()\n{\n    Rect rect;\n\n    win_get_rect(windows[currentWindow].window, &rect);\n\n    return rect.ulx;\n}\n\n// NOTE: Unused.\n//\n// 0x4B77A4\nint windowSY()\n{\n    Rect rect;\n\n    win_get_rect(windows[currentWindow].window, &rect);\n\n    return rect.uly;\n}\n\n// NOTE: Unused.\n//\n// 0x4B77D4\nint pointInWindow(int x, int y)\n{\n    Rect rect;\n\n    win_get_rect(windows[currentWindow].window, &rect);\n\n    return x >= rect.ulx && x <= rect.lrx && y >= rect.uly && y <= rect.lry;\n}\n\n// NOTE: Unused.\n//\n// 0x4B7828\nint windowGetRect(Rect* rect)\n{\n    return win_get_rect(windows[currentWindow].window, rect);\n}\n\n// NOTE: Unused.\n//\n// 0x4B7854\nint windowGetID()\n{\n    return currentWindow;\n}\n\n// NOTE: Inlined.\n//\n// 0x4B785C\nint windowGetGNWID()\n{\n    return windows[currentWindow].window;\n}\n\n// NOTE: Unused.\n//\n// 0x4B787C\nint windowGetSpecificGNWID(int windowIndex)\n{\n    if (windowIndex >= 0 && windowIndex < MANAGED_WINDOW_COUNT) {\n        return windows[windowIndex].window;\n    }\n\n    return -1;\n}\n\n// 0x4B78A4\nbool deleteWindow(const char* windowName)\n{\n    int index;\n    for (index = 0; index < MANAGED_WINDOW_COUNT; index++) {\n        ManagedWindow* managedWindow = &(windows[index]);\n        if (stricmp(managedWindow->name, windowName) == 0) {\n            break;\n        }\n    }\n\n    if (index == MANAGED_WINDOW_COUNT) {\n        return false;\n    }\n\n    if (deleteWindowFunc != NULL) {\n        deleteWindowFunc(index, windowName);\n    }\n\n    ManagedWindow* managedWindow = &(windows[index]);\n    win_delete_widgets(managedWindow->window);\n    win_delete(managedWindow->window);\n    managedWindow->window = -1;\n    managedWindow->name[0] = '\\0';\n\n    if (managedWindow->buttons != NULL) {\n        for (int index = 0; index < managedWindow->buttonsLength; index++) {\n            ManagedButton* button = &(managedWindow->buttons[index]);\n            if (button->hover != NULL) {\n                myfree(button->hover, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 802\n            }\n\n            if (button->field_4C != NULL) {\n                myfree(button->field_4C, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 804\n            }\n\n            if (button->pressed != NULL) {\n                myfree(button->pressed, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 806\n            }\n\n            if (button->normal != NULL) {\n                myfree(button->normal, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 808\n            }\n        }\n\n        myfree(managedWindow->buttons, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 810\n    }\n\n    if (managedWindow->regions != NULL) {\n        for (int index = 0; index < managedWindow->regionsLength; index++) {\n            Region* region = managedWindow->regions[index];\n            if (region != NULL) {\n                regionDelete(region);\n            }\n        }\n\n        myfree(managedWindow->regions, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 818\n        managedWindow->regions = NULL;\n    }\n\n    return true;\n}\n\n// 0x4B7AC4\nint resizeWindow(const char* windowName, int x, int y, int width, int height)\n{\n    // TODO: Incomplete.\n    return -1;\n}\n\n// 0x4B7E7C\nint scaleWindow(const char* windowName, int x, int y, int width, int height)\n{\n    // TODO: Incomplete.\n    return -1;\n}\n\n// 0x4B7F3C\nint createWindow(const char* windowName, int x, int y, int width, int height, int a6, int flags)\n{\n    int windowIndex = -1;\n\n    // NOTE: Original code is slightly different.\n    for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) {\n        ManagedWindow* managedWindow = &(windows[index]);\n        if (managedWindow->window == -1) {\n            windowIndex = index;\n            break;\n        } else {\n            if (stricmp(managedWindow->name, windowName) == 0) {\n                deleteWindow(windowName);\n                windowIndex = index;\n                break;\n            }\n        }\n    }\n\n    if (windowIndex == -1) {\n        return -1;\n    }\n\n    ManagedWindow* managedWindow = &(windows[windowIndex]);\n    strncpy(managedWindow->name, windowName, 32);\n    managedWindow->field_54 = 1.0;\n    managedWindow->field_58 = 1.0;\n    managedWindow->field_38 = 0;\n    managedWindow->regions = NULL;\n    managedWindow->regionsLength = 0;\n    managedWindow->width = width;\n    managedWindow->height = height;\n    managedWindow->buttons = NULL;\n    managedWindow->buttonsLength = 0;\n\n    flags |= 0x101;\n    if (createWindowFunc != NULL) {\n        createWindowFunc(windowIndex, managedWindow->name, &flags);\n    }\n\n    managedWindow->window = win_add(x, y, width, height, a6, flags);\n    managedWindow->field_48 = 0;\n    managedWindow->field_44 = 0;\n    managedWindow->field_4C = a6;\n    managedWindow->field_50 = flags;\n\n    return windowIndex;\n}\n\n// 0x4B80A4\nint windowOutput(char* string)\n{\n    if (currentWindow == -1) {\n        return 0;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n\n    int x = (int)(managedWindow->field_44 * managedWindow->field_54);\n    int y = (int)(managedWindow->field_48 * managedWindow->field_58);\n    // NOTE: Uses `add` at 0x4B810E, not bitwise `or`.\n    int flags = windowGetTextColor() + windowGetTextFlags();\n    win_print(managedWindow->window, string, 0, x, y, flags);\n\n    return 1;\n}\n\n// 0x4B814C\nbool windowGotoXY(int x, int y)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    managedWindow->field_44 = (int)(x * managedWindow->field_54);\n    managedWindow->field_48 = (int)(y * managedWindow->field_58);\n\n    return true;\n}\n\n// 0x4B81C4\nbool selectWindowID(int index)\n{\n    if (index < 0 || index >= MANAGED_WINDOW_COUNT) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[index]);\n    if (managedWindow->window == -1) {\n        return false;\n    }\n\n    currentWindow = index;\n\n    if (selectWindowFunc != NULL) {\n        selectWindowFunc(index, managedWindow->name);\n    }\n\n    return true;\n}\n\n// 0x4B821C\nint selectWindow(const char* windowName)\n{\n    if (currentWindow != -1) {\n        ManagedWindow* managedWindow = &(windows[currentWindow]);\n        if (stricmp(managedWindow->name, windowName) == 0) {\n            return currentWindow;\n        }\n    }\n\n    int index;\n    for (index = 0; index < MANAGED_WINDOW_COUNT; index++) {\n        ManagedWindow* managedWindow = &(windows[index]);\n        if (managedWindow->window != -1) {\n            if (stricmp(managedWindow->name, windowName) == 0) {\n                break;\n            }\n        }\n    }\n\n    if (selectWindowID(index)) {\n        return index;\n    }\n\n    return -1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B82A0\nint windowGetDefined(const char* name)\n{\n    int index;\n\n    for (index = 0; index < MANAGED_WINDOW_COUNT; index++) {\n        if (windows[index].window != -1 && stricmp(windows[index].name, name) == 0) {\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4B82DC\nunsigned char* windowGetBuffer()\n{\n    if (currentWindow != -1) {\n        ManagedWindow* managedWindow = &(windows[currentWindow]);\n        return win_get_buf(managedWindow->window);\n    }\n\n    return NULL;\n}\n\n// NOTE: Unused.\n//\n// 0x4B8308\nchar* windowGetName()\n{\n    if (currentWindow != -1) {\n        return windows[currentWindow].name;\n    }\n\n    return NULL;\n}\n\n// 0x4B8330\nint pushWindow(const char* windowName)\n{\n    if (winTOS >= MANAGED_WINDOW_COUNT) {\n        return -1;\n    }\n\n    int oldCurrentWindowIndex = currentWindow;\n\n    int windowIndex = selectWindow(windowName);\n    if (windowIndex == -1) {\n        return -1;\n    }\n\n    // TODO: Check.\n    for (int index = 0; index < winTOS; index++) {\n        if (winStack[index] == oldCurrentWindowIndex) {\n            memcpy(&(winStack[index]), &(winStack[index + 1]), sizeof(*winStack) * (winTOS - index));\n            break;\n        }\n    }\n\n    winTOS++;\n    winStack[winTOS] = oldCurrentWindowIndex;\n\n    return windowIndex;\n}\n\n// 0x4B83D4\nint popWindow()\n{\n    if (winTOS == -1) {\n        return -1;\n    }\n\n    int windowIndex = winStack[winTOS];\n    ManagedWindow* managedWindow = &(windows[windowIndex]);\n    winTOS--;\n\n    return selectWindow(managedWindow->name);\n}\n\n// 0x4B8414\nvoid windowPrintBuf(int win, char* string, int stringLength, int width, int maxY, int x, int y, int flags, int textAlignment)\n{\n    if (y + text_height() > maxY) {\n        return;\n    }\n\n    if (stringLength > 255) {\n        stringLength = 255;\n    }\n\n    char* stringCopy = (char*)mymalloc(stringLength + 1, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1078\n    strncpy(stringCopy, string, stringLength);\n    stringCopy[stringLength] = '\\0';\n\n    int stringWidth = text_width(stringCopy);\n    int stringHeight = text_height();\n    if (stringWidth == 0 || stringHeight == 0) {\n        myfree(stringCopy, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1085\n        return;\n    }\n\n    if ((flags & FONT_SHADOW) != 0) {\n        stringWidth++;\n        stringHeight++;\n    }\n\n    unsigned char* backgroundBuffer = (unsigned char*)mycalloc(stringWidth, stringHeight, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1093\n    unsigned char* backgroundBufferPtr = backgroundBuffer;\n    text_to_buf(backgroundBuffer, stringCopy, stringWidth, stringWidth, flags);\n\n    switch (textAlignment) {\n    case TEXT_ALIGNMENT_LEFT:\n        if (stringWidth < width) {\n            width = stringWidth;\n        }\n        break;\n    case TEXT_ALIGNMENT_RIGHT:\n        if (stringWidth <= width) {\n            x += (width - stringWidth);\n            width = stringWidth;\n        } else {\n            backgroundBufferPtr = backgroundBuffer + stringWidth - width;\n        }\n        break;\n    case TEXT_ALIGNMENT_CENTER:\n        if (stringWidth <= width) {\n            x += (width - stringWidth) / 2;\n            width = stringWidth;\n        } else {\n            backgroundBufferPtr = backgroundBuffer + (stringWidth - width) / 2;\n        }\n        break;\n    }\n\n    if (stringHeight + y > win_height(win)) {\n        stringHeight = win_height(win) - y;\n    }\n\n    if ((flags & 0x2000000) != 0) {\n        trans_buf_to_buf(backgroundBufferPtr, width, stringHeight, stringWidth, win_get_buf(win) + win_width(win) * y + x, win_width(win));\n    } else {\n        buf_to_buf(backgroundBufferPtr, width, stringHeight, stringWidth, win_get_buf(win) + win_width(win) * y + x, win_width(win));\n    }\n\n    myfree(backgroundBuffer, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1130\n    myfree(stringCopy, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1131\n}\n\n// 0x4B8638\nchar** windowWordWrap(char* string, int maxLength, int a3, int* substringListLengthPtr)\n{\n    if (string == NULL) {\n        *substringListLengthPtr = 0;\n        return NULL;\n    }\n\n    char** substringList = NULL;\n    int substringListLength = 0;\n\n    char* start = string;\n    char* pch = string;\n    int v1 = a3;\n    while (*pch != '\\0') {\n        v1 += text_char_width(*pch & 0xFF);\n        if (*pch != '\\n' && v1 <= maxLength) {\n            v1 += text_spacing();\n            pch++;\n        } else {\n            while (v1 > maxLength) {\n                v1 -= text_char_width(*pch);\n                pch--;\n            }\n\n            if (*pch != '\\n') {\n                while (pch != start && *pch != ' ') {\n                    pch--;\n                }\n            }\n\n            if (substringList != NULL) {\n                substringList = (char**)myrealloc(substringList, sizeof(*substringList) * (substringListLength + 1), __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1166\n            } else {\n                substringList = (char**)mymalloc(sizeof(*substringList), __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1167\n            }\n\n            char* substring = (char*)mymalloc(pch - start + 1, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1169\n            strncpy(substring, start, pch - start);\n            substring[pch - start] = '\\0';\n\n            substringList[substringListLength] = substring;\n\n            while (*pch == ' ') {\n                pch++;\n            }\n\n            v1 = 0;\n            start = pch;\n            substringListLength++;\n        }\n    }\n\n    if (start != pch) {\n        if (substringList != NULL) {\n            substringList = (char**)myrealloc(substringList, sizeof(*substringList) * (substringListLength + 1), __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1184\n        } else {\n            substringList = (char**)mymalloc(sizeof(*substringList), __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1185\n        }\n\n        char* substring = (char*)mymalloc(pch - start + 1, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1169\n        strncpy(substring, start, pch - start);\n        substring[pch - start] = '\\0';\n\n        substringList[substringListLength] = substring;\n        substringListLength++;\n    }\n\n    *substringListLengthPtr = substringListLength;\n\n    return substringList;\n}\n\n// 0x4B880C\nvoid windowFreeWordList(char** substringList, int substringListLength)\n{\n    if (substringList == NULL) {\n        return;\n    }\n\n    for (int index = 0; index < substringListLength; index++) {\n        myfree(substringList[index], __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1200\n    }\n\n    myfree(substringList, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1201\n}\n\n// Renders multiline string in the specified bounding box.\n//\n// 0x4B8854\nvoid windowWrapLineWithSpacing(int win, char* string, int width, int height, int x, int y, int flags, int textAlignment, int a9)\n{\n    if (string == NULL) {\n        return;\n    }\n\n    int substringListLength;\n    char** substringList = windowWordWrap(string, width, 0, &substringListLength);\n\n    for (int index = 0; index < substringListLength; index++) {\n        int v1 = y + index * (a9 + text_height());\n        windowPrintBuf(win, substringList[index], strlen(substringList[index]), width, height + y, x, v1, flags, textAlignment);\n    }\n\n    windowFreeWordList(substringList, substringListLength);\n}\n\n// Renders multiline string in the specified bounding box.\n//\n// 0x4B88FC\nvoid windowWrapLine(int win, char* string, int width, int height, int x, int y, int flags, int textAlignment)\n{\n    windowWrapLineWithSpacing(win, string, width, height, x, y, flags, textAlignment, 0);\n}\n\n// 0x4B8920\nbool windowPrintRect(char* string, int a2, int textAlignment)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    int width = (int)(a2 * managedWindow->field_54);\n    int height = win_height(managedWindow->window);\n    int x = managedWindow->field_44;\n    int y = managedWindow->field_48;\n    int flags = windowGetTextColor() | 0x2000000;\n\n    // NOTE: Uninline.\n    windowWrapLine(managedWindow->window, string, width, height, x, y, flags, textAlignment);\n\n    return true;\n}\n\n// 0x4B89B0\nbool windowFormatMessage(char* string, int x, int y, int width, int height, int textAlignment)\n{\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    int flags = windowGetTextColor() | 0x2000000;\n\n    // NOTE: Uninline.\n    windowWrapLine(managedWindow->window, string, width, height, x, y, flags, textAlignment);\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x4B8A14\nint windowFormatMessageColor(char* string, int x, int y, int width, int height, int textAlignment, int flags)\n{\n    windowWrapLine(windows[currentWindow].window, string, width, height, x, y, flags, textAlignment);\n\n    return 1;\n}\n\n// 0x4B8A60\nbool windowPrint(char* string, int a2, int x, int y, int a5)\n{\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    x = (int)(x * managedWindow->field_54);\n    y = (int)(y * managedWindow->field_58);\n\n    win_print(managedWindow->window, string, a2, x, y, a5);\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x4B8ADC\nint windowPrintFont(char* string, int a2, int x, int y, int a5, int font)\n{\n    int oldFont;\n\n    oldFont = text_curr();\n    text_font(font);\n\n    windowPrint(string, a2, x, y, a5);\n\n    text_font(oldFont);\n\n    return 1;\n}\n\n// 0x4B8B10\nvoid displayInWindow(unsigned char* data, int width, int height, int pitch)\n{\n    if (displayFunc != NULL) {\n        // NOTE: The second parameter is unclear as there is no distinction\n        // between address of entire window struct and it's name (since it's the\n        // first field). I bet on name since it matches WindowDeleteCallback,\n        // which accepts window index and window name as seen at 0x4B7927).\n        displayFunc(currentWindow,\n            windows[currentWindow].name,\n            data,\n            width,\n            height);\n    }\n\n    if (width == pitch) {\n        // NOTE: Uninline.\n        if (pitch == windowWidth() && height == windowHeight()) {\n            // NOTE: Uninline.\n            unsigned char* windowBuffer = windowGetBuffer();\n            memcpy(windowBuffer, data, height * width);\n        } else {\n            // NOTE: Uninline.\n            unsigned char* windowBuffer = windowGetBuffer();\n            drawScaledBuf(windowBuffer, windowWidth(), windowHeight(), data, width, height);\n        }\n    } else {\n        // NOTE: Uninline.\n        unsigned char* windowBuffer = windowGetBuffer();\n        drawScaled(windowBuffer,\n            windowWidth(),\n            windowHeight(),\n            windowWidth(),\n            data,\n            width,\n            height,\n            pitch);\n    }\n}\n\n// 0x4B8C68\nvoid displayFile(char* fileName)\n{\n    int width;\n    int height;\n    unsigned char* data = loadDataFile(fileName, &width, &height);\n    if (data != NULL) {\n        displayInWindow(data, width, height, width);\n        myfree(data, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1294\n    }\n}\n\n// 0x4B8CA8\nvoid displayFileRaw(char* fileName)\n{\n    int width;\n    int height;\n    unsigned char* data = loadRawDataFile(fileName, &width, &height);\n    if (data != NULL) {\n        displayInWindow(data, width, height, width);\n        myfree(data, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1305\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4B8E0C\nint windowDisplayRaw(char* fileName)\n{\n    int imageWidth;\n    int imageHeight;\n    unsigned char* imageData;\n\n    imageData = loadDataFile(fileName, &imageWidth, &imageHeight);\n    if (imageData == NULL) {\n        return 0;\n    }\n\n    displayInWindow(imageData, imageWidth, imageHeight, imageWidth);\n\n    myfree(imageData, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1363\n\n    return 1;\n}\n\n// 0x4B8E50\nbool windowDisplay(char* fileName, int x, int y, int width, int height)\n{\n    int imageWidth;\n    int imageHeight;\n    unsigned char* imageData = loadDataFile(fileName, &imageWidth, &imageHeight);\n    if (imageData == NULL) {\n        return false;\n    }\n\n    windowDisplayBuf(imageData, imageWidth, imageHeight, x, y, width, height);\n\n    myfree(imageData, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1376\n\n    return true;\n}\n\n// NOTE: Unused\n//\n// 0x4B8EA0\nint windowDisplayScaled(char* fileName, int x, int y, int width, int height)\n{\n    int imageWidth;\n    int imageHeight;\n    unsigned char* imageData;\n\n    imageData = loadDataFile(fileName, &imageWidth, &imageHeight);\n    if (imageData == NULL) {\n        return 0;\n    }\n\n    windowDisplayBufScaled(imageData, imageWidth, imageHeight, x, y, width, height);\n\n    myfree(imageData, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1389\n\n    return 1;\n}\n\n// 0x4B8EF0\nbool windowDisplayBuf(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight)\n{\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    unsigned char* windowBuffer = win_get_buf(managedWindow->window);\n\n    buf_to_buf(src,\n        destWidth,\n        destHeight,\n        srcWidth,\n        windowBuffer + managedWindow->width * destY + destX,\n        managedWindow->width);\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x4B8F64\nint windowDisplayTransBuf(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight)\n{\n    unsigned char* windowBuffer;\n\n    windowBuffer = win_get_buf(windows[currentWindow].window);\n\n    trans_buf_to_buf(src,\n        destWidth,\n        destHeight,\n        srcWidth,\n        windowBuffer + destY * windows[currentWindow].width + destX,\n        windows[currentWindow].width);\n\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4B8FD8\nint windowDisplayBufScaled(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight)\n{\n    unsigned char* windowBuffer;\n\n    windowBuffer = win_get_buf(windows[currentWindow].window);\n    drawScaled(windowBuffer + destY * windows[currentWindow].width + destX,\n        destWidth,\n        destHeight,\n        windows[currentWindow].width,\n        src,\n        srcWidth,\n        srcHeight,\n        srcWidth);\n\n    return 1;\n}\n\n// 0x4B9048\nint windowGetXres()\n{\n    return xres;\n}\n\n// 0x4B9050\nint windowGetYres()\n{\n    return yres;\n}\n\n// 0x4B9058\nstatic void removeProgramReferences(Program* program)\n{\n    for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) {\n        ManagedWindow* managedWindow = &(windows[index]);\n        if (managedWindow->window != -1) {\n            for (int index = 0; index < managedWindow->buttonsLength; index++) {\n                ManagedButton* managedButton = &(managedWindow->buttons[index]);\n                if (program == managedButton->program) {\n                    managedButton->program = NULL;\n                    managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_ENTER] = 0;\n                    managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_EXIT] = 0;\n                    managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN] = 0;\n                    managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP] = 0;\n                }\n            }\n\n            for (int index = 0; index < managedWindow->regionsLength; index++) {\n                Region* region = managedWindow->regions[index];\n                if (region != NULL) {\n                    if (program == region->program) {\n                        region->program = NULL;\n                        region->procs[1] = 0;\n                        region->procs[0] = 0;\n                        region->procs[3] = 0;\n                        region->procs[2] = 0;\n                    }\n                }\n            }\n        }\n    }\n}\n\n// 0x4B9190\nvoid initWindow(int resolution, int a2)\n{\n    char err[MAX_PATH];\n    int rc;\n    int i, j;\n\n    interpretRegisterProgramDeleteCallback(removeProgramReferences);\n\n    currentTextColorR = 0;\n    currentTextColorG = 0;\n    currentTextColorB = 0;\n    currentHighlightColorR = 0;\n    currentHighlightColorG = 0;\n    currentTextFlags = 0x2010000;\n\n    yres = sizes[resolution].height; // screen height\n    currentHighlightColorB = 0;\n    xres = sizes[resolution].width; // screen width\n\n    for (int i = 0; i < MANAGED_WINDOW_COUNT; i++) {\n        windows[i].window = -1;\n    }\n\n    rc = win_init(gfx_init[resolution], GNW95_reset_mode, a2);\n    if (rc != WINDOW_MANAGER_OK) {\n        switch (rc) {\n        case WINDOW_MANAGER_ERR_INITIALIZING_VIDEO_MODE:\n            sprintf(err, \"Error initializing video mode %dx%d\\n\", xres, yres);\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_NO_MEMORY:\n            sprintf(err, \"Not enough memory to initialize video mode\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_INITIALIZING_TEXT_FONTS:\n            sprintf(err, \"Couldn't find/load text fonts\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_WINDOW_SYSTEM_ALREADY_INITIALIZED:\n            sprintf(err, \"Attempt to initialize window system twice\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_WINDOW_SYSTEM_NOT_INITIALIZED:\n            sprintf(err, \"Window system not initialized\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_CURRENT_WINDOWS_TOO_BIG:\n            sprintf(err, \"Current windows are too big for new resolution\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE:\n            sprintf(err, \"Error initializing default database.\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_8:\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_ALREADY_RUNNING:\n            sprintf(err, \"Program already running.\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_TITLE_NOT_SET:\n            sprintf(err, \"Program title not set.\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        case WINDOW_MANAGER_ERR_INITIALIZING_INPUT:\n            sprintf(err, \"Failure initializing input devices.\\n\");\n            GNWSystemError(err);\n            exit(1);\n            break;\n        default:\n            sprintf(err, \"Unknown error code %d\\n\", rc);\n            GNWSystemError(err);\n            exit(1);\n            break;\n        }\n    }\n\n    currentFont = 100;\n    text_font(100);\n\n    initMousemgr();\n\n    mousemgrSetNameMangler(interpretMangleName);\n\n    for (i = 0; i < 64; i++) {\n        for (j = 0; j < 256; j++) {\n            alphaBlendTable[(i << 8) + j] = ((i * j) >> 9);\n        }\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4B9454\nvoid windowSetWindowFuncs(ManagedWindowCreateCallback* createCallback, ManagedWindowSelectFunc* selectCallback, WindowDeleteCallback* deleteCallback, DisplayInWindowCallback* displayCallback)\n{\n    if (createCallback != NULL) {\n        createWindowFunc = createCallback;\n    }\n\n    if (selectCallback != NULL) {\n        selectWindowFunc = selectCallback;\n    }\n\n    if (deleteCallback != NULL) {\n        deleteWindowFunc = deleteCallback;\n    }\n\n    if (displayCallback != NULL) {\n        displayFunc = displayCallback;\n    }\n}\n\n// 0x4B947C\nvoid windowClose()\n{\n    for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) {\n        ManagedWindow* managedWindow = &(windows[index]);\n        if (managedWindow->window != -1) {\n            deleteWindow(managedWindow->name);\n        }\n    }\n\n    if (inputFunc != NULL) {\n        myfree(inputFunc, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1573\n    }\n\n    mousemgrClose();\n    db_exit();\n    win_exit();\n}\n\n// Deletes button with the specified name or all buttons if it's NULL.\n//\n// 0x4B9548\nbool windowDeleteButton(const char* buttonName)\n{\n    if (currentWindow != -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->buttonsLength == 0) {\n        return false;\n    }\n\n    if (buttonName == NULL) {\n        for (int index = 0; index < managedWindow->buttonsLength; index++) {\n            ManagedButton* managedButton = &(managedWindow->buttons[index]);\n            win_delete_button(managedButton->btn);\n\n            if (managedButton->hover != NULL) {\n                myfree(managedButton->hover, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1648\n                managedButton->hover = NULL;\n            }\n\n            if (managedButton->field_4C != NULL) {\n                myfree(managedButton->field_4C, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1649\n                managedButton->field_4C = NULL;\n            }\n\n            if (managedButton->pressed != NULL) {\n                myfree(managedButton->pressed, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1650\n                managedButton->pressed = NULL;\n            }\n\n            if (managedButton->normal != NULL) {\n                myfree(managedButton->normal, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1651\n                managedButton->normal = NULL;\n            }\n\n            if (managedButton->field_50 != NULL) {\n                myfree(managedButton->normal, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1652\n                managedButton->field_50 = NULL;\n            }\n        }\n\n        myfree(managedWindow->buttons, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1654\n        managedWindow->buttons = NULL;\n        managedWindow->buttonsLength = 0;\n\n        return true;\n    }\n\n    for (int index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            win_delete_button(managedButton->btn);\n\n            if (managedButton->hover != NULL) {\n                myfree(managedButton->hover, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1665\n                managedButton->hover = NULL;\n            }\n\n            if (managedButton->field_4C != NULL) {\n                myfree(managedButton->field_4C, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1666\n                managedButton->field_4C = NULL;\n            }\n\n            if (managedButton->pressed != NULL) {\n                myfree(managedButton->pressed, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1667\n                managedButton->pressed = NULL;\n            }\n\n            if (managedButton->normal != NULL) {\n                myfree(managedButton->normal, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1668\n                managedButton->normal = NULL;\n            }\n\n            // FIXME: Probably leaking field_50. It's freed when deleting all\n            // buttons, but not the specific button.\n\n            if (index != managedWindow->buttonsLength - 1) {\n                // Move remaining buttons up. The last item is not reclaimed.\n                memcpy(managedWindow->buttons + index, managedWindow->buttons + index + 1, sizeof(*(managedWindow->buttons)) * (managedWindow->buttonsLength - index - 1));\n            }\n\n            managedWindow->buttonsLength--;\n            if (managedWindow->buttonsLength == 0) {\n                myfree(managedWindow->buttons, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 1672\n                managedWindow->buttons = NULL;\n            }\n\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// NOTE: Unused.\n//\n// 0x4B97F8\nvoid windowEnableButton(const char* buttonName, int enabled)\n{\n    int index;\n\n    for (index = 0; index < windows[currentWindow].buttonsLength; index++) {\n        if (stricmp(windows[currentWindow].buttons[index].name, buttonName) == 0) {\n            if (enabled) {\n                if (soundPressFunc != NULL || soundReleaseFunc != NULL) {\n                    win_register_button_sound_func(windows[currentWindow].buttons[index].btn, soundPressFunc, soundReleaseFunc);\n                }\n\n                windows[currentWindow].buttons[index].flags &= ~0x02;\n            } else {\n                if (soundDisableFunc != NULL) {\n                    win_register_button_sound_func(windows[currentWindow].buttons[index].btn, soundDisableFunc, NULL);\n                }\n\n                windows[currentWindow].buttons[index].flags |= 0x02;\n            }\n        }\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4B98C4\nint windowGetButtonID(const char* buttonName)\n{\n    int index;\n\n    for (index = 0; index < windows[currentWindow].buttonsLength; index++) {\n        if (stricmp(windows[currentWindow].buttons[index].name, buttonName) == 0) {\n            return windows[currentWindow].buttons[index].btn;\n        }\n    }\n\n    return -1;\n}\n\n// 0x4B9928\nbool windowSetButtonFlag(const char* buttonName, int value)\n{\n    if (currentWindow != -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->buttons == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            managedButton->flags |= value;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// NOTE: Unused.\n//\n// 0x4B99B4\nvoid windowRegisterButtonSoundFunc(ButtonCallback* soundPressFunc, ButtonCallback* soundReleaseFunc, ButtonCallback* soundDisableFunc)\n{\n    soundPressFunc = soundPressFunc;\n    soundReleaseFunc = soundReleaseFunc;\n    soundDisableFunc = soundDisableFunc;\n}\n\n// 0x4B99C8\nbool windowAddButton(const char* buttonName, int x, int y, int width, int height, int flags)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    int index;\n    for (index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            win_delete_button(managedButton->btn);\n\n            if (managedButton->hover != NULL) {\n                myfree(managedButton->hover, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1748\n                managedButton->hover = NULL;\n            }\n\n            if (managedButton->field_4C != NULL) {\n                myfree(managedButton->field_4C, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1749\n                managedButton->field_4C = NULL;\n            }\n\n            if (managedButton->pressed != NULL) {\n                myfree(managedButton->pressed, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1750\n                managedButton->pressed = NULL;\n            }\n\n            if (managedButton->normal != NULL) {\n                myfree(managedButton->normal, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1751\n                managedButton->normal = NULL;\n            }\n\n            break;\n        }\n    }\n\n    if (index == managedWindow->buttonsLength) {\n        if (managedWindow->buttons == NULL) {\n            managedWindow->buttons = (ManagedButton*)mymalloc(sizeof(*managedWindow->buttons), __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1758\n        } else {\n            managedWindow->buttons = (ManagedButton*)myrealloc(managedWindow->buttons, sizeof(*managedWindow->buttons) * (managedWindow->buttonsLength + 1), __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1761\n        }\n        managedWindow->buttonsLength += 1;\n    }\n\n    x = (int)(x * managedWindow->field_54);\n    y = (int)(y * managedWindow->field_58);\n    width = (int)(width * managedWindow->field_54);\n    height = (int)(height * managedWindow->field_58);\n\n    ManagedButton* managedButton = &(managedWindow->buttons[index]);\n    strncpy(managedButton->name, buttonName, 31);\n    managedButton->program = NULL;\n    managedButton->flags = 0;\n    managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP] = 0;\n    managedButton->rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_UP] = 0;\n    managedButton->mouseEventCallback = NULL;\n    managedButton->rightMouseEventCallback = NULL;\n    managedButton->field_50 = 0;\n    managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN] = 0;\n    managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_EXIT] = 0;\n    managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_ENTER] = 0;\n    managedButton->rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_DOWN] = 0;\n    managedButton->width = width;\n    managedButton->height = height;\n    managedButton->x = x;\n    managedButton->y = y;\n\n    unsigned char* normal = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1792\n    unsigned char* pressed = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 1793\n\n    if ((flags & BUTTON_FLAG_TRANSPARENT) != 0) {\n        memset(normal, 0, width * height);\n        memset(pressed, 0, width * height);\n    } else {\n        setButtonGFX(width, height, normal, pressed, NULL);\n    }\n\n    managedButton->btn = win_register_button(\n        managedWindow->window,\n        x,\n        y,\n        width,\n        height,\n        -1,\n        -1,\n        -1,\n        -1,\n        normal,\n        pressed,\n        NULL,\n        flags);\n\n    if (soundPressFunc != NULL || soundReleaseFunc != NULL) {\n        win_register_button_sound_func(managedButton->btn, soundPressFunc, soundReleaseFunc);\n    }\n\n    managedButton->hover = NULL;\n    managedButton->pressed = pressed;\n    managedButton->normal = normal;\n    managedButton->field_18 = flags;\n    managedButton->field_4C = NULL;\n    win_register_button_func(managedButton->btn, doButtonOn, doButtonOff, doButtonPress, doButtonRelease);\n    windowSetButtonFlag(buttonName, 1);\n\n    if ((flags & BUTTON_FLAG_TRANSPARENT) != 0) {\n        win_register_button_mask(managedButton->btn, normal);\n    }\n\n    return true;\n}\n\n// 0x4B9DD0\nbool windowAddButtonGfx(const char* buttonName, char* pressedFileName, char* normalFileName, char* hoverFileName)\n{\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    for (int index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            int width;\n            int height;\n\n            if (pressedFileName != NULL) {\n                unsigned char* pressed = loadDataFile(pressedFileName, &width, &height);\n                if (pressed != NULL) {\n                    drawScaledBuf(managedButton->pressed, managedButton->width, managedButton->height, pressed, width, height);\n                    myfree(pressed, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C, 1834\n                }\n            }\n\n            if (normalFileName != NULL) {\n                unsigned char* normal = loadDataFile(normalFileName, &width, &height);\n                if (normal != NULL) {\n                    drawScaledBuf(managedButton->normal, managedButton->width, managedButton->height, normal, width, height);\n                    myfree(normal, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C, 1842\n                }\n            }\n\n            if (hoverFileName != NULL) {\n                unsigned char* hover = loadDataFile(normalFileName, &width, &height);\n                if (hover != NULL) {\n                    if (managedButton->hover == NULL) {\n                        managedButton->hover = (unsigned char*)mymalloc(managedButton->height * managedButton->width, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C, 1849\n                    }\n\n                    drawScaledBuf(managedButton->hover, managedButton->width, managedButton->height, hover, width, height);\n                    myfree(hover, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C, 1853\n                }\n            }\n\n            if ((managedButton->field_18 & 0x20) != 0) {\n                win_register_button_mask(managedButton->btn, managedButton->normal);\n            }\n\n            win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, 0);\n\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// NOTE: Unused.\n//\n// 0x4B9F40\nint windowAddButtonMask(const char* buttonName, unsigned char* buffer)\n{\n    int index;\n    ManagedButton* button;\n    unsigned char* copy;\n\n    for (index = 0; index < windows[currentWindow].buttonsLength; index++) {\n        button = &(windows[currentWindow].buttons[index]);\n        if (stricmp(button->name, buttonName) == 0) {\n            copy = (unsigned char*)mymalloc(button->width * button->height, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C, 1871\n            memcpy(copy, buffer, button->width * button->height);\n            win_register_button_mask(button->btn, copy);\n            button->field_50 = copy;\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4B9FC8\nint windowAddButtonBuf(const char* buttonName, unsigned char* normal, unsigned char* pressed, unsigned char* hover, int width, int height, int pitch)\n{\n    int index;\n    ManagedButton* button;\n\n    for (index = 0; index < windows[currentWindow].buttonsLength; index++) {\n        button = &(windows[currentWindow].buttons[index]);\n        if (stricmp(button->name, buttonName) == 0) {\n            if (normal != NULL) {\n                memset(button->normal, 0, button->width * button->height);\n                drawScaled(button->normal,\n                    button->width,\n                    button->height,\n                    button->width,\n                    normal,\n                    width,\n                    height,\n                    pitch);\n            }\n\n            if (pressed != NULL) {\n                memset(button->pressed, 0, button->width * button->height);\n                drawScaled(button->pressed,\n                    button->width,\n                    button->height,\n                    button->width,\n                    pressed,\n                    width,\n                    height,\n                    pitch);\n            }\n\n            if (hover != NULL) {\n                memset(button->hover, 0, button->width * button->height);\n                drawScaled(button->hover,\n                    button->width,\n                    button->height,\n                    button->width,\n                    hover,\n                    width,\n                    height,\n                    pitch);\n            }\n\n            if ((button->field_18 & 0x20) != 0) {\n                win_register_button_mask(button->btn, button->normal);\n            }\n\n            win_register_button_image(button->btn, button->normal, button->pressed, button->hover, 0);\n\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4BA11C\nbool windowAddButtonProc(const char* buttonName, Program* program, int mouseEnterProc, int mouseExitProc, int mouseDownProc, int mouseUpProc)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->buttons == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_ENTER] = mouseEnterProc;\n            managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_EXIT] = mouseExitProc;\n            managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN] = mouseDownProc;\n            managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP] = mouseUpProc;\n            managedButton->program = program;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x4BA1B4\nbool windowAddButtonRightProc(const char* buttonName, Program* program, int rightMouseDownProc, int rightMouseUpProc)\n{\n    if (currentWindow != -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->buttons == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            managedButton->rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_UP] = rightMouseUpProc;\n            managedButton->rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_DOWN] = rightMouseDownProc;\n            managedButton->program = program;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// NOTE: Unused.\n//\n// 0x4BA238\nbool windowAddButtonCfunc(const char* buttonName, ManagedButtonMouseEventCallback* callback, void* userData)\n{\n    if (currentWindow != -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->buttons == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            managedButton->mouseEventCallbackUserData = userData;\n            managedButton->mouseEventCallback = callback;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// NOTE: Unused.\n//\n// 0x4BA2B4\nbool windowAddButtonRightCfunc(const char* buttonName, ManagedButtonMouseEventCallback* callback, void* userData)\n{\n    if (currentWindow != -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->buttons == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            managedButton->rightMouseEventCallback = callback;\n            managedButton->rightMouseEventCallbackUserData = userData;\n            win_register_right_button(managedButton->btn, -1, -1, doRightButtonPress, doRightButtonRelease);\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x4BA34C\nbool windowAddButtonText(const char* buttonName, const char* text)\n{\n    return windowAddButtonTextWithOffsets(buttonName, text, 2, 2, 0, 0);\n}\n\n// 0x4BA364\nbool windowAddButtonTextWithOffsets(const char* buttonName, const char* text, int pressedImageOffsetX, int pressedImageOffsetY, int normalImageOffsetX, int normalImageOffsetY)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->buttons == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < managedWindow->buttonsLength; index++) {\n        ManagedButton* managedButton = &(managedWindow->buttons[index]);\n        if (stricmp(managedButton->name, buttonName) == 0) {\n            int normalImageHeight = text_height() + 1;\n            int normalImageWidth = text_width(text) + 1;\n            unsigned char* buffer = (unsigned char*)mymalloc(normalImageHeight * normalImageWidth, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 2010\n\n            int normalImageX = (managedButton->width - normalImageWidth) / 2 + normalImageOffsetX;\n            int normalImageY = (managedButton->height - normalImageHeight) / 2 + normalImageOffsetY;\n\n            if (normalImageX < 0) {\n                normalImageWidth -= normalImageX;\n                normalImageX = 0;\n            }\n\n            if (normalImageX + normalImageWidth >= managedButton->width) {\n                normalImageWidth = managedButton->width - normalImageX;\n            }\n\n            if (normalImageY < 0) {\n                normalImageHeight -= normalImageY;\n                normalImageY = 0;\n            }\n\n            if (normalImageY + normalImageHeight >= managedButton->height) {\n                normalImageHeight = managedButton->height - normalImageY;\n            }\n\n            if (managedButton->normal != NULL) {\n                buf_to_buf(managedButton->normal + managedButton->width * normalImageY + normalImageX,\n                    normalImageWidth,\n                    normalImageHeight,\n                    managedButton->width,\n                    buffer,\n                    normalImageWidth);\n            } else {\n                memset(buffer, 0, normalImageHeight * normalImageWidth);\n            }\n\n            text_to_buf(buffer,\n                text,\n                normalImageWidth,\n                normalImageWidth,\n                windowGetTextColor() + windowGetTextFlags());\n\n            trans_buf_to_buf(buffer,\n                normalImageWidth,\n                normalImageHeight,\n                normalImageWidth,\n                managedButton->normal + managedButton->width * normalImageY + normalImageX,\n                managedButton->width);\n\n            int pressedImageWidth = text_width(text) + 1;\n            int pressedImageHeight = text_height() + 1;\n\n            int pressedImageX = (managedButton->width - pressedImageWidth) / 2 + pressedImageOffsetX;\n            int pressedImageY = (managedButton->height - pressedImageHeight) / 2 + pressedImageOffsetY;\n\n            if (pressedImageX < 0) {\n                pressedImageWidth -= pressedImageX;\n                pressedImageX = 0;\n            }\n\n            if (pressedImageX + pressedImageWidth >= managedButton->width) {\n                pressedImageWidth = managedButton->width - pressedImageX;\n            }\n\n            if (pressedImageY < 0) {\n                pressedImageHeight -= pressedImageY;\n                pressedImageY = 0;\n            }\n\n            if (pressedImageY + pressedImageHeight >= managedButton->height) {\n                pressedImageHeight = managedButton->height - pressedImageY;\n            }\n\n            if (managedButton->pressed != NULL) {\n                buf_to_buf(managedButton->pressed + managedButton->width * pressedImageY + pressedImageX,\n                    pressedImageWidth,\n                    pressedImageHeight,\n                    managedButton->width,\n                    buffer,\n                    pressedImageWidth);\n            } else {\n                memset(buffer, 0, pressedImageHeight * pressedImageWidth);\n            }\n\n            text_to_buf(buffer,\n                text,\n                pressedImageWidth,\n                pressedImageWidth,\n                windowGetTextColor() + windowGetTextFlags());\n\n            trans_buf_to_buf(buffer,\n                pressedImageWidth,\n                normalImageHeight,\n                normalImageWidth,\n                managedButton->pressed + managedButton->width * pressedImageY + pressedImageX,\n                managedButton->width);\n\n            myfree(buffer, __FILE__, __LINE__); // \"..\\\\int\\\\WINDOW.C\", 2078\n\n            if ((managedButton->field_18 & 0x20) != 0) {\n                win_register_button_mask(managedButton->btn, managedButton->normal);\n            }\n\n            win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, 0);\n\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x4BA694\nbool windowFill(float r, float g, float b)\n{\n    int colorIndex;\n    int wid;\n\n    colorIndex = ((int)(r * 31.0) << 10) | ((int)(g * 31.0) << 5) | (int)(b * 31.0);\n\n    // NOTE: Uninline.\n    wid = windowGetGNWID();\n\n    win_fill(wid,\n        0,\n        0,\n        windowWidth(),\n        windowHeight(),\n        colorTable[colorIndex]);\n\n    return true;\n}\n\n// 0x4BA738\nbool windowFillRect(int x, int y, int width, int height, float r, float g, float b)\n{\n    ManagedWindow* managedWindow;\n    int colorIndex;\n    int wid;\n\n    managedWindow = &(windows[currentWindow]);\n    x = (int)(x * managedWindow->field_54);\n    y = (int)(y * managedWindow->field_58);\n    width = (int)(width * managedWindow->field_54);\n    height = (int)(height * managedWindow->field_58);\n\n    colorIndex = ((int)(r * 31.0) << 10) | ((int)(g * 31.0) << 5) | (int)(b * 31.0);\n\n    // NOTE: Uninline.\n    wid = windowGetGNWID();\n\n    win_fill(wid,\n        x,\n        y,\n        width,\n        height,\n        colorTable[colorIndex]);\n\n    return true;\n}\n\n// TODO: There is a value returned, not sure which one - could be either\n// currentRegionIndex or points array. For now it can be safely ignored since\n// the only caller of this function is op_addregion, which ignores the returned\n// value.\n//\n// 0x4BA844\nvoid windowEndRegion()\n{\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    Region* region = managedWindow->regions[managedWindow->currentRegionIndex];\n    windowAddRegionPoint(region->points->x, region->points->y, false);\n    regionSetBound(region);\n}\n\n// NOTE: Unused.\n//\n// 0x4BA8A4\nvoid* windowRegionGetUserData(const char* windowRegionName)\n{\n    int index;\n    char* regionName;\n\n    if (currentWindow == -1) {\n        return NULL;\n    }\n\n    for (index = 0; index < windows[currentWindow].regionsLength; index++) {\n        regionName = windows[currentWindow].regions[index]->name;\n        if (stricmp(regionName, windowRegionName) == 0) {\n            return regionGetUserData(windows[currentWindow].regions[index]);\n        }\n    }\n\n    return NULL;\n}\n\n// NOTE: Unused.\n//\n// 0x4BA914\nvoid windowRegionSetUserData(const char* windowRegionName, void* userData)\n{\n    int index;\n    char* regionName;\n\n    if (currentWindow == -1) {\n        return;\n    }\n\n    for (index = 0; index < windows[currentWindow].regionsLength; index++) {\n        regionName = windows[currentWindow].regions[index]->name;\n        if (stricmp(regionName, windowRegionName) == 0) {\n            regionSetUserData(windows[currentWindow].regions[index], userData);\n            return;\n        }\n    }\n}\n\n// 0x4BA988\nbool windowCheckRegionExists(const char* regionName)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->window == -1) {\n        return false;\n    }\n\n    for (int index = 0; index < managedWindow->regionsLength; index++) {\n        Region* region = managedWindow->regions[index];\n        if (region != NULL) {\n            if (stricmp(regionGetName(region), regionName) == 0) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n// 0x4BA9FC\nbool windowStartRegion(int initialCapacity)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    int newRegionIndex;\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->regions == NULL) {\n        managedWindow->regions = (Region**)mymalloc(sizeof(&(managedWindow->regions)), __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 2167\n        managedWindow->regionsLength = 1;\n        newRegionIndex = 0;\n    } else {\n        newRegionIndex = 0;\n        for (int index = 0; index < managedWindow->regionsLength; index++) {\n            if (managedWindow->regions[index] == NULL) {\n                break;\n            }\n            newRegionIndex++;\n        }\n\n        if (newRegionIndex == managedWindow->regionsLength) {\n            managedWindow->regions = (Region**)myrealloc(managedWindow->regions, sizeof(&(managedWindow->regions)) * (managedWindow->regionsLength + 1), __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 2178\n            managedWindow->regionsLength++;\n        }\n    }\n\n    Region* newRegion;\n    if (initialCapacity != 0) {\n        newRegion = allocateRegion(initialCapacity + 1);\n    } else {\n        newRegion = NULL;\n    }\n\n    managedWindow->regions[newRegionIndex] = newRegion;\n    managedWindow->currentRegionIndex = newRegionIndex;\n\n    return true;\n}\n\n// 0x4BAB68\nbool windowAddRegionPoint(int x, int y, bool a3)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    Region* region = managedWindow->regions[managedWindow->currentRegionIndex];\n    if (region == NULL) {\n        region = managedWindow->regions[managedWindow->currentRegionIndex] = allocateRegion(1);\n    }\n\n    if (a3) {\n        x = (int)(x * managedWindow->field_54);\n        y = (int)(y * managedWindow->field_58);\n    }\n\n    regionAddPoint(region, x, y);\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x4BAC5\nint windowAddRegionRect(int a1, int a2, int a3, int a4, int a5)\n{\n    windowAddRegionPoint(a1, a2, a5);\n    windowAddRegionPoint(a3, a2, a5);\n    windowAddRegionPoint(a3, a4, a5);\n    windowAddRegionPoint(a1, a4, a5);\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4BACA0\nint windowAddRegionCfunc(const char* regionName, RegionMouseEventCallback* callback, void* userData)\n{\n    int index;\n    Region* region;\n\n    if (currentWindow == -1) {\n        return 0;\n    }\n\n    for (index = 0; index < windows[currentWindow].regionsLength; index++) {\n        region = windows[currentWindow].regions[index];\n        if (region != NULL && stricmp(region->name, regionName) == 0) {\n            region->mouseEventCallback = callback;\n            region->mouseEventCallbackUserData = userData;\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4BAD30\nint windowAddRegionRightCfunc(const char* regionName, RegionMouseEventCallback* callback, void* userData)\n{\n    int index;\n    Region* region;\n\n    if (currentWindow == -1) {\n        return 0;\n    }\n\n    for (index = 0; index < windows[currentWindow].regionsLength; index++) {\n        region = windows[currentWindow].regions[index];\n        if (region != NULL && stricmp(region->name, regionName) == 0) {\n            region->rightMouseEventCallback = callback;\n            region->rightMouseEventCallbackUserData = userData;\n            return 1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4BADC0\nbool windowAddRegionProc(const char* regionName, Program* program, int a3, int a4, int a5, int a6)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    for (int index = 0; index < managedWindow->regionsLength; index++) {\n        Region* region = managedWindow->regions[index];\n        if (region != NULL) {\n            if (stricmp(region->name, regionName) == 0) {\n                region->procs[2] = a3;\n                region->procs[3] = a4;\n                region->procs[0] = a5;\n                region->procs[1] = a6;\n                region->program = program;\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n// 0x4BAE8C\nbool windowAddRegionRightProc(const char* regionName, Program* program, int a3, int a4)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    for (int index = 0; index < managedWindow->regionsLength; index++) {\n        Region* region = managedWindow->regions[index];\n        if (region != NULL) {\n            if (stricmp(region->name, regionName) == 0) {\n                region->rightProcs[0] = a3;\n                region->rightProcs[1] = a4;\n                region->program = program;\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n// 0x4BAF2C\nbool windowSetRegionFlag(const char* regionName, int value)\n{\n    if (currentWindow != -1) {\n        ManagedWindow* managedWindow = &(windows[currentWindow]);\n        for (int index = 0; index < managedWindow->regionsLength; index++) {\n            Region* region = managedWindow->regions[index];\n            if (region != NULL) {\n                if (stricmp(region->name, regionName) == 0) {\n                    regionSetFlag(region, value);\n                    return true;\n                }\n            }\n        }\n    }\n\n    return false;\n}\n\n// 0x4BAFA8\nbool windowAddRegionName(const char* regionName)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    Region* region = managedWindow->regions[managedWindow->currentRegionIndex];\n    if (region == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < managedWindow->regionsLength; index++) {\n        if (index != managedWindow->currentRegionIndex) {\n            Region* other = managedWindow->regions[index];\n            if (other != NULL) {\n                if (stricmp(regionGetName(other), regionName) == 0) {\n                    regionDelete(other);\n                    managedWindow->regions[index] = NULL;\n                    break;\n                }\n            }\n        }\n    }\n\n    regionAddName(region, regionName);\n\n    return true;\n}\n\n// Delete region with the specified name or all regions if it's NULL.\n//\n// 0x4BB0A8\nbool windowDeleteRegion(const char* regionName)\n{\n    if (currentWindow == -1) {\n        return false;\n    }\n\n    ManagedWindow* managedWindow = &(windows[currentWindow]);\n    if (managedWindow->window == -1) {\n        return false;\n    }\n\n    if (regionName != NULL) {\n        for (int index = 0; index < managedWindow->regionsLength; index++) {\n            Region* region = managedWindow->regions[index];\n            if (region != NULL) {\n                if (stricmp(regionGetName(region), regionName) == 0) {\n                    regionDelete(region);\n                    managedWindow->regions[index] = NULL;\n                    managedWindow->field_38++;\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    managedWindow->field_38++;\n\n    if (managedWindow->regions != NULL) {\n        for (int index = 0; index < managedWindow->regionsLength; index++) {\n            Region* region = managedWindow->regions[index];\n            if (region != NULL) {\n                regionDelete(region);\n            }\n        }\n\n        myfree(managedWindow->regions, __FILE__, __LINE__); // \"..\\int\\WINDOW.C\", 2353\n\n        managedWindow->regions = NULL;\n        managedWindow->regionsLength = 0;\n    }\n\n    return true;\n}\n\n// 0x4BB220\nvoid updateWindows()\n{\n    movieUpdate();\n    mousemgrUpdate();\n    checkAllRegions();\n    update_widgets();\n}\n\n// 0x4BB234\nint windowMoviePlaying()\n{\n    return moviePlaying();\n}\n\n// 0x4BB23C\nbool windowSetMovieFlags(int flags)\n{\n    if (movieSetFlags(flags) != 0) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4BB24C\nbool windowPlayMovie(char* filePath)\n{\n    int wid;\n\n    // NOTE: Uninline.\n    wid = windowGetGNWID();\n\n    if (movieRun(wid, filePath) != 0) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4BB280\nbool windowPlayMovieRect(char* filePath, int a2, int a3, int a4, int a5)\n{\n    int wid;\n\n    // NOTE: Uninline.\n    wid = windowGetGNWID();\n\n    if (movieRunRect(wid, filePath, a2, a3, a4, a5) != 0) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4BB2C4\nvoid windowStopMovie()\n{\n    movieStop();\n}\n\n// 0x4BB3A8\nvoid drawScaled(unsigned char* dest, int destWidth, int destHeight, int destPitch, unsigned char* src, int srcWidth, int srcHeight, int srcPitch)\n{\n    if (destWidth == srcWidth && destHeight == srcHeight) {\n        buf_to_buf(src, srcWidth, srcHeight, srcPitch, dest, destPitch);\n        return;\n    }\n\n    int incrementX = (srcWidth << 16) / destWidth;\n    int incrementY = (srcHeight << 16) / destHeight;\n    int stepX = incrementX >> 16;\n    int stepY = incrementY >> 16;\n    int destSkip = destPitch - destWidth;\n    int srcSkip = stepY * srcPitch;\n\n    if (srcSkip != 0) {\n        // Downscaling.\n        int srcPosY = 0;\n        for (int y = 0; y < destHeight; y++) {\n            int srcPosX = 0;\n            int offset = 0;\n            for (int x = 0; x < destWidth; x++) {\n                *dest++ = src[offset];\n                offset += stepX;\n\n                srcPosX += incrementX;\n                if (srcPosX >= 0x10000) {\n                    srcPosX &= 0xFFFF;\n                }\n            }\n\n            dest += destSkip;\n            src += srcSkip;\n\n            srcPosY += stepY;\n            if (srcPosY >= 0x10000) {\n                srcPosY &= 0xFFFF;\n                src += srcPitch;\n            }\n        }\n    } else {\n        // Upscaling.\n        int y = 0;\n        int srcPosY = 0;\n        while (y < destHeight) {\n            unsigned char* destPtr = dest;\n\n            int srcPosX = 0;\n            int offset = 0;\n            for (int x = 0; x < destWidth; x++) {\n                *dest++ = src[offset];\n                offset += stepX;\n\n                srcPosX += stepX;\n                if (srcPosX >= 0x10000) {\n                    offset++;\n                    srcPosX &= 0xFFFF;\n                }\n            }\n\n            y++;\n            if (y < destHeight) {\n                dest += destSkip;\n                srcPosY += incrementY;\n\n                while (y < destHeight && srcPosY < 0x10000) {\n                    memcpy(dest, destPtr, destWidth);\n                    dest += destWidth;\n                    srcPosY += incrementY;\n                    y++;\n                }\n\n                srcPosY &= 0xFFFF;\n                src += srcPitch;\n            }\n        }\n    }\n}\n\n// 0x4BB5D0\nvoid drawScaledBuf(unsigned char* dest, int destWidth, int destHeight, unsigned char* src, int srcWidth, int srcHeight)\n{\n    if (destWidth == srcWidth && destHeight == srcHeight) {\n        memcpy(dest, src, srcWidth * srcHeight);\n        return;\n    }\n\n    int incrementX = (srcWidth << 16) / destWidth;\n    int incrementY = (srcHeight << 16) / destHeight;\n    int stepX = incrementX >> 16;\n    int stepY = incrementY >> 16;\n    int srcSkip = stepY * srcWidth;\n\n    if (srcSkip != 0) {\n        // Downscaling.\n        int srcPosY = 0;\n        for (int y = 0; y < destHeight; y++) {\n            int srcPosX = 0;\n            int offset = 0;\n            for (int x = 0; x < destWidth; x++) {\n                *dest++ = src[offset];\n                offset += stepX;\n\n                srcPosX += incrementX;\n                if (srcPosX >= 0x10000) {\n                    srcPosX &= 0xFFFF;\n                }\n            }\n\n            src += srcSkip;\n\n            srcPosY += stepY;\n            if (srcPosY >= 0x10000) {\n                srcPosY &= 0xFFFF;\n                src += srcWidth;\n            }\n        }\n    } else {\n        // Upscaling.\n        int y = 0;\n        int srcPosY = 0;\n        while (y < destHeight) {\n            unsigned char* destPtr = dest;\n\n            int srcPosX = 0;\n            int offset = 0;\n            for (int x = 0; x < destWidth; x++) {\n                *dest++ = src[offset];\n                offset += stepX;\n\n                srcPosX += stepX;\n                if (srcPosX >= 0x10000) {\n                    offset++;\n                    srcPosX &= 0xFFFF;\n                }\n            }\n\n            y++;\n            if (y < destHeight) {\n                srcPosY += incrementY;\n\n                while (y < destHeight && srcPosY < 0x10000) {\n                    memcpy(dest, destPtr, destWidth);\n                    dest += destWidth;\n                    srcPosY += incrementY;\n                    y++;\n                }\n\n                srcPosY &= 0xFFFF;\n                src += srcWidth;\n            }\n        }\n    }\n}\n\n// 0x4BB7D8\nvoid alphaBltBuf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* alphaWindowBuffer, unsigned char* alphaBuffer, unsigned char* dest, int destPitch)\n{\n    for (int y = 0; y < srcHeight; y++) {\n        for (int x = 0; x < srcWidth; x++) {\n            int rle = (alphaBuffer[0] << 8) + alphaBuffer[1];\n            alphaBuffer += 2;\n            if ((rle & 0x8000) != 0) {\n                rle &= ~0x8000;\n            } else if ((rle & 0x4000) != 0) {\n                rle &= ~0x4000;\n                memcpy(dest, src, rle);\n            } else {\n                unsigned char* destPtr = dest;\n                unsigned char* srcPtr = src;\n                unsigned char* alphaWindowBufferPtr = alphaWindowBuffer;\n                unsigned char* alphaBufferPtr = alphaBuffer;\n                for (int index = 0; index < rle; index++) {\n                    // TODO: Check.\n                    unsigned char* v1 = &(cmap[*srcPtr * 3]);\n                    unsigned char* v2 = &(cmap[*alphaWindowBufferPtr * 3]);\n                    unsigned char alpha = *alphaBufferPtr;\n\n                    // NOTE: Original code is slightly different.\n                    unsigned int r = alphaBlendTable[(v1[0] << 8) | alpha] + alphaBlendTable[(v2[0] << 8) | alpha];\n                    unsigned int g = alphaBlendTable[(v1[1] << 8) | alpha] + alphaBlendTable[(v2[1] << 8) | alpha];\n                    unsigned int b = alphaBlendTable[(v1[2] << 8) | alpha] + alphaBlendTable[(v2[2] << 8) | alpha];\n                    unsigned int colorIndex = (r << 10) | (g << 5) | b;\n\n                    *destPtr = colorTable[colorIndex];\n\n                    destPtr++;\n                    srcPtr++;\n                    alphaWindowBufferPtr++;\n                    alphaBufferPtr++;\n                }\n\n                alphaBuffer += rle;\n                if ((rle & 1) != 0) {\n                    alphaBuffer++;\n                }\n            }\n\n            src += rle;\n            dest += rle;\n            alphaWindowBuffer += rle;\n        }\n\n        src += srcPitch - srcWidth;\n        dest += destPitch - srcWidth;\n    }\n}\n\n// 0x4BBFC4\nvoid alphaBltBufRect(unsigned char* src, int srcWidth, int srcHeight, unsigned char* dest, int destWidth, int destHeight)\n{\n    int chunkWidth = srcWidth / 3;\n    int chunkHeight = srcHeight / 3;\n\n    // Middle Middle\n    unsigned char* ptr = src + srcWidth * chunkHeight + chunkWidth;\n    for (int x = 0; x < destWidth; x += chunkWidth) {\n        for (int y = 0; y < destHeight; y += chunkHeight) {\n            int middleWidth;\n            if (x + chunkWidth >= destWidth) {\n                middleWidth = destWidth - x;\n            } else {\n                middleWidth = chunkWidth;\n            }\n            int middleY = y + chunkHeight;\n            if (middleY >= destHeight) {\n                middleY = destHeight;\n            }\n            buf_to_buf(ptr,\n                middleWidth,\n                middleY - y,\n                srcWidth,\n                dest + destWidth * y + x,\n                destWidth);\n        }\n    }\n\n    // Middle Column\n    for (int x = 0; x < destWidth; x += chunkWidth) {\n        // Top Middle\n        int topMiddleX = chunkWidth + x;\n        if (topMiddleX >= destWidth) {\n            topMiddleX = destWidth;\n        }\n        int topMiddleHeight = chunkHeight;\n        if (topMiddleHeight >= destHeight) {\n            topMiddleHeight = destHeight;\n        }\n        buf_to_buf(src + chunkWidth,\n            topMiddleX - x,\n            topMiddleHeight,\n            srcWidth,\n            dest + x,\n            destWidth);\n\n        // Bottom Middle\n        int bottomMiddleX = chunkWidth + x;\n        if (bottomMiddleX >= destWidth) {\n            bottomMiddleX = destWidth;\n        }\n        buf_to_buf(src + srcWidth * 2 * chunkHeight + chunkWidth,\n            bottomMiddleX - x,\n            destHeight - (destHeight - chunkHeight),\n            srcWidth,\n            dest + destWidth * (destHeight - chunkHeight) + x,\n            destWidth);\n    }\n\n    // Middle Row\n    for (int y = 0; y < destHeight; y += chunkHeight) {\n        // Middle Left\n        int middleLeftWidth = chunkWidth;\n        if (middleLeftWidth >= destWidth) {\n            middleLeftWidth = destWidth;\n        }\n        int middleLeftY = chunkHeight + y;\n        if (middleLeftY >= destHeight) {\n            middleLeftY = destHeight;\n        }\n        buf_to_buf(src + srcWidth * chunkHeight,\n            middleLeftWidth,\n            middleLeftY - y,\n            srcWidth,\n            dest + destWidth * y,\n            destWidth);\n\n        // Middle Right\n        int middleRightY = chunkHeight + y;\n        if (middleRightY >= destHeight) {\n            middleRightY = destHeight;\n        }\n        buf_to_buf(src + 2 * chunkWidth + srcWidth * chunkHeight,\n            destWidth - (destWidth - chunkWidth),\n            middleRightY - y,\n            srcWidth,\n            dest + destWidth * y + destWidth - chunkWidth,\n            destWidth);\n    }\n\n    // Top Left\n    int topLeftWidth = chunkWidth;\n    if (topLeftWidth >= destWidth) {\n        topLeftWidth = destWidth;\n    }\n    int topLeftHeight = chunkHeight;\n    if (topLeftHeight >= destHeight) {\n        topLeftHeight = destHeight;\n    }\n    buf_to_buf(src,\n        topLeftWidth,\n        topLeftHeight,\n        srcWidth,\n        dest,\n        destWidth);\n\n    // Bottom Left\n    int bottomLeftHeight = chunkHeight;\n    if (chunkHeight >= destHeight) {\n        bottomLeftHeight = destHeight;\n    }\n    buf_to_buf(src + chunkWidth * 2,\n        destWidth - (destWidth - chunkWidth),\n        bottomLeftHeight,\n        srcWidth,\n        dest + destWidth - chunkWidth,\n        destWidth);\n\n    // Top Right\n    int topRightWidth = chunkWidth;\n    if (chunkWidth >= destWidth) {\n        topRightWidth = destWidth;\n    }\n    buf_to_buf(src + srcWidth * 2 * chunkHeight,\n        topRightWidth,\n        destHeight - (destHeight - chunkHeight),\n        srcWidth,\n        dest + destWidth * (destHeight - chunkHeight),\n        destWidth);\n\n    // Bottom Right\n    buf_to_buf(src + 2 * chunkWidth + srcWidth * 2 * chunkHeight,\n        destWidth - (destWidth - chunkWidth),\n        destHeight - (destHeight - chunkHeight),\n        srcWidth,\n        dest + destWidth * (destHeight - chunkHeight) + (destWidth - chunkWidth),\n        destWidth);\n}\n\n// NOTE: Unused.\n//\n// 0x4BC5E0\nint windowEnableCheckRegion()\n{\n    checkRegionEnable = 1;\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4BC5F0\nint windowDisableCheckRegion()\n{\n    checkRegionEnable = 0;\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4BC600\nint windowSetHoldTime(int value)\n{\n    holdTime = value;\n    return 1;\n}\n\n// NOTE: Unused.\n//\n// 0x4BC60C\nint windowAddTextRegion(int x, int y, int width, int font, int textAlignment, int textFlags, int backgroundColor)\n{\n    if (currentWindow == -1) {\n        return -1;\n    }\n\n    if (windows[currentWindow].window == -1) {\n        return -1;\n    }\n\n    return win_add_text_region(windows[currentWindow].window,\n        x,\n        y,\n        width,\n        font,\n        textAlignment,\n        textFlags,\n        backgroundColor);\n}\n\n// NOTE: Unused.\n//\n// 0x4BC668\nint windowPrintTextRegion(int textRegionId, char* string)\n{\n    return win_print_text_region(textRegionId, string);\n}\n\n// NOTE: Unused.\n//\n// 0x4BC670\nint windowUpdateTextRegion(int textRegionId)\n{\n    return win_update_text_region(textRegionId);\n}\n\n// NOTE: Unused.\n//\n// 0x4BC678\nint windowDeleteTextRegion(int textRegionId)\n{\n    return win_delete_text_region(textRegionId);\n}\n\n// NOTE: Unused.\n//\n// 0x4BC680\nint windowTextRegionStyle(int textRegionId, int font, int textAlignment, int textFlags, int backgroundColor)\n{\n    return win_text_region_style(textRegionId, font, textAlignment, textFlags, backgroundColor);\n}\n\n// NOTE: Unused.\n//\n// 0x4BC698\nint windowAddTextInputRegion(int textRegionId, char* text, int a3, int a4)\n{\n    return win_add_text_input_region(textRegionId, text, a3, a4);\n}\n\n// NOTE: Unused.\n//\n// 0x4BC6A0\nint windowDeleteTextInputRegion(int textInputRegionId)\n{\n    if (textInputRegionId != -1) {\n        return win_delete_text_input_region(textInputRegionId);\n    }\n\n    if (currentWindow == -1) {\n        return 0;\n    }\n\n    if (windows[currentWindow].window == -1) {\n        return 0;\n    }\n\n    return win_delete_all_text_input_regions(windows[currentWindow].window);\n}\n\n// NOTE: Unused.\n//\n// 0x4BC6E4\nint windowSetTextInputDeleteFunc(int textInputRegionId, TextInputRegionDeleteFunc* deleteFunc, void* userData)\n{\n    return win_set_text_input_delete_func(textInputRegionId, deleteFunc, userData);\n}\n"
  },
  {
    "path": "src/int/window.h",
    "content": "#ifndef WINDOW_H\n#define WINDOW_H\n\n#include <stdbool.h>\n\n#include \"plib/gnw/rect.h\"\n#include \"int/intrpret.h\"\n#include \"int/region.h\"\n#include \"int/widget.h\"\n#include \"plib/gnw/gnw.h\"\n\n#define MANAGED_WINDOW_COUNT (16)\n\ntypedef bool(WindowInputHandler)(int key);\ntypedef void(WindowDeleteCallback)(int windowIndex, const char* windowName);\ntypedef void(DisplayInWindowCallback)(int windowIndex, const char* windowName, unsigned char* data, int width, int height);\ntypedef void(ManagedButtonMouseEventCallback)(void* userData, int eventType);\ntypedef void(ManagedWindowCreateCallback)(int windowIndex, const char* windowName, int* flagsPtr);\ntypedef void(ManagedWindowSelectFunc)(int windowIndex, const char* windowName);\n\ntypedef enum TextAlignment {\n    TEXT_ALIGNMENT_LEFT,\n    TEXT_ALIGNMENT_RIGHT,\n    TEXT_ALIGNMENT_CENTER,\n} TextAlignment;\n\nint windowGetFont();\nint windowSetFont(int a1);\nvoid windowResetTextAttributes();\nint windowGetTextFlags();\nint windowSetTextFlags(int a1);\nunsigned char windowGetTextColor();\nunsigned char windowGetHighlightColor();\nint windowSetTextColor(float r, float g, float b);\nint windowSetHighlightColor(float r, float g, float b);\nbool windowCheckRegion(int windowIndex, int mouseX, int mouseY, int mouseEvent);\nbool windowRefreshRegions();\nvoid windowAddInputFunc(WindowInputHandler* handler);\nbool windowActivateRegion(const char* regionName, int a2);\nint getInput();\nint windowHide();\nint windowShow();\nint windowDraw();\nint windowDrawRect(int left, int top, int right, int bottom);\nint windowDrawRectID(int windowId, int left, int top, int right, int bottom);\nint windowWidth();\nint windowHeight();\nint windowSX();\nint windowSY();\nint pointInWindow(int x, int y);\nint windowGetRect(Rect* rect);\nint windowGetID();\nint windowGetGNWID();\nint windowGetSpecificGNWID(int windowIndex);\nbool deleteWindow(const char* windowName);\nint resizeWindow(const char* windowName, int x, int y, int width, int height);\nint scaleWindow(const char* windowName, int x, int y, int width, int height);\nint createWindow(const char* windowName, int x, int y, int width, int height, int a6, int flags);\nint windowOutput(char* string);\nbool windowGotoXY(int x, int y);\nbool selectWindowID(int index);\nint selectWindow(const char* windowName);\nint windowGetDefined(const char* name);\nunsigned char* windowGetBuffer();\nchar* windowGetName();\nint pushWindow(const char* windowName);\nint popWindow();\nvoid windowPrintBuf(int win, char* string, int stringLength, int width, int maxY, int x, int y, int flags, int textAlignment);\nchar** windowWordWrap(char* string, int maxLength, int a3, int* substringListLengthPtr);\nvoid windowFreeWordList(char** substringList, int substringListLength);\nvoid windowWrapLineWithSpacing(int win, char* string, int width, int height, int x, int y, int flags, int textAlignment, int a9);\nvoid windowWrapLine(int win, char* string, int width, int height, int x, int y, int flags, int textAlignment);\nbool windowPrintRect(char* string, int a2, int textAlignment);\nbool windowFormatMessage(char* string, int x, int y, int width, int height, int textAlignment);\nint windowFormatMessageColor(char* string, int x, int y, int width, int height, int textAlignment, int flags);\nbool windowPrint(char* string, int a2, int x, int y, int a5);\nint windowPrintFont(char* string, int a2, int x, int y, int a5, int font);\nvoid displayInWindow(unsigned char* data, int width, int height, int pitch);\nvoid displayFile(char* fileName);\nvoid displayFileRaw(char* fileName);\nint windowDisplayRaw(char* fileName);\nbool windowDisplay(char* fileName, int x, int y, int width, int height);\nint windowDisplayScaled(char* fileName, int x, int y, int width, int height);\nbool windowDisplayBuf(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight);\nint windowDisplayTransBuf(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight);\nint windowDisplayBufScaled(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight);\nint windowGetXres();\nint windowGetYres();\nvoid initWindow(int resolution, int a2);\nvoid windowSetWindowFuncs(ManagedWindowCreateCallback* createCallback, ManagedWindowSelectFunc* selectCallback, WindowDeleteCallback* deleteCallback, DisplayInWindowCallback* displayCallback);\nvoid windowClose();\nbool windowDeleteButton(const char* buttonName);\nvoid windowEnableButton(const char* buttonName, int enabled);\nint windowGetButtonID(const char* buttonName);\nbool windowSetButtonFlag(const char* buttonName, int value);\nvoid windowRegisterButtonSoundFunc(ButtonCallback* soundPressFunc, ButtonCallback* soundReleaseFunc, ButtonCallback* soundDisableFunc);\nbool windowAddButton(const char* buttonName, int x, int y, int width, int height, int flags);\nbool windowAddButtonGfx(const char* buttonName, char* a2, char* a3, char* a4);\nint windowAddButtonMask(const char* buttonName, unsigned char* buffer);\nint windowAddButtonBuf(const char* buttonName, unsigned char* normal, unsigned char* pressed, unsigned char* hover, int width, int height, int pitch);\nbool windowAddButtonProc(const char* buttonName, Program* program, int mouseEnterProc, int mouseExitProc, int mouseDownProc, int mouseUpProc);\nbool windowAddButtonRightProc(const char* buttonName, Program* program, int rightMouseDownProc, int rightMouseUpProc);\nbool windowAddButtonCfunc(const char* buttonName, ManagedButtonMouseEventCallback* callback, void* userData);\nbool windowAddButtonRightCfunc(const char* buttonName, ManagedButtonMouseEventCallback* callback, void* userData);\nbool windowAddButtonText(const char* buttonName, const char* text);\nbool windowAddButtonTextWithOffsets(const char* buttonName, const char* text, int pressedImageOffsetX, int pressedImageOffsetY, int normalImageOffsetX, int normalImageOffsetY);\nbool windowFill(float r, float g, float b);\nbool windowFillRect(int x, int y, int width, int height, float r, float g, float b);\nvoid windowEndRegion();\nvoid* windowRegionGetUserData(const char* windowRegionName);\nvoid windowRegionSetUserData(const char* windowRegionName, void* userData);\nbool windowCheckRegionExists(const char* regionName);\nbool windowStartRegion(int initialCapacity);\nbool windowAddRegionPoint(int x, int y, bool a3);\nint windowAddRegionRect(int a1, int a2, int a3, int a4, int a5);\nint windowAddRegionCfunc(const char* regionName, RegionMouseEventCallback* callback, void* userData);\nint windowAddRegionRightCfunc(const char* regionName, RegionMouseEventCallback* callback, void* userData);\nbool windowAddRegionProc(const char* regionName, Program* program, int a3, int a4, int a5, int a6);\nbool windowAddRegionRightProc(const char* regionName, Program* program, int a3, int a4);\nbool windowSetRegionFlag(const char* regionName, int value);\nbool windowAddRegionName(const char* regionName);\nbool windowDeleteRegion(const char* regionName);\nvoid updateWindows();\nint windowMoviePlaying();\nbool windowSetMovieFlags(int flags);\nbool windowPlayMovie(char* filePath);\nbool windowPlayMovieRect(char* filePath, int a2, int a3, int a4, int a5);\nvoid windowStopMovie();\nvoid drawScaled(unsigned char* dest, int destWidth, int destHeight, int destPitch, unsigned char* src, int srcWidth, int srcHeight, int srcPitch);\nvoid drawScaledBuf(unsigned char* dest, int destWidth, int destHeight, unsigned char* src, int srcWidth, int srcHeight);\nvoid alphaBltBuf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* a5, unsigned char* a6, unsigned char* dest, int destPitch);\nvoid alphaBltBufRect(unsigned char* src, int srcWidth, int srcHeight, unsigned char* dest, int destWidth, int destHeight);\nint windowEnableCheckRegion();\nint windowDisableCheckRegion();\nint windowSetHoldTime(int value);\nint windowAddTextRegion(int x, int y, int width, int font, int textAlignment, int textFlags, int backgroundColor);\nint windowPrintTextRegion(int textRegionId, char* string);\nint windowUpdateTextRegion(int textRegionId);\nint windowDeleteTextRegion(int textRegionId);\nint windowTextRegionStyle(int textRegionId, int font, int textAlignment, int textFlags, int backgroundColor);\nint windowAddTextInputRegion(int textRegionId, char* text, int a3, int a4);\nint windowDeleteTextInputRegion(int textInputRegionId);\nint windowSetTextInputDeleteFunc(int textInputRegionId, TextInputRegionDeleteFunc* deleteFunc, void* userData);\n\n#endif /* WINDOW_H */\n"
  },
  {
    "path": "src/memory_defs.h",
    "content": "#ifndef MEMORY_DEFS_H\n#define MEMORY_DEFS_H\n\n#include <stddef.h>\n\ntypedef void*(MallocProc)(size_t size);\ntypedef void*(ReallocProc)(void* ptr, size_t newSize);\ntypedef void(FreeProc)(void* ptr);\n\n#endif /* MEMORY_DEFS_H */\n"
  },
  {
    "path": "src/mmx.c",
    "content": "#include \"mmx.h\"\n\n#include <string.h>\n\n#include \"plib/gnw/svga.h\"\n\n// Return `true` if CPU supports MMX.\n//\n// 0x4E08A0\nbool mmxIsSupported()\n{\n    int v1;\n\n    // TODO: There are other ways to determine MMX using FLAGS register.\n\n    __asm\n    {\n        mov eax, 1\n        cpuid\n        and edx, 0x800000\n        mov v1, edx\n    }\n\n    return v1 != 0;\n}\n\n// 0x4E0DB0\nvoid mmxBlit(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height)\n{\n    if (mmxEnabled) {\n        // TODO: Blit with MMX.\n        mmxEnabled = false;\n        mmxBlit(dest, destPitch, src, srcPitch, width, height);\n        mmxEnabled = true;\n    } else {\n        for (int y = 0; y < height; y++) {\n            memcpy(dest, src, width);\n            dest += destPitch;\n            src += srcPitch;\n        }\n    }\n}\n\n// 0x4E0ED5\nvoid mmxBlitTrans(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height)\n{\n    if (mmxEnabled) {\n        // TODO: Blit with MMX.\n        mmxEnabled = false;\n        mmxBlitTrans(dest, destPitch, src, srcPitch, width, height);\n        mmxEnabled = true;\n    } else {\n        int destSkip = destPitch - width;\n        int srcSkip = srcPitch - width;\n\n        for (int y = 0; y < height; y++) {\n            for (int x = 0; x < width; x++) {\n                unsigned char c = *src++;\n                if (c != 0) {\n                    *dest = c;\n                }\n                dest++;\n            }\n            src += srcSkip;\n            dest += destSkip;\n        }\n    }\n}\n"
  },
  {
    "path": "src/mmx.h",
    "content": "#ifndef MMX_H\n#define MMX_H\n\n#include <stdbool.h>\n\nbool mmxIsSupported();\nvoid mmxBlit(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height);\nvoid mmxBlitTrans(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height);\n\n#endif /* MMX_H */\n"
  },
  {
    "path": "src/movie_lib.c",
    "content": "// NOTE: This module is completely standalone. It does not have external\n// dependencies and uses __cdecl calling convention, which probably means it\n// was implemented as a separate library and linked statically.\n\n#include \"movie_lib.h\"\n\n#include <assert.h>\n#include <stdio.h>\n#include <string.h>\n\n#include <timeapi.h>\n\n// 0x51EBD8\nint dword_51EBD8 = 0;\n\n// 0x51EBDC\nint dword_51EBDC = 4;\n\n// 0x51EBE0\nunsigned short word_51EBE0[256] = {\n    // clang-format off\n    0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,\n    0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,\n    0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,\n    0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,\n    0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,\n    0x0028, 0x0029, 0x002A, 0x002B, 0x002F, 0x0033, 0x0038, 0x003D,\n    0x0042, 0x0048, 0x004F, 0x0056, 0x005E, 0x0066, 0x0070, 0x007A,\n    0x0085, 0x0091, 0x009E, 0x00AD, 0x00BD, 0x00CE, 0x00E1, 0x00F5,\n    0x010B, 0x0124, 0x013E, 0x015C, 0x017B, 0x019E, 0x01C4, 0x01ED,\n    0x021A, 0x024B, 0x0280, 0x02BB, 0x02FB, 0x0340, 0x038C, 0x03DF,\n    0x0439, 0x049C, 0x0508, 0x057D, 0x05FE, 0x0689, 0x0722, 0x07C9,\n    0x087F, 0x0945, 0x0A1E, 0x0B0A, 0x0C0C, 0x0D25, 0x0E58, 0x0FA8,\n    0x1115, 0x12A4, 0x1458, 0x1633, 0x183A, 0x1A6F, 0x1CD9, 0x1F7B,\n    0x225A, 0x257D, 0x28E8, 0x2CA4, 0x30B7, 0x3529, 0x3A03, 0x3F4E,\n    0x4515, 0x4B62, 0x5244, 0x59C5, 0x61F6, 0x6AE7, 0x74A8, 0x7F4D,\n    0x8AEB, 0x9798, 0xA56E, 0xB486, 0xC4FF, 0xD6F9, 0xEA97, 0xFFFF,\n    0x0001, 0x0001, 0x1569, 0x2907, 0x3B01, 0x4B7A, 0x5A92, 0x6868,\n    0x7515, 0x80B3, 0x8B58, 0x9519, 0x9E0A, 0xA63B, 0xADBC, 0xB49E,\n    0xBAEB, 0xC0B2, 0xC5FD, 0xCAD7, 0xCF49, 0xD35C, 0xD718, 0xDA83,\n    0xDDA6, 0xE085, 0xE327, 0xE591, 0xE7C6, 0xE9CD, 0xEBA8, 0xED5C,\n    0xEEEB, 0xF058, 0xF1A8, 0xF2DB, 0xF3F4, 0xF4F6, 0xF5E2, 0xF6BB,\n    0xF781, 0xF837, 0xF8DE, 0xF977, 0xFA02, 0xFA83, 0xFAF8, 0xFB64,\n    0xFBC7, 0xFC21, 0xFC74, 0xFCC0, 0xFD05, 0xFD45, 0xFD80, 0xFDB5,\n    0xFDE6, 0xFE13, 0xFE3C, 0xFE62, 0xFE85, 0xFEA4, 0xFEC2, 0xFEDC,\n    0xFEF5, 0xFF0B, 0xFF1F, 0xFF32, 0xFF43, 0xFF53, 0xFF62, 0xFF6F,\n    0xFF7B, 0xFF86, 0xFF90, 0xFF9A, 0xFFA2, 0xFFAA, 0xFFB1, 0xFFB8,\n    0xFFBE, 0xFFC3, 0xFFC8, 0xFFCD, 0xFFD1, 0xFFD5, 0xFFD6, 0xFFD7,\n    0xFFD8, 0xFFD9, 0xFFDA, 0xFFDB, 0xFFDC, 0xFFDD, 0xFFDE, 0xFFDF,\n    0xFFE0, 0xFFE1, 0xFFE2, 0xFFE3, 0xFFE4, 0xFFE5, 0xFFE6, 0xFFE7,\n    0xFFE8, 0xFFE9, 0xFFEA, 0xFFEB, 0xFFEC, 0xFFEE, 0xFFED, 0xFFEF,\n    0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7,\n    0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF,\n    // clang-format on\n};\n\n// 0x51EDE0\nLPDIRECTDRAW gMovieLibDirectDraw = NULL;\n\n// 0x51EDE4\nint _sync_active = 0;\n\n// 0x51EDE8\nint _sync_late = 0;\n\n// 0x51EDEC\nint _sync_FrameDropped = 0;\n\n// 0x51EDF0\nLPDIRECTSOUND gMovieLibDirectSound = NULL;\n\n// 0x51EDF4\nLPDIRECTSOUNDBUFFER gMovieLibDirectSoundBuffer = NULL;\n\n// 0x51EDF8\nint gMovieLibVolume = 0;\n\n// 0x51EDFC\nint gMovieLibPan = 0;\n\n// 0x51EE00\nLPDIRECTDRAWSURFACE gMovieDirectDrawSurface1 = NULL;\n\n// 0x51EE04\nLPDIRECTDRAWSURFACE gMovieDirectDrawSurface2 = NULL;\n\n// 0x51EE08\nvoid (*_sf_ShowFrame)(LPDIRECTDRAWSURFACE, int, int, int, int, int, int, int, int) = _do_nothing_2;\n\n// 0x51EE0C\nint dword_51EE0C = 1;\n\n// TODO: There is a default function (not yet implemented).\n//\n// 0x51EE14\nvoid (*_pal_SetPalette)(unsigned char*, int, int) = NULL;\n\n// 0x51EE18\nint _rm_hold = 0;\n\n// 0x51EE1C\nint _rm_active = 0;\n\n// 0x51EE20\nbool dword_51EE20 = false;\n\n// 0x51F018\nint dword_51F018[256];\n\n// 0x51F418\nunsigned short word_51F418[256] = {\n    // clang-format off\n    0xF8F8, 0xF8F9, 0xF8FA, 0xF8FB, 0xF8FC, 0xF8FD, 0xF8FE, 0xF8FF,\n    0xF800, 0xF801, 0xF802, 0xF803, 0xF804, 0xF805, 0xF806, 0xF807,\n    0xF9F8, 0xF9F9, 0xF9FA, 0xF9FB, 0xF9FC, 0xF9FD, 0xF9FE, 0xF9FF,\n    0xF900, 0xF901, 0xF902, 0xF903, 0xF904, 0xF905, 0xF906, 0xF907,\n    0xFAF8, 0xFAF9, 0xFAFA, 0xFAFB, 0xFAFC, 0xFAFD, 0xFAFE, 0xFAFF,\n    0xFA00, 0xFA01, 0xFA02, 0xFA03, 0xFA04, 0xFA05, 0xFA06, 0xFA07,\n    0xFBF8, 0xFBF9, 0xFBFA, 0xFBFB, 0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF,\n    0xFB00, 0xFB01, 0xFB02, 0xFB03, 0xFB04, 0xFB05, 0xFB06, 0xFB07,\n    0xFCF8, 0xFCF9, 0xFCFA, 0xFCFB, 0xFCFC, 0xFCFD, 0xFCFE, 0xFCFF,\n    0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04, 0xFC05, 0xFC06, 0xFC07,\n    0xFDF8, 0xFDF9, 0xFDFA, 0xFDFB, 0xFDFC, 0xFDFD, 0xFDFE, 0xFDFF,\n    0xFD00, 0xFD01, 0xFD02, 0xFD03, 0xFD04, 0xFD05, 0xFD06, 0xFD07,\n    0xFEF8, 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0xFEFD, 0xFEFE, 0xFEFF,\n    0xFE00, 0xFE01, 0xFE02, 0xFE03, 0xFE04, 0xFE05, 0xFE06, 0xFE07,\n    0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF,\n    0xFF00, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07,\n    0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF,\n    0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,\n    0x01F8, 0x01F9, 0x01FA, 0x01FB, 0x01FC, 0x01FD, 0x01FE, 0x01FF,\n    0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107,\n    0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF,\n    0x0200, 0x0201, 0x0202, 0x0203, 0x0204, 0x0205, 0x0206, 0x0207,\n    0x03F8, 0x03F9, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF,\n    0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,\n    0x04F8, 0x04F9, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,\n    0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407,\n    0x05F8, 0x05F9, 0x05FA, 0x05FB, 0x05FC, 0x05FD, 0x05FE, 0x05FF,\n    0x0500, 0x0501, 0x0502, 0x0503, 0x0504, 0x0505, 0x0506, 0x0507,\n    0x06F8, 0x06F9, 0x06FA, 0x06FB, 0x06FC, 0x06FD, 0x06FE, 0x06FF,\n    0x0600, 0x0601, 0x0602, 0x0603, 0x0604, 0x0605, 0x0606, 0x0607,\n    0x07F8, 0x07F9, 0x07FA, 0x07FB, 0x07FC, 0x07FD, 0x07FE, 0x07FF,\n    0x0700, 0x0701, 0x0702, 0x0703, 0x0704, 0x0705, 0x0706, 0x0707,\n    // clang-format on\n};\n\n// 0x51F618\nunsigned short word_51F618[256] = {\n    // clang-format off\n    0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x0108,\n    0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x0208, 0x0209,\n    0x020A, 0x020B, 0x020C, 0x020D, 0x020E, 0x0308, 0x0309, 0x030A,\n    0x030B, 0x030C, 0x030D, 0x030E, 0x0408, 0x0409, 0x040A, 0x040B,\n    0x040C, 0x040D, 0x040E, 0x0508, 0x0509, 0x050A, 0x050B, 0x050C,\n    0x050D, 0x050E, 0x0608, 0x0609, 0x060A, 0x060B, 0x060C, 0x060D,\n    0x060E, 0x0708, 0x0709, 0x070A, 0x070B, 0x070C, 0x070D, 0x070E,\n    0x08F2, 0x08F3, 0x08F4, 0x08F5, 0x08F6, 0x08F7, 0x08F8, 0x08F9,\n    0x08FA, 0x08FB, 0x08FC, 0x08FD, 0x08FE, 0x08FF, 0x0800, 0x0801,\n    0x0802, 0x0803, 0x0804, 0x0805, 0x0806, 0x0807, 0x0808, 0x0809,\n    0x080A, 0x080B, 0x080C, 0x080D, 0x080E, 0x09F2, 0x09F3, 0x09F4,\n    0x09F5, 0x09F6, 0x09F7, 0x09F8, 0x09F9, 0x09FA, 0x09FB, 0x09FC,\n    0x09FD, 0x09FE, 0x09FF, 0x0900, 0x0901, 0x0902, 0x0903, 0x0904,\n    0x0905, 0x0906, 0x0907, 0x0908, 0x0909, 0x090A, 0x090B, 0x090C,\n    0x090D, 0x090E, 0x0AF2, 0x0AF3, 0x0AF4, 0x0AF5, 0x0AF6, 0x0AF7,\n    0x0AF8, 0x0AF9, 0x0AFA, 0x0AFB, 0x0AFC, 0x0AFD, 0x0AFE, 0x0AFF,\n    0x0A00, 0x0A01, 0x0A02, 0x0A03, 0x0A04, 0x0A05, 0x0A06, 0x0A07,\n    0x0A08, 0x0A09, 0x0A0A, 0x0A0B, 0x0A0C, 0x0A0D, 0x0A0E, 0x0BF2,\n    0x0BF3, 0x0BF4, 0x0BF5, 0x0BF6, 0x0BF7, 0x0BF8, 0x0BF9, 0x0BFA,\n    0x0BFB, 0x0BFC, 0x0BFD, 0x0BFE, 0x0BFF, 0x0B00, 0x0B01, 0x0B02,\n    0x0B03, 0x0B04, 0x0B05, 0x0B06, 0x0B07, 0x0B08, 0x0B09, 0x0B0A,\n    0x0B0B, 0x0B0C, 0x0B0D, 0x0B0E, 0x0CF2, 0x0CF3, 0x0CF4, 0x0CF5,\n    0x0CF6, 0x0CF7, 0x0CF8, 0x0CF9, 0x0CFA, 0x0CFB, 0x0CFC, 0x0CFD,\n    0x0CFE, 0x0CFF, 0x0C00, 0x0C01, 0x0C02, 0x0C03, 0x0C04, 0x0C05,\n    0x0C06, 0x0C07, 0x0C08, 0x0C09, 0x0C0A, 0x0C0B, 0x0C0C, 0x0C0D,\n    0x0C0E, 0x0DF2, 0x0DF3, 0x0DF4, 0x0DF5, 0x0DF6, 0x0DF7, 0x0DF8,\n    0x0DF9, 0x0DFA, 0x0DFB, 0x0DFC, 0x0DFD, 0x0DFE, 0x0DFF, 0x0D00,\n    0x0D01, 0x0D02, 0x0D03, 0x0D04, 0x0D05, 0x0D06, 0x0D07, 0x0D08,\n    0x0D09, 0x0D0A, 0x0D0B, 0x0D0C, 0x0D0D, 0x0D0E, 0x0EF2, 0x0EF3,\n    0x0EF4, 0x0EF5, 0x0EF6, 0x0EF7, 0x0EF8, 0x0EF9, 0x0EFA, 0x0EFB,\n    0x0EFC, 0x0EFD, 0x0EFE, 0x0EFF, 0x0E00, 0x0E01, 0x0E02, 0x0E03,\n    0x0E04, 0x0E05, 0x0E06, 0x0E07, 0x0E08, 0x0E09, 0x0E0A, 0x0E0B,\n    // clang-format on\n};\n\n// 0x51F818\nunsigned int _$$R0053[16] = {\n    // clang-format off\n    0xC3C3C3C3, 0xC3C3C1C3, 0xC3C3C3C1, 0xC3C3C1C1, 0xC1C3C3C3, 0xC1C3C1C3, 0xC1C3C3C1, 0xC1C3C1C1,\n    0xC3C1C3C3, 0xC3C1C1C3, 0xC3C1C3C1, 0xC3C1C1C1, 0xC1C1C3C3, 0xC1C1C1C3, 0xC1C1C3C1, 0xC1C1C1C1,\n    // clang-format on\n};\n\n// 0x51F858\nunsigned int _$$R0004[256] = {\n    // clang-format off\n    0xC3C3C3C3, 0xC3C3C2C3, 0xC3C3C1C3, 0xC3C3C5C3, 0xC3C3C3C2, 0xC3C3C2C2, 0xC3C3C1C2, 0xC3C3C5C2,\n    0xC3C3C3C1, 0xC3C3C2C1, 0xC3C3C1C1, 0xC3C3C5C1, 0xC3C3C3C5, 0xC3C3C2C5, 0xC3C3C1C5, 0xC3C3C5C5,\n    0xC2C3C3C3, 0xC2C3C2C3, 0xC2C3C1C3, 0xC2C3C5C3, 0xC2C3C3C2, 0xC2C3C2C2, 0xC2C3C1C2, 0xC2C3C5C2,\n    0xC2C3C3C1, 0xC2C3C2C1, 0xC2C3C1C1, 0xC2C3C5C1, 0xC2C3C3C5, 0xC2C3C2C5, 0xC2C3C1C5, 0xC2C3C5C5,\n    0xC1C3C3C3, 0xC1C3C2C3, 0xC1C3C1C3, 0xC1C3C5C3, 0xC1C3C3C2, 0xC1C3C2C2, 0xC1C3C1C2, 0xC1C3C5C2,\n    0xC1C3C3C1, 0xC1C3C2C1, 0xC1C3C1C1, 0xC1C3C5C1, 0xC1C3C3C5, 0xC1C3C2C5, 0xC1C3C1C5, 0xC1C3C5C5,\n    0xC5C3C3C3, 0xC5C3C2C3, 0xC5C3C1C3, 0xC5C3C5C3, 0xC5C3C3C2, 0xC5C3C2C2, 0xC5C3C1C2, 0xC5C3C5C2,\n    0xC5C3C3C1, 0xC5C3C2C1, 0xC5C3C1C1, 0xC5C3C5C1, 0xC5C3C3C5, 0xC5C3C2C5, 0xC5C3C1C5, 0xC5C3C5C5,\n    0xC3C2C3C3, 0xC3C2C2C3, 0xC3C2C1C3, 0xC3C2C5C3, 0xC3C2C3C2, 0xC3C2C2C2, 0xC3C2C1C2, 0xC3C2C5C2,\n    0xC3C2C3C1, 0xC3C2C2C1, 0xC3C2C1C1, 0xC3C2C5C1, 0xC3C2C3C5, 0xC3C2C2C5, 0xC3C2C1C5, 0xC3C2C5C5,\n    0xC2C2C3C3, 0xC2C2C2C3, 0xC2C2C1C3, 0xC2C2C5C3, 0xC2C2C3C2, 0xC2C2C2C2, 0xC2C2C1C2, 0xC2C2C5C2,\n    0xC2C2C3C1, 0xC2C2C2C1, 0xC2C2C1C1, 0xC2C2C5C1, 0xC2C2C3C5, 0xC2C2C2C5, 0xC2C2C1C5, 0xC2C2C5C5,\n    0xC1C2C3C3, 0xC1C2C2C3, 0xC1C2C1C3, 0xC1C2C5C3, 0xC1C2C3C2, 0xC1C2C2C2, 0xC1C2C1C2, 0xC1C2C5C2,\n    0xC1C2C3C1, 0xC1C2C2C1, 0xC1C2C1C1, 0xC1C2C5C1, 0xC1C2C3C5, 0xC1C2C2C5, 0xC1C2C1C5, 0xC1C2C5C5,\n    0xC5C2C3C3, 0xC5C2C2C3, 0xC5C2C1C3, 0xC5C2C5C3, 0xC5C2C3C2, 0xC5C2C2C2, 0xC5C2C1C2, 0xC5C2C5C2,\n    0xC5C2C3C1, 0xC5C2C2C1, 0xC5C2C1C1, 0xC5C2C5C1, 0xC5C2C3C5, 0xC5C2C2C5, 0xC5C2C1C5, 0xC5C2C5C5,\n    0xC3C1C3C3, 0xC3C1C2C3, 0xC3C1C1C3, 0xC3C1C5C3, 0xC3C1C3C2, 0xC3C1C2C2, 0xC3C1C1C2, 0xC3C1C5C2,\n    0xC3C1C3C1, 0xC3C1C2C1, 0xC3C1C1C1, 0xC3C1C5C1, 0xC3C1C3C5, 0xC3C1C2C5, 0xC3C1C1C5, 0xC3C1C5C5,\n    0xC2C1C3C3, 0xC2C1C2C3, 0xC2C1C1C3, 0xC2C1C5C3, 0xC2C1C3C2, 0xC2C1C2C2, 0xC2C1C1C2, 0xC2C1C5C2,\n    0xC2C1C3C1, 0xC2C1C2C1, 0xC2C1C1C1, 0xC2C1C5C1, 0xC2C1C3C5, 0xC2C1C2C5, 0xC2C1C1C5, 0xC2C1C5C5,\n    0xC1C1C3C3, 0xC1C1C2C3, 0xC1C1C1C3, 0xC1C1C5C3, 0xC1C1C3C2, 0xC1C1C2C2, 0xC1C1C1C2, 0xC1C1C5C2,\n    0xC1C1C3C1, 0xC1C1C2C1, 0xC1C1C1C1, 0xC1C1C5C1, 0xC1C1C3C5, 0xC1C1C2C5, 0xC1C1C1C5, 0xC1C1C5C5,\n    0xC5C1C3C3, 0xC5C1C2C3, 0xC5C1C1C3, 0xC5C1C5C3, 0xC5C1C3C2, 0xC5C1C2C2, 0xC5C1C1C2, 0xC5C1C5C2,\n    0xC5C1C3C1, 0xC5C1C2C1, 0xC5C1C1C1, 0xC5C1C5C1, 0xC5C1C3C5, 0xC5C1C2C5, 0xC5C1C1C5, 0xC5C1C5C5,\n    0xC3C5C3C3, 0xC3C5C2C3, 0xC3C5C1C3, 0xC3C5C5C3, 0xC3C5C3C2, 0xC3C5C2C2, 0xC3C5C1C2, 0xC3C5C5C2,\n    0xC3C5C3C1, 0xC3C5C2C1, 0xC3C5C1C1, 0xC3C5C5C1, 0xC3C5C3C5, 0xC3C5C2C5, 0xC3C5C1C5, 0xC3C5C5C5,\n    0xC2C5C3C3, 0xC2C5C2C3, 0xC2C5C1C3, 0xC2C5C5C3, 0xC2C5C3C2, 0xC2C5C2C2, 0xC2C5C1C2, 0xC2C5C5C2,\n    0xC2C5C3C1, 0xC2C5C2C1, 0xC2C5C1C1, 0xC2C5C5C1, 0xC2C5C3C5, 0xC2C5C2C5, 0xC2C5C1C5, 0xC2C5C5C5,\n    0xC1C5C3C3, 0xC1C5C2C3, 0xC1C5C1C3, 0xC1C5C5C3, 0xC1C5C3C2, 0xC1C5C2C2, 0xC1C5C1C2, 0xC1C5C5C2,\n    0xC1C5C3C1, 0xC1C5C2C1, 0xC1C5C1C1, 0xC1C5C5C1, 0xC1C5C3C5, 0xC1C5C2C5, 0xC1C5C1C5, 0xC1C5C5C5,\n    0xC5C5C3C3, 0xC5C5C2C3, 0xC5C5C1C3, 0xC5C5C5C3, 0xC5C5C3C2, 0xC5C5C2C2, 0xC5C5C1C2, 0xC5C5C5C2,\n    0xC5C5C3C1, 0xC5C5C2C1, 0xC5C5C1C1, 0xC5C5C5C1, 0xC5C5C3C5, 0xC5C5C2C5, 0xC5C5C1C5, 0xC5C5C5C5,\n    // clang-format on\n};\n\n// 0x51FC58\nunsigned int _$$R0063[256] = {\n    // clang-format off\n    0xE3C3E3C3, 0xE3C7E3C3, 0xE3C1E3C3, 0xE3C5E3C3, 0xE7C3E3C3, 0xE7C7E3C3, 0xE7C1E3C3, 0xE7C5E3C3,\n    0xE1C3E3C3, 0xE1C7E3C3, 0xE1C1E3C3, 0xE1C5E3C3, 0xE5C3E3C3, 0xE5C7E3C3, 0xE5C1E3C3, 0xE5C5E3C3,\n    0xE3C3E3C7, 0xE3C7E3C7, 0xE3C1E3C7, 0xE3C5E3C7, 0xE7C3E3C7, 0xE7C7E3C7, 0xE7C1E3C7, 0xE7C5E3C7,\n    0xE1C3E3C7, 0xE1C7E3C7, 0xE1C1E3C7, 0xE1C5E3C7, 0xE5C3E3C7, 0xE5C7E3C7, 0xE5C1E3C7, 0xE5C5E3C7,\n    0xE3C3E3C1, 0xE3C7E3C1, 0xE3C1E3C1, 0xE3C5E3C1, 0xE7C3E3C1, 0xE7C7E3C1, 0xE7C1E3C1, 0xE7C5E3C1,\n    0xE1C3E3C1, 0xE1C7E3C1, 0xE1C1E3C1, 0xE1C5E3C1, 0xE5C3E3C1, 0xE5C7E3C1, 0xE5C1E3C1, 0xE5C5E3C1,\n    0xE3C3E3C5, 0xE3C7E3C5, 0xE3C1E3C5, 0xE3C5E3C5, 0xE7C3E3C5, 0xE7C7E3C5, 0xE7C1E3C5, 0xE7C5E3C5,\n    0xE1C3E3C5, 0xE1C7E3C5, 0xE1C1E3C5, 0xE1C5E3C5, 0xE5C3E3C5, 0xE5C7E3C5, 0xE5C1E3C5, 0xE5C5E3C5,\n    0xE3C3E7C3, 0xE3C7E7C3, 0xE3C1E7C3, 0xE3C5E7C3, 0xE7C3E7C3, 0xE7C7E7C3, 0xE7C1E7C3, 0xE7C5E7C3,\n    0xE1C3E7C3, 0xE1C7E7C3, 0xE1C1E7C3, 0xE1C5E7C3, 0xE5C3E7C3, 0xE5C7E7C3, 0xE5C1E7C3, 0xE5C5E7C3,\n    0xE3C3E7C7, 0xE3C7E7C7, 0xE3C1E7C7, 0xE3C5E7C7, 0xE7C3E7C7, 0xE7C7E7C7, 0xE7C1E7C7, 0xE7C5E7C7,\n    0xE1C3E7C7, 0xE1C7E7C7, 0xE1C1E7C7, 0xE1C5E7C7, 0xE5C3E7C7, 0xE5C7E7C7, 0xE5C1E7C7, 0xE5C5E7C7,\n    0xE3C3E7C1, 0xE3C7E7C1, 0xE3C1E7C1, 0xE3C5E7C1, 0xE7C3E7C1, 0xE7C7E7C1, 0xE7C1E7C1, 0xE7C5E7C1,\n    0xE1C3E7C1, 0xE1C7E7C1, 0xE1C1E7C1, 0xE1C5E7C1, 0xE5C3E7C1, 0xE5C7E7C1, 0xE5C1E7C1, 0xE5C5E7C1,\n    0xE3C3E7C5, 0xE3C7E7C5, 0xE3C1E7C5, 0xE3C5E7C5, 0xE7C3E7C5, 0xE7C7E7C5, 0xE7C1E7C5, 0xE7C5E7C5,\n    0xE1C3E7C5, 0xE1C7E7C5, 0xE1C1E7C5, 0xE1C5E7C5, 0xE5C3E7C5, 0xE5C7E7C5, 0xE5C1E7C5, 0xE5C5E7C5,\n    0xE3C3E1C3, 0xE3C7E1C3, 0xE3C1E1C3, 0xE3C5E1C3, 0xE7C3E1C3, 0xE7C7E1C3, 0xE7C1E1C3, 0xE7C5E1C3,\n    0xE1C3E1C3, 0xE1C7E1C3, 0xE1C1E1C3, 0xE1C5E1C3, 0xE5C3E1C3, 0xE5C7E1C3, 0xE5C1E1C3, 0xE5C5E1C3,\n    0xE3C3E1C7, 0xE3C7E1C7, 0xE3C1E1C7, 0xE3C5E1C7, 0xE7C3E1C7, 0xE7C7E1C7, 0xE7C1E1C7, 0xE7C5E1C7,\n    0xE1C3E1C7, 0xE1C7E1C7, 0xE1C1E1C7, 0xE1C5E1C7, 0xE5C3E1C7, 0xE5C7E1C7, 0xE5C1E1C7, 0xE5C5E1C7,\n    0xE3C3E1C1, 0xE3C7E1C1, 0xE3C1E1C1, 0xE3C5E1C1, 0xE7C3E1C1, 0xE7C7E1C1, 0xE7C1E1C1, 0xE7C5E1C1,\n    0xE1C3E1C1, 0xE1C7E1C1, 0xE1C1E1C1, 0xE1C5E1C1, 0xE5C3E1C1, 0xE5C7E1C1, 0xE5C1E1C1, 0xE5C5E1C1,\n    0xE3C3E1C5, 0xE3C7E1C5, 0xE3C1E1C5, 0xE3C5E1C5, 0xE7C3E1C5, 0xE7C7E1C5, 0xE7C1E1C5, 0xE7C5E1C5,\n    0xE1C3E1C5, 0xE1C7E1C5, 0xE1C1E1C5, 0xE1C5E1C5, 0xE5C3E1C5, 0xE5C7E1C5, 0xE5C1E1C5, 0xE5C5E1C5,\n    0xE3C3E5C3, 0xE3C7E5C3, 0xE3C1E5C3, 0xE3C5E5C3, 0xE7C3E5C3, 0xE7C7E5C3, 0xE7C1E5C3, 0xE7C5E5C3,\n    0xE1C3E5C3, 0xE1C7E5C3, 0xE1C1E5C3, 0xE1C5E5C3, 0xE5C3E5C3, 0xE5C7E5C3, 0xE5C1E5C3, 0xE5C5E5C3,\n    0xE3C3E5C7, 0xE3C7E5C7, 0xE3C1E5C7, 0xE3C5E5C7, 0xE7C3E5C7, 0xE7C7E5C7, 0xE7C1E5C7, 0xE7C5E5C7,\n    0xE1C3E5C7, 0xE1C7E5C7, 0xE1C1E5C7, 0xE1C5E5C7, 0xE5C3E5C7, 0xE5C7E5C7, 0xE5C1E5C7, 0xE5C5E5C7,\n    0xE3C3E5C1, 0xE3C7E5C1, 0xE3C1E5C1, 0xE3C5E5C1, 0xE7C3E5C1, 0xE7C7E5C1, 0xE7C1E5C1, 0xE7C5E5C1,\n    0xE1C3E5C1, 0xE1C7E5C1, 0xE1C1E5C1, 0xE1C5E5C1, 0xE5C3E5C1, 0xE5C7E5C1, 0xE5C1E5C1, 0xE5C5E5C1,\n    0xE3C3E5C5, 0xE3C7E5C5, 0xE3C1E5C5, 0xE3C5E5C5, 0xE7C3E5C5, 0xE7C7E5C5, 0xE7C1E5C5, 0xE7C5E5C5,\n    0xE1C3E5C5, 0xE1C7E5C5, 0xE1C1E5C5, 0xE1C5E5C5, 0xE5C3E5C5, 0xE5C7E5C5, 0xE5C1E5C5, 0xE5C5E5C5,\n    // clang-format on\n};\n\n// 0x6B3660\nint dword_6B3660;\n\n// 0x6B3668\nDSBCAPS stru_6B3668;\n\n// 0x6B367C\nint _sf_ScreenWidth;\n\n// 0x6B3680\nint dword_6B3680;\n\n// 0x6B3684\nint _rm_FrameDropCount;\n\n// 0x6B3688\nint _snd_buf;\n\n// 0x6B3690\nSTRUCT_6B3690 _io_mem_buf;\n\n// 0x6B369C\nint _io_next_hdr;\n\n// 0x6B36A0\nint dword_6B36A0;\n\n// 0x6B36A4\nint dword_6B36A4;\n\n// 0x6B36A8\nint _rm_FrameCount;\n\n// 0x6B36AC\nint _sf_ScreenHeight;\n\n// 0x6B36B0\nint dword_6B36B0;\n\n// 0x6B36B8\nunsigned char _palette_entries1[768];\n\n// 0x6B39B8\nMallocProc* gMovieLibMallocProc;\n\n// 0x6B39BC\nint (*_rm_ctl)();\n\n// 0x6B39C0\nint _rm_dx;\n\n// 0x6B39C4\nint _rm_dy;\n\n// 0x6B39C8\nint _gSoundTimeBase;\n\n// 0x6B39CC\nint _io_handle;\n\n// 0x6B39D0\nint _rm_len;\n\n// 0x6B39D4\nFreeProc* gMovieLibFreeProc;\n\n// 0x6B39D8\nint _snd_comp;\n\n// 0x6B39DC\nunsigned char* _rm_p;\n\n// 0x6B39E0\nint dword_6B39E0[60];\n\n// 0x6B3AD0\nint _sync_wait_quanta;\n\n// 0x6B3AD4\nint dword_6B3AD4;\n\n// 0x6B3AD8\nint _rm_track_bit;\n\n// 0x6B3ADC\nint _sync_time;\n\n// 0x6B3AE0\nMovieReadProc* gMovieLibReadProc;\n\n// 0x6B3AE4\nint dword_6B3AE4;\n\n// 0x6B3AE8\nint dword_6B3AE8;\n\n// 0x6B3CEC\nint dword_6B3CEC;\n\n// 0x6B3CF0\nint dword_6B3CF0;\n\n// 0x6B3CF4\nint dword_6B3CF4;\n\n// 0x6B3CF8\nint dword_6B3CF8;\n\n// 0x6B3CFC\nint _mveBW;\n\n// 0x6B3D00\nint dword_6B3D00;\n\n// 0x6B3D04\nint dword_6B3D04;\n\n// 0x6B3D08\nint dword_6B3D08;\n\n// 0x6B3D0C\nunsigned char _pal_tbl[768];\n\n// 0x6B400C\nunsigned char byte_6B400C;\n\n// 0x6B400D\nunsigned char byte_6B400D;\n\n// 0x6B400E\nint dword_6B400E;\n\n// 0x6B4012\nint dword_6B4012;\n\n// 0x6B4016\nunsigned char byte_6B4016;\n\n// 0x6B4017\nint dword_6B4017;\n\n// 0x6B401B\nint dword_6B401B;\n\n// 0x6B401F\nint dword_6B401F;\n\n// 0x6B4023\nint dword_6B4023;\n\n// 0x6B4027\nint dword_6B4027;\n\n// 0x6B402B\nint dword_6B402B;\n\n// 0x6B402F\nint _mveBH;\n\n// 0x6B4033\nunsigned char* gMovieDirectDrawSurfaceBuffer1;\n\n// 0x6B4037\nunsigned char* gMovieDirectDrawSurfaceBuffer2;\n\n// 0x6B403B\nint dword_6B403B;\n\n// 0x6B403F\nint dword_6B403F;\n\n// 0x4F4800\nvoid movieLibSetMemoryProcs(MallocProc* mallocProc, FreeProc* freeProc)\n{\n    gMovieLibMallocProc = mallocProc;\n    gMovieLibFreeProc = freeProc;\n}\n\n// 0x4F4860\nvoid movieLibSetReadProc(MovieReadProc* readProc)\n{\n    gMovieLibReadProc = readProc;\n}\n\n// 0x4F4890\nvoid _MVE_MemInit(STRUCT_6B3690* a1, int a2, void* a3)\n{\n    if (a3 == NULL) {\n        return;\n    }\n\n    _MVE_MemFree(a1);\n\n    a1->field_0 = a3;\n    a1->field_4 = a2;\n    a1->field_8 = 0;\n}\n\n// 0x4F48C0\nvoid _MVE_MemFree(STRUCT_6B3690* a1)\n{\n    if (a1->field_8 && gMovieLibFreeProc != NULL) {\n        gMovieLibFreeProc(a1->field_0);\n        a1->field_8 = 0;\n    }\n    a1->field_4 = 0;\n}\n\n// 0x4F48F0\nvoid movieLibSetDirectSound(LPDIRECTSOUND ds)\n{\n    gMovieLibDirectSound = ds;\n}\n\n// 0x4F4900\nvoid movieLibSetVolume(int volume)\n{\n    gMovieLibVolume = volume;\n\n    if (gMovieLibDirectSoundBuffer != NULL) {\n        IDirectSoundBuffer_SetVolume(gMovieLibDirectSoundBuffer, volume);\n    }\n}\n\n// 0x4F4920\nvoid movieLibSetPan(int pan)\n{\n    gMovieLibPan = pan;\n\n    if (gMovieLibDirectSoundBuffer != NULL) {\n        IDirectSoundBuffer_SetPan(gMovieLibDirectSoundBuffer, pan);\n    }\n}\n\n// 0x4F4940\nvoid _MVE_sfSVGA(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)\n{\n    _sf_ScreenWidth = a1;\n    _sf_ScreenHeight = a2;\n    dword_6B3AD4 = a1;\n    dword_6B36B0 = a2;\n    dword_6B3D04 = a3;\n    if (dword_51EBD8 & 4)\n        dword_6B3D04 = 2 * a3;\n    dword_6B403F = a4;\n    dword_6B3CF4 = a6;\n    dword_6B400E = a5;\n    dword_6B403B = a7;\n    dword_6B3CF0 = a6 + a5;\n    dword_6B3D08 = a8;\n    if (a7)\n        dword_6B4012 = a6 / a7;\n    else\n        dword_6B4012 = 1;\n    dword_51EE0C = 0;\n    dword_6B3680 = a9;\n}\n\n// 0x4F49F0\nvoid _MVE_sfCallbacks(void (*fn)(LPDIRECTDRAWSURFACE, int, int, int, int, int, int, int, int))\n{\n    _sf_ShowFrame = fn;\n}\n\n// 0x4F4A00\nvoid _do_nothing_2(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9)\n{\n}\n\n// 0x4F4A10\nvoid movieLibSetPaletteEntriesProc(void (*fn)(unsigned char*, int, int))\n{\n    _pal_SetPalette = fn;\n}\n\n// 0x4F4B50\nint _sub_4F4B5()\n{\n    return 0;\n}\n\n// 0x4F4B80\nvoid movieLibSetDirectDraw(LPDIRECTDRAW dd)\n{\n    gMovieLibDirectDraw = dd;\n}\n\n// 0x4F4B90\nvoid _MVE_rmCallbacks(int (*fn)())\n{\n    _rm_ctl = fn;\n}\n\n// 0x4F4BB0\nvoid _sub_4F4BB(int a1)\n{\n    if (a1 == 3) {\n        dword_51EBDC = 3;\n    } else {\n        dword_51EBDC = 4;\n    }\n}\n\n// 0x4F4BD0\nvoid _MVE_rmFrameCounts(int* a1, int* a2)\n{\n    *a1 = _rm_FrameCount;\n    *a2 = _rm_FrameDropCount;\n}\n\n// 0x4F4BF0\nint _MVE_rmPrepMovie(int fileHandle, int a2, int a3, char a4)\n{\n    _sub_4F4DD();\n\n    if (gMovieLibDirectDraw == NULL) {\n        return -11;\n    }\n\n    _rm_dx = a2;\n    _rm_dy = a3;\n    _rm_track_bit = 1 << a4;\n\n    if (_rm_track_bit == 0) {\n        _rm_track_bit = 1;\n    }\n\n    if (!_ioReset(fileHandle)) {\n        _MVE_rmEndMovie();\n        return -8;\n    }\n\n    _rm_p = _ioNextRecord();\n    _rm_len = 0;\n\n    if (!_rm_p) {\n        _MVE_rmEndMovie();\n        return -2;\n    }\n\n    _rm_active = 1;\n    _rm_hold = 0;\n    _rm_FrameCount = 0;\n    _rm_FrameDropCount = 0;\n\n    return 0;\n}\n\n// 0x4F4C90\nint _ioReset(int stream)\n{\n    Mve* mve;\n\n    _io_handle = stream;\n\n    mve = (Mve*)_ioRead(sizeof(Mve));\n    if (mve == NULL) {\n        return 0;\n    }\n\n    if (strncmp(mve->sig, \"Interplay MVE File\\x1A\\x00\", 20) != 0) {\n        return 0;\n    }\n\n    if (~mve->field_16 - mve->field_18 != 0xFFFFEDCC) {\n        return 0;\n    }\n\n    if (mve->field_16 != 256) {\n        return 0;\n    }\n\n    if (mve->field_14 != 26) {\n        return 0;\n    }\n\n    _io_next_hdr = mve->field_1A;\n\n    return 1;\n}\n\n// Reads data from movie file.\n//\n// 0x4F4D00\nvoid* _ioRead(int size)\n{\n    void* buf;\n\n    buf = _MVE_MemAlloc(&_io_mem_buf, size);\n    if (buf == NULL) {\n        return NULL;\n    }\n\n    return gMovieLibReadProc(_io_handle, buf, size) < 1 ? NULL : buf;\n}\n\n// 0x4F4D40\nvoid* _MVE_MemAlloc(STRUCT_6B3690* a1, unsigned int a2)\n{\n    void* ptr;\n\n    if (a1->field_4 >= a2) {\n        return a1->field_0;\n    }\n\n    if (gMovieLibMallocProc == NULL) {\n        return NULL;\n    }\n\n    _MVE_MemFree(a1);\n\n    ptr = gMovieLibMallocProc(a2 + 100);\n    if (ptr == NULL) {\n        return NULL;\n    }\n\n    _MVE_MemInit(a1, a2 + 100, ptr);\n\n    a1->field_8 = 1;\n\n    return a1->field_0;\n}\n\n// 0x4F4DA0\nunsigned char* _ioNextRecord()\n{\n    unsigned char* buf;\n\n    buf = (unsigned char*)_ioRead((_io_next_hdr & 0xFFFF) + 4);\n    if (buf == NULL) {\n        return NULL;\n    }\n\n    _io_next_hdr = *(int*)(buf + (_io_next_hdr & 0xFFFF));\n\n    return buf;\n}\n\n// 0x4F4DD0\nvoid _sub_4F4DD()\n{\n    if (dword_51EE20) {\n        return;\n    }\n\n    // TODO: Incomplete.\n\n    dword_51EE20 = true;\n}\n\n// 0x4F4E20\nint _MVE_rmHoldMovie()\n{\n    if (!_rm_hold) {\n        _MVE_sndPause();\n        _rm_hold = 1;\n    }\n    _syncWait();\n    return 0;\n}\n\n// 0x4F4E40\nint _syncWait()\n{\n    int result;\n\n    result = 0;\n    if (_sync_active) {\n        if (((_sync_time + 1000 * timeGetTime()) & 0x80000000) != 0) {\n            result = 1;\n            while (((_sync_time + 1000 * timeGetTime()) & 0x80000000) != 0)\n                ;\n        }\n        _sync_time += _sync_wait_quanta;\n    }\n\n    return result;\n}\n\n// 0x4F4EA0\nvoid _MVE_sndPause()\n{\n    if (gMovieLibDirectSoundBuffer != NULL) {\n        IDirectSoundBuffer_Stop(gMovieLibDirectSoundBuffer);\n    }\n}\n\n// 0x4F4EC0\nint _MVE_rmStepMovie()\n{\n    int v0;\n    unsigned short* v1;\n    unsigned int v5;\n    int v6;\n    int v7;\n    int v8;\n    int v9;\n    int v10;\n    int v11;\n    int v12;\n    int v13;\n    unsigned short* v3;\n    unsigned short* v21;\n    unsigned short v22;\n    int v18;\n    int v19;\n    int v20;\n    unsigned char* v14;\n\n    v0 = _rm_len;\n    v1 = (unsigned short*)_rm_p;\n\n    if (!_rm_active) {\n        return -10;\n    }\n\n    if (_rm_hold) {\n        _MVE_sndResume();\n        _rm_hold = 0;\n    }\n\nLABEL_5:\n    v21 = NULL;\n    v3 = NULL;\n    if (!v1) {\n        v6 = -2;\n        _MVE_rmEndMovie();\n        return v6;\n    }\n\n    while (1) {\n        v5 = *(unsigned int*)((unsigned char*)v1 + v0);\n        v1 = (unsigned short*)((unsigned char*)v1 + v0 + 4);\n        v0 = v5 & 0xFFFF;\n\n        switch ((v5 >> 16) & 0xFF) {\n        case 0:\n            return -1;\n        case 1:\n            v0 = 0;\n            v1 = (unsigned short*)_ioNextRecord();\n            goto LABEL_5;\n        case 2:\n            if (!_syncInit(v1[0], v1[2])) {\n                v6 = -3;\n                break;\n            }\n            continue;\n        case 3:\n            if ((v5 >> 24) < 1) {\n                v7 = 0;\n            } else {\n                v7 = (v1[1] & 0x04) >> 2;\n            }\n            v8 = *(unsigned int*)((unsigned char*)v1 + 6);\n            if ((v5 >> 24) == 0) {\n                v8 &= 0xFFFF;\n            }\n\n            if (_MVE_sndConfigure(v1[0], v8, v1[1] & 0x01, v1[2], (v1[1] & 0x02) >> 1, v7)) {\n                continue;\n            }\n\n            v6 = -4;\n            break;\n        case 4:\n            // initialize audio buffers\n            _MVE_sndSync();\n            continue;\n        case 5:\n            v9 = 0;\n            if ((v5 >> 24) >= 2) {\n                v9 = v1[3];\n            }\n\n            v10 = 1;\n            if ((v5 >> 24) >= 1) {\n                v10 = v1[2];\n            }\n\n            if (!_nfConfig(v1[0], v1[1], v10, v9)) {\n                v6 = -5;\n                break;\n            }\n\n            v11 = 4 * _mveBW / dword_51EBDC & 0xFFFFFFF0;\n            if (dword_6B4027) {\n                v11 >>= 1;\n            }\n\n            v12 = _rm_dx;\n            if (v12 < 0) {\n                v12 = 0;\n            }\n\n            if (v11 + v12 > _sf_ScreenWidth) {\n                v6 = -6;\n                break;\n            }\n\n            v13 = _rm_dy;\n            if (v13 < 0) {\n                v13 = 0;\n            }\n\n            if (_mveBH + v13 > _sf_ScreenHeight) {\n                v6 = -6;\n                break;\n            }\n\n            if (dword_6B4027 && !dword_6B3680) {\n                v6 = -6;\n                break;\n            }\n\n            continue;\n        case 7:\n            ++_rm_FrameCount;\n\n            v18 = 0;\n            if ((v5 >> 24) >= 1) {\n                v18 = v1[2];\n            }\n\n            v19 = v1[1];\n            if (v19 == 0 || v21 || dword_6B3680) {\n                _SetPalette_1(v1[0], v19);\n            } else {\n                _SetPalette_(v1[0], v19);\n            }\n\n            if (v21) {\n                _do_nothing_(_rm_dx, _rm_dy, v21);\n            } else if (!_sync_late || v1[1]) {\n                _sfShowFrame(_rm_dx, _rm_dy, v18);\n            } else {\n                _sync_FrameDropped = 1;\n                ++_rm_FrameDropCount;\n            }\n\n            v20 = v1[1];\n            if (v20 && !v21 && !dword_6B3680) {\n                _SetPalette_1(v1[0], v20);\n            }\n\n            _rm_p = (unsigned char*)v1;\n            _rm_len = v0;\n\n            return 0;\n        case 8:\n        case 9:\n            // push data to audio buffers?\n            if (v1[1] & _rm_track_bit) {\n                v14 = (unsigned char*)v1 + 6;\n                if ((v5 >> 16) != 8) {\n                    v14 = NULL;\n                }\n                _CallsSndBuff_Loc(v14, v1[2]);\n            }\n            continue;\n        case 10:\n            if (!dword_51EE0C) {\n                continue;\n            }\n\n            // TODO: Probably never reached.\n\n            continue;\n        case 11:\n            // some kind of palette rotation\n            _palMakeSynthPalette(v1[0], v1[1], v1[2], v1[3], v1[4], v1[5]);\n            continue;\n        case 12:\n            // palette\n            _palLoadPalette((unsigned char*)v1 + 4, v1[0], v1[1]);\n            continue;\n        case 14:\n            // save current position\n            v21 = v1;\n            continue;\n        case 15:\n            // save current position\n            v3 = v1;\n            continue;\n        case 17:\n            // decode video chunk\n            if ((v5 >> 24) < 3) {\n                v6 = -8;\n                break;\n            }\n\n            // swap movie surfaces\n            if (v1[6] & 0x01) {\n                movieSwapSurfaces();\n            }\n\n            if (dword_6B4027) {\n                if (dword_51EBD8) {\n                    v6 = -8;\n                    break;\n                }\n\n                // lock\n                if (!movieLockSurfaces()) {\n                    v6 = -12;\n                    break;\n                }\n\n                // TODO: Incomplete.\n                assert(false);\n                // _nfHPkDecomp(v3, v1[7], v1[2], v1[3], v1[4], v1[5]);\n\n                // unlock\n                movieUnlockSurfaces();\n                continue;\n            }\n\n            if ((dword_51EBD8 & 3) == 1) {\n                // lock\n                if (!movieLockSurfaces()) {\n                    v6 = -12;\n                    break;\n                }\n\n                // TODO: Incomplete.\n                assert(false);\n                // _nfPkDecompH(v3, v1[7], v1[2], v1[3], v1[4], v1[5]);\n\n                // unlock\n                movieUnlockSurfaces();\n                continue;\n            }\n\n            if ((dword_51EBD8 & 3) == 2) {\n                // lock\n                if (!movieLockSurfaces()) {\n                    v6 = -12;\n                    break;\n                }\n\n                // TODO: Incomplete.\n                assert(false);\n                // _nfPkDecompH(v3, v1[7], v1[2], v1[3], v1[4], v1[5]);\n\n                // unlock\n                movieUnlockSurfaces();\n                continue;\n            }\n\n            // lock\n            if (!movieLockSurfaces()) {\n                v6 = -12;\n                break;\n            }\n\n            _nfPkDecomp((unsigned char*)v3, (unsigned char*)&v1[7], v1[2], v1[3], v1[4], v1[5]);\n\n            // unlock\n            movieUnlockSurfaces();\n            continue;\n        default:\n            // unknown chunk\n            continue;\n        }\n    }\n\n    _MVE_rmEndMovie();\n    return v6;\n}\n\n// 0x4F54F0\nint _syncInit(int a1, int a2)\n{\n    int v2;\n\n    v2 = -((a2 >> 1) + a1 * a2);\n\n    if (_sync_active && _sync_wait_quanta == v2) {\n        return 1;\n    }\n\n    _syncWait();\n\n    _sync_wait_quanta = v2;\n\n    _syncReset(v2);\n\n    return 1;\n}\n\n// 0x4F5540\nvoid _syncReset(int a1)\n{\n    _sync_active = 1;\n    _sync_time = -1000 * timeGetTime() + a1;\n}\n\n// 0x4F5570\nint _MVE_sndConfigure(int a1, int a2, int a3, int a4, int a5, int a6)\n{\n    DSBUFFERDESC dsbd;\n    WAVEFORMATEX wfxFormat;\n\n    if (gMovieLibDirectSound == NULL) {\n        return 1;\n    }\n\n    _MVE_sndReset();\n\n    _snd_comp = a3;\n    dword_6B36A0 = a5;\n    _snd_buf = a6;\n\n    dsbd.dwSize = sizeof(DSBUFFERDESC);\n    dsbd.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME;\n    dsbd.dwBufferBytes = (a2 + (a2 >> 1)) & 0xFFFFFFFC;\n    dsbd.dwReserved = 0;\n    dsbd.lpwfxFormat = &wfxFormat;\n\n    wfxFormat.wFormatTag = 1;\n    wfxFormat.nSamplesPerSec = a4;\n    wfxFormat.nChannels = 2 - (a3 < 1);\n    wfxFormat.nBlockAlign = wfxFormat.nChannels * (2 - (a5 < 1));\n    wfxFormat.cbSize = 0;\n    wfxFormat.nAvgBytesPerSec = wfxFormat.nSamplesPerSec * wfxFormat.nBlockAlign;\n    wfxFormat.wBitsPerSample = a5 < 1 ? 8 : 16;\n\n    dword_6B3AE4 = 0;\n    dword_6B3660 = 0;\n\n    if (IDirectSound_CreateSoundBuffer(gMovieLibDirectSound, &dsbd, &gMovieLibDirectSoundBuffer, NULL) != DS_OK) {\n        return 0;\n    }\n\n    IDirectSoundBuffer_SetVolume(gMovieLibDirectSoundBuffer, gMovieLibVolume);\n    IDirectSoundBuffer_SetPan(gMovieLibDirectSoundBuffer, gMovieLibPan);\n\n    dword_6B36A4 = 0;\n\n    stru_6B3668.dwSize = sizeof(DSBCAPS);\n    if (IDirectSoundBuffer_GetCaps(gMovieLibDirectSoundBuffer, &stru_6B3668) != DS_OK) {\n        return 0;\n    }\n\n    return 1;\n}\n\n// 0x4F56C0\nvoid _MVE_syncSync()\n{\n    if (_sync_active) {\n        while (((_sync_time + 1000 * timeGetTime()) & 0x80000000) != 0) {\n        }\n    }\n}\n\n// 0x4F56F0\nvoid _MVE_sndReset()\n{\n    if (gMovieLibDirectSoundBuffer != NULL) {\n        IDirectSoundBuffer_Stop(gMovieLibDirectSoundBuffer);\n        IDirectSoundBuffer_Release(gMovieLibDirectSoundBuffer);\n        gMovieLibDirectSoundBuffer = NULL;\n    }\n}\n\n// 0x4F5720\nvoid _MVE_sndSync()\n{\n    DWORD dwCurrentPlayCursor;\n    DWORD dwCurrentWriteCursor;\n    bool v10;\n    DWORD dwStatus;\n    int v1;\n    bool v2;\n    int v3;\n    int v4;\n    bool v5;\n    bool v0;\n    int v6;\n    int v7;\n    int v8;\n    int v9;\n\n    v0 = false;\n\n    _sync_late = _syncWaitLevel(_sync_wait_quanta >> 2) > -_sync_wait_quanta >> 1 && !_sync_FrameDropped;\n    _sync_FrameDropped = 0;\n\n    if (gMovieLibDirectSound == NULL) {\n        return;\n    }\n\n    if (gMovieLibDirectSoundBuffer == NULL) {\n        return;\n    }\n\n    while (1) {\n        if (IDirectSoundBuffer_GetStatus(gMovieLibDirectSoundBuffer, &dwStatus) != DS_OK) {\n            return;\n        }\n\n        if (IDirectSoundBuffer_GetCurrentPosition(gMovieLibDirectSoundBuffer, &dwCurrentPlayCursor, &dwCurrentWriteCursor) != DS_OK) {\n            return;\n        }\n\n        dwCurrentWriteCursor = dword_6B36A4;\n\n        v1 = (stru_6B3668.dwBufferBytes + dword_6B39E0[dword_6B3660] - _gSoundTimeBase)\n            % stru_6B3668.dwBufferBytes;\n\n        if (dwCurrentPlayCursor <= dword_6B36A4) {\n            if (v1 < dwCurrentPlayCursor || v1 >= dword_6B36A4) {\n                v2 = false;\n            } else {\n                v2 = true;\n            }\n        } else {\n            if (v1 < dwCurrentPlayCursor && v1 >= dword_6B36A4) {\n                v2 = false;\n            } else {\n                v2 = true;\n            }\n        }\n\n        if (!v2 || !(dwStatus & DSBSTATUS_PLAYING)) {\n            if (v0) {\n                _syncReset(_sync_wait_quanta + (_sync_wait_quanta >> 2));\n            }\n\n            v3 = dword_6B39E0[dword_6B3660];\n\n            if (!(dwStatus & DSBSTATUS_PLAYING)) {\n                v4 = (stru_6B3668.dwBufferBytes + v3) % stru_6B3668.dwBufferBytes;\n\n                if (dwCurrentWriteCursor >= dwCurrentPlayCursor) {\n                    if (v4 >= dwCurrentPlayCursor && v4 < dwCurrentWriteCursor) {\n                        v5 = true;\n                    } else {\n                        v5 = false;\n                    }\n                } else if (v4 >= dwCurrentPlayCursor || v4 < dwCurrentWriteCursor) {\n                    v5 = true;\n                } else {\n                    v5 = false;\n                }\n\n                if (v5) {\n                    if (IDirectSoundBuffer_SetCurrentPosition(gMovieLibDirectSoundBuffer, v4) != DS_OK) {\n                        return;\n                    }\n\n                    if (IDirectSoundBuffer_Play(gMovieLibDirectSoundBuffer, 0, 0, 1) != DS_OK) {\n                        return;\n                    }\n                }\n\n                break;\n            }\n\n            v6 = (stru_6B3668.dwBufferBytes + _gSoundTimeBase + v3) % stru_6B3668.dwBufferBytes;\n            v7 = dwCurrentWriteCursor - dwCurrentPlayCursor;\n\n            if (((dwCurrentWriteCursor - dwCurrentPlayCursor) & 0x80000000) != 0) {\n                v7 += stru_6B3668.dwBufferBytes;\n            }\n\n            v8 = stru_6B3668.dwBufferBytes - v7 - 1;\n            if (stru_6B3668.dwBufferBytes / 2 < v8) {\n                v8 = stru_6B3668.dwBufferBytes >> 1;\n            }\n\n            v9 = (stru_6B3668.dwBufferBytes + dwCurrentPlayCursor - v8) % stru_6B3668.dwBufferBytes;\n\n            dwCurrentPlayCursor = v9;\n\n            if (dwCurrentWriteCursor >= v9) {\n                if (v6 < dwCurrentPlayCursor || v6 >= dwCurrentWriteCursor) {\n                    v10 = false;\n                } else {\n                    v10 = true;\n                }\n            } else {\n                if (v6 >= v9 || v6 < dwCurrentWriteCursor) {\n                    v10 = true;\n                } else {\n                    v10 = false;\n                }\n            }\n\n            if (!v10) {\n                IDirectSoundBuffer_Stop(gMovieLibDirectSoundBuffer);\n            }\n\n            break;\n        }\n        v0 = true;\n    }\n\n    if (dword_6B3660 != dword_6B3AE4) {\n        if (dword_6B3660 == 59) {\n            dword_6B3660 = 0;\n        } else {\n            ++dword_6B3660;\n        }\n    }\n}\n\n// 0x4F59B0\nint _syncWaitLevel(int a1)\n{\n    int v2;\n    int result;\n\n    if (!_sync_active) {\n        return 0;\n    }\n\n    v2 = _sync_time + a1;\n    do {\n        result = v2 + 1000 * timeGetTime();\n    } while (result < 0);\n\n    _sync_time += _sync_wait_quanta;\n\n    return result;\n}\n\n// 0x4F5A00\nvoid _CallsSndBuff_Loc(unsigned char* a1, int a2)\n{\n    int v2;\n    int v3;\n    int v5;\n    DWORD dwCurrentPlayCursor;\n    DWORD dwCurrentWriteCursor;\n    LPVOID lpvAudioPtr1;\n    DWORD dwAudioBytes1;\n    LPVOID lpvAudioPtr2;\n    DWORD dwAudioBytes2;\n\n    _gSoundTimeBase = a2;\n\n    if (gMovieLibDirectSoundBuffer == NULL) {\n        return;\n    }\n\n    v5 = 60;\n    if (dword_6B3660) {\n        v5 = dword_6B3660;\n    }\n\n    if (dword_6B3AE4 - v5 == -1) {\n        return;\n    }\n\n    if (IDirectSoundBuffer_GetCurrentPosition(gMovieLibDirectSoundBuffer, &dwCurrentPlayCursor, &dwCurrentWriteCursor) != DS_OK) {\n        return;\n    }\n\n    dwCurrentWriteCursor = dword_6B36A4;\n\n    if (IDirectSoundBuffer_Lock(gMovieLibDirectSoundBuffer, dword_6B36A4, a2, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2, 0) != DS_OK) {\n        return;\n    }\n\n    v2 = 0;\n    v3 = 1;\n    if (dwAudioBytes1 != 0) {\n        v2 = _MVE_sndAdd((unsigned char*)lpvAudioPtr1, &a1, dwAudioBytes1, 0, 1);\n        v3 = 0;\n        dword_6B36A4 += dwAudioBytes1;\n    }\n\n    if (dwAudioBytes2 != 0) {\n        _MVE_sndAdd((unsigned char*)lpvAudioPtr2, &a1, dwAudioBytes2, v2, v3);\n        dword_6B36A4 = dwAudioBytes2;\n    }\n\n    if (dword_6B36A4 == stru_6B3668.dwBufferBytes) {\n        dword_6B36A4 = 0;\n    }\n\n    IDirectSoundBuffer_Unlock(gMovieLibDirectSoundBuffer, lpvAudioPtr1, dwAudioBytes1, lpvAudioPtr2, dwAudioBytes2);\n\n    dword_6B39E0[dword_6B3AE4] = dwCurrentWriteCursor;\n\n    if (dword_6B3AE4 == 59) {\n        dword_6B3AE4 = 0;\n    } else {\n        ++dword_6B3AE4;\n    }\n}\n\n// 0x4F5B70\nint _MVE_sndAdd(unsigned char* dest, unsigned char** src_ptr, int a3, int a4, int a5)\n{\n    unsigned char* src;\n    int v9;\n    unsigned short* v10;\n    int v11;\n    int result;\n\n    int v12;\n    unsigned short* v13;\n    int v14;\n\n    src = *src_ptr;\n\n    if (*src_ptr == NULL) {\n        memset(dest, dword_6B36A0 < 1 ? 0x80 : 0, a3);\n        *src_ptr = NULL;\n        return a4;\n    }\n\n    if (!_snd_buf) {\n        memcpy(dest, src_ptr, a3);\n        *src_ptr += a3;\n        return a4;\n    }\n\n    if (!_snd_comp) {\n        if (a5) {\n            v9 = *(unsigned short*)src;\n            src += 2;\n\n            *(unsigned short*)dest = v9;\n            v10 = (unsigned short*)(dest + 2);\n            v11 = a3 - 2;\n        } else {\n            v9 = a4;\n            v10 = (unsigned short*)dest;\n            v11 = a3;\n        }\n\n        result = _MVE_sndDecompM16(v10, src, v11 >> 1, v9);\n        *src_ptr = src + (v11 >> 1);\n        return result;\n    }\n\n    if (a5) {\n        v12 = *(unsigned int*)src;\n        src += 4;\n\n        *(unsigned int*)dest = v12;\n        v13 = (unsigned short*)(dest + 4);\n        v14 = a3 - 4;\n    } else {\n        v13 = (unsigned short*)dest;\n        v14 = a3;\n        v12 = a4;\n    }\n\n    result = _MVE_sndDecompS16(v13, src, v14 >> 2, v12);\n    *src_ptr = src + (v14 >> 1);\n\n    return result;\n}\n\n// 0x4F5CA0\nvoid _MVE_sndResume()\n{\n}\n\n// 0x4F5CB0\nint _nfConfig(int a1, int a2, int a3, int a4)\n{\n    DDSURFACEDESC ddsd;\n\n    if (gMovieDirectDrawSurface1 != NULL) {\n        IDirectDrawSurface_Release(gMovieDirectDrawSurface1);\n        gMovieDirectDrawSurface1 = NULL;\n    }\n\n    if (gMovieDirectDrawSurface2 != NULL) {\n        IDirectDrawSurface_Release(gMovieDirectDrawSurface2);\n        gMovieDirectDrawSurface2 = NULL;\n    }\n\n    byte_6B400D = a1;\n    byte_6B400C = a2;\n    byte_6B4016 = a3;\n    _mveBW = 8 * a1;\n    _mveBH = 8 * a2 * a3;\n\n    if (dword_51EBD8) {\n        _mveBH >>= 1;\n    }\n\n    memset(&ddsd, 0, sizeof(DDSURFACEDESC));\n\n    ddsd.dwSize = sizeof(DDSURFACEDESC);\n    ddsd.dwFlags = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT);\n    ddsd.dwWidth = _mveBW;\n    ddsd.dwHeight = _mveBH;\n    ddsd.ddsCaps.dwCaps = (DDSCAPS_SYSTEMMEMORY | DDSCAPS_OFFSCREENPLAIN);\n    ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);\n\n    if (a4) {\n        ddsd.ddpfPixelFormat.dwFlags = 64;\n        ddsd.ddpfPixelFormat.dwRGBBitCount = 16;\n        ddsd.ddpfPixelFormat.dwRBitMask = 0x7C00;\n        ddsd.ddpfPixelFormat.dwGBitMask = 0x3E0;\n        ddsd.ddpfPixelFormat.dwBBitMask = 0x1F;\n    } else {\n        ddsd.ddpfPixelFormat.dwFlags = 96;\n        ddsd.ddpfPixelFormat.dwRGBBitCount = 8;\n    }\n\n    if (IDirectDraw_CreateSurface(gMovieLibDirectDraw, &ddsd, &gMovieDirectDrawSurface1, NULL) != DD_OK) {\n        return 0;\n    }\n\n    if (IDirectDraw_CreateSurface(gMovieLibDirectDraw, &ddsd, &gMovieDirectDrawSurface2, NULL) != DD_OK) {\n        return 0;\n    }\n\n    dword_6B4027 = a4;\n    dword_6B402B = a3 * _mveBW - 8;\n\n    if (a4) {\n        _mveBW *= 2;\n        dword_6B402B *= 2;\n    }\n\n    dword_6B3D00 = 8 * a3 * _mveBW;\n    dword_6B3CEC = 7 * a3 * _mveBW;\n\n    _nfPkConfig();\n\n    return 1;\n}\n\n// 0x4F5E60\nbool movieLockSurfaces()\n{\n    DDSURFACEDESC ddsd;\n\n    ddsd.dwSize = sizeof(DDSURFACEDESC);\n\n    if (gMovieDirectDrawSurface1 != NULL && gMovieDirectDrawSurface2 != NULL) {\n        if (IDirectDrawSurface_Lock(gMovieDirectDrawSurface1, NULL, &ddsd, 0, NULL) != DD_OK) {\n            return false;\n        }\n\n        gMovieDirectDrawSurfaceBuffer1 = (unsigned char*)ddsd.lpSurface;\n\n        if (IDirectDrawSurface_Lock(gMovieDirectDrawSurface2, NULL, &ddsd, 0, NULL) != DD_OK) {\n            return false;\n        }\n\n        gMovieDirectDrawSurfaceBuffer2 = (unsigned char*)ddsd.lpSurface;\n    }\n\n    return true;\n}\n\n// 0x4F5EF0\nvoid movieUnlockSurfaces()\n{\n    IDirectDrawSurface_Unlock(gMovieDirectDrawSurface1, NULL);\n    IDirectDrawSurface_Unlock(gMovieDirectDrawSurface2, NULL);\n}\n\n// 0x4F5F20\nvoid movieSwapSurfaces()\n{\n    LPDIRECTDRAWSURFACE tmp = gMovieDirectDrawSurface2;\n    gMovieDirectDrawSurface2 = gMovieDirectDrawSurface1;\n    gMovieDirectDrawSurface1 = tmp;\n}\n\n// 0x4F5F40\nvoid _sfShowFrame(int a1, int a2, int a3)\n{\n    int v3;\n    int v4;\n    int v5;\n    int v6;\n    int v7;\n\n    v4 = ((4 * _mveBW / dword_51EBDC - 12) & 0xFFFFFFF0) + 12;\n\n    dword_6B3CF8 = _mveBW - dword_51EBDC * (v4 >> 2);\n\n    v3 = a1;\n    if (a1 < 0) {\n        if (dword_6B4027) {\n            v3 = (_sf_ScreenWidth - (v4 >> 1)) >> 1;\n        } else {\n            v3 = (_sf_ScreenWidth - v4) >> 1;\n        }\n    }\n\n    if (dword_6B4027) {\n        v3 *= 2;\n    }\n\n    v5 = a2;\n    if (a2 >= 0) {\n        v6 = _mveBH;\n    } else {\n        v6 = _mveBH;\n        if (dword_51EBD8 & 4) {\n            v5 = (_sf_ScreenHeight - 2 * _mveBH) >> 1;\n        } else {\n            v5 = (_sf_ScreenHeight - _mveBH) >> 1;\n        }\n    }\n\n    v7 = v3 & 0xFFFFFFFC;\n    if (dword_51EBD8 & 4) {\n        v5 >>= 1;\n    }\n\n    if (a3) {\n        // TODO: Incomplete.\n        // _mve_ShowFrameField(off_6B4033, _mveBW, v6, dword_6B401B, dword_6B401F, dword_6B4017, dword_6B4023, v7, v5, a3);\n    } else if (dword_51EBDC == 4) {\n        _sf_ShowFrame(gMovieDirectDrawSurface1, _mveBW, v6, dword_6B401B, dword_6B401F, dword_6B4017, dword_6B4023, v7, v5);\n    } else {\n        _sf_ShowFrame(gMovieDirectDrawSurface1, _mveBW, v6, 0, dword_6B401F, ((4 * _mveBW / dword_51EBDC - 12) & 0xFFFFFFF0) + 12, dword_6B4023, v7, v5);\n    }\n}\n\n// 0x4F6080\nvoid _do_nothing_(int a1, int a2, unsigned short* a3)\n{\n}\n\n// 0x4F6090\nvoid _SetPalette_1(int a1, int a2)\n{\n    if (!dword_6B4027) {\n        _pal_SetPalette(_pal_tbl, a1, a2);\n    }\n}\n\n// 0x4F60C0\nvoid _SetPalette_(int a1, int a2)\n{\n    if (!dword_6B4027) {\n        _pal_SetPalette(_palette_entries1, a1, a2);\n    }\n}\n\n// 0x4F60F0\nvoid _palMakeSynthPalette(int a1, int a2, int a3, int a4, int a5, int a6)\n{\n    int i;\n    int j;\n\n    for (i = 0; i < a2; i++) {\n        for (j = 0; j < a3; j++) {\n            _pal_tbl[3 * a1 + 3 * j] = (63 * i) / (a2 - 1);\n            _pal_tbl[3 * a1 + 3 * j + 1] = 0;\n            _pal_tbl[3 * a1 + 3 * j + 2] = 5 * ((63 * j) / (a3 - 1)) / 8;\n        }\n    }\n\n    for (i = 0; i < a5; i++) {\n        for (j = 0; j < a6; j++) {\n            _pal_tbl[3 * a4 + 3 * j] = 0;\n            _pal_tbl[3 * a4 + 3 * j + 1] = (63 * i) / (a5 - 1);\n            _pal_tbl[3 * a1 + 3 * j + 2] = 5 * ((63 * j) / (a6 - 1)) / 8;\n        }\n    }\n}\n\n// 0x4F6210\nvoid _palLoadPalette(unsigned char* palette, int a2, int a3)\n{\n    memcpy(_pal_tbl + 3 * a2, palette, 3 * a3);\n}\n\n// 0x4F6240\nvoid _MVE_rmEndMovie()\n{\n    if (_rm_active) {\n        _syncWait();\n        _syncRelease();\n        _MVE_sndReset();\n        _rm_active = 0;\n    }\n}\n\n// 0x4F6270\nvoid _syncRelease()\n{\n    _sync_active = 0;\n}\n\n// 0x4F6350\nvoid _MVE_ReleaseMem()\n{\n    _MVE_rmEndMovie();\n    _ioRelease();\n    _MVE_sndRelease();\n    _nfRelease();\n}\n\n// 0x4F6370\nvoid _ioRelease()\n{\n    _MVE_MemFree(&_io_mem_buf);\n}\n\n// 0x4F6380\nvoid _MVE_sndRelease()\n{\n}\n\n// 0x4F6390\nvoid _nfRelease()\n{\n    if (gMovieDirectDrawSurface1 != NULL) {\n        IDirectDrawSurface_Release(gMovieDirectDrawSurface1);\n        gMovieDirectDrawSurface1 = NULL;\n    }\n\n    if (gMovieDirectDrawSurface2 != NULL) {\n        IDirectDrawSurface_Release(gMovieDirectDrawSurface2);\n        gMovieDirectDrawSurface2 = NULL;\n    }\n}\n\n// 0x4F6550\nvoid _frLoad(STRUCT_4F6930* a1)\n{\n    gMovieLibReadProc = a1->readProc;\n    _io_mem_buf.field_0 = a1->field_8.field_0;\n    _io_mem_buf.field_4 = a1->field_8.field_4;\n    _io_mem_buf.field_8 = a1->field_8.field_8;\n    _io_handle = a1->fileHandle;\n    _io_next_hdr = a1->field_18;\n    gMovieDirectDrawSurface1 = a1->field_24;\n    gMovieDirectDrawSurface2 = a1->field_28;\n    dword_6B3AE8 = a1->field_2C;\n    gMovieDirectDrawSurfaceBuffer1 = a1->field_30;\n    gMovieDirectDrawSurfaceBuffer2 = a1->field_34;\n    byte_6B400D = a1->field_38;\n    byte_6B400C = a1->field_39;\n    byte_6B4016 = a1->field_3A;\n    dword_6B4027 = a1->field_3C;\n    _mveBW = a1->field_40;\n    _mveBH = a1->field_44;\n    dword_6B402B = a1->field_48;\n    dword_6B3D00 = a1->field_4C;\n    dword_6B3CEC = a1->field_50;\n}\n\n// 0x4F6610\nvoid _frSave(STRUCT_4F6930* a1)\n{\n    STRUCT_6B3690* ptr;\n\n    ptr = &(a1->field_8);\n    a1->readProc = gMovieLibReadProc;\n    ptr->field_0 = _io_mem_buf.field_0;\n    ptr->field_4 = _io_mem_buf.field_4;\n    ptr->field_8 = _io_mem_buf.field_8;\n    a1->fileHandle = _io_handle;\n    a1->field_18 = _io_next_hdr;\n    a1->field_24 = gMovieDirectDrawSurface1;\n    a1->field_28 = gMovieDirectDrawSurface2;\n    a1->field_2C = dword_6B3AE8;\n    a1->field_30 = gMovieDirectDrawSurfaceBuffer1;\n    a1->field_34 = gMovieDirectDrawSurfaceBuffer2;\n    a1->field_38 = byte_6B400D;\n    a1->field_39 = byte_6B400C;\n    a1->field_3A = byte_6B4016;\n    a1->field_3C = dword_6B4027;\n    a1->field_40 = _mveBW;\n    a1->field_44 = _mveBH;\n    a1->field_48 = dword_6B402B;\n    a1->field_4C = dword_6B3D00;\n    a1->field_50 = dword_6B3CEC;\n}\n\n// 0x4F6930\nvoid _MVE_frClose(STRUCT_4F6930* a1)\n{\n    STRUCT_4F6930 v1;\n\n    _frSave(&v1);\n    _frLoad(a1);\n    _ioRelease();\n    _nfRelease();\n    _frLoad(&v1);\n\n    if (gMovieLibFreeProc != NULL) {\n        gMovieLibFreeProc(a1);\n    }\n}\n\n// 0x4F697C\nint _MVE_sndDecompM16(unsigned short* a1, unsigned char* a2, int a3, int a4)\n{\n    int i;\n    int v8;\n    unsigned short result;\n\n    result = a4;\n\n    v8 = 0;\n    for (i = 0; i < a3; i++) {\n        v8 = *a2++;\n        result += word_51EBE0[v8];\n        *a1++ = result;\n    }\n\n    return result;\n}\n\n// 0x4F69AD\nint _MVE_sndDecompS16(unsigned short* a1, unsigned char* a2, int a3, int a4)\n{\n    int i;\n    unsigned short v4;\n    unsigned short v5;\n    unsigned short v9;\n\n    v4 = a4 & 0xFFFF;\n    v5 = (a4 >> 16) & 0xFFFF;\n\n    v9 = 0;\n    for (i = 0; i < a3; i++) {\n        v9 = *a2++;\n        v4 = (word_51EBE0[v9] + v4) & 0xFFFF;\n        *a1++ = v4;\n\n        v9 = *a2++;\n        v5 = (word_51EBE0[v9] + v5) & 0xFFFF;\n        *a1++ = v5;\n    }\n\n    return (v5 << 16) | v4;\n}\n\n// 0x4F731D\nvoid _nfPkConfig()\n{\n    int* ptr;\n    int v1;\n    int v2;\n    int v3;\n    int v4;\n    int v5;\n\n    ptr = dword_51F018;\n    v1 = _mveBW;\n    v2 = 0;\n\n    v3 = 128;\n    do {\n        *ptr++ = v2;\n        v2 += v1;\n        --v3;\n    } while (v3);\n\n    v4 = -128 * v1;\n    v5 = 128;\n    do {\n        *ptr++ = v4;\n        v4 += v1;\n        --v5;\n    } while (v5);\n}\n\n// 0x4F7359\nvoid _nfPkDecomp(unsigned char* a1, unsigned char* a2, int a3, int a4, int a5, int a6)\n{\n    int v49;\n    unsigned char* dest;\n    int v8;\n    int v7;\n    int i;\n    int j;\n    int v10;\n    int v11;\n    int v13;\n    int byte;\n    unsigned int value1;\n    unsigned int value2;\n    int var_10;\n    unsigned char map1[512];\n    unsigned int map2[256];\n    int var_8;\n    unsigned int* src_ptr;\n    unsigned int* dest_ptr;\n    unsigned int nibbles[2];\n\n    dword_6B401B = 8 * a3;\n    dword_6B4017 = 8 * a5;\n    dword_6B401F = 8 * a4 * byte_6B4016;\n    dword_6B4023 = 8 * a6 * byte_6B4016;\n\n    var_8 = dword_6B3D00 - dword_6B4017;\n    dest = gMovieDirectDrawSurfaceBuffer1;\n\n    var_10 = dword_6B3CEC - 8;\n\n    if (a3 || a4) {\n        dest = gMovieDirectDrawSurfaceBuffer1 + dword_6B401B + _mveBW * dword_6B401F;\n    }\n\n    while (a6--) {\n        v49 = a5 >> 1;\n        while (v49--) {\n            v8 = *a1++;\n            nibbles[0] = v8 & 0xF;\n            nibbles[1] = v8 >> 4;\n            for (j = 0; j < 2; j++) {\n                v7 = nibbles[j];\n\n                switch (v7) {\n                case 1:\n                    dest += 8;\n                    break;\n                case 0:\n                case 2:\n                case 3:\n                case 4:\n                case 5:\n                    switch (v7) {\n                    case 0:\n                        v10 = gMovieDirectDrawSurfaceBuffer2 - gMovieDirectDrawSurfaceBuffer1;\n                        break;\n                    case 2:\n                    case 3:\n                        byte = *a2++;\n                        v11 = word_51F618[byte];\n                        if (v7 == 3) {\n                            v11 = ((-(v11 & 0xFF)) & 0xFF) | ((-(v11 >> 8) & 0xFF) << 8);\n                        } else {\n                            v11 = v11;\n                        }\n                        v10 = ((v11 << 24) >> 24) + dword_51F018[v11 >> 8];\n                        break;\n                    case 4:\n                    case 5:\n                        if (v7 == 4) {\n                            byte = *a2++;\n                            v13 = word_51F418[byte];\n                        } else {\n                            v13 = *(unsigned short*)a2;\n                            a2 += 2;\n                        }\n\n                        v10 = ((v13 << 24) >> 24) + dword_51F018[v13 >> 8] + (gMovieDirectDrawSurfaceBuffer2 - gMovieDirectDrawSurfaceBuffer1);\n                        break;\n                    }\n\n                    value2 = _mveBW;\n\n                    for (i = 0; i < 8; i++) {\n                        src_ptr = (unsigned int*)(dest + v10);\n                        dest_ptr = (unsigned int*)dest;\n\n                        dest_ptr[0] = src_ptr[0];\n                        dest_ptr[1] = src_ptr[1];\n\n                        dest += value2;\n                    }\n\n                    dest -= value2;\n\n                    dest -= var_10;\n\n                    break;\n                case 6:\n                    nibbles[0] += 2;\n                    while (nibbles[0]--) {\n                        dest += 16;\n\n                        if (v49--) {\n                            continue;\n                        }\n\n                        dest += var_8;\n\n                        a6--;\n                        v49 = (a5 >> 1) - 1;\n                    }\n                    break;\n                case 7:\n                    if (a2[0] > a2[1]) {\n                        // 7/1\n                        for (i = 0; i < 2; i++) {\n                            value1 = _$$R0053[a2[2 + i] & 0xF];\n                            map1[i * 8] = value1 & 0xFF;\n                            map1[i * 8 + 1] = (value1 >> 8) & 0xFF;\n                            map1[i * 8 + 2] = (value1 >> 16) & 0xFF;\n                            map1[i * 8 + 3] = (value1 >> 24) & 0xFF;\n\n                            value1 = _$$R0053[a2[2 + i] >> 4];\n                            map1[i * 8 + 4] = value1 & 0xFF;\n                            map1[i * 8 + 5] = (value1 >> 8) & 0xFF;\n                            map1[i * 8 + 6] = (value1 >> 16) & 0xFF;\n                            map1[i * 8 + 7] = (value1 >> 24) & 0xFF;\n                        }\n\n                        map2[0xC1] = (a2[1] << 8) | a2[1]; // cx\n                        map2[0xC3] = (a2[0] << 8) | a2[0]; // bx\n\n                        value2 = _mveBW;\n\n                        for (i = 0; i < 4; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[i * 4]] << 16) | (map2[map1[i * 4 + 1]]);\n\n                            dest_ptr = (unsigned int*)(dest + value2);\n                            dest_ptr[0] = (map2[map1[i * 4]] << 16) | (map2[map1[i * 4 + 1]]);\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[1] = (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]]);\n\n                            dest_ptr = (unsigned int*)(dest + value2);\n                            dest_ptr[1] = (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]]);\n\n                            dest += value2 * 2;\n                        }\n\n                        dest -= value2;\n\n                        a2 += 4;\n                        dest -= var_10;\n                    } else {\n                        // 7/2\n                        // VERIFIED\n                        for (i = 0; i < 8; i++) {\n                            value1 = _$$R0004[a2[2 + i]];\n                            map1[i * 4] = value1 & 0xFF;\n                            map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        map2[0xC1] = (a2[1] << 8) | a2[0]; // cx\n                        map2[0xC3] = (a2[0] << 8) | a2[0]; // bx\n                        map2[0xC2] = (a2[0] << 8) | a2[1]; // dx\n                        map2[0xC5] = (a2[1] << 8) | a2[1]; // bp\n\n                        value2 = _mveBW;\n\n                        for (i = 0; i < 8; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[i * 4]] << 16) | map2[map1[i * 4 + 1]];\n                            dest_ptr[1] = (map2[map1[i * 4 + 2]] << 16) | map2[map1[i * 4 + 3]];\n\n                            dest += value2;\n                        }\n\n                        dest -= value2;\n\n                        a2 += 10;\n                        dest -= var_10;\n                    }\n\n                    break;\n                case 8:\n                    if (a2[0] > a2[1]) {\n                        if (a2[6] > a2[7]) {\n                            // 8/1\n                            for (i = 0; i < 4; i++) {\n                                value1 = _$$R0004[a2[2 + i]];\n                                map1[i * 4] = value1 & 0xFF;\n                                map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            for (i = 0; i < 4; i++) {\n                                value1 = _$$R0004[a2[8 + i]];\n                                map1[16 + i * 4] = value1 & 0xFF;\n                                map1[16 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[16 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[16 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            value2 = _mveBW;\n\n                            map2[0xC1] = (a2[1] << 8) | a2[0]; // cx\n                            map2[0xC3] = (a2[0] << 8) | a2[0]; // bx\n                            map2[0xC2] = (a2[0] << 8) | a2[1]; // dx\n                            map2[0xC5] = (a2[1] << 8) | a2[1]; // bp\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 4]] << 16) | map2[map1[i * 4 + 1]];\n                                dest_ptr[1] = (map2[map1[i * 4 + 2]] << 16) | map2[map1[i * 4 + 3]];\n\n                                dest += value2;\n                            }\n\n                            map2[0xC1] = (a2[6 + 1] << 8) | a2[6 + 0]; // cx\n                            map2[0xC3] = (a2[6 + 0] << 8) | a2[6 + 0]; // bx\n                            map2[0xC2] = (a2[6 + 0] << 8) | a2[6 + 1]; // dx\n                            map2[0xC5] = (a2[6 + 1] << 8) | a2[6 + 1]; // bp\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[16 + i * 4]] << 16) | map2[map1[16 + i * 4 + 1]];\n                                dest_ptr[1] = (map2[map1[16 + i * 4 + 2]] << 16) | map2[map1[16 + i * 4 + 3]];\n\n                                dest += value2;\n                            }\n\n                            dest -= value2;\n\n                            a2 += 12;\n                            dest -= var_10;\n                        } else {\n                            // 8/2\n                            for (i = 0; i < 4; i++) {\n                                value1 = _$$R0004[a2[2 + i]];\n                                map1[i * 4] = value1 & 0xFF;\n                                map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            for (i = 0; i < 4; i++) {\n                                value1 = _$$R0004[a2[8 + i]];\n                                map1[16 + i * 4] = value1 & 0xFF;\n                                map1[16 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[16 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[16 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            value2 = _mveBW;\n\n                            map2[0xC1] = (a2[1] << 8) | a2[0]; // cx\n                            map2[0xC3] = (a2[0] << 8) | a2[0]; // bx\n                            map2[0xC2] = (a2[0] << 8) | a2[1]; // dx\n                            map2[0xC5] = (a2[1] << 8) | a2[1]; // bp\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 4]] << 16) | map2[map1[i * 4 + 1]];\n                                dest += value2;\n\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 4 + 2]] << 16) | map2[map1[i * 4 + 3]];\n                                dest += value2;\n                            }\n\n                            dest -= value2 * 8 - 4;\n\n                            map2[0xC1] = (a2[6 + 1] << 8) | a2[6 + 0]; // cx\n                            map2[0xC3] = (a2[6 + 0] << 8) | a2[6 + 0]; // bx\n                            map2[0xC2] = (a2[6 + 0] << 8) | a2[6 + 1]; // dx\n                            map2[0xC5] = (a2[6 + 1] << 8) | a2[6 + 1]; // bp\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[16 + i * 4]] << 16) | map2[map1[16 + i * 4 + 1]];\n                                dest += value2;\n\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[16 + i * 4 + 2]] << 16) | map2[map1[16 + i * 4 + 3]];\n                                dest += value2;\n                            }\n\n                            dest -= value2;\n\n                            a2 += 12;\n                            dest -= 4;\n                            dest -= var_10;\n                        }\n                    } else {\n                        // 8/3\n                        // VERIFIED\n                        for (i = 0; i < 2; i++) {\n                            value1 = _$$R0004[a2[2 + i]];\n                            map1[i * 4] = value1 & 0xFF;\n                            map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        for (i = 0; i < 2; i++) {\n                            value1 = _$$R0004[a2[6 + i]];\n                            map1[8 + i * 4] = value1 & 0xFF;\n                            map1[8 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[8 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[8 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        for (i = 0; i < 2; i++) {\n                            value1 = _$$R0004[a2[10 + i]];\n                            map1[16 + i * 4] = value1 & 0xFF;\n                            map1[16 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[16 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[16 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        for (i = 0; i < 2; i++) {\n                            value1 = _$$R0004[a2[14 + i]];\n                            map1[24 + i * 4] = value1 & 0xFF;\n                            map1[24 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[24 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[24 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        value2 = _mveBW;\n\n                        map2[0xC1] = (a2[1] << 8) | a2[0]; // cx\n                        map2[0xC3] = (a2[0] << 8) | a2[0]; // bx\n                        map2[0xC2] = (a2[0] << 8) | a2[1]; // dx\n                        map2[0xC5] = (a2[1] << 8) | a2[1]; // bp\n\n                        for (i = 0; i < 2; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[i * 4]] << 16) | map2[map1[i * 4 + 1]];\n                            dest += value2;\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[i * 4 + 2]] << 16) | map2[map1[i * 4 + 3]];\n                            dest += value2;\n                        }\n\n                        map2[0xC1] = (a2[4 + 1] << 8) | a2[4 + 0]; // cx\n                        map2[0xC3] = (a2[4 + 0] << 8) | a2[4 + 0]; // bx\n                        map2[0xC2] = (a2[4 + 0] << 8) | a2[4 + 1]; // dx\n                        map2[0xC5] = (a2[4 + 1] << 8) | a2[4 + 1]; // bp\n\n                        for (i = 0; i < 2; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[8 + i * 4]] << 16) | map2[map1[8 + i * 4 + 1]];\n                            dest += value2;\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[8 + i * 4 + 2]] << 16) | map2[map1[8 + i * 4 + 3]];\n                            dest += value2;\n                        }\n\n                        dest -= value2 * 8 - 4;\n\n                        map2[0xC1] = (a2[8 + 1] << 8) | a2[8 + 0]; // cx\n                        map2[0xC3] = (a2[8 + 0] << 8) | a2[8 + 0]; // bx\n                        map2[0xC2] = (a2[8 + 0] << 8) | a2[8 + 1]; // dx\n                        map2[0xC5] = (a2[8 + 1] << 8) | a2[8 + 1]; // bp\n\n                        for (i = 0; i < 2; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[16 + i * 4]] << 16) | map2[map1[16 + i * 4 + 1]];\n                            dest += value2;\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[16 + i * 4 + 2]] << 16) | map2[map1[16 + i * 4 + 3]];\n                            dest += value2;\n                        }\n\n                        map2[0xC1] = (a2[12 + 1] << 8) | a2[12 + 0]; // cx\n                        map2[0xC3] = (a2[12 + 0] << 8) | a2[12 + 0]; // bx\n                        map2[0xC2] = (a2[12 + 0] << 8) | a2[12 + 1]; // dx\n                        map2[0xC5] = (a2[12 + 1] << 8) | a2[12 + 1]; // bp\n\n                        for (i = 0; i < 2; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[24 + i * 4]] << 16) | map2[map1[24 + i * 4 + 1]];\n                            dest += value2;\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[24 + i * 4 + 2]] << 16) | map2[map1[24 + i * 4 + 3]];\n                            dest += value2;\n                        }\n\n                        dest -= value2;\n\n                        a2 += 16;\n                        dest -= 4;\n                        dest -= var_10;\n                    }\n\n                    break;\n                case 9:\n                    if (a2[0] > a2[1]) {\n                        if (a2[2] > a2[3]) {\n                            // 9/1\n                            // VERIFIED\n                            for (i = 0; i < 8; i++) {\n                                value1 = _$$R0063[a2[4 + i]];\n                                map1[i * 4] = value1 & 0xFF;\n                                map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            map2[0xC1] = a2[2]; // mov al, cl\n                            map2[0xC3] = a2[0]; // mov al, bl\n                            map2[0xC5] = a2[3]; // mov al, ch\n                            map2[0xC7] = a2[1]; // mov al, bh\n                            map2[0xE1] = a2[2]; // mov ah, cl\n                            map2[0xE3] = a2[0]; // mov ah, bl\n                            map2[0xE5] = a2[3]; // mov ah, ch\n                            map2[0xE7] = a2[1]; // mov ah, bh\n\n                            value2 = _mveBW;\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 8]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8);\n                                dest_ptr[1] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8);\n\n                                dest_ptr = (unsigned int*)(dest + value2);\n                                dest_ptr[0] = (map2[map1[i * 8]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8);\n                                dest_ptr[1] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8);\n\n                                dest += value2 * 2;\n                            }\n\n                            dest -= value2;\n\n                            a2 += 12;\n                            dest -= var_10;\n                        } else {\n                            // 9/2\n                            // VERIFIED\n                            for (i = 0; i < 8; i++) {\n                                value1 = _$$R0063[a2[4 + i]];\n                                map1[i * 4 + 3] = value1 & 0xFF;\n                                map1[i * 4 + 2] = (value1 >> 8) & 0xFF;\n                                map1[i * 4 + 1] = (value1 >> 16) & 0xFF;\n                                map1[i * 4] = (value1 >> 24) & 0xFF;\n                            }\n\n                            map2[0xC1] = a2[2]; // mov al, cl\n                            map2[0xC3] = a2[0]; // mov al, bl\n                            map2[0xC5] = a2[3]; // mov al, ch\n                            map2[0xC7] = a2[1]; // mov al, bh\n                            map2[0xE1] = a2[2]; // mov ah, cl\n                            map2[0xE3] = a2[0]; // mov ah, bl\n                            map2[0xE5] = a2[3]; // mov ah, ch\n                            map2[0xE7] = a2[1]; // mov ah, bh\n\n                            value2 = _mveBW;\n\n                            for (i = 0; i < 8; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 4]] << 24) | (map2[map1[i * 4 + 0]] << 16) | (map2[map1[i * 4 + 1]] << 8) | (map2[map1[i * 4 + 1]]);\n                                dest_ptr[1] = (map2[map1[i * 4 + 2]] << 24) | (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]] << 8) | (map2[map1[i * 4 + 3]]);\n\n                                dest += value2;\n                            }\n\n                            dest -= value2;\n\n                            a2 += 12;\n                            dest -= var_10;\n                        }\n                    } else {\n                        if (a2[2] > a2[3]) {\n                            // 9/3\n                            // VERIFIED\n                            for (i = 0; i < 4; i++) {\n                                value1 = _$$R0063[a2[4 + i]];\n                                map1[i * 4 + 3] = value1 & 0xFF;\n                                map1[i * 4 + 2] = (value1 >> 8) & 0xFF;\n                                map1[i * 4 + 1] = (value1 >> 16) & 0xFF;\n                                map1[i * 4] = (value1 >> 24) & 0xFF;\n                            }\n\n                            map2[0xC1] = a2[2]; // mov al, cl\n                            map2[0xC3] = a2[0]; // mov al, bl\n                            map2[0xC5] = a2[3]; // mov al, ch\n                            map2[0xC7] = a2[1]; // mov al, bh\n                            map2[0xE1] = a2[2]; // mov ah, cl\n                            map2[0xE3] = a2[0]; // mov ah, bl\n                            map2[0xE5] = a2[3]; // mov ah, ch\n                            map2[0xE7] = a2[1]; // mov ah, bh\n\n                            value2 = _mveBW;\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 4 + 0]] << 24) | (map2[map1[i * 4 + 0]] << 16) | (map2[map1[i * 4 + 1]] << 8) | (map2[map1[i * 4 + 1]]);\n                                dest_ptr[1] = (map2[map1[i * 4 + 2]] << 24) | (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]] << 8) | (map2[map1[i * 4 + 3]]);\n\n                                dest += value2;\n\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 4 + 0]] << 24) | (map2[map1[i * 4 + 0]] << 16) | (map2[map1[i * 4 + 1]] << 8) | (map2[map1[i * 4 + 1]]);\n                                dest_ptr[1] = (map2[map1[i * 4 + 2]] << 24) | (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]] << 8) | (map2[map1[i * 4 + 3]]);\n\n                                dest += value2;\n                            }\n\n                            dest -= value2;\n\n                            a2 += 8;\n                            dest -= var_10;\n                        } else {\n                            // 9/4\n                            // VERIFIED\n                            for (i = 0; i < 16; i++) {\n                                value1 = _$$R0063[a2[4 + i]];\n                                map1[i * 4] = value1 & 0xFF;\n                                map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            map2[0xC1] = a2[2]; // mov al, cl\n                            map2[0xC3] = a2[0]; // mov al, bl\n                            map2[0xC5] = a2[3]; // mov al, ch\n                            map2[0xC7] = a2[1]; // mov al, bh\n                            map2[0xE1] = a2[2]; // mov ah, cl\n                            map2[0xE3] = a2[0]; // mov ah, bl\n                            map2[0xE5] = a2[3]; // mov ah, ch\n                            map2[0xE7] = a2[1]; // mov ah, bh\n\n                            value2 = _mveBW;\n\n                            for (i = 0; i < 8; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 8 + 0]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8);\n                                dest_ptr[1] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8);\n                                dest += value2;\n                            }\n\n                            dest -= value2;\n\n                            a2 += 20;\n                            dest -= var_10;\n                        }\n                    }\n                    break;\n                case 10:\n                    if (a2[0] > a2[1]) {\n                        if (a2[12] > a2[13]) {\n                            // 10/1\n                            // VERIFIED\n                            for (i = 0; i < 8; i++) {\n                                value1 = _$$R0063[a2[4 + i]];\n                                map1[i * 4] = value1 & 0xFF;\n                                map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            for (i = 0; i < 8; i++) {\n                                value1 = _$$R0063[a2[16 + i]];\n                                map1[32 + i * 4] = value1 & 0xFF;\n                                map1[32 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[32 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[32 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            value2 = _mveBW;\n\n                            map2[0xC1] = a2[2]; // mov al, cl\n                            map2[0xC3] = a2[0]; // mov al, bl\n                            map2[0xC5] = a2[3]; // mov al, ch\n                            map2[0xC7] = a2[1]; // mov al, bh\n                            map2[0xE1] = a2[2]; // mov ah, cl\n                            map2[0xE3] = a2[0]; // mov ah, bl\n                            map2[0xE5] = a2[3]; // mov ah, ch\n                            map2[0xE7] = a2[1]; // mov ah, bh\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 8 + 0]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8);\n                                dest_ptr[1] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8);\n                                dest += value2;\n                            }\n\n                            map2[0xC1] = a2[0x0C + 2]; // mov al, cl\n                            map2[0xC3] = a2[0x0C + 0]; // mov al, bl\n                            map2[0xC5] = a2[0x0C + 3]; // mov al, ch\n                            map2[0xC7] = a2[0x0C + 1]; // mov al, bh\n                            map2[0xE1] = a2[0x0C + 2]; // mov ah, cl\n                            map2[0xE3] = a2[0x0C + 0]; // mov ah, bl\n                            map2[0xE5] = a2[0x0C + 3]; // mov ah, ch\n                            map2[0xE7] = a2[0x0C + 1]; // mov ah, bh\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[32 + i * 8 + 0]] << 16) | (map2[map1[32 + i * 8 + 1]] << 24) | (map2[map1[32 + i * 8 + 2]]) | (map2[map1[32 + i * 8 + 3]] << 8);\n                                dest_ptr[1] = (map2[map1[32 + i * 8 + 4]] << 16) | (map2[map1[32 + i * 8 + 5]] << 24) | (map2[map1[32 + i * 8 + 6]]) | (map2[map1[32 + i * 8 + 7]] << 8);\n                                dest += value2;\n                            }\n\n                            dest -= value2;\n\n                            a2 += 24;\n                            dest -= var_10;\n                        } else {\n                            // 10/2\n                            // VERIFIED\n                            for (i = 0; i < 8; i++) {\n                                value1 = _$$R0063[a2[4 + i]];\n                                map1[i * 4] = value1 & 0xFF;\n                                map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            for (i = 0; i < 8; i++) {\n                                value1 = _$$R0063[a2[16 + i]];\n                                map1[32 + i * 4] = value1 & 0xFF;\n                                map1[32 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                                map1[32 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                                map1[32 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                            }\n\n                            value2 = _mveBW;\n\n                            map2[0xC1] = a2[2]; // mov al, cl\n                            map2[0xC3] = a2[0]; // mov al, bl\n                            map2[0xC5] = a2[3]; // mov al, ch\n                            map2[0xC7] = a2[1]; // mov al, bh\n                            map2[0xE1] = a2[2]; // mov ah, cl\n                            map2[0xE3] = a2[0]; // mov ah, bl\n                            map2[0xE5] = a2[3]; // mov ah, ch\n                            map2[0xE7] = a2[1]; // mov ah, bh\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 8 + 0]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8);\n                                dest += value2;\n\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8);\n                                dest += value2;\n                            }\n\n                            dest -= value2 * 8 - 4;\n\n                            map2[0xC1] = a2[0x0C + 2]; // mov al, cl\n                            map2[0xC3] = a2[0x0C + 0]; // mov al, bl\n                            map2[0xC5] = a2[0x0C + 3]; // mov al, ch\n                            map2[0xC7] = a2[0x0C + 1]; // mov al, bh\n                            map2[0xE1] = a2[0x0C + 2]; // mov ah, cl\n                            map2[0xE3] = a2[0x0C + 0]; // mov ah, bl\n                            map2[0xE5] = a2[0x0C + 3]; // mov ah, ch\n                            map2[0xE7] = a2[0x0C + 1]; // mov ah, bh\n\n                            for (i = 0; i < 4; i++) {\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[32 + i * 8 + 0]] << 16) | (map2[map1[32 + i * 8 + 1]] << 24) | (map2[map1[32 + i * 8 + 2]]) | (map2[map1[32 + i * 8 + 3]] << 8);\n                                dest += value2;\n\n                                dest_ptr = (unsigned int*)dest;\n                                dest_ptr[0] = (map2[map1[32 + i * 8 + 4]] << 16) | (map2[map1[32 + i * 8 + 5]] << 24) | (map2[map1[32 + i * 8 + 6]]) | (map2[map1[32 + i * 8 + 7]] << 8);\n                                dest += value2;\n                            }\n\n                            dest -= value2;\n\n                            a2 += 24;\n                            dest -= 4;\n                            dest -= var_10;\n                        }\n                    } else {\n                        // 10/3\n                        // VERIFIED\n                        for (i = 0; i < 4; i++) {\n                            value1 = _$$R0063[a2[4 + i]];\n                            map1[i * 4] = value1 & 0xFF;\n                            map1[i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        for (i = 0; i < 4; i++) {\n                            value1 = _$$R0063[a2[12 + i]];\n                            map1[16 + i * 4] = value1 & 0xFF;\n                            map1[16 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[16 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[16 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        for (i = 0; i < 4; i++) {\n                            value1 = _$$R0063[a2[20 + i]];\n                            map1[32 + i * 4] = value1 & 0xFF;\n                            map1[32 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[32 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[32 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        for (i = 0; i < 4; i++) {\n                            value1 = _$$R0063[a2[28 + i]];\n                            map1[48 + i * 4] = value1 & 0xFF;\n                            map1[48 + i * 4 + 1] = (value1 >> 8) & 0xFF;\n                            map1[48 + i * 4 + 2] = (value1 >> 16) & 0xFF;\n                            map1[48 + i * 4 + 3] = (value1 >> 24) & 0xFF;\n                        }\n\n                        value2 = _mveBW;\n\n                        map2[0xC1] = a2[2]; // mov al, cl\n                        map2[0xC3] = a2[0]; // mov al, bl\n                        map2[0xC5] = a2[3]; // mov al, ch\n                        map2[0xC7] = a2[1]; // mov al, bh\n                        map2[0xE1] = a2[2]; // mov ah, cl\n                        map2[0xE3] = a2[0]; // mov ah, bl\n                        map2[0xE5] = a2[3]; // mov ah, ch\n                        map2[0xE7] = a2[1]; // mov ah, bh\n\n                        for (i = 0; i < 2; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[i * 8 + 0]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8);\n                            dest += value2;\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8);\n                            dest += value2;\n                        }\n\n                        map2[0xC1] = a2[0x08 + 2]; // mov al, cl\n                        map2[0xC3] = a2[0x08 + 0]; // mov al, bl\n                        map2[0xC5] = a2[0x08 + 3]; // mov al, ch\n                        map2[0xC7] = a2[0x08 + 1]; // mov al, bh\n                        map2[0xE1] = a2[0x08 + 2]; // mov ah, cl\n                        map2[0xE3] = a2[0x08 + 0]; // mov ah, bl\n                        map2[0xE5] = a2[0x08 + 3]; // mov ah, ch\n                        map2[0xE7] = a2[0x08 + 1]; // mov ah, bh\n\n                        for (i = 0; i < 2; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[16 + i * 8 + 0]] << 16) | (map2[map1[16 + i * 8 + 1]] << 24) | (map2[map1[16 + i * 8 + 2]]) | (map2[map1[16 + i * 8 + 3]] << 8);\n                            dest += value2;\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[16 + i * 8 + 4]] << 16) | (map2[map1[16 + i * 8 + 5]] << 24) | (map2[map1[16 + i * 8 + 6]]) | (map2[map1[16 + i * 8 + 7]] << 8);\n                            dest += value2;\n                        }\n\n                        dest -= value2 * 8 - 4;\n\n                        map2[0xC1] = a2[0x10 + 2]; // mov al, cl\n                        map2[0xC3] = a2[0x10 + 0]; // mov al, bl\n                        map2[0xC5] = a2[0x10 + 3]; // mov al, ch\n                        map2[0xC7] = a2[0x10 + 1]; // mov al, bh\n                        map2[0xE1] = a2[0x10 + 2]; // mov ah, cl\n                        map2[0xE3] = a2[0x10 + 0]; // mov ah, bl\n                        map2[0xE5] = a2[0x10 + 3]; // mov ah, ch\n                        map2[0xE7] = a2[0x10 + 1]; // mov ah, bh\n\n                        for (i = 0; i < 2; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[32 + i * 8 + 0]] << 16) | (map2[map1[32 + i * 8 + 1]] << 24) | (map2[map1[32 + i * 8 + 2]]) | (map2[map1[32 + i * 8 + 3]] << 8);\n                            dest += value2;\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[32 + i * 8 + 4]] << 16) | (map2[map1[32 + i * 8 + 5]] << 24) | (map2[map1[32 + i * 8 + 6]]) | (map2[map1[32 + i * 8 + 7]] << 8);\n                            dest += value2;\n                        }\n\n                        map2[0xC1] = a2[0x18 + 2]; // mov al, cl\n                        map2[0xC3] = a2[0x18 + 0]; // mov al, bl\n                        map2[0xC5] = a2[0x18 + 3]; // mov al, ch\n                        map2[0xC7] = a2[0x18 + 1]; // mov al, bh\n                        map2[0xE1] = a2[0x18 + 2]; // mov ah, cl\n                        map2[0xE3] = a2[0x18 + 0]; // mov ah, bl\n                        map2[0xE5] = a2[0x18 + 3]; // mov ah, ch\n                        map2[0xE7] = a2[0x18 + 1]; // mov ah, bh\n\n                        for (i = 0; i < 2; i++) {\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[48 + i * 8 + 0]] << 16) | (map2[map1[48 + i * 8 + 1]] << 24) | (map2[map1[48 + i * 8 + 2]]) | (map2[map1[48 + i * 8 + 3]] << 8);\n                            dest += value2;\n\n                            dest_ptr = (unsigned int*)dest;\n                            dest_ptr[0] = (map2[map1[48 + i * 8 + 4]] << 16) | (map2[map1[48 + i * 8 + 5]] << 24) | (map2[map1[48 + i * 8 + 6]]) | (map2[map1[48 + i * 8 + 7]] << 8);\n                            dest += value2;\n                        }\n\n                        dest -= value2;\n\n                        a2 += 32;\n                        dest -= 4;\n                        dest -= var_10;\n                    }\n                    break;\n                case 11:\n                    value2 = _mveBW;\n\n                    src_ptr = (unsigned int*)a2;\n                    for (i = 0; i < 8; i++) {\n                        dest_ptr = (unsigned int*)dest;\n                        dest_ptr[0] = src_ptr[i * 2];\n                        dest_ptr[1] = src_ptr[i * 2 + 1];\n                        dest += value2;\n                    }\n\n                    dest -= value2;\n\n                    a2 += 64;\n                    dest -= var_10;\n                    break;\n                case 12:\n                    value2 = _mveBW;\n\n                    for (i = 0; i < 4; i++) {\n                        byte = a2[i * 4 + 0];\n                        value1 = byte | (byte << 8);\n\n                        byte = a2[i * 4 + 1];\n                        value1 |= (byte << 16) | (byte << 24);\n\n                        byte = a2[i * 4 + 2];\n                        value2 = byte | (byte << 8);\n\n                        byte = a2[i * 4 + 3];\n                        value2 |= (byte << 16) | (byte << 24);\n\n                        dest_ptr = (unsigned int*)dest;\n                        dest_ptr[0] = value1;\n                        dest_ptr[1] = value2;\n\n                        dest_ptr = (unsigned int*)(dest + _mveBW);\n                        dest_ptr[0] = value1;\n                        dest_ptr[1] = value2;\n\n                        dest += _mveBW * 2;\n                    }\n\n                    dest -= _mveBW;\n\n                    a2 += 16;\n                    dest -= var_10;\n                    break;\n                case 13:\n                    byte = a2[0];\n                    value1 = byte | (byte << 8) | (byte << 16) | (byte << 24);\n\n                    byte = a2[1];\n                    value2 = byte | (byte << 8) | (byte << 16) | (byte << 24);\n\n                    for (i = 0; i < 2; i++) {\n                        dest_ptr = (unsigned int*)dest;\n                        dest_ptr[0] = value1;\n                        dest_ptr[1] = value2;\n\n                        dest_ptr = (unsigned int*)(dest + _mveBW);\n                        dest_ptr[0] = value1;\n                        dest_ptr[1] = value2;\n\n                        dest += _mveBW * 2;\n                    }\n\n                    byte = a2[2];\n                    value1 = byte | (byte << 8) | (byte << 16) | (byte << 24);\n\n                    byte = a2[3];\n                    value2 = byte | (byte << 8) | (byte << 16) | (byte << 24);\n\n                    for (i = 0; i < 2; i++) {\n                        dest_ptr = (unsigned int*)dest;\n                        dest_ptr[0] = value1;\n                        dest_ptr[1] = value2;\n\n                        dest_ptr = (unsigned int*)(dest + _mveBW);\n                        dest_ptr[0] = value1;\n                        dest_ptr[1] = value2;\n\n                        dest += _mveBW * 2;\n                    }\n\n                    dest -= _mveBW;\n\n                    a2 += 4;\n                    dest -= var_10;\n                    break;\n                case 14:\n                case 15:\n                    if (v7 == 14) {\n                        byte = *a2++;\n                        value1 = byte | (byte << 8) | (byte << 16) | (byte << 24);\n                        value2 = value1;\n                    } else {\n                        byte = *(unsigned short*)a2;\n                        a2 += 2;\n                        value1 = byte | (byte << 16);\n                        value2 = value1;\n                        value2 = _rotl(value2, 8);\n                    }\n\n                    for (i = 0; i < 4; i++) {\n                        dest_ptr = (unsigned int*)dest;\n                        dest_ptr[0] = value1;\n                        dest_ptr[1] = value1;\n                        dest += _mveBW;\n\n                        dest_ptr = (unsigned int*)dest;\n                        dest_ptr[0] = value2;\n                        dest_ptr[1] = value2;\n                        dest += _mveBW;\n                    }\n\n                    dest -= _mveBW;\n\n                    dest -= var_10;\n                    break;\n                }\n            }\n        }\n\n        dest += var_8;\n    }\n}\n"
  },
  {
    "path": "src/movie_lib.h",
    "content": "#ifndef MOVIE_LIB_H\n#define MOVIE_LIB_H\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#define DIRECTDRAW_VERSION 0x0300\n#include <ddraw.h>\n#include <mmreg.h>\n\n#define DIRECTSOUND_VERSION 0x0300\n#include <dsound.h>\n\n#include <stdbool.h>\n\n#include \"memory_defs.h\"\n\ntypedef struct STRUCT_6B3690 {\n    void* field_0;\n    int field_4;\n    int field_8;\n} STRUCT_6B3690;\n\n#pragma pack(2)\ntypedef struct Mve {\n    char sig[20];\n    short field_14;\n    short field_16;\n    short field_18;\n    int field_1A;\n} Mve;\n#pragma pack()\n\ntypedef bool MovieReadProc(int fileHandle, void* buffer, int count);\n\ntypedef struct STRUCT_4F6930 {\n    int field_0;\n    MovieReadProc* readProc;\n    STRUCT_6B3690 field_8;\n    int fileHandle;\n    int field_18;\n    LPDIRECTDRAWSURFACE field_24;\n    LPDIRECTDRAWSURFACE field_28;\n    int field_2C;\n    unsigned char* field_30;\n    unsigned char* field_34;\n    unsigned char field_38;\n    unsigned char field_39;\n    unsigned char field_3A;\n    unsigned char field_3B;\n    int field_3C;\n    int field_40;\n    int field_44;\n    int field_48;\n    int field_4C;\n    int field_50;\n} STRUCT_4F6930;\n\nextern int dword_51EBD8;\nextern int dword_51EBDC;\nextern unsigned short word_51EBE0[256];\nextern LPDIRECTDRAW gMovieLibDirectDraw;\nextern int _sync_active;\nextern int _sync_late;\nextern int _sync_FrameDropped;\nextern LPDIRECTSOUND gMovieLibDirectSound;\nextern LPDIRECTSOUNDBUFFER gMovieLibDirectSoundBuffer;\nextern int gMovieLibVolume;\nextern int gMovieLibPan;\nextern LPDIRECTDRAWSURFACE gMovieDirectDrawSurface1;\nextern LPDIRECTDRAWSURFACE gMovieDirectDrawSurface2;\nextern void (*_sf_ShowFrame)(LPDIRECTDRAWSURFACE, int, int, int, int, int, int, int, int);\nextern int dword_51EE0C;\nextern void (*_pal_SetPalette)(unsigned char*, int, int);\nextern int _rm_hold;\nextern int _rm_active;\nextern bool dword_51EE20;\nextern int dword_51F018[256];\nextern unsigned short word_51F418[256];\nextern unsigned short word_51F618[256];\nextern unsigned int _$$R0053[16];\nextern unsigned int _$$R0004[256];\nextern unsigned int _$$R0063[256];\n\nextern int dword_6B3660;\nextern DSBCAPS stru_6B3668;\nextern int _sf_ScreenWidth;\nextern int dword_6B3680;\nextern int _rm_FrameDropCount;\nextern int _snd_buf;\nextern STRUCT_6B3690 _io_mem_buf;\nextern int _io_next_hdr;\nextern int dword_6B36A0;\nextern int dword_6B36A4;\nextern int _rm_FrameCount;\nextern int _sf_ScreenHeight;\nextern int dword_6B36B0;\nextern unsigned char _palette_entries1[768];\nextern MallocProc* gMovieLibMallocProc;\nextern int (*_rm_ctl)();\nextern int _rm_dx;\nextern int _rm_dy;\nextern int _gSoundTimeBase;\nextern int _io_handle;\nextern int _rm_len;\nextern FreeProc* gMovieLibFreeProc;\nextern int _snd_comp;\nextern unsigned char* _rm_p;\nextern int dword_6B39E0[60];\nextern int _sync_wait_quanta;\nextern int dword_6B3AD4;\nextern int _rm_track_bit;\nextern int _sync_time;\nextern MovieReadProc* gMovieLibReadProc;\nextern int dword_6B3AE4;\nextern int dword_6B3AE8;\nextern int dword_6B3CEC;\nextern int dword_6B3CF0;\nextern int dword_6B3CF4;\nextern int dword_6B3CF8;\nextern int _mveBW;\nextern int dword_6B3D00;\nextern int dword_6B3D04;\nextern int dword_6B3D08;\nextern unsigned char _pal_tbl[768];\nextern unsigned char byte_6B400C;\nextern unsigned char byte_6B400D;\nextern int dword_6B400E;\nextern int dword_6B4012;\nextern unsigned char byte_6B4016;\nextern int dword_6B4017;\nextern int dword_6B401B;\nextern int dword_6B401F;\nextern int dword_6B4023;\nextern int dword_6B4027;\nextern int dword_6B402B;\nextern int _mveBH;\nextern unsigned char* gMovieDirectDrawSurfaceBuffer1;\nextern unsigned char* gMovieDirectDrawSurfaceBuffer2;\nextern int dword_6B403B;\nextern int dword_6B403F;\n\nvoid movieLibSetMemoryProcs(MallocProc* mallocProc, FreeProc* freeProc);\nvoid movieLibSetReadProc(MovieReadProc* readProc);\nvoid _MVE_MemInit(STRUCT_6B3690* a1, int a2, void* a3);\nvoid _MVE_MemFree(STRUCT_6B3690* a1);\nvoid movieLibSetDirectSound(LPDIRECTSOUND ds);\nvoid movieLibSetVolume(int volume);\nvoid movieLibSetPan(int pan);\nvoid _MVE_sfSVGA(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9);\nvoid _MVE_sfCallbacks(void (*fn)(LPDIRECTDRAWSURFACE, int, int, int, int, int, int, int, int));\nvoid _do_nothing_2(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9);\nvoid movieLibSetPaletteEntriesProc(void (*fn)(unsigned char*, int, int));\nint _sub_4F4B5();\nvoid movieLibSetDirectDraw(LPDIRECTDRAW dd);\nvoid _MVE_rmCallbacks(int (*fn)());\nvoid _sub_4F4BB(int a1);\nvoid _MVE_rmFrameCounts(int* a1, int* a2);\nint _MVE_rmPrepMovie(int fileHandle, int a2, int a3, char a4);\nint _ioReset(int fileHandle);\nvoid* _ioRead(int size);\nvoid* _MVE_MemAlloc(STRUCT_6B3690* a1, unsigned int a2);\nunsigned char* _ioNextRecord();\nvoid _sub_4F4DD();\nint _MVE_rmHoldMovie();\nint _syncWait();\nvoid _MVE_sndPause();\nint _MVE_rmStepMovie();\nint _syncInit(int a1, int a2);\nvoid _syncReset(int a1);\nint _MVE_sndConfigure(int a1, int a2, int a3, int a4, int a5, int a6);\nvoid _MVE_syncSync();\nvoid _MVE_sndReset();\nvoid _MVE_sndSync();\nint _syncWaitLevel(int a1);\nvoid _CallsSndBuff_Loc(unsigned char* a1, int a2);\nint _MVE_sndAdd(unsigned char* dest, unsigned char** src_ptr, int a3, int a4, int a5);\nvoid _MVE_sndResume();\nint _nfConfig(int a1, int a2, int a3, int a4);\nbool movieLockSurfaces();\nvoid movieUnlockSurfaces();\nvoid movieSwapSurfaces();\nvoid _sfShowFrame(int a1, int a2, int a3);\nvoid _do_nothing_(int a1, int a2, unsigned short* a3);\nvoid _SetPalette_1(int a1, int a2);\nvoid _SetPalette_(int a1, int a2);\nvoid _palMakeSynthPalette(int a1, int a2, int a3, int a4, int a5, int a6);\nvoid _palLoadPalette(unsigned char* palette, int a2, int a3);\nvoid _MVE_rmEndMovie();\nvoid _syncRelease();\nvoid _MVE_ReleaseMem();\nvoid _ioRelease();\nvoid _MVE_sndRelease();\nvoid _nfRelease();\nvoid _frLoad(STRUCT_4F6930* a1);\nvoid _frSave(STRUCT_4F6930* a1);\nvoid _MVE_frClose(STRUCT_4F6930* a1);\nint _MVE_sndDecompM16(unsigned short* a1, unsigned char* a2, int a3, int a4);\nint _MVE_sndDecompS16(unsigned short* a1, unsigned char* a2, int a3, int a4);\nvoid _nfPkConfig();\nvoid _nfPkDecomp(unsigned char* buf, unsigned char* a2, int a3, int a4, int a5, int a6);\n\n#endif /* MOVIE_LIB_H */\n"
  },
  {
    "path": "src/plib/assoc/assoc.c",
    "content": "#include \"plib/assoc/assoc.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n\n// NOTE: I guess this marker is used as a type discriminator for implementing\n// nested dictionaries. That's why every dictionary-related function starts\n// with a check for this value.\n#define DICTIONARY_MARKER 0xFEBAFEBA\n\nstatic void* default_malloc(size_t t);\nstatic void* default_realloc(void* p, size_t t);\nstatic void default_free(void* p);\nstatic int assoc_find(assoc_array* a, const char* name, int* position);\nstatic int assoc_read_long(FILE* fp, long* theLong);\nstatic int assoc_read_assoc_array(FILE* fp, assoc_array* a);\nstatic int assoc_write_long(FILE* fp, long theLong);\nstatic int assoc_write_assoc_array(FILE* fp, assoc_array* a);\n\n// 0x51E408\nstatic assoc_malloc_func* internal_malloc = default_malloc;\n\n// 0x51E40C\nstatic assoc_realloc_func* internal_realloc = default_realloc;\n\n// 0x51E410\nstatic assoc_free_func* internal_free = default_free;\n\n// 0x4D9B90\nstatic void* default_malloc(size_t t)\n{\n    return malloc(t);\n}\n\n// 0x4D9B98\nstatic void* default_realloc(void* p, size_t t)\n{\n    return realloc(p, t);\n}\n\n// 0x4D9BA0\nstatic void default_free(void* p)\n{\n    free(p);\n}\n\n// 0x4D9BA8\nint assoc_init(assoc_array* a, int n, size_t datasize, assoc_func_list* assoc_funcs)\n{\n    a->max = n;\n    a->datasize = datasize;\n    a->size = 0;\n\n    if (assoc_funcs != NULL) {\n        memcpy(&(a->load_save_funcs), assoc_funcs, sizeof(*assoc_funcs));\n    } else {\n        a->load_save_funcs.loadFunc = NULL;\n        a->load_save_funcs.saveFunc = NULL;\n        a->load_save_funcs.loadFuncDB = NULL;\n        a->load_save_funcs.saveFuncDB = NULL;\n    }\n\n    int rc = 0;\n\n    if (n != 0) {\n        a->list = (assoc_pair*)internal_malloc(sizeof(*a->list) * n);\n        if (a->list == NULL) {\n            rc = -1;\n        }\n    } else {\n        a->list = NULL;\n    }\n\n    if (rc != -1) {\n        a->init_flag = DICTIONARY_MARKER;\n    }\n\n    return rc;\n}\n\n// 0x4D9C0C\nint assoc_resize(assoc_array* a, int n)\n{\n    if (a->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    if (n < a->size) {\n        return -1;\n    }\n\n    assoc_pair* entries = (assoc_pair*)internal_realloc(a->list, sizeof(*a->list) * n);\n    if (entries == NULL) {\n        return -1;\n    }\n\n    a->max = n;\n    a->list = entries;\n\n    return 0;\n}\n\n// 0x4D9C48\nint assoc_free(assoc_array* a)\n{\n    if (a->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    for (int index = 0; index < a->size; index++) {\n        assoc_pair* entry = &(a->list[index]);\n        if (entry->name != NULL) {\n            internal_free(entry->name);\n        }\n\n        if (entry->data != NULL) {\n            internal_free(entry->data);\n        }\n    }\n\n    if (a->list != NULL) {\n        internal_free(a->list);\n    }\n\n    memset(a, 0, sizeof(*a));\n\n    return 0;\n}\n\n// Finds index for the given key.\n//\n// Returns 0 if key is found. Otherwise returns -1, in this case [indexPtr]\n// specifies an insertion point for given key.\n//\n// 0x4D9CC4\nstatic int assoc_find(assoc_array* a, const char* name, int* position)\n{\n    if (a->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    if (a->size == 0) {\n        *position = 0;\n        return -1;\n    }\n\n    int r = a->size - 1;\n    int l = 0;\n    int mid = 0;\n    int cmp = 0;\n    while (r >= l) {\n        mid = (l + r) / 2;\n\n        cmp = stricmp(name, a->list[mid].name);\n        if (cmp == 0) {\n            break;\n        }\n\n        if (cmp > 0) {\n            l = l + 1;\n        } else {\n            r = r - 1;\n        }\n    }\n\n    if (cmp == 0) {\n        *position = mid;\n        return 0;\n    }\n\n    if (cmp < 0) {\n        *position = mid;\n    } else {\n        *position = mid + 1;\n    }\n\n    return -1;\n}\n\n// Returns the index of the entry for the specified key, or -1 if it's not\n// present in the dictionary.\n//\n// 0x4D9D5C\nint assoc_search(assoc_array* a, const char* name)\n{\n    if (a->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    int index;\n    if (assoc_find(a, name, &index) != 0) {\n        return -1;\n    }\n\n    return index;\n}\n\n// Adds key-value pair to the dictionary if the specified key is not already\n// present.\n//\n// Returns 0 on success, or -1 on any error (including key already exists\n// error).\n//\n// 0x4D9D88\nint assoc_insert(assoc_array* a, const char* name, const void* data)\n{\n    if (a->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    int newElementIndex;\n    if (assoc_find(a, name, &newElementIndex) == 0) {\n        // Element for this key is already exists.\n        return -1;\n    }\n\n    if (a->size == a->max) {\n        // assoc array reached it's capacity and needs to be enlarged.\n        if (assoc_resize(a, 2 * (a->max + 1)) == -1) {\n            return -1;\n        }\n    }\n\n    // Make a copy of the key.\n    char* keyCopy = (char*)internal_malloc(strlen(name) + 1);\n    if (keyCopy == NULL) {\n        return -1;\n    }\n\n    strcpy(keyCopy, name);\n\n    // Make a copy of the value.\n    void* valueCopy = NULL;\n    if (data != NULL && a->datasize != 0) {\n        valueCopy = internal_malloc(a->datasize);\n        if (valueCopy == NULL) {\n            internal_free(keyCopy);\n            return -1;\n        }\n    }\n\n    if (valueCopy != NULL && a->datasize != 0) {\n        memcpy(valueCopy, data, a->datasize);\n    }\n\n    // Starting at the end of entries array loop backwards and move entries down\n    // one by one until we reach insertion point.\n    for (int index = a->size; index > newElementIndex; index--) {\n        assoc_pair* src = &(a->list[index - 1]);\n        assoc_pair* dest = &(a->list[index]);\n        memcpy(dest, src, sizeof(*a->list));\n    }\n\n    assoc_pair* entry = &(a->list[newElementIndex]);\n    entry->name = keyCopy;\n    entry->data = valueCopy;\n\n    a->size++;\n\n    return 0;\n}\n\n// Removes key-value pair from the dictionary if specified key is present in\n// the dictionary.\n//\n// Returns 0 on success, -1 on any error (including key not present error).\n//\n// 0x4D9EE8\nint assoc_delete(assoc_array* a, const char* name)\n{\n    if (a->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    int indexToRemove;\n    if (assoc_find(a, name, &indexToRemove) == -1) {\n        return -1;\n    }\n\n    assoc_pair* entry = &(a->list[indexToRemove]);\n\n    // Free key and value (which are copies).\n    internal_free(entry->name);\n    if (entry->data != NULL) {\n        internal_free(entry->data);\n    }\n\n    a->size--;\n\n    // Starting from the index of the entry we've just removed, loop thru the\n    // remaining of the array and move entries up one by one.\n    for (int index = indexToRemove; index < a->size; index++) {\n        assoc_pair* src = &(a->list[index + 1]);\n        assoc_pair* dest = &(a->list[index]);\n        memcpy(dest, src, sizeof(*a->list));\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4D9F84\nint assoc_copy(assoc_array* dst, assoc_array* src)\n{\n    if (src->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    if (assoc_init(dst, src->max, src->datasize, &(src->load_save_funcs)) != 0) {\n        // FIXME: Should return -1, as we were unable to initialize dictionary.\n        return 0;\n    }\n\n    for (int index = 0; index < src->size; index++) {\n        assoc_pair* entry = &(src->list[index]);\n        if (assoc_insert(dst, entry->name, entry->data) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4DA090\nstatic int assoc_read_long(FILE* fp, long* theLong)\n{\n    int c;\n    int temp;\n\n    c = fgetc(fp);\n    if (c == -1) {\n        return -1;\n    }\n\n    temp = (c & 0xFF);\n\n    c = fgetc(fp);\n    if (c == -1) {\n        return -1;\n    }\n\n    temp = (temp << 8) | (c & 0xFF);\n\n    c = fgetc(fp);\n    if (c == -1) {\n        return -1;\n    }\n\n    temp = (temp << 8) | (c & 0xFF);\n\n    c = fgetc(fp);\n    if (c == -1) {\n        return -1;\n    }\n\n    temp = (temp << 8) | (c & 0xFF);\n\n    *theLong = temp;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4DA0F4\nstatic int assoc_read_assoc_array(FILE* fp, assoc_array* a)\n{\n    long temp;\n\n    if (assoc_read_long(fp, &temp) != 0) return -1;\n    a->size = temp;\n\n    if (assoc_read_long(fp, &temp) != 0) return -1;\n    a->max = temp;\n\n    if (assoc_read_long(fp, &temp) != 0) return -1;\n    a->datasize = temp;\n\n    if (assoc_read_long(fp, &temp) != 0) return -1;\n    // FIXME: Reading pointer.\n    a->list = (assoc_pair*)temp;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4DA158\nint assoc_load(FILE* fp, assoc_array* a, int flags)\n{\n    if (a->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    for (int index = 0; index < a->size; index++) {\n        assoc_pair* entry = &(a->list[index]);\n        if (entry->name != NULL) {\n            internal_free(entry->name);\n        }\n\n        if (entry->data != NULL) {\n            internal_free(entry->data);\n        }\n    }\n\n    if (a->list != NULL) {\n        internal_free(a->list);\n    }\n\n    if (assoc_read_assoc_array(fp, a) != 0) {\n        return -1;\n    }\n\n    a->list = NULL;\n\n    if (a->max <= 0) {\n        return 0;\n    }\n\n    a->list = (assoc_pair*)internal_malloc(sizeof(*a->list) * a->max);\n    if (a->list == NULL) {\n        return -1;\n    }\n\n    for (int index = 0; index < a->size; index++) {\n        assoc_pair* entry = &(a->list[index]);\n        entry->name = NULL;\n        entry->data = NULL;\n    }\n\n    if (a->size <= 0) {\n        return 0;\n    }\n\n    for (int index = 0; index < a->size; index++) {\n        assoc_pair* entry = &(a->list[index]);\n        int keyLength = fgetc(fp);\n        if (keyLength == -1) {\n            return -1;\n        }\n\n        entry->name = (char*)internal_malloc(keyLength + 1);\n        if (entry->name == NULL) {\n            return -1;\n        }\n\n        if (fgets(entry->name, keyLength, fp) == NULL) {\n            return -1;\n        }\n\n        if (a->datasize != 0) {\n            entry->data = internal_malloc(a->datasize);\n            if (entry->data == NULL) {\n                return -1;\n            }\n\n            if (a->load_save_funcs.loadFunc != NULL) {\n                if (a->load_save_funcs.loadFunc(fp, entry->data, a->datasize, flags) != 0) {\n                    return -1;\n                }\n            } else {\n                if (fread(entry->data, a->datasize, 1, fp) != 1) {\n                    return -1;\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4DA2EC\nstatic int assoc_write_long(FILE* fp, long theLong)\n{\n    if (fputc((theLong >> 24) & 0xFF, fp) == -1) return -1;\n    if (fputc((theLong >> 16) & 0xFF, fp) == -1) return -1;\n    if (fputc((theLong >> 8) & 0xFF, fp) == -1) return -1;\n    if (fputc(theLong & 0xFF, fp) == -1) return -1;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4DA360\nstatic int assoc_write_assoc_array(FILE* fp, assoc_array* a)\n{\n    if (assoc_write_long(fp, a->size) != 0) return -1;\n    if (assoc_write_long(fp, a->max) != 0) return -1;\n    if (assoc_write_long(fp, a->datasize) != 0) return -1;\n    // FIXME: Writing pointer.\n    if (assoc_write_long(fp, (int)a->list) != 0) return -1;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4DA3A4\nint assoc_save(FILE* fp, assoc_array* a, int flags)\n{\n    if (a->init_flag != DICTIONARY_MARKER) {\n        return -1;\n    }\n\n    if (assoc_write_assoc_array(fp, a) != 0) {\n        return -1;\n    }\n\n    for (int index = 0; index < a->size; index++) {\n        assoc_pair* entry = &(a->list[index]);\n        int keyLength = strlen(entry->name);\n        if (fputc(keyLength, fp) == -1) {\n            return -1;\n        }\n\n        if (fputs(entry->name, fp) == -1) {\n            return -1;\n        }\n\n        if (a->load_save_funcs.saveFunc != NULL) {\n            if (a->datasize != 0) {\n                if (a->load_save_funcs.saveFunc(fp, entry->data, a->datasize, flags) != 0) {\n                    return -1;\n                }\n            }\n        } else {\n            if (a->datasize != 0) {\n                if (fwrite(entry->data, a->datasize, 1, fp) != 1) {\n                    return -1;\n                }\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4DA498\nvoid assoc_register_mem(assoc_malloc_func* malloc_func, assoc_realloc_func* realloc_func, assoc_free_func* free_func)\n{\n    if (malloc_func != NULL && realloc_func != NULL && free_func != NULL) {\n        internal_malloc = malloc_func;\n        internal_realloc = realloc_func;\n        internal_free = free_func;\n    } else {\n        internal_malloc = default_malloc;\n        internal_realloc = default_realloc;\n        internal_free = default_free;\n    }\n}\n"
  },
  {
    "path": "src/plib/assoc/assoc.h",
    "content": "#ifndef FALLOUT_PLIB_ASSOC_ASSOC_H_\n#define FALLOUT_PLIB_ASSOC_ASSOC_H_\n\n#include <stdio.h>\n\ntypedef void*(assoc_malloc_func)(size_t size);\ntypedef void*(assoc_realloc_func)(void* ptr, size_t newSize);\ntypedef void(assoc_free_func)(void* ptr);\ntypedef int(assoc_load_func)(FILE* stream, void* buffer, size_t size, int flags);\ntypedef int(assoc_save_func)(FILE* stream, void* buffer, size_t size, int flags);\n// TODO: Check, probably wrong.\ntypedef void(assoc_load_func_db)();\ntypedef void(assoc_save_func_db)();\n\ntypedef struct assoc_func_list {\n    assoc_load_func* loadFunc;\n    assoc_save_func* saveFunc;\n    assoc_load_func_db* loadFuncDB;\n    assoc_save_func_db* saveFuncDB;\n    assoc_load_func* newLoadFunc;\n} assoc_func_list;\n\n// A tuple containing individual key-value pair of an assoc array.\ntypedef struct assoc_pair {\n    char* name;\n    void* data;\n} assoc_pair;\n\n// A collection of key/value pairs.\n//\n// The keys in assoc array are always strings. Internally pairs are kept sorted\n// by the key. Both keys and values are copied when new entry is added to\n// assoc array. For this reason the size of the value's type is provided during\n// assoc array initialization.\ntypedef struct assoc_array {\n    int init_flag;\n\n    // The number of key/value pairs in the array.\n    int size;\n\n    // The capacity of key/value pairs in [entries] array.\n    int max;\n\n    // The size of the values in bytes.\n    size_t datasize;\n\n    // IO callbacks.\n    assoc_func_list load_save_funcs;\n\n    // The array of key-value pairs.\n    assoc_pair* list;\n} assoc_array;\n\nint assoc_init(assoc_array* a, int n, size_t datasize, assoc_func_list* assoc_funcs);\nint assoc_resize(assoc_array* a, int n);\nint assoc_free(assoc_array* a);\nint assoc_search(assoc_array* a, const char* name);\nint assoc_insert(assoc_array* a, const char* name, const void* data);\nint assoc_delete(assoc_array* a, const char* name);\nint assoc_copy(assoc_array* dst, assoc_array* src);\nint assoc_load(FILE* fp, assoc_array* a, int flags);\nint assoc_save(FILE* fp, assoc_array* a, int flags);\nvoid assoc_register_mem(assoc_malloc_func* malloc_func, assoc_realloc_func* realloc_func, assoc_free_func* free_func);\n\n#endif /* FALLOUT_PLIB_ASSOC_ASSOC_H_ */\n"
  },
  {
    "path": "src/plib/color/color.c",
    "content": "#include \"plib/color/color.h\"\n\n#include <math.h>\n#include <string.h>\n\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/svga.h\"\n\nstatic int colorOpen(const char* filePath, int flags);\nstatic int colorRead(int fd, void* buffer, size_t size);\nstatic int colorClose(int fd);\nstatic void* defaultMalloc(size_t size);\nstatic void* defaultRealloc(void* ptr, size_t size);\nstatic void defaultFree(void* ptr);\nstatic void setIntensityTableColor(int a1);\nstatic void setIntensityTables();\nstatic void setMixTableColor(int a1);\nstatic void setMixTable();\nstatic void buildBlendTable(unsigned char* ptr, unsigned char ch);\nstatic void rebuildColorBlendTables();\nstatic void maxfill();\n\n// 0x50F930\nstatic char _aColor_cNoError[] = \"color.c: No errors\\n\";\n\n// 0x50F95C\nstatic char _aColor_cColorTa[] = \"color.c: color table not found\\n\";\n\n// 0x50F984\nstatic char _aColor_cColorpa[] = \"color.c: colorpalettestack overflow\";\n\n// 0x50F9AC\nstatic char aColor_cColor_0[] = \"color.c: colorpalettestack underflow\";\n\n// 0x51DF10\nstatic char* errorStr = _aColor_cNoError;\n\n// 0x51DF14\nstatic bool colorsInited = false;\n\n// 0x51DF18\nstatic double currentGamma = 1.0;\n\n// 0x51DF20\nstatic fade_bk_func* colorFadeBkFuncP = NULL;\n\n// 0x51DF24\nstatic MallocProc* mallocPtr = defaultMalloc;\n\n// 0x51DF28\nstatic ReallocProc* reallocPtr = defaultRealloc;\n\n// 0x51DF2C\nstatic FreeProc* freePtr = defaultFree;\n\n// 0x51DF30\nstatic ColorNameMangleFunc* colorNameMangler = NULL;\n\n// 0x51DF34\nunsigned char cmap[768] = {\n    0x3F, 0x3F, 0x3F\n};\n\n// 0x673050\nstatic ColorPaletteStackEntry* colorPaletteStack[COLOR_PALETTE_STACK_CAPACITY];\n\n// 0x673090\nstatic unsigned char systemCmap[256 * 3];\n\n// 0x673390\nstatic unsigned char currentGammaTable[64];\n\n// 0x6733D0\nstatic unsigned char* blendTable[256];\n\n// 0x6737D0\nunsigned char mappedColor[256];\n\n// 0x6738D0\nColor colorMixAddTable[256][256];\n\n// 0x6838D0\nColor intensityColorTable[256][256];\n\n// 0x6938D0\nColor colorMixMulTable[256][256];\n\n// 0x6A38D0\nunsigned char colorTable[32768];\n\n// 0x6AB8D0\nstatic int tos;\n\n// 0x6AB928\nstatic ColorReadFunc* readFunc;\n\n// 0x6AB92C\nstatic ColorCloseFunc* closeFunc;\n\n// 0x6AB930\nstatic ColorOpenFunc* openFunc;\n\n// NOTE: Inlined.\n//\n// 0x4C7200\nstatic int colorOpen(const char* filePath, int flags)\n{\n    if (openFunc != NULL) {\n        return openFunc(filePath, flags);\n    }\n\n    return -1;\n}\n\n// NOTE: Inlined.\n//\n// 0x4C7218\nstatic int colorRead(int fd, void* buffer, size_t size)\n{\n    if (readFunc != NULL) {\n        return readFunc(fd, buffer, size);\n    }\n\n    return -1;\n}\n\n// NOTE: Inlined.\n//\n// 0x4C7230\nstatic int colorClose(int fd)\n{\n    if (closeFunc != NULL) {\n        return closeFunc(fd);\n    }\n\n    return -1;\n}\n\n// 0x4C7248\nvoid colorInitIO(ColorOpenFunc* openProc, ColorReadFunc* readProc, ColorCloseFunc* closeProc)\n{\n    openFunc = openProc;\n    readFunc = readProc;\n    closeFunc = closeProc;\n}\n\n// 0x4C725C\nstatic void* defaultMalloc(size_t size)\n{\n    return malloc(size);\n}\n\n// 0x4C7264\nstatic void* defaultRealloc(void* ptr, size_t size)\n{\n    return realloc(ptr, size);\n}\n\n// 0x4C726C\nstatic void defaultFree(void* ptr)\n{\n    free(ptr);\n}\n\n// 0x4C7274\nvoid colorSetNameMangler(ColorNameMangleFunc* c)\n{\n    colorNameMangler = c;\n}\n\n// 0x4C727C\nColor colorMixAdd(Color a, Color b)\n{\n    return colorMixAddTable[a][b];\n}\n\n// 0x4C7298\nColor colorMixMul(Color a, Color b)\n{\n    return colorMixMulTable[a][b];\n}\n\n// 0x4C72B4\nint calculateColor(int a1, int a2)\n{\n    return intensityColorTable[a2][a1 >> 9];\n}\n\n// 0x4C72CC\nColor RGB2Color(ColorRGB c)\n{\n    return colorTable[c];\n}\n\n// 0x4C72E0\nint Color2RGB(int a1)\n{\n    int v1, v2, v3;\n\n    v1 = cmap[3 * a1] >> 1;\n    v2 = cmap[3 * a1 + 1] >> 1;\n    v3 = cmap[3 * a1 + 2] >> 1;\n\n    return (((v1 << 5) | v2) << 5) | v3;\n}\n\n// Performs animated palette transition.\n//\n// 0x4C7320\nvoid fadeSystemPalette(unsigned char* oldPalette, unsigned char* newPalette, int steps)\n{\n    for (int step = 0; step < steps; step++) {\n        unsigned char palette[768];\n\n        for (int index = 0; index < 768; index++) {\n            palette[index] = oldPalette[index] - (oldPalette[index] - newPalette[index]) * step / steps;\n        }\n\n        if (colorFadeBkFuncP != NULL) {\n            if (step % 128 == 0) {\n                colorFadeBkFuncP();\n            }\n        }\n\n        setSystemPalette(palette);\n    }\n\n    setSystemPalette(newPalette);\n}\n\n// 0x4C73D4\nvoid colorSetFadeBkFunc(fade_bk_func* callback)\n{\n    colorFadeBkFuncP = callback;\n}\n\n// 0x4C73DC\nvoid setBlackSystemPalette()\n{\n    // 0x6AB934\n    static unsigned char tmp[768];\n    setSystemPalette(tmp);\n}\n\n// 0x4C73E4\nvoid setSystemPalette(unsigned char* palette)\n{\n    unsigned char newPalette[768];\n\n    for (int index = 0; index < 768; index++) {\n        newPalette[index] = currentGammaTable[palette[index]];\n        systemCmap[index] = palette[index];\n    }\n\n    GNW95_SetPalette(newPalette);\n}\n\n// 0x4C7420\nunsigned char* getSystemPalette()\n{\n    return systemCmap;\n}\n\n// 0x4C7428\nvoid setSystemPaletteEntries(unsigned char* palette, int start, int end)\n{\n    unsigned char newPalette[768];\n\n    int length = end - start + 1;\n    for (int index = 0; index < length; index++) {\n        newPalette[index * 3] = currentGammaTable[palette[index * 3]];\n        newPalette[index * 3 + 1] = currentGammaTable[palette[index * 3 + 1]];\n        newPalette[index * 3 + 2] = currentGammaTable[palette[index * 3 + 2]];\n\n        systemCmap[start * 3 + index * 3] = palette[index * 3];\n        systemCmap[start * 3 + index * 3 + 1] = palette[index * 3 + 1];\n        systemCmap[start * 3 + index * 3 + 2] = palette[index * 3 + 2];\n    }\n\n    GNW95_SetPaletteEntries(newPalette, start, end - start + 1);\n}\n\n// 0x4C74D0\nvoid setSystemPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b)\n{\n    int baseIndex;\n\n    baseIndex = entry * 3;\n    systemCmap[baseIndex] = r;\n    systemCmap[baseIndex + 1] = g;\n    systemCmap[baseIndex + 2] = b;\n    GNW95_SetPaletteEntry(entry, currentGammaTable[r], currentGammaTable[g], currentGammaTable[b]);\n}\n\n// 0x4C752C\nvoid getSystemPaletteEntry(int entry, unsigned char* r, unsigned char* g, unsigned char* b)\n{\n    int baseIndex;\n\n    baseIndex = entry * 3;\n    *r = systemCmap[baseIndex];\n    *g = systemCmap[baseIndex + 1];\n    *b = systemCmap[baseIndex + 2];\n}\n\n// 0x4C7550\nstatic void setIntensityTableColor(int a1)\n{\n    int v1, v2, v3, v4, v5, v6, v7, v8, v9;\n\n    v5 = 0;\n\n    for (int index = 0; index < 128; index++) {\n        v1 = (Color2RGB(a1) & 0x7C00) >> 10;\n        v2 = (Color2RGB(a1) & 0x3E0) >> 5;\n        v3 = (Color2RGB(a1) & 0x1F);\n\n        v4 = (((v1 * v5) >> 16) << 10) | (((v2 * v5) >> 16) << 5) | ((v3 * v5) >> 16);\n        intensityColorTable[a1][index] = colorTable[v4];\n\n        v6 = v1 + (((0x1F - v1) * v5) >> 16);\n        v7 = v2 + (((0x1F - v2) * v5) >> 16);\n        v8 = v3 + (((0x1F - v3) * v5) >> 16);\n\n        v9 = (v6 << 10) | (v7 << 5) | v8;\n        intensityColorTable[a1][0x7F + index + 1] = colorTable[v9];\n\n        v5 += 0x200;\n    }\n}\n\n// 0x4C7658\nstatic void setIntensityTables()\n{\n    for (int index = 0; index < 256; index++) {\n        if (mappedColor[index] != 0) {\n            setIntensityTableColor(index);\n        } else {\n            memset(intensityColorTable[index], 0, 256);\n        }\n    }\n}\n\n// 0x4C769C\nstatic void setMixTableColor(int a1)\n{\n    int i;\n    int v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19;\n    int v20, v21, v22, v23, v24, v25, v26, v27, v28, v29;\n\n    for (i = 0; i < 256; i++) {\n        if (mappedColor[a1] && mappedColor[i]) {\n            v2 = (Color2RGB(a1) & 0x7C00) >> 10;\n            v3 = (Color2RGB(a1) & 0x3E0) >> 5;\n            v4 = (Color2RGB(a1) & 0x1F);\n\n            v5 = (Color2RGB(i) & 0x7C00) >> 10;\n            v6 = (Color2RGB(i) & 0x3E0) >> 5;\n            v7 = (Color2RGB(i) & 0x1F);\n\n            v8 = v2 + v5;\n            v9 = v3 + v6;\n            v10 = v4 + v7;\n\n            v11 = v8;\n\n            if (v9 > v11) {\n                v11 = v9;\n            }\n\n            if (v10 > v11) {\n                v11 = v10;\n            }\n\n            if (v11 <= 0x1F) {\n                int paletteIndex = (v8 << 10) | (v9 << 5) | v10;\n                v12 = colorTable[paletteIndex];\n            } else {\n                v13 = v11 - 0x1F;\n\n                v14 = v8 - v13;\n                v15 = v9 - v13;\n                v16 = v10 - v13;\n\n                if (v14 < 0) {\n                    v14 = 0;\n                }\n\n                if (v15 < 0) {\n                    v15 = 0;\n                }\n\n                if (v16 < 0) {\n                    v16 = 0;\n                }\n\n                v17 = (v14 << 10) | (v15 << 5) | v16;\n                v18 = colorTable[v17];\n\n                v19 = (int)((((double)v11 + (-31.0)) * 0.0078125 + 1.0) * 65536.0);\n                v12 = calculateColor(v19, v18);\n            }\n\n            colorMixAddTable[a1][i] = v12;\n\n            v20 = (Color2RGB(a1) & 0x7C00) >> 10;\n            v21 = (Color2RGB(a1) & 0x3E0) >> 5;\n            v22 = (Color2RGB(a1) & 0x1F);\n\n            v23 = (Color2RGB(i) & 0x7C00) >> 10;\n            v24 = (Color2RGB(i) & 0x3E0) >> 5;\n            v25 = (Color2RGB(i) & 0x1F);\n\n            v26 = (v20 * v23) >> 5;\n            v27 = (v21 * v24) >> 5;\n            v28 = (v22 * v25) >> 5;\n\n            v29 = (v26 << 10) | (v27 << 5) | v28;\n            colorMixMulTable[a1][i] = colorTable[v29];\n        } else {\n            if (mappedColor[i]) {\n                colorMixAddTable[a1][i] = i;\n                colorMixMulTable[a1][i] = i;\n            } else {\n                colorMixAddTable[a1][i] = a1;\n                colorMixMulTable[a1][i] = a1;\n            }\n        }\n    }\n}\n\n// 0x4C78CC\nstatic void setMixTable()\n{\n    int i;\n\n    for (i = 0; i < 256; i++) {\n        setMixTableColor(i);\n    }\n}\n\n// 0x4C78E4\nbool loadColorTable(const char* path)\n{\n    if (colorNameMangler != NULL) {\n        path = colorNameMangler(path);\n    }\n\n    // NOTE: Uninline.\n    int fd = colorOpen(path, 0x200);\n    if (fd == -1) {\n        errorStr = _aColor_cColorTa;\n        return false;\n    }\n\n    for (int index = 0; index < 256; index++) {\n        unsigned char r;\n        unsigned char g;\n        unsigned char b;\n\n        // NOTE: Uninline.\n        colorRead(fd, &r, sizeof(r));\n\n        // NOTE: Uninline.\n        colorRead(fd, &g, sizeof(g));\n\n        // NOTE: Uninline.\n        colorRead(fd, &b, sizeof(b));\n\n        if (r <= 0x3F && g <= 0x3F && b <= 0x3F) {\n            mappedColor[index] = 1;\n        } else {\n            r = 0;\n            g = 0;\n            b = 0;\n            mappedColor[index] = 0;\n        }\n\n        cmap[index * 3] = r;\n        cmap[index * 3 + 1] = g;\n        cmap[index * 3 + 2] = b;\n    }\n\n    // NOTE: Uninline.\n    colorRead(fd, colorTable, 0x8000);\n\n    unsigned int type;\n    // NOTE: Uninline.\n    colorRead(fd, &type, sizeof(type));\n\n    // NOTE: The value is \"NEWC\". Original code uses cmp opcode, not stricmp,\n    // or comparing characters one-by-one.\n    if (type == 0x4E455743) {\n        // NOTE: Uninline.\n        colorRead(fd, intensityColorTable, 0x10000);\n\n        // NOTE: Uninline.\n        colorRead(fd, colorMixAddTable, 0x10000);\n\n        // NOTE: Uninline.\n        colorRead(fd, colorMixMulTable, 0x10000);\n    } else {\n        setIntensityTables();\n\n        for (int index = 0; index < 256; index++) {\n            setMixTableColor(index);\n        }\n    }\n\n    rebuildColorBlendTables();\n\n    // NOTE: Uninline.\n    colorClose(fd);\n\n    return true;\n}\n\n// 0x4C7AB4\nchar* colorError()\n{\n    return errorStr;\n}\n\n// 0x4C7ABC\nvoid setColorPalette(unsigned char* pal)\n{\n    memcpy(cmap, pal, sizeof(cmap));\n    memset(mappedColor, 1, sizeof(mappedColor));\n}\n\n// 0x4C7AF8\nvoid setColorPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b)\n{\n    int baseIndex;\n\n    baseIndex = entry * 3;\n    cmap[baseIndex] = r;\n    cmap[baseIndex + 1] = g;\n    cmap[baseIndex + 2] = b;\n}\n\n// 0x4C7B20\nvoid getColorPaletteEntry(int entry, unsigned char* r, unsigned char* g, unsigned char* b)\n{\n    int baseIndex;\n\n    baseIndex = entry * 3;\n    *r = cmap[baseIndex];\n    *g = cmap[baseIndex + 1];\n    *b = cmap[baseIndex + 2];\n}\n\n// 0x4C7B44\nstatic void buildBlendTable(unsigned char* ptr, unsigned char ch)\n{\n    int r, g, b;\n    int i, j;\n    int v12, v14, v16;\n    unsigned char* beg;\n\n    beg = ptr;\n\n    r = (Color2RGB(ch) & 0x7C00) >> 10;\n    g = (Color2RGB(ch) & 0x3E0) >> 5;\n    b = (Color2RGB(ch) & 0x1F);\n\n    for (i = 0; i < 256; i++) {\n        ptr[i] = i;\n    }\n\n    ptr += 256;\n\n    int b_1 = b;\n    int v31 = 6;\n    int g_1 = g;\n    int r_1 = r;\n\n    int b_2 = b_1;\n    int g_2 = g_1;\n    int r_2 = r_1;\n\n    for (j = 0; j < 7; j++) {\n        for (i = 0; i < 256; i++) {\n            v12 = (Color2RGB(i) & 0x7C00) >> 10;\n            v14 = (Color2RGB(i) & 0x3E0) >> 5;\n            v16 = (Color2RGB(i) & 0x1F);\n            int index = 0;\n            index |= (r_2 + v12 * v31) / 7 << 10;\n            index |= (g_2 + v14 * v31) / 7 << 5;\n            index |= (b_2 + v16 * v31) / 7;\n            ptr[i] = colorTable[index];\n        }\n        v31--;\n        ptr += 256;\n        r_2 += r_1;\n        g_2 += g_1;\n        b_2 += b_1;\n    }\n\n    int v18 = 0;\n    for (j = 0; j < 6; j++) {\n        int v20 = v18 / 7 + 0xFFFF;\n\n        for (i = 0; i < 256; i++) {\n            ptr[i] = calculateColor(v20, ch);\n        }\n\n        v18 += 0x10000;\n        ptr += 256;\n    }\n}\n\n// 0x4C7D90\nstatic void rebuildColorBlendTables()\n{\n    int i;\n\n    for (i = 0; i < 256; i++) {\n        if (blendTable[i]) {\n            buildBlendTable(blendTable[i], i);\n        }\n    }\n}\n\n// 0x4C7DC0\nunsigned char* getColorBlendTable(int ch)\n{\n    unsigned char* ptr;\n\n    if (blendTable[ch] == NULL) {\n        ptr = (unsigned char*)mallocPtr(4100);\n        *(int*)ptr = 1;\n        blendTable[ch] = ptr + 4;\n        buildBlendTable(blendTable[ch], ch);\n    }\n\n    ptr = blendTable[ch];\n    *(int*)((unsigned char*)ptr - 4) = *(int*)((unsigned char*)ptr - 4) + 1;\n\n    return ptr;\n}\n\n// 0x4C7E20\nvoid freeColorBlendTable(int a1)\n{\n    unsigned char* v2 = blendTable[a1];\n    if (v2 != NULL) {\n        int* count = (int*)(v2 - sizeof(int));\n        *count -= 1;\n        if (*count == 0) {\n            freePtr(count);\n            blendTable[a1] = NULL;\n        }\n    }\n}\n\n// 0x4C7E58\nvoid colorRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc)\n{\n    mallocPtr = mallocProc;\n    reallocPtr = reallocProc;\n    freePtr = freeProc;\n}\n\n// 0x4C7E6C\nvoid colorGamma(double value)\n{\n    currentGamma = value;\n\n    for (int i = 0; i < 64; i++) {\n        double value = pow(i, currentGamma);\n        currentGammaTable[i] = (unsigned char)min(max(value, 0.0), 63.0);\n    }\n\n    setSystemPalette(systemCmap);\n}\n\n// 0x4C7F0C\ndouble colorGetGamma()\n{\n    return currentGamma;\n}\n\n// 0x4C7F14\nint colorMappedColor(ColorIndex i)\n{\n    return mappedColor[i];\n}\n\n// 0x4C8804\nstatic void maxfill(unsigned long* buffer, int side)\n{\n    unsigned long maxv;\n    long i;\n    unsigned long* bp;\n\n    bp = buffer;\n    maxv = side * side * side;\n    for (i = maxv; i > 0; i--) {\n        *bp++ = -1;\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4C8828\nbool colorPushColorPalette()\n{\n    if (tos >= COLOR_PALETTE_STACK_CAPACITY) {\n        errorStr = _aColor_cColorpa;\n        return false;\n    }\n\n    ColorPaletteStackEntry* entry = (ColorPaletteStackEntry*)malloc(sizeof(*entry));\n    colorPaletteStack[tos] = entry;\n\n    memcpy(entry->mappedColors, mappedColor, sizeof(mappedColor));\n    memcpy(entry->cmap, cmap, sizeof(cmap));\n    memcpy(entry->colorTable, colorTable, sizeof(colorTable));\n\n    tos++;\n\n    return true;\n}\n\n// NOTE: Unused.\n//\n// 0x4C88E0\nbool colorPopColorPalette()\n{\n    if (tos == 0) {\n        errorStr = aColor_cColor_0;\n        return false;\n    }\n\n    tos--;\n\n    ColorPaletteStackEntry* entry = colorPaletteStack[tos];\n\n    memcpy(mappedColor, entry->mappedColors, sizeof(mappedColor));\n    memcpy(cmap, entry->cmap, sizeof(cmap));\n    memcpy(colorTable, entry->colorTable, sizeof(colorTable));\n\n    free(entry);\n    colorPaletteStack[tos] = NULL;\n\n    setIntensityTables();\n\n    for (int index = 0; index < 256; index++) {\n        setMixTableColor(index);\n    }\n\n    rebuildColorBlendTables();\n\n    return true;\n}\n\n// 0x4C89CC\nbool initColors()\n{\n    if (colorsInited) {\n        return true;\n    }\n\n    colorsInited = true;\n\n    colorGamma(1.0);\n\n    if (!loadColorTable(\"color.pal\")) {\n        return false;\n    }\n\n    setSystemPalette(cmap);\n\n    return true;\n}\n\n// 0x4C8A18\nvoid colorsClose()\n{\n    for (int index = 0; index < 256; index++) {\n        freeColorBlendTable(index);\n    }\n\n    for (int index = 0; index < tos; index++) {\n        free(colorPaletteStack[index]);\n    }\n\n    tos = 0;\n}\n\n// 0x4C8A64\nunsigned char* getColorPalette()\n{\n    return cmap;\n}\n"
  },
  {
    "path": "src/plib/color/color.h",
    "content": "#ifndef FALLOUT_PLIB_COLOR_COLOR_H_\n#define FALLOUT_PLIB_COLOR_COLOR_H_\n\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"memory_defs.h\"\n\n#define COLOR_PALETTE_STACK_CAPACITY 16\n\ntypedef unsigned char Color;\ntypedef long ColorRGB;\ntypedef unsigned char ColorIndex;\n\ntypedef const char*(ColorNameMangleFunc)(const char*);\ntypedef void(fade_bk_func)();\n\ntypedef int(ColorOpenFunc)(const char* path, int mode);\ntypedef int(ColorReadFunc)(int fd, void* buffer, size_t size);\ntypedef int(ColorCloseFunc)(int fd);\n\ntypedef struct ColorPaletteStackEntry {\n    unsigned char mappedColors[256];\n    unsigned char cmap[768];\n    unsigned char colorTable[32768];\n} ColorPaletteStackEntry;\n\nextern unsigned char cmap[768];\n\nextern unsigned char mappedColor[256];\nextern Color colorMixAddTable[256][256];\nextern unsigned char intensityColorTable[256][256];\nextern Color colorMixMulTable[256][256];\nextern unsigned char colorTable[32768];\n\nvoid colorInitIO(ColorOpenFunc* openProc, ColorReadFunc* readProc, ColorCloseFunc* closeProc);\nvoid colorSetNameMangler(ColorNameMangleFunc* c);\nColor colorMixAdd(Color a, Color b);\nColor colorMixMul(Color a, Color b);\nint calculateColor(int a1, int a2);\nColor RGB2Color(ColorRGB c);\nint Color2RGB(int a1);\nvoid fadeSystemPalette(unsigned char* oldPalette, unsigned char* newPalette, int steps);\nvoid colorSetFadeBkFunc(fade_bk_func* callback);\nvoid setBlackSystemPalette();\nvoid setSystemPalette(unsigned char* palette);\nunsigned char* getSystemPalette();\nvoid setSystemPaletteEntries(unsigned char* a1, int a2, int a3);\nvoid setSystemPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b);\nvoid getSystemPaletteEntry(int entry, unsigned char* r, unsigned char* g, unsigned char* b);\nbool loadColorTable(const char* path);\nchar* colorError();\nvoid setColorPalette(unsigned char* pal);\nvoid setColorPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b);\nvoid getColorPaletteEntry(int entry, unsigned char* r, unsigned char* g, unsigned char* b);\nunsigned char* getColorBlendTable(int ch);\nvoid freeColorBlendTable(int a1);\nvoid colorRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc);\nvoid colorGamma(double value);\ndouble colorGetGamma();\nint colorMappedColor(ColorIndex i);\nbool colorPushColorPalette();\nbool colorPopColorPalette();\nbool initColors();\nvoid colorsClose();\nunsigned char* getColorPalette();\n\n#endif /* FALLOUT_PLIB_COLOR_COLOR_H_ */\n"
  },
  {
    "path": "src/plib/db/db.c",
    "content": "#include \"plib/db/db.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/xfile/xfile.h\"\n\nstatic int db_list_compare(const void* p1, const void* p2);\n\n// Generic file progress report handler.\n//\n// 0x51DEEC\nstatic FileReadProgressHandler* read_callback = NULL;\n\n// Bytes read so far while tracking progress.\n//\n// Once this value reaches [read_threshold] the handler is called\n// and this value resets to zero.\n//\n// 0x51DEF0\nstatic int read_counter = 0;\n\n// The number of bytes to read between calls to progress handler.\n//\n// 0x673040\nstatic int read_threshold;\n\n// 0x673044\nstatic FileList* db_file_lists;\n\n// Opens file database.\n//\n// Returns -1 if [filePath1] was specified, but could not be opened by the\n// underlying xbase implementation. Result of opening [filePath2] is ignored.\n// Returns 0 on success.\n//\n// NOTE: There are two unknown parameters passed via edx and ecx. The [a2] is\n// always 0 at the calling sites, and [a4] is always 1. Both parameters are not\n// used, so it's impossible to figure out their meaning.\n//\n// 0x4C5D30\nint db_init(const char* filePath1, int a2, const char* filePath2, int a4)\n{\n    if (filePath1 != NULL) {\n        if (!xaddpath(filePath1)) {\n            return -1;\n        }\n    }\n\n    if (filePath2 != NULL) {\n        xaddpath(filePath2);\n    }\n\n    return 0;\n}\n\n// 0x4C5D54\nint db_select(int dbHandle)\n{\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x4C5D54.\nint db_current()\n{\n    return 0;\n}\n\n// 0x4C5D58\nint db_total()\n{\n    return 0;\n}\n\n// 0x4C5D60\nvoid db_exit()\n{\n    xsetpath(NULL);\n}\n\n// TODO: sizePtr should be long*.\n//\n// 0x4C5D68\nint db_dir_entry(const char* filePath, int* sizePtr)\n{\n    assert(filePath); // \"filename\", \"db.c\", 108\n    assert(sizePtr); // \"de\", \"db.c\", 109\n\n    File* stream = xfopen(filePath, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    *sizePtr = xfilelength(stream);\n\n    xfclose(stream);\n\n    return 0;\n}\n\n// 0x4C5DD4\nint db_read_to_buf(const char* filePath, void* ptr)\n{\n    assert(filePath); // \"filename\", \"db.c\", 141\n    assert(ptr); // \"buf\", \"db.c\", 142\n\n    File* stream = xfopen(filePath, \"rb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    long size = xfilelength(stream);\n    if (read_callback != NULL) {\n        unsigned char* byteBuffer = (unsigned char*)ptr;\n\n        long remainingSize = size;\n        long chunkSize = read_threshold - read_counter;\n\n        while (remainingSize >= chunkSize) {\n            size_t bytesRead = xfread(byteBuffer, sizeof(*byteBuffer), chunkSize, stream);\n            byteBuffer += bytesRead;\n            remainingSize -= bytesRead;\n\n            read_counter = 0;\n            read_callback();\n\n            chunkSize = read_threshold;\n        }\n\n        if (remainingSize != 0) {\n            read_counter += xfread(byteBuffer, sizeof(*byteBuffer), remainingSize, stream);\n        }\n    } else {\n        xfread(ptr, 1, size, stream);\n    }\n\n    xfclose(stream);\n\n    return 0;\n}\n\n// 0x4C5EB4\nint db_fclose(File* stream)\n{\n    return xfclose(stream);\n}\n\n// 0x4C5EC8\nFile* db_fopen(const char* filename, const char* mode)\n{\n    return xfopen(filename, mode);\n}\n\n// 0x4C5ED0\nint db_fprintf(File* stream, const char* format, ...)\n{\n    assert(format); // \"format\", \"db.c\", 224\n\n    va_list args;\n    va_start(args, format);\n\n    int rc = xvfprintf(stream, format, args);\n\n    va_end(args);\n\n    return rc;\n}\n\n// 0x4C5F24\nint db_fgetc(File* stream)\n{\n    if (read_callback != NULL) {\n        int ch = xfgetc(stream);\n\n        read_counter++;\n        if (read_counter >= read_threshold) {\n            read_callback();\n            read_counter = 0;\n        }\n\n        return ch;\n    }\n\n    return xfgetc(stream);\n}\n\n// 0x4C5F70\nchar* db_fgets(char* string, size_t size, File* stream)\n{\n    if (read_callback != NULL) {\n        if (xfgets(string, size, stream) == NULL) {\n            return NULL;\n        }\n\n        read_counter += strlen(string);\n        while (read_counter >= read_threshold) {\n            read_callback();\n            read_counter -= read_threshold;\n        }\n\n        return string;\n    }\n\n    return xfgets(string, size, stream);\n}\n\n// 0x4C5FEC\nint db_fputs(const char* string, File* stream)\n{\n    return xfputs(string, stream);\n}\n\n// 0x4C5FFC\nsize_t db_fread(void* ptr, size_t size, size_t count, File* stream)\n{\n    if (read_callback != NULL) {\n        unsigned char* byteBuffer = (unsigned char*)ptr;\n\n        size_t totalBytesRead = 0;\n        long remainingSize = size * count;\n        long chunkSize = read_threshold - read_counter;\n\n        while (remainingSize >= chunkSize) {\n            size_t bytesRead = xfread(byteBuffer, sizeof(*byteBuffer), chunkSize, stream);\n            byteBuffer += bytesRead;\n            totalBytesRead += bytesRead;\n            remainingSize -= bytesRead;\n\n            read_counter = 0;\n            read_callback();\n\n            chunkSize = read_threshold;\n        }\n\n        if (remainingSize != 0) {\n            size_t bytesRead = xfread(byteBuffer, sizeof(*byteBuffer), remainingSize, stream);\n            read_counter += bytesRead;\n            totalBytesRead += bytesRead;\n        }\n\n        return totalBytesRead / size;\n    }\n\n    return xfread(ptr, size, count, stream);\n}\n\n// 0x4C60B8\nsize_t db_fwrite(const void* buf, size_t size, size_t count, File* stream)\n{\n    return xfwrite(buf, size, count, stream);\n}\n\n// 0x4C60C0\nint db_fseek(File* stream, long offset, int origin)\n{\n    return xfseek(stream, offset, origin);\n}\n\n// 0x4C60C8\nlong db_ftell(File* stream)\n{\n    return xftell(stream);\n}\n\n// 0x4C60D0\nvoid db_rewind(File* stream)\n{\n    xrewind(stream);\n}\n\n// 0x4C60D8\nint db_feof(File* stream)\n{\n    return xfeof(stream);\n}\n\n// NOTE: Not sure about signness.\n//\n// 0x4C60E0\nint db_freadByte(File* stream, unsigned char* valuePtr)\n{\n    int value = db_fgetc(stream);\n    if (value == -1) {\n        return -1;\n    }\n\n    *valuePtr = value & 0xFF;\n\n    return 0;\n}\n\n// NOTE: Not sure about signness.\n//\n// 0x4C60F4\nint db_freadShort(File* stream, unsigned short* valuePtr)\n{\n    unsigned char high;\n    // NOTE: Uninline.\n    if (db_freadByte(stream, &high) == -1) {\n        return -1;\n    }\n\n    unsigned char low;\n    // NOTE: Uninline.\n    if (db_freadByte(stream, &low) == -1) {\n        return -1;\n    }\n\n    *valuePtr = (high << 8) | low;\n\n    return 0;\n}\n\n// 0x4C614C\nint db_freadInt(File* stream, int* valuePtr)\n{\n    int value;\n\n    if (xfread(&value, 4, 1, stream) == -1) {\n        return -1;\n    }\n\n    *valuePtr = ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000);\n\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x4C614C.\nint db_freadLong(File* stream, unsigned long* valuePtr)\n{\n    return db_freadInt(stream, valuePtr);\n}\n\n// NOTE: Uncollapsed 0x4C614C.\nint db_freadFloat(File* stream, float* valuePtr)\n{\n    return db_freadInt(stream, (unsigned long*)valuePtr);\n}\n\nint fileReadBool(File* stream, bool* valuePtr)\n{\n    int value;\n    if (db_freadInt(stream, &value) == -1) {\n        return -1;\n    }\n\n    *valuePtr = (value != 0);\n\n    return 0;\n}\n\n// NOTE: Not sure about signness.\n//\n// 0x4C61AC\nint db_fwriteByte(File* stream, unsigned char value)\n{\n    return xfputc(value, stream);\n};\n\n// 0x4C61C8\nint db_fwriteShort(File* stream, unsigned short value)\n{\n    // NOTE: Uninline.\n    if (db_fwriteByte(stream, (value >> 8) & 0xFF) == -1) {\n        return -1;\n    }\n\n    // NOTE: Uninline.\n    if (db_fwriteByte(stream, value & 0xFF) == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4C6214\nint db_fwriteInt(File* stream, int value)\n{\n    // NOTE: Uninline.\n    return db_fwriteLong(stream, value);\n}\n\n// 0x4C6244\nint db_fwriteLong(File* stream, unsigned long value)\n{\n    if (db_fwriteShort(stream, (value >> 16) & 0xFFFF) == -1) {\n        return -1;\n    }\n\n    if (db_fwriteShort(stream, value & 0xFFFF) == -1) {\n        return -1;\n    }\n\n    return 0;\n}\n\n// 0x4C62C4\nint db_fwriteFloat(File* stream, float value)\n{\n    // NOTE: Uninline.\n    return db_fwriteLong(stream, *(unsigned long*)&value);\n}\n\nint fileWriteBool(File* stream, bool value)\n{\n    return db_fwriteLong(stream, value ? 1 : 0);\n}\n\n// 0x4C62FC\nint db_freadByteCount(File* stream, unsigned char* arr, int count)\n{\n    for (int index = 0; index < count; index++) {\n        unsigned char ch;\n        // NOTE: Uninline.\n        if (db_freadByte(stream, &ch) == -1) {\n            return -1;\n        }\n\n        arr[index] = ch;\n    }\n\n    return 0;\n}\n\n// 0x4C6330\nint db_freadShortCount(File* stream, unsigned short* arr, int count)\n{\n    for (int index = 0; index < count; index++) {\n        short value;\n        // NOTE: Uninline.\n        if (db_freadShort(stream, &value) == -1) {\n            return -1;\n        }\n\n        arr[index] = value;\n    }\n\n    return 0;\n}\n\n// 0x4C63BC\nint db_freadIntCount(File* stream, int* arr, int count)\n{\n    if (count == 0) {\n        return 0;\n    }\n\n    if (db_fread(arr, sizeof(*arr) * count, 1, stream) < 1) {\n        return -1;\n    }\n\n    for (int index = 0; index < count; index++) {\n        int value = arr[index];\n        arr[index] = ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000);\n    }\n\n    return 0;\n}\n\n// NOTE: Uncollapsed 0x4C63BC.\nint db_freadLongCount(File* stream, unsigned long* arr, int count)\n{\n    return db_freadIntCount(stream, arr, count);\n}\n\n// 0x4C6464\nint db_fwriteByteCount(File* stream, unsigned char* arr, int count)\n{\n    for (int index = 0; index < count; index++) {\n        // NOTE: Uninline.\n        if (db_fwriteByte(stream, arr[index]) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C6490\nint db_fwriteShortCount(File* stream, unsigned short* arr, int count)\n{\n    for (int index = 0; index < count; index++) {\n        // NOTE: Uninline.\n        if (db_fwriteShort(stream, arr[index]) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C64F8\nint db_fwriteIntCount(File* stream, int* arr, int count)\n{\n    for (int index = 0; index < count; index++) {\n        // NOTE: Uninline.\n        if (db_fwriteLong(stream, arr[index]) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C6550\nint db_fwriteLongCount(File* stream, unsigned long* arr, int count)\n{\n    for (int index = 0; index < count; index++) {\n        int value = arr[index];\n\n        // NOTE: Uninline.\n        if (db_fwriteShort(stream, (value >> 16) & 0xFFFF) == -1) {\n            return -1;\n        }\n\n        // NOTE: Uninline.\n        if (db_fwriteShort(stream, value & 0xFFFF) == -1) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C6628\nint db_get_file_list(const char* pattern, char*** fileNameListPtr, int a3, int a4)\n{\n    FileList* fileList = (FileList*)malloc(sizeof(*fileList));\n    if (fileList == NULL) {\n        return 0;\n    }\n\n    memset(fileList, 0, sizeof(*fileList));\n\n    XList* xlist = &(fileList->xlist);\n    if (!xbuild_filelist(pattern, xlist)) {\n        free(fileList);\n        return 0;\n    }\n\n    int length = 0;\n    if (xlist->fileNamesLength != 0) {\n        qsort(xlist->fileNames, xlist->fileNamesLength, sizeof(*xlist->fileNames), db_list_compare);\n\n        int fileNamesLength = xlist->fileNamesLength;\n        for (int index = 0; index < fileNamesLength - 1; index++) {\n            if (stricmp(xlist->fileNames[index], xlist->fileNames[index + 1]) == 0) {\n                char* temp = xlist->fileNames[index + 1];\n                memmove(&(xlist->fileNames[index + 1]), &(xlist->fileNames[index + 2]), sizeof(*xlist->fileNames) * (xlist->fileNamesLength - index - 1));\n                xlist->fileNames[xlist->fileNamesLength - 1] = temp;\n\n                fileNamesLength--;\n                index--;\n            }\n        }\n\n        bool isWildcard = *pattern == '*';\n\n        for (int index = 0; index < fileNamesLength; index += 1) {\n            const char* name = xlist->fileNames[index];\n            char dir[_MAX_DIR];\n            char fileName[_MAX_FNAME];\n            char extension[_MAX_EXT];\n            _splitpath(name, NULL, dir, fileName, extension);\n\n            if (!isWildcard || *dir == '\\0' || strchr(dir, '\\\\') == NULL) {\n                // FIXME: There is a buffer overlow bug in this implementation.\n                // `fileNames` entries are dynamically allocated strings\n                // themselves produced by `strdup` in `xlistenumfunc`.\n                // In some circumstances we can end up placing long file name\n                // in a short buffer (if that shorter buffer is alphabetically\n                // preceding current file name).\n                //\n                // It can be easily spotted by creating `a.txt` in the game\n                // directory and then trying print character data from character\n                // editor. Because of compiler differencies original game will\n                // crash immediately, and RE can crash anytime after closing\n                // file dialog.\n                sprintf(xlist->fileNames[length], \"%s%s\", fileName, extension);\n                length++;\n            }\n        }\n    }\n\n    fileList->next = db_file_lists;\n    db_file_lists = fileList;\n\n    *fileNameListPtr = xlist->fileNames;\n\n    return length;\n}\n\n// 0x4C6868\nvoid db_free_file_list(char*** fileNameListPtr, int a2)\n{\n    if (db_file_lists == NULL) {\n        return;\n    }\n\n    FileList* currentFileList = db_file_lists;\n    FileList* previousFileList = db_file_lists;\n    while (*fileNameListPtr != currentFileList->xlist.fileNames) {\n        previousFileList = currentFileList;\n        currentFileList = currentFileList->next;\n        if (currentFileList == NULL) {\n            return;\n        }\n    }\n\n    if (previousFileList == db_file_lists) {\n        db_file_lists = currentFileList->next;\n    } else {\n        previousFileList->next = currentFileList->next;\n    }\n\n    xfree_filelist(&(currentFileList->xlist));\n\n    free(currentFileList);\n}\n\n// NOTE: This function does nothing. It was probably used to set memory procs\n// for building file name list.\n//\n// 0x4C68B8\nvoid db_register_mem(MallocProc* mallocProc, StrdupProc* strdupProc, FreeProc* freeProc)\n{\n}\n\n// TODO: Return type should be long.\n//\n// 0x4C68BC\nint db_filelength(File* stream)\n{\n    return xfilelength(stream);\n}\n\n// 0x4C68C4\nvoid db_register_callback(FileReadProgressHandler* handler, int size)\n{\n    if (handler != NULL && size != 0) {\n        read_callback = handler;\n        read_threshold = size;\n    } else {\n        read_callback = NULL;\n        read_threshold = 0;\n    }\n}\n\n// NOTE: This function is called when fallout2.cfg has \"hashing\" enabled, but\n// it does nothing. It's impossible to guess it's name.\n//\n// 0x4C68E4\nvoid db_enable_hash_table()\n{\n}\n\n// 0x4C68E8\nstatic int db_list_compare(const void* p1, const void* p2)\n{\n    return stricmp(*(const char**)p1, *(const char**)p2);\n}\n"
  },
  {
    "path": "src/plib/db/db.h",
    "content": "#ifndef FALLOUT_PLIB_DB_DB_H_\n#define FALLOUT_PLIB_DB_DB_H_\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"memory_defs.h\"\n#include \"plib/xfile/xfile.h\"\n\ntypedef XFile File;\ntypedef void FileReadProgressHandler();\ntypedef char* StrdupProc(const char* string);\n\ntypedef struct FileList {\n    XList xlist;\n    struct FileList* next;\n} FileList;\n\nint db_init(const char* filePath1, int a2, const char* filePath2, int a4);\nint db_select(int dbHandle);\nint db_current();\nint db_total();\nvoid db_exit();\nint db_dir_entry(const char* filePath, int* sizePtr);\nint db_read_to_buf(const char* filePath, void* ptr);\nint db_fclose(File* stream);\nFile* db_fopen(const char* filename, const char* mode);\nint db_fprintf(File* stream, const char* format, ...);\nint db_fgetc(File* stream);\nchar* db_fgets(char* str, size_t size, File* stream);\nint db_fputs(const char* s, File* stream);\nsize_t db_fread(void* buf, size_t size, size_t count, File* stream);\nsize_t db_fwrite(const void* buf, size_t size, size_t count, File* stream);\nint db_fseek(File* stream, long offset, int origin);\nlong db_ftell(File* stream);\nvoid db_rewind(File* stream);\nint db_feof(File* stream);\nint db_freadByte(File* stream, unsigned char* valuePtr);\nint db_freadShort(File* stream, unsigned short* valuePtr);\nint db_freadInt(File* stream, int* valuePtr);\nint db_freadLong(File* stream, unsigned long* valuePtr);\nint db_freadFloat(File* stream, float* valuePtr);\nint fileReadBool(File* stream, bool* valuePtr);\nint db_fwriteByte(File* stream, unsigned char value);\nint db_fwriteShort(File* stream, unsigned short value);\nint db_fwriteInt(File* stream, int value);\nint db_fwriteLong(File* stream, unsigned long value);\nint db_fwriteFloat(File* stream, float value);\nint fileWriteBool(File* stream, bool value);\nint db_freadByteCount(File* stream, unsigned char* arr, int count);\nint db_freadShortCount(File* stream, unsigned short* arr, int count);\nint db_freadIntCount(File* stream, int* arr, int count);\nint db_freadLongCount(File* stream, unsigned long* arr, int count);\nint db_fwriteByteCount(File* stream, unsigned char* arr, int count);\nint db_fwriteShortCount(File* stream, unsigned short* arr, int count);\nint db_fwriteIntCount(File* stream, int* arr, int count);\nint db_fwriteLongCount(File* stream, unsigned long* arr, int count);\nint db_get_file_list(const char* pattern, char*** fileNames, int a3, int a4);\nvoid db_free_file_list(char*** fileNames, int a2);\nvoid db_register_mem(MallocProc* mallocProc, StrdupProc* strdupProc, FreeProc* freeProc);\nint db_filelength(File* stream);\nvoid db_register_callback(FileReadProgressHandler* handler, int size);\nvoid db_enable_hash_table();\n\n#endif /* FALLOUT_PLIB_DB_DB_H_ */\n"
  },
  {
    "path": "src/plib/gnw/button.c",
    "content": "#include \"plib/gnw/button.h\"\n\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"plib/color/color.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/memory.h\"\n#include \"plib/gnw/mouse.h\"\n#include \"plib/gnw/text.h\"\n\n// 0x51E404\nstatic int last_button_winID = -1;\n\n// 0x6ADF40\nstatic RadioGroup btn_grp[RADIO_GROUP_LIST_CAPACITY];\n\nstatic Button* button_create(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, int flags, unsigned char* up, unsigned char* dn, unsigned char* hover);\nstatic bool button_under_mouse(Button* button, Rect* rect);\nstatic int button_check_group(Button* button);\nstatic void button_draw(Button* button, Window* window, unsigned char* data, int a4, Rect* a5, int a6);\n\n// 0x4D8260\nint win_register_button(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, unsigned char* up, unsigned char* dn, unsigned char* hover, int flags)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (w == NULL) {\n        return -1;\n    }\n\n    if (up == NULL && (dn != NULL || hover != NULL)) {\n        return -1;\n    }\n\n    Button* button = button_create(win, x, y, width, height, mouseEnterEventCode, mouseExitEventCode, mouseDownEventCode, mouseUpEventCode, flags | BUTTON_FLAG_0x010000, up, dn, hover);\n    if (button == NULL) {\n        return -1;\n    }\n\n    button_draw(button, w, button->mouseUpImage, 0, NULL, 0);\n\n    return button->id;\n}\n\n// 0x4D8308\nint win_register_text_button(int win, int x, int y, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, const char* title, int flags)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (w == NULL) {\n        return -1;\n    }\n\n    int buttonWidth = text_width(title) + 16;\n    int buttonHeight = text_height() + 7;\n    unsigned char* normal = (unsigned char*)mem_malloc(buttonWidth * buttonHeight);\n    if (normal == NULL) {\n        return -1;\n    }\n\n    unsigned char* pressed = (unsigned char*)mem_malloc(buttonWidth * buttonHeight);\n    if (pressed == NULL) {\n        mem_free(normal);\n        return -1;\n    }\n\n    if (w->field_20 == 256 && GNW_texture != NULL) {\n        // TODO: Incomplete.\n    } else {\n        buf_fill(normal, buttonWidth, buttonHeight, buttonWidth, w->field_20);\n        buf_fill(pressed, buttonWidth, buttonHeight, buttonWidth, w->field_20);\n    }\n\n    lighten_buf(normal, buttonWidth, buttonHeight, buttonWidth);\n\n    text_to_buf(normal + buttonWidth * 3 + 8, title, buttonWidth, buttonWidth, colorTable[GNW_wcolor[3]]);\n    draw_shaded_box(normal,\n        buttonWidth,\n        2,\n        2,\n        buttonWidth - 3,\n        buttonHeight - 3,\n        colorTable[GNW_wcolor[1]],\n        colorTable[GNW_wcolor[2]]);\n    draw_shaded_box(normal,\n        buttonWidth,\n        1,\n        1,\n        buttonWidth - 2,\n        buttonHeight - 2,\n        colorTable[GNW_wcolor[1]],\n        colorTable[GNW_wcolor[2]]);\n    draw_box(normal, buttonWidth, 0, 0, buttonWidth - 1, buttonHeight - 1, colorTable[0]);\n\n    text_to_buf(pressed + buttonWidth * 4 + 9, title, buttonWidth, buttonWidth, colorTable[GNW_wcolor[3]]);\n    draw_shaded_box(pressed,\n        buttonWidth,\n        2,\n        2,\n        buttonWidth - 3,\n        buttonHeight - 3,\n        colorTable[GNW_wcolor[2]],\n        colorTable[GNW_wcolor[1]]);\n    draw_shaded_box(pressed,\n        buttonWidth,\n        1,\n        1,\n        buttonWidth - 2,\n        buttonHeight - 2,\n        colorTable[GNW_wcolor[2]],\n        colorTable[GNW_wcolor[1]]);\n    draw_box(pressed, buttonWidth, 0, 0, buttonWidth - 1, buttonHeight - 1, colorTable[0]);\n\n    Button* button = button_create(win,\n        x,\n        y,\n        buttonWidth,\n        buttonHeight,\n        mouseEnterEventCode,\n        mouseExitEventCode,\n        mouseDownEventCode,\n        mouseUpEventCode,\n        flags,\n        normal,\n        pressed,\n        NULL);\n    if (button == NULL) {\n        mem_free(normal);\n        mem_free(pressed);\n        return -1;\n    }\n\n    button_draw(button, w, button->mouseUpImage, 0, NULL, 0);\n\n    return button->id;\n}\n\n// 0x4D8674\nint win_register_button_disable(int btn, unsigned char* up, unsigned char* down, unsigned char* hover)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Button* button = GNW_find_button(btn, NULL);\n    if (button == NULL) {\n        return -1;\n    }\n\n    button->field_3C = up;\n    button->field_40 = down;\n    button->field_44 = hover;\n\n    return 0;\n}\n\n// 0x4D86A8\nint win_register_button_image(int btn, unsigned char* up, unsigned char* down, unsigned char* hover, int a5)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (up == NULL && (down != NULL || hover != NULL)) {\n        return -1;\n    }\n\n    Window* w;\n    Button* button = GNW_find_button(btn, &w);\n    if (button == NULL) {\n        return -1;\n    }\n\n    if (!(button->flags & BUTTON_FLAG_0x010000)) {\n        return -1;\n    }\n\n    unsigned char* data = button->currentImage;\n    if (data == button->mouseUpImage) {\n        button->currentImage = up;\n    } else if (data == button->mouseDownImage) {\n        button->currentImage = down;\n    } else if (data == button->mouseHoverImage) {\n        button->currentImage = hover;\n    }\n\n    button->mouseUpImage = up;\n    button->mouseDownImage = down;\n    button->mouseHoverImage = hover;\n\n    button_draw(button, w, button->currentImage, a5, NULL, 0);\n\n    return 0;\n}\n\n// Sets primitive callbacks on the button.\n//\n// 0x4D8758\nint win_register_button_func(int btn, ButtonCallback* mouseEnterProc, ButtonCallback* mouseExitProc, ButtonCallback* mouseDownProc, ButtonCallback* mouseUpProc)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Button* button = GNW_find_button(btn, NULL);\n    if (button == NULL) {\n        return -1;\n    }\n\n    button->mouseEnterProc = mouseEnterProc;\n    button->mouseExitProc = mouseExitProc;\n    button->leftMouseDownProc = mouseDownProc;\n    button->leftMouseUpProc = mouseUpProc;\n\n    return 0;\n}\n\n// 0x4D8798\nint win_register_right_button(int btn, int rightMouseDownEventCode, int rightMouseUpEventCode, ButtonCallback* rightMouseDownProc, ButtonCallback* rightMouseUpProc)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Button* button = GNW_find_button(btn, NULL);\n    if (button == NULL) {\n        return -1;\n    }\n\n    button->rightMouseDownEventCode = rightMouseDownEventCode;\n    button->rightMouseUpEventCode = rightMouseUpEventCode;\n    button->rightMouseDownProc = rightMouseDownProc;\n    button->rightMouseUpProc = rightMouseUpProc;\n\n    if (rightMouseDownEventCode != -1 || rightMouseUpEventCode != -1 || rightMouseDownProc != NULL || rightMouseUpProc != NULL) {\n        button->flags |= BUTTON_FLAG_RIGHT_MOUSE_BUTTON_CONFIGURED;\n    } else {\n        button->flags &= ~BUTTON_FLAG_RIGHT_MOUSE_BUTTON_CONFIGURED;\n    }\n\n    return 0;\n}\n\n// Sets button state callbacks.\n// [a2] - when button is transitioning to pressed state\n// [a3] - when button is returned to unpressed state\n//\n// The changes in the state are tied to graphical state, therefore these callbacks are not generated for\n// buttons with no graphics.\n//\n// These callbacks can be triggered several times during tracking if mouse leaves button's rectangle without releasing mouse buttons.\n//\n// 0x4D87F8\nint win_register_button_sound_func(int btn, ButtonCallback* onPressed, ButtonCallback* onUnpressed)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Button* button = GNW_find_button(btn, NULL);\n    if (button == NULL) {\n        return -1;\n    }\n\n    button->onPressed = onPressed;\n    button->onUnpressed = onUnpressed;\n\n    return 0;\n}\n\n// 0x4D8828\nint win_register_button_mask(int btn, unsigned char* mask)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Button* button = GNW_find_button(btn, NULL);\n    if (button == NULL) {\n        return -1;\n    }\n\n    button->mask = mask;\n\n    return 0;\n}\n\n// 0x4D8854\nstatic Button* button_create(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, int flags, unsigned char* up, unsigned char* dn, unsigned char* hover)\n{\n    Window* w = GNW_find(win);\n    if (w == NULL) {\n        return NULL;\n    }\n\n    Button* button = (Button*)mem_malloc(sizeof(*button));\n    if (button == NULL) {\n        return NULL;\n    }\n\n    if ((flags & BUTTON_FLAG_0x01) == 0) {\n        if ((flags & BUTTON_FLAG_0x02) != 0) {\n            flags &= ~BUTTON_FLAG_0x02;\n        }\n\n        if ((flags & BUTTON_FLAG_0x04) != 0) {\n            flags &= ~BUTTON_FLAG_0x04;\n        }\n    }\n\n    // NOTE: Uninline.\n    int buttonId = button_new_id();\n\n    button->id = buttonId;\n    button->flags = flags;\n    button->rect.ulx = x;\n    button->rect.uly = y;\n    button->rect.lrx = x + width - 1;\n    button->rect.lry = y + height - 1;\n    button->mouseEnterEventCode = mouseEnterEventCode;\n    button->mouseExitEventCode = mouseExitEventCode;\n    button->lefMouseDownEventCode = mouseDownEventCode;\n    button->leftMouseUpEventCode = mouseUpEventCode;\n    button->rightMouseDownEventCode = -1;\n    button->rightMouseUpEventCode = -1;\n    button->mouseUpImage = up;\n    button->mouseDownImage = dn;\n    button->mouseHoverImage = hover;\n    button->field_3C = NULL;\n    button->field_40 = NULL;\n    button->field_44 = NULL;\n    button->currentImage = NULL;\n    button->mask = NULL;\n    button->mouseEnterProc = NULL;\n    button->mouseExitProc = NULL;\n    button->leftMouseDownProc = NULL;\n    button->leftMouseUpProc = NULL;\n    button->rightMouseDownProc = NULL;\n    button->rightMouseUpProc = NULL;\n    button->onPressed = NULL;\n    button->onUnpressed = NULL;\n    button->radioGroup = NULL;\n    button->prev = NULL;\n\n    button->next = w->buttonListHead;\n    if (button->next != NULL) {\n        button->next->prev = button;\n    }\n    w->buttonListHead = button;\n\n    return button;\n}\n\n// 0x4D89E4\nbool win_button_down(int btn)\n{\n    if (!GNW_win_init_flag) {\n        return false;\n    }\n\n    Button* button = GNW_find_button(btn, NULL);\n    if (button == NULL) {\n        return false;\n    }\n\n    if ((button->flags & BUTTON_FLAG_0x01) != 0 && (button->flags & BUTTON_FLAG_0x020000) != 0) {\n        return true;\n    }\n\n    return false;\n}\n\n// 0x4D8A10\nint GNW_check_buttons(Window* w, int* keyCodePtr)\n{\n    Rect v58;\n    Button* field_34;\n    Button* field_38;\n    Button* button;\n\n    if ((w->flags & WINDOW_HIDDEN) != 0) {\n        return -1;\n    }\n\n    button = w->buttonListHead;\n    field_34 = w->field_34;\n    field_38 = w->field_38;\n\n    if (field_34 != NULL) {\n        rectCopy(&v58, &(field_34->rect));\n        rectOffset(&v58, w->rect.ulx, w->rect.uly);\n    } else if (field_38 != NULL) {\n        rectCopy(&v58, &(field_38->rect));\n        rectOffset(&v58, w->rect.ulx, w->rect.uly);\n    }\n\n    *keyCodePtr = -1;\n\n    if (mouse_click_in(w->rect.ulx, w->rect.uly, w->rect.lrx, w->rect.lry)) {\n        int mouseEvent = mouse_get_buttons();\n        if ((w->flags & WINDOW_FLAG_0x40) || (mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) == 0) {\n            if (mouseEvent == 0) {\n                w->field_38 = NULL;\n            }\n        } else {\n            win_show(w->id);\n        }\n\n        if (field_34 != NULL) {\n            if (!button_under_mouse(field_34, &v58)) {\n                if (!(field_34->flags & BUTTON_FLAG_DISABLED)) {\n                    *keyCodePtr = field_34->mouseExitEventCode;\n                }\n\n                if ((field_34->flags & BUTTON_FLAG_0x01) && (field_34->flags & BUTTON_FLAG_0x020000)) {\n                    button_draw(field_34, w, field_34->mouseDownImage, 1, NULL, 1);\n                } else {\n                    button_draw(field_34, w, field_34->mouseUpImage, 1, NULL, 1);\n                }\n\n                w->field_34 = NULL;\n\n                last_button_winID = w->id;\n\n                if (!(field_34->flags & BUTTON_FLAG_DISABLED)) {\n                    if (field_34->mouseExitProc != NULL) {\n                        field_34->mouseExitProc(field_34->id, *keyCodePtr);\n                        if (!(field_34->flags & BUTTON_FLAG_0x40)) {\n                            *keyCodePtr = -1;\n                        }\n                    }\n                }\n                return 0;\n            }\n            button = field_34;\n        } else if (field_38 != NULL) {\n            if (button_under_mouse(field_38, &v58)) {\n                if (!(field_38->flags & BUTTON_FLAG_DISABLED)) {\n                    *keyCodePtr = field_38->mouseEnterEventCode;\n                }\n\n                if ((field_38->flags & BUTTON_FLAG_0x01) && (field_38->flags & BUTTON_FLAG_0x020000)) {\n                    button_draw(field_38, w, field_38->mouseDownImage, 1, NULL, 1);\n                } else {\n                    button_draw(field_38, w, field_38->mouseUpImage, 1, NULL, 1);\n                }\n\n                w->field_34 = field_38;\n\n                last_button_winID = w->id;\n\n                if (!(field_38->flags & BUTTON_FLAG_DISABLED)) {\n                    if (field_38->mouseEnterProc != NULL) {\n                        field_38->mouseEnterProc(field_38->id, *keyCodePtr);\n                        if (!(field_38->flags & BUTTON_FLAG_0x40)) {\n                            *keyCodePtr = -1;\n                        }\n                    }\n                }\n                return 0;\n            }\n        }\n\n        int v25 = last_button_winID;\n        if (last_button_winID != -1 && last_button_winID != w->id) {\n            Window* v26 = GNW_find(last_button_winID);\n            if (v26 != NULL) {\n                last_button_winID = -1;\n\n                Button* v28 = v26->field_34;\n                if (v28 != NULL) {\n                    if (!(v28->flags & BUTTON_FLAG_DISABLED)) {\n                        *keyCodePtr = v28->mouseExitEventCode;\n                    }\n\n                    if ((v28->flags & BUTTON_FLAG_0x01) && (v28->flags & BUTTON_FLAG_0x020000)) {\n                        button_draw(v28, v26, v28->mouseDownImage, 1, NULL, 1);\n                    } else {\n                        button_draw(v28, v26, v28->mouseUpImage, 1, NULL, 1);\n                    }\n\n                    v26->field_38 = NULL;\n                    v26->field_34 = NULL;\n\n                    if (!(v28->flags & BUTTON_FLAG_DISABLED)) {\n                        if (v28->mouseExitProc != NULL) {\n                            v28->mouseExitProc(v28->id, *keyCodePtr);\n                            if (!(v28->flags & BUTTON_FLAG_0x40)) {\n                                *keyCodePtr = -1;\n                            }\n                        }\n                    }\n                    return 0;\n                }\n            }\n        }\n\n        ButtonCallback* cb = NULL;\n\n        while (button != NULL) {\n            if (!(button->flags & BUTTON_FLAG_DISABLED)) {\n                rectCopy(&v58, &(button->rect));\n                rectOffset(&v58, w->rect.ulx, w->rect.uly);\n                if (button_under_mouse(button, &v58)) {\n                    if (!(button->flags & BUTTON_FLAG_DISABLED)) {\n                        if ((mouseEvent & MOUSE_EVENT_ANY_BUTTON_DOWN) != 0) {\n                            if ((mouseEvent & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0 && (button->flags & BUTTON_FLAG_RIGHT_MOUSE_BUTTON_CONFIGURED) == 0) {\n                                button = NULL;\n                                break;\n                            }\n\n                            if (button != w->field_34 && button != w->field_38) {\n                                break;\n                            }\n\n                            w->field_38 = button;\n                            w->field_34 = button;\n\n                            if ((button->flags & BUTTON_FLAG_0x01) != 0) {\n                                if ((button->flags & BUTTON_FLAG_0x02) != 0) {\n                                    if ((button->flags & BUTTON_FLAG_0x020000) != 0) {\n                                        if (!(button->flags & BUTTON_FLAG_0x04)) {\n                                            if (button->radioGroup != NULL) {\n                                                button->radioGroup->field_4--;\n                                            }\n\n                                            if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n                                                *keyCodePtr = button->leftMouseUpEventCode;\n                                                cb = button->leftMouseUpProc;\n                                            } else {\n                                                *keyCodePtr = button->rightMouseUpEventCode;\n                                                cb = button->rightMouseUpProc;\n                                            }\n\n                                            button->flags &= ~BUTTON_FLAG_0x020000;\n                                        }\n                                    } else {\n                                        if (button_check_group(button) == -1) {\n                                            button = NULL;\n                                            break;\n                                        }\n\n                                        if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n                                            *keyCodePtr = button->lefMouseDownEventCode;\n                                            cb = button->leftMouseDownProc;\n                                        } else {\n                                            *keyCodePtr = button->rightMouseDownEventCode;\n                                            cb = button->rightMouseDownProc;\n                                        }\n\n                                        button->flags |= BUTTON_FLAG_0x020000;\n                                    }\n                                }\n                            } else {\n                                if (button_check_group(button) == -1) {\n                                    button = NULL;\n                                    break;\n                                }\n\n                                if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) {\n                                    *keyCodePtr = button->lefMouseDownEventCode;\n                                    cb = button->leftMouseDownProc;\n                                } else {\n                                    *keyCodePtr = button->rightMouseDownEventCode;\n                                    cb = button->rightMouseDownProc;\n                                }\n                            }\n\n                            button_draw(button, w, button->mouseDownImage, 1, NULL, 1);\n                            break;\n                        }\n\n                        Button* v49 = w->field_38;\n                        if (button == v49 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_UP) != 0) {\n                            w->field_38 = NULL;\n                            w->field_34 = v49;\n\n                            if (v49->flags & BUTTON_FLAG_0x01) {\n                                if (!(v49->flags & BUTTON_FLAG_0x02)) {\n                                    if (v49->flags & BUTTON_FLAG_0x020000) {\n                                        if (!(v49->flags & BUTTON_FLAG_0x04)) {\n                                            if (v49->radioGroup != NULL) {\n                                                v49->radioGroup->field_4--;\n                                            }\n\n                                            if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n                                                *keyCodePtr = button->leftMouseUpEventCode;\n                                                cb = button->leftMouseUpProc;\n                                            } else {\n                                                *keyCodePtr = button->rightMouseUpEventCode;\n                                                cb = button->rightMouseUpProc;\n                                            }\n\n                                            button->flags &= ~BUTTON_FLAG_0x020000;\n                                        }\n                                    } else {\n                                        if (button_check_group(v49) == -1) {\n                                            button = NULL;\n                                            button_draw(v49, w, v49->mouseUpImage, 1, NULL, 1);\n                                            break;\n                                        }\n\n                                        if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n                                            *keyCodePtr = v49->lefMouseDownEventCode;\n                                            cb = v49->leftMouseDownProc;\n                                        } else {\n                                            *keyCodePtr = v49->rightMouseDownEventCode;\n                                            cb = v49->rightMouseDownProc;\n                                        }\n\n                                        v49->flags |= BUTTON_FLAG_0x020000;\n                                    }\n                                }\n                            } else {\n                                if (v49->flags & BUTTON_FLAG_0x020000) {\n                                    if (v49->radioGroup != NULL) {\n                                        v49->radioGroup->field_4--;\n                                    }\n                                }\n\n                                if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {\n                                    *keyCodePtr = v49->leftMouseUpEventCode;\n                                    cb = v49->leftMouseUpProc;\n                                } else {\n                                    *keyCodePtr = v49->rightMouseUpEventCode;\n                                    cb = v49->rightMouseUpProc;\n                                }\n                            }\n\n                            if (button->mouseHoverImage != NULL) {\n                                button_draw(button, w, button->mouseHoverImage, 1, NULL, 1);\n                            } else {\n                                button_draw(button, w, button->mouseUpImage, 1, NULL, 1);\n                            }\n                            break;\n                        }\n                    }\n\n                    if (w->field_34 == NULL && mouseEvent == 0) {\n                        w->field_34 = button;\n                        if (!(button->flags & BUTTON_FLAG_DISABLED)) {\n                            *keyCodePtr = button->mouseEnterEventCode;\n                            cb = button->mouseEnterProc;\n                        }\n\n                        button_draw(button, w, button->mouseHoverImage, 1, NULL, 1);\n                    }\n                    break;\n                }\n            }\n            button = button->next;\n        }\n\n        if (button != NULL) {\n            if ((button->flags & BUTTON_FLAG_0x10) != 0\n                && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_DOWN) != 0\n                && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_REPEAT) == 0) {\n                win_drag(w->id);\n                button_draw(button, w, button->mouseUpImage, 1, NULL, 1);\n            }\n        } else if ((w->flags & WINDOW_FLAG_0x80) != 0) {\n            v25 |= mouseEvent << 8;\n            if ((mouseEvent & MOUSE_EVENT_ANY_BUTTON_DOWN) != 0\n                && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_REPEAT) == 0) {\n                win_drag(w->id);\n            }\n        }\n\n        last_button_winID = w->id;\n\n        if (button != NULL) {\n            if (cb != NULL) {\n                cb(button->id, *keyCodePtr);\n                if (!(button->flags & BUTTON_FLAG_0x40)) {\n                    *keyCodePtr = -1;\n                }\n            }\n        }\n\n        return 0;\n    }\n\n    if (field_34 != NULL) {\n        *keyCodePtr = field_34->mouseExitEventCode;\n\n        unsigned char* data;\n        if ((field_34->flags & BUTTON_FLAG_0x01) && (field_34->flags & BUTTON_FLAG_0x020000)) {\n            data = field_34->mouseDownImage;\n        } else {\n            data = field_34->mouseUpImage;\n        }\n\n        button_draw(field_34, w, data, 1, NULL, 1);\n\n        w->field_34 = NULL;\n    }\n\n    if (*keyCodePtr != -1) {\n        last_button_winID = w->id;\n\n        if ((field_34->flags & BUTTON_FLAG_DISABLED) == 0) {\n            if (field_34->mouseExitProc != NULL) {\n                field_34->mouseExitProc(field_34->id, *keyCodePtr);\n                if (!(field_34->flags & BUTTON_FLAG_0x40)) {\n                    *keyCodePtr = -1;\n                }\n            }\n        }\n        return 0;\n    }\n\n    if (field_34 != NULL) {\n        if ((field_34->flags & BUTTON_FLAG_DISABLED) == 0) {\n            if (field_34->mouseExitProc != NULL) {\n                field_34->mouseExitProc(field_34->id, *keyCodePtr);\n            }\n        }\n    }\n\n    return -1;\n}\n\n// 0x4D9214\nstatic bool button_under_mouse(Button* button, Rect* rect)\n{\n    if (!mouse_click_in(rect->ulx, rect->uly, rect->lrx, rect->lry)) {\n        return false;\n    }\n\n    if (button->mask == NULL) {\n        return true;\n    }\n\n    int x;\n    int y;\n    mouse_get_position(&x, &y);\n    x -= rect->ulx;\n    y -= rect->uly;\n\n    int width = button->rect.lrx - button->rect.ulx + 1;\n    return button->mask[width * y + x] != 0;\n}\n\n// 0x4D927C\nint win_button_winID(int btn)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Window* w;\n    if (GNW_find_button(btn, &w) == NULL) {\n        return -1;\n    }\n\n    return w->id;\n}\n\n// 0x4D92B4\nint win_last_button_winID()\n{\n    return last_button_winID;\n}\n\n// 0x4D92BC\nint win_delete_button(int btn)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Window* w;\n    Button* button = GNW_find_button(btn, &w);\n    if (button == NULL) {\n        return -1;\n    }\n\n    if (button->prev != NULL) {\n        button->prev->next = button->next;\n    } else {\n        w->buttonListHead = button->next;\n    }\n\n    if (button->next != NULL) {\n        button->next->prev = button->prev;\n    }\n\n    win_fill(w->id, button->rect.ulx, button->rect.uly, button->rect.lrx - button->rect.ulx + 1, button->rect.lry - button->rect.uly + 1, w->field_20);\n\n    if (button == w->field_34) {\n        w->field_34 = NULL;\n    }\n\n    if (button == w->field_38) {\n        w->field_38 = NULL;\n    }\n\n    GNW_delete_button(button);\n\n    return 0;\n}\n\n// 0x4D9374\nvoid GNW_delete_button(Button* button)\n{\n    if ((button->flags & BUTTON_FLAG_0x010000) == 0) {\n        if (button->mouseUpImage != NULL) {\n            mem_free(button->mouseUpImage);\n        }\n\n        if (button->mouseDownImage != NULL) {\n            mem_free(button->mouseDownImage);\n        }\n\n        if (button->mouseHoverImage != NULL) {\n            mem_free(button->mouseHoverImage);\n        }\n\n        if (button->field_3C != NULL) {\n            mem_free(button->field_3C);\n        }\n\n        if (button->field_40 != NULL) {\n            mem_free(button->field_40);\n        }\n\n        if (button->field_44 != NULL) {\n            mem_free(button->field_44);\n        }\n    }\n\n    RadioGroup* radioGroup = button->radioGroup;\n    if (radioGroup != NULL) {\n        for (int index = 0; index < radioGroup->buttonsLength; index++) {\n            if (button == radioGroup->buttons[index]) {\n                for (; index < radioGroup->buttonsLength - 1; index++) {\n                    radioGroup->buttons[index] = radioGroup->buttons[index + 1];\n                }\n\n                radioGroup->buttonsLength--;\n\n                break;\n            }\n        }\n    }\n\n    mem_free(button);\n}\n\n// NOTE: Unused.\n//\n// 0x4D9430\nvoid win_delete_button_win(int btn, int inputEvent)\n{\n    Button* button;\n    Window* w;\n\n    button = GNW_find_button(btn, &w);\n    if (button != NULL) {\n        win_delete(w->id);\n        GNW_add_input_buffer(inputEvent);\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4D9458\nint button_new_id()\n{\n    int btn;\n\n    btn = 1;\n    while (GNW_find_button(btn, NULL) != NULL) {\n        btn++;\n    }\n\n    return btn;\n}\n\n// 0x4D9474\nint win_enable_button(int btn)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Window* w;\n    Button* button = GNW_find_button(btn, &w);\n    if (button == NULL) {\n        return -1;\n    }\n\n    if ((button->flags & BUTTON_FLAG_DISABLED) != 0) {\n        button->flags &= ~BUTTON_FLAG_DISABLED;\n        button_draw(button, w, button->currentImage, 1, NULL, 0);\n    }\n\n    return 0;\n}\n\n// 0x4D94D0\nint win_disable_button(int btn)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Window* w;\n    Button* button = GNW_find_button(btn, &w);\n    if (button == NULL) {\n        return -1;\n    }\n\n    if ((button->flags & BUTTON_FLAG_DISABLED) == 0) {\n        button->flags |= BUTTON_FLAG_DISABLED;\n\n        button_draw(button, w, button->currentImage, 1, NULL, 0);\n\n        if (button == w->field_34) {\n            if (w->field_34->mouseExitEventCode != -1) {\n                GNW_add_input_buffer(w->field_34->mouseExitEventCode);\n                w->field_34 = NULL;\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4D9554\nint win_set_button_rest_state(int btn, bool a2, int a3)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Window* w;\n    Button* button = GNW_find_button(btn, &w);\n    if (button == NULL) {\n        return -1;\n    }\n\n    if ((button->flags & BUTTON_FLAG_0x01) != 0) {\n        int keyCode = -1;\n\n        if ((button->flags & BUTTON_FLAG_0x020000) != 0) {\n            if (!a2) {\n                button->flags &= ~BUTTON_FLAG_0x020000;\n\n                if ((a3 & 0x02) == 0) {\n                    button_draw(button, w, button->mouseUpImage, 1, NULL, 0);\n                }\n\n                if (button->radioGroup != NULL) {\n                    button->radioGroup->field_4--;\n                }\n\n                keyCode = button->leftMouseUpEventCode;\n            }\n        } else {\n            if (a2) {\n                button->flags |= BUTTON_FLAG_0x020000;\n\n                if ((a3 & 0x02) == 0) {\n                    button_draw(button, w, button->mouseDownImage, 1, NULL, 0);\n                }\n\n                if (button->radioGroup != NULL) {\n                    button->radioGroup->field_4++;\n                }\n\n                keyCode = button->lefMouseDownEventCode;\n            }\n        }\n\n        if (keyCode != -1) {\n            if ((a3 & 0x01) != 0) {\n                GNW_add_input_buffer(keyCode);\n            }\n        }\n    }\n\n    return 0;\n}\n\n// 0x4D962C\nint win_group_check_buttons(int buttonCount, int* btns, int a3, void (*a4)(int))\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (buttonCount >= RADIO_GROUP_BUTTON_LIST_CAPACITY) {\n        return -1;\n    }\n\n    for (int groupIndex = 0; groupIndex < RADIO_GROUP_LIST_CAPACITY; groupIndex++) {\n        RadioGroup* radioGroup = &(btn_grp[groupIndex]);\n        if (radioGroup->buttonsLength == 0) {\n            radioGroup->field_4 = 0;\n\n            for (int buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) {\n                Button* button = GNW_find_button(btns[buttonIndex], NULL);\n                if (button == NULL) {\n                    return -1;\n                }\n\n                radioGroup->buttons[buttonIndex] = button;\n\n                button->radioGroup = radioGroup;\n\n                if ((button->flags & BUTTON_FLAG_0x020000) != 0) {\n                    radioGroup->field_4++;\n                }\n            }\n\n            radioGroup->buttonsLength = buttonCount;\n            radioGroup->field_0 = a3;\n            radioGroup->field_8 = a4;\n            return 0;\n        }\n    }\n\n    return -1;\n}\n\n// 0x4D96EC\nint win_group_radio_buttons(int count, int* btns)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (win_group_check_buttons(count, btns, 1, NULL) == -1) {\n        return -1;\n    }\n\n    Button* button = GNW_find_button(btns[0], NULL);\n    RadioGroup* radioGroup = button->radioGroup;\n\n    for (int index = 0; index < radioGroup->buttonsLength; index++) {\n        Button* v1 = radioGroup->buttons[index];\n        v1->flags |= BUTTON_FLAG_0x040000;\n    }\n\n    return 0;\n}\n\n// 0x4D9744\nstatic int button_check_group(Button* button)\n{\n    if (button->radioGroup == NULL) {\n        return 0;\n    }\n\n    if ((button->flags & BUTTON_FLAG_0x040000) != 0) {\n        if (button->radioGroup->field_4 > 0) {\n            for (int index = 0; index < button->radioGroup->buttonsLength; index++) {\n                Button* v1 = button->radioGroup->buttons[index];\n                if ((v1->flags & BUTTON_FLAG_0x020000) != 0) {\n                    v1->flags &= ~BUTTON_FLAG_0x020000;\n\n                    Window* w;\n                    GNW_find_button(v1->id, &w);\n                    button_draw(v1, w, v1->mouseUpImage, 1, NULL, 1);\n\n                    if (v1->leftMouseUpProc != NULL) {\n                        v1->leftMouseUpProc(v1->id, v1->leftMouseUpEventCode);\n                    }\n                }\n            }\n        }\n\n        if ((button->flags & BUTTON_FLAG_0x020000) == 0) {\n            button->radioGroup->field_4++;\n        }\n\n        return 0;\n    }\n\n    if (button->radioGroup->field_4 < button->radioGroup->field_0) {\n        if ((button->flags & BUTTON_FLAG_0x020000) == 0) {\n            button->radioGroup->field_4++;\n        }\n\n        return 0;\n    }\n\n    if (button->radioGroup->field_8 != NULL) {\n        button->radioGroup->field_8(button->id);\n    }\n\n    return -1;\n}\n\n// 0x4D9808\nstatic void button_draw(Button* button, Window* w, unsigned char* data, int a4, Rect* a5, int a6)\n{\n    unsigned char* previousImage = NULL;\n    if (data != NULL) {\n        Rect v2;\n        rectCopy(&v2, &(button->rect));\n        rectOffset(&v2, w->rect.ulx, w->rect.uly);\n\n        Rect v3;\n        if (a5 != NULL) {\n            if (rect_inside_bound(&v2, a5, &v2) == -1) {\n                return;\n            }\n\n            rectCopy(&v3, &v2);\n            rectOffset(&v3, -w->rect.ulx, -w->rect.uly);\n        } else {\n            rectCopy(&v3, &(button->rect));\n        }\n\n        if (data == button->mouseUpImage && (button->flags & BUTTON_FLAG_0x020000)) {\n            data = button->mouseDownImage;\n        }\n\n        if (button->flags & BUTTON_FLAG_DISABLED) {\n            if (data == button->mouseUpImage) {\n                data = button->field_3C;\n            } else if (data == button->mouseDownImage) {\n                data = button->field_40;\n            } else if (data == button->mouseHoverImage) {\n                data = button->field_44;\n            }\n        } else {\n            if (data == button->field_3C) {\n                data = button->mouseUpImage;\n            } else if (data == button->field_40) {\n                data = button->mouseDownImage;\n            } else if (data == button->field_44) {\n                data = button->mouseHoverImage;\n            }\n        }\n\n        if (data) {\n            if (a4 == 0) {\n                int width = button->rect.lrx - button->rect.ulx + 1;\n                if ((button->flags & BUTTON_FLAG_TRANSPARENT) != 0) {\n                    trans_buf_to_buf(\n                        data + (v3.uly - button->rect.uly) * width + v3.ulx - button->rect.ulx,\n                        v3.lrx - v3.ulx + 1,\n                        v3.lry - v3.uly + 1,\n                        width,\n                        w->buffer + w->width * v3.uly + v3.ulx,\n                        w->width);\n                } else {\n                    buf_to_buf(\n                        data + (v3.uly - button->rect.uly) * width + v3.ulx - button->rect.ulx,\n                        v3.lrx - v3.ulx + 1,\n                        v3.lry - v3.uly + 1,\n                        width,\n                        w->buffer + w->width * v3.uly + v3.ulx,\n                        w->width);\n                }\n            }\n\n            previousImage = button->currentImage;\n            button->currentImage = data;\n\n            if (a4 != 0) {\n                GNW_win_refresh(w, &v2, 0);\n            }\n        }\n    }\n\n    if (a6) {\n        if (previousImage != data) {\n            if (data == button->mouseDownImage && button->onPressed != NULL) {\n                button->onPressed(button->id, button->lefMouseDownEventCode);\n            } else if (data == button->mouseUpImage && button->onUnpressed != NULL) {\n                button->onUnpressed(button->id, button->leftMouseUpEventCode);\n            }\n        }\n    }\n}\n\n// 0x4D9A58\nvoid GNW_button_refresh(Window* w, Rect* rect)\n{\n    Button* button = w->buttonListHead;\n    if (button != NULL) {\n        while (button->next != NULL) {\n            button = button->next;\n        }\n    }\n\n    while (button != NULL) {\n        button_draw(button, w, button->currentImage, 0, rect, 0);\n        button = button->prev;\n    }\n}\n\n// 0x4D9AA0\nint win_button_press_and_release(int btn)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Window* w;\n    Button* button = GNW_find_button(btn, &w);\n    if (button == NULL) {\n        return -1;\n    }\n\n    button_draw(button, w, button->mouseDownImage, 1, NULL, 1);\n\n    if (button->leftMouseDownProc != NULL) {\n        button->leftMouseDownProc(btn, button->lefMouseDownEventCode);\n\n        if ((button->flags & BUTTON_FLAG_0x40) != 0) {\n            GNW_add_input_buffer(button->lefMouseDownEventCode);\n        }\n    } else {\n        if (button->lefMouseDownEventCode != -1) {\n            GNW_add_input_buffer(button->lefMouseDownEventCode);\n        }\n    }\n\n    button_draw(button, w, button->mouseUpImage, 1, NULL, 1);\n\n    if (button->leftMouseUpProc != NULL) {\n        button->leftMouseUpProc(btn, button->leftMouseUpEventCode);\n\n        if ((button->flags & BUTTON_FLAG_0x40) != 0) {\n            GNW_add_input_buffer(button->leftMouseUpEventCode);\n        }\n    } else {\n        if (button->leftMouseUpEventCode != -1) {\n            GNW_add_input_buffer(button->leftMouseUpEventCode);\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/plib/gnw/button.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_BUTTON_H_\n#define FALLOUT_PLIB_GNW_BUTTON_H_\n\n#include <stdbool.h>\n\n#include \"plib/gnw/gnw_types.h\"\n\nint win_register_button(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, unsigned char* up, unsigned char* dn, unsigned char* hover, int flags);\nint win_register_text_button(int win, int x, int y, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, const char* title, int flags);\nint win_register_button_disable(int btn, unsigned char* up, unsigned char* down, unsigned char* hover);\nint win_register_button_image(int btn, unsigned char* up, unsigned char* down, unsigned char* hover, int a5);\nint win_register_button_func(int btn, ButtonCallback* mouseEnterProc, ButtonCallback* mouseExitProc, ButtonCallback* mouseDownProc, ButtonCallback* mouseUpProc);\nint win_register_right_button(int btn, int rightMouseDownEventCode, int rightMouseUpEventCode, ButtonCallback* rightMouseDownProc, ButtonCallback* rightMouseUpProc);\nint win_register_button_sound_func(int btn, ButtonCallback* onPressed, ButtonCallback* onUnpressed);\nint win_register_button_mask(int btn, unsigned char* mask);\nbool win_button_down(int btn);\nint GNW_check_buttons(Window* window, int* out_a2);\nint win_button_winID(int btn);\nint win_last_button_winID();\nint win_delete_button(int btn);\nvoid GNW_delete_button(Button* ptr);\nvoid win_delete_button_win(int btn, int inputEvent);\nint button_new_id();\nint win_enable_button(int btn);\nint win_disable_button(int btn);\nint win_set_button_rest_state(int btn, bool a2, int a3);\nint win_group_check_buttons(int a1, int* a2, int a3, void (*a4)(int));\nint win_group_radio_buttons(int a1, int* a2);\nvoid GNW_button_refresh(Window* window, Rect* rect);\nint win_button_press_and_release(int btn);\n\n#endif /* FALLOUT_PLIB_GNW_BUTTON_H_ */\n"
  },
  {
    "path": "src/plib/gnw/debug.c",
    "content": "#include \"plib/gnw/debug.h\"\n\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"plib/gnw/memory.h\"\n#include \"plib/gnw/intrface.h\"\n\nstatic int debug_mono(char* string);\nstatic int debug_log(char* string);\nstatic int debug_screen(char* string);\nstatic void debug_putc(int ch);\nstatic void debug_scroll();\nstatic void debug_exit(void);\n\n// 0x51DEF8\nstatic FILE* fd = NULL;\n\n// 0x51DEFC\nstatic int curx = 0;\n\n// 0x51DF00\nstatic int cury = 0;\n\n// 0x51DF04\nstatic DebugPrintProc* debug_func = NULL;\n\n// 0x4C6CD0\nvoid GNW_debug_init()\n{\n    atexit(debug_exit);\n}\n\n// 0x4C6CDC\nvoid debug_register_mono()\n{\n    if (debug_func != debug_mono) {\n        if (fd != NULL) {\n            fclose(fd);\n            fd = NULL;\n        }\n\n        debug_func = debug_mono;\n        debug_clear();\n    }\n}\n\n// 0x4C6D18\nvoid debug_register_log(const char* fileName, const char* mode)\n{\n    if ((mode[0] == 'w' && mode[1] == 'a') && mode[1] == 't') {\n        if (fd != NULL) {\n            fclose(fd);\n        }\n\n        fd = fopen(fileName, mode);\n        debug_func = debug_log;\n    }\n}\n\n// 0x4C6D5C\nvoid debug_register_screen()\n{\n    if (debug_func != debug_screen) {\n        if (fd != NULL) {\n            fclose(fd);\n            fd = NULL;\n        }\n\n        debug_func = debug_screen;\n    }\n}\n\n// 0x4C6D90\nvoid debug_register_env()\n{\n    const char* type = getenv(\"DEBUGACTIVE\");\n    if (type == NULL) {\n        return;\n    }\n\n    char* copy = (char*)mem_malloc(strlen(type) + 1);\n    if (copy == NULL) {\n        return;\n    }\n\n    strcpy(copy, type);\n    strlwr(copy);\n\n    if (strcmp(copy, \"mono\") == 0) {\n        // NOTE: Uninline.\n        debug_register_mono();\n    } else if (strcmp(copy, \"log\") == 0) {\n        debug_register_log(\"debug.log\", \"wt\");\n    } else if (strcmp(copy, \"screen\") == 0) {\n        // NOTE: Uninline.\n        debug_register_screen();\n    } else if (strcmp(copy, \"gnw\") == 0) {\n        if (debug_func != win_debug) {\n            if (fd != NULL) {\n                fclose(fd);\n                fd = NULL;\n            }\n\n            debug_func = win_debug;\n        }\n    }\n\n    mem_free(copy);\n}\n\n// 0x4C6F18\nvoid debug_register_func(DebugPrintProc* proc)\n{\n    if (debug_func != proc) {\n        if (fd != NULL) {\n            fclose(fd);\n            fd = NULL;\n        }\n\n        debug_func = proc;\n    }\n}\n\n// 0x4C6F48\nint debug_printf(const char* format, ...)\n{\n    va_list args;\n    va_start(args, format);\n\n    int rc;\n\n    if (debug_func != NULL) {\n        char string[260];\n        vsprintf(string, format, args);\n\n        rc = debug_func(string);\n    } else {\n#ifdef _DEBUG\n        char string[260];\n        vsprintf(string, format, args);\n        OutputDebugStringA(string);\n#endif\n        rc = -1;\n    }\n\n    va_end(args);\n\n    return rc;\n}\n\n// 0x4C6F94\nint debug_puts(char* string)\n{\n    if (debug_func != NULL) {\n        return debug_func(string);\n    }\n\n    return -1;\n}\n\n// 0x4C6FAC\nvoid debug_clear()\n{\n    char* buffer;\n    int x;\n    int y;\n\n    buffer = NULL;\n\n    if (debug_func == debug_mono) {\n        buffer = (char*)0xB0000;\n    } else if (debug_func == debug_screen) {\n        buffer = (char*)0xB8000;\n    }\n\n    if (buffer != NULL) {\n        for (y = 0; y < 25; y++) {\n            for (x = 0; x < 80; x++) {\n                *buffer++ = ' ';\n                *buffer++ = 7;\n            }\n        }\n        cury = 0;\n        curx = 0;\n    }\n}\n\n// 0x4C7004\nstatic int debug_mono(char* string)\n{\n    if (debug_func == debug_mono) {\n        while (*string != '\\0') {\n            char ch = *string++;\n            debug_putc(ch);\n        }\n    }\n    return 0;\n}\n\n// 0x4C7028\nstatic int debug_log(char* string)\n{\n    if (debug_func == debug_log) {\n        if (fd == NULL) {\n            return -1;\n        }\n\n        if (fprintf(fd, string) < 0) {\n            return -1;\n        }\n\n        if (fflush(fd) == EOF) {\n            return -1;\n        }\n    }\n\n    return 0;\n}\n\n// 0x4C7068\nstatic int debug_screen(char* string)\n{\n    if (debug_func == debug_screen) {\n        printf(string);\n    }\n\n    return 0;\n}\n\n// 0x4C709C\nstatic void debug_putc(int ch)\n{\n    char* buffer;\n\n    buffer = (char*)0xB0000;\n\n    switch (ch) {\n    case 7:\n        printf(\"\\x07\");\n        return;\n    case 8:\n        if (curx > 0) {\n            curx--;\n            buffer += 2 * curx + 2 * 80 * cury;\n            *buffer++ = ' ';\n            *buffer = 7;\n        }\n        return;\n    case 9:\n        do {\n            debug_putc(' ');\n        } while ((curx - 1) % 4 != 0);\n        return;\n    case 13:\n        curx = 0;\n        return;\n    default:\n        buffer += 2 * curx + 2 * 80 * cury;\n        *buffer++ = ch;\n        *buffer = 7;\n        curx++;\n        if (curx < 80) {\n            return;\n        }\n        // FALLTHROUGH\n    case 10:\n        curx = 0;\n        cury++;\n        if (cury > 24) {\n            cury = 24;\n            debug_scroll();\n        }\n        return;\n    }\n}\n\n// 0x4C71AC\nstatic void debug_scroll()\n{\n    char* buffer;\n    int x;\n    int y;\n\n    buffer = (char*)0xB0000;\n\n    for (y = 0; y < 24; y++) {\n        for (x = 0; x < 80 * 2; x++) {\n            buffer[0] = buffer[80 * 2];\n            buffer++;\n        }\n    }\n\n    for (x = 0; x < 80; x++) {\n        *buffer++ = ' ';\n        *buffer++ = 7;\n    }\n}\n\n// 0x4C71E8\nstatic void debug_exit(void)\n{\n    if (fd != NULL) {\n        fclose(fd);\n    }\n}\n"
  },
  {
    "path": "src/plib/gnw/debug.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_DEBUG_H_\n#define FALLOUT_PLIB_GNW_DEBUG_H_\n\ntypedef int(DebugPrintProc)(char* string);\n\nvoid GNW_debug_init();\nvoid debug_register_mono();\nvoid debug_register_log(const char* fileName, const char* mode);\nvoid debug_register_screen();\nvoid debug_register_env();\nvoid debug_register_func(DebugPrintProc* proc);\nint debug_printf(const char* format, ...);\nint debug_puts(char* string);\nvoid debug_clear();\n\n#endif /* FALLOUT_PLIB_GNW_DEBUG_H_ */\n"
  },
  {
    "path": "src/plib/gnw/doscmdln.c",
    "content": "#include \"plib/gnw/doscmdln.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n// 0x4E3B90\nvoid DOSCmdLineInit(DOSCmdLine* d)\n{\n    if (d != NULL) {\n        d->numArgs = 0;\n        d->args = NULL;\n    }\n}\n\n// 0x4E3BA4\nbool DOSCmdLineCreate(DOSCmdLine* d, char* commandLine)\n{\n    const char* delim = \" \\t\";\n\n    int argc = 0;\n\n    // Get the number of arguments in command line.\n    if (*commandLine != '\\0') {\n        char* copy = strdup(commandLine);\n        if (copy == NULL) {\n            DOSCmdLineDestroy(d);\n            return false;\n        }\n\n        char* tok = strtok(copy, delim);\n        while (tok != NULL) {\n            argc++;\n            tok = strtok(NULL, delim);\n        }\n\n        free(copy);\n    }\n\n    // Make a room for argv[0] - program name.\n    argc++;\n\n    d->numArgs = argc;\n    d->args = (char**)malloc(sizeof(*d->args) * argc);\n    if (d->args == NULL) {\n        // NOTE: Uninline.\n        return DOSCmdLineFatalError(d);\n    }\n\n    for (int arg = 0; arg < argc; arg++) {\n        d->args[arg] = NULL;\n    }\n\n    // Copy program name into argv[0].\n    char moduleFileName[MAX_PATH];\n    int moduleFileNameLength = GetModuleFileNameA(NULL, moduleFileName, MAX_PATH);\n    if (moduleFileNameLength == 0) {\n        // NOTE: Uninline.\n        return DOSCmdLineFatalError(d);\n    }\n\n    if (moduleFileNameLength >= MAX_PATH) {\n        moduleFileNameLength = MAX_PATH - 1;\n    }\n\n    moduleFileName[moduleFileNameLength] = '\\0';\n\n    d->args[0] = strdup(moduleFileName);\n    if (d->args[0] == NULL) {\n        // NOTE: Uninline.\n        return DOSCmdLineFatalError(d);\n    }\n\n    // Copy arguments from command line into argv.\n    if (*commandLine != '\\0') {\n        char* copy = strdup(commandLine);\n        if (copy == NULL) {\n            DOSCmdLineDestroy(d);\n            return false;\n        }\n\n        int arg = 1;\n\n        char* tok = strtok(copy, delim);\n        while (tok != NULL) {\n            d->args[arg] = strdup(tok);\n            tok = strtok(NULL, delim);\n            arg++;\n        }\n\n        free(copy);\n    }\n\n    return true;\n}\n\n// 0x4E3D3C\nvoid DOSCmdLineDestroy(DOSCmdLine* d)\n{\n    if (d->args != NULL) {\n        // NOTE: Compiled code is slightly different - it decrements argc.\n        for (int index = 0; index < d->numArgs; index++) {\n            if (d->args[index] != NULL) {\n                free(d->args[index]);\n            }\n        }\n        free(d->args);\n    }\n\n    d->numArgs = 0;\n    d->args = NULL;\n}\n\n// NOTE: Inlined.\n//\n// 0x4E3D88\nbool DOSCmdLineFatalError(DOSCmdLine* d)\n{\n    DOSCmdLineDestroy(d);\n    return false;\n}\n"
  },
  {
    "path": "src/plib/gnw/doscmdln.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_DOSCMDLN_H_\n#define FALLOUT_PLIB_GNW_DOSCMDLN_H_\n\n#include <stdbool.h>\n\ntypedef struct tagDOSCmdLine {\n    int numArgs;\n    char** args;\n} DOSCmdLine;\n\nvoid DOSCmdLineInit(DOSCmdLine* d);\nbool DOSCmdLineCreate(DOSCmdLine* d, char* windowsCmdLine);\nvoid DOSCmdLineDestroy(DOSCmdLine* d);\nbool DOSCmdLineFatalError(DOSCmdLine* d);\n\n#endif /* FALLOUT_PLIB_GNW_DOSCMDLN_H_ */\n"
  },
  {
    "path": "src/plib/gnw/dxinput.c",
    "content": "#include <initguid.h>\n\n#include \"plib/gnw/dxinput.h\"\n#include \"plib/gnw/gnw95dx.h\"\n#include \"plib/gnw/winmain.h\"\n\n#define KEYBOARD_DEVICE_DATA_CAPACITY 32\n\n// NOTE: There is no such define in DirectX SDK. I've taken it from Wine at\n// https://github.com/wine-mirror/wine/blob/master/dlls/dinput/data_formats.c.\n#define DIDFT_OPTIONAL 0x80000000\n\n// NOTE: This define is different in the newer DirectX. Check DirectX SDK 3.0\n// at https://github.com/masonmc/dxsdk3/blob/master/sdk/inc/dinput.h.\n#undef DIDFT_ANYINSTANCE\n#define DIDFT_ANYINSTANCE 0x0000FF00\n\nstatic bool dxinput_mouse_init();\nstatic void dxinput_mouse_exit();\nstatic bool dxinput_keyboard_init();\nstatic void dxinput_keyboard_exit();\n\n// 0x4FCE90\nstatic const DIOBJECTDATAFORMAT dfDIMouse[] = {\n    { &GUID_XAxis, DIMOFS_X, DIDFT_ANYINSTANCE | DIDFT_AXIS, 0 },\n    { &GUID_YAxis, DIMOFS_Y, DIDFT_ANYINSTANCE | DIDFT_AXIS, 0 },\n    { &GUID_ZAxis, DIMOFS_Z, DIDFT_OPTIONAL | DIDFT_ANYINSTANCE | DIDFT_AXIS, 0 },\n    { NULL, DIMOFS_BUTTON0, DIDFT_ANYINSTANCE | DIDFT_BUTTON, 0 },\n    { NULL, DIMOFS_BUTTON1, DIDFT_ANYINSTANCE | DIDFT_BUTTON, 0 },\n    { NULL, DIMOFS_BUTTON2, DIDFT_OPTIONAL | DIDFT_ANYINSTANCE | DIDFT_BUTTON, 0 },\n    { NULL, DIMOFS_BUTTON3, DIDFT_OPTIONAL | DIDFT_ANYINSTANCE | DIDFT_BUTTON, 0 },\n};\n\n// 0x4FCF00\nstatic const DIDATAFORMAT c_dfDIMouse = {\n    sizeof(DIDATAFORMAT),\n    sizeof(DIOBJECTDATAFORMAT),\n    DIDF_RELAXIS,\n    sizeof(DIMOUSESTATE),\n    sizeof(dfDIMouse) / sizeof(dfDIMouse[0]),\n    (LPDIOBJECTDATAFORMAT)dfDIMouse\n};\n\n// 0x4FCF20\nstatic const DIOBJECTDATAFORMAT dfDIKeyboard[] = {\n    { &GUID_Key, 0, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(0), 0 },\n    { &GUID_Key, 1, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(1), 0 },\n    { &GUID_Key, 2, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(2), 0 },\n    { &GUID_Key, 3, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(3), 0 },\n    { &GUID_Key, 4, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(4), 0 },\n    { &GUID_Key, 5, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(5), 0 },\n    { &GUID_Key, 6, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(6), 0 },\n    { &GUID_Key, 7, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(7), 0 },\n    { &GUID_Key, 8, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(8), 0 },\n    { &GUID_Key, 9, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(9), 0 },\n    { &GUID_Key, 10, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(10), 0 },\n    { &GUID_Key, 11, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(11), 0 },\n    { &GUID_Key, 12, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(12), 0 },\n    { &GUID_Key, 13, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(13), 0 },\n    { &GUID_Key, 14, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(14), 0 },\n    { &GUID_Key, 15, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(15), 0 },\n    { &GUID_Key, 16, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(16), 0 },\n    { &GUID_Key, 17, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(17), 0 },\n    { &GUID_Key, 18, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(18), 0 },\n    { &GUID_Key, 19, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(19), 0 },\n    { &GUID_Key, 20, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(20), 0 },\n    { &GUID_Key, 21, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(21), 0 },\n    { &GUID_Key, 22, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(22), 0 },\n    { &GUID_Key, 23, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(23), 0 },\n    { &GUID_Key, 24, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(24), 0 },\n    { &GUID_Key, 25, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(25), 0 },\n    { &GUID_Key, 26, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(26), 0 },\n    { &GUID_Key, 27, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(27), 0 },\n    { &GUID_Key, 28, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(28), 0 },\n    { &GUID_Key, 29, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(29), 0 },\n    { &GUID_Key, 30, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(30), 0 },\n    { &GUID_Key, 31, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(31), 0 },\n    { &GUID_Key, 32, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(32), 0 },\n    { &GUID_Key, 33, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(33), 0 },\n    { &GUID_Key, 34, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(34), 0 },\n    { &GUID_Key, 35, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(35), 0 },\n    { &GUID_Key, 36, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(36), 0 },\n    { &GUID_Key, 37, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(37), 0 },\n    { &GUID_Key, 38, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(38), 0 },\n    { &GUID_Key, 39, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(39), 0 },\n    { &GUID_Key, 40, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(40), 0 },\n    { &GUID_Key, 41, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(41), 0 },\n    { &GUID_Key, 42, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(42), 0 },\n    { &GUID_Key, 43, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(43), 0 },\n    { &GUID_Key, 44, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(44), 0 },\n    { &GUID_Key, 45, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(45), 0 },\n    { &GUID_Key, 46, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(46), 0 },\n    { &GUID_Key, 47, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(47), 0 },\n    { &GUID_Key, 48, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(48), 0 },\n    { &GUID_Key, 49, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(49), 0 },\n    { &GUID_Key, 50, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(50), 0 },\n    { &GUID_Key, 51, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(51), 0 },\n    { &GUID_Key, 52, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(52), 0 },\n    { &GUID_Key, 53, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(53), 0 },\n    { &GUID_Key, 54, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(54), 0 },\n    { &GUID_Key, 55, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(55), 0 },\n    { &GUID_Key, 56, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(56), 0 },\n    { &GUID_Key, 57, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(57), 0 },\n    { &GUID_Key, 58, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(58), 0 },\n    { &GUID_Key, 59, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(59), 0 },\n    { &GUID_Key, 60, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(60), 0 },\n    { &GUID_Key, 61, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(61), 0 },\n    { &GUID_Key, 62, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(62), 0 },\n    { &GUID_Key, 63, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(63), 0 },\n    { &GUID_Key, 64, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(64), 0 },\n    { &GUID_Key, 65, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(65), 0 },\n    { &GUID_Key, 66, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(66), 0 },\n    { &GUID_Key, 67, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(67), 0 },\n    { &GUID_Key, 68, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(68), 0 },\n    { &GUID_Key, 69, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(69), 0 },\n    { &GUID_Key, 70, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(70), 0 },\n    { &GUID_Key, 71, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(71), 0 },\n    { &GUID_Key, 72, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(72), 0 },\n    { &GUID_Key, 73, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(73), 0 },\n    { &GUID_Key, 74, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(74), 0 },\n    { &GUID_Key, 75, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(75), 0 },\n    { &GUID_Key, 76, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(76), 0 },\n    { &GUID_Key, 77, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(77), 0 },\n    { &GUID_Key, 78, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(78), 0 },\n    { &GUID_Key, 79, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(79), 0 },\n    { &GUID_Key, 80, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(80), 0 },\n    { &GUID_Key, 81, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(81), 0 },\n    { &GUID_Key, 82, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(82), 0 },\n    { &GUID_Key, 83, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(83), 0 },\n    { &GUID_Key, 84, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(84), 0 },\n    { &GUID_Key, 85, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(85), 0 },\n    { &GUID_Key, 86, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(86), 0 },\n    { &GUID_Key, 87, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(87), 0 },\n    { &GUID_Key, 88, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(88), 0 },\n    { &GUID_Key, 89, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(89), 0 },\n    { &GUID_Key, 90, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(90), 0 },\n    { &GUID_Key, 91, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(91), 0 },\n    { &GUID_Key, 92, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(92), 0 },\n    { &GUID_Key, 93, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(93), 0 },\n    { &GUID_Key, 94, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(94), 0 },\n    { &GUID_Key, 95, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(95), 0 },\n    { &GUID_Key, 96, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(96), 0 },\n    { &GUID_Key, 97, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(97), 0 },\n    { &GUID_Key, 98, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(98), 0 },\n    { &GUID_Key, 99, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(99), 0 },\n    { &GUID_Key, 100, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(100), 0 },\n    { &GUID_Key, 101, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(101), 0 },\n    { &GUID_Key, 102, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(102), 0 },\n    { &GUID_Key, 103, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(103), 0 },\n    { &GUID_Key, 104, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(104), 0 },\n    { &GUID_Key, 105, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(105), 0 },\n    { &GUID_Key, 106, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(106), 0 },\n    { &GUID_Key, 107, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(107), 0 },\n    { &GUID_Key, 108, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(108), 0 },\n    { &GUID_Key, 109, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(109), 0 },\n    { &GUID_Key, 110, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(110), 0 },\n    { &GUID_Key, 111, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(111), 0 },\n    { &GUID_Key, 112, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(112), 0 },\n    { &GUID_Key, 113, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(113), 0 },\n    { &GUID_Key, 114, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(114), 0 },\n    { &GUID_Key, 115, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(115), 0 },\n    { &GUID_Key, 116, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(116), 0 },\n    { &GUID_Key, 117, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(117), 0 },\n    { &GUID_Key, 118, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(118), 0 },\n    { &GUID_Key, 119, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(119), 0 },\n    { &GUID_Key, 120, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(120), 0 },\n    { &GUID_Key, 121, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(121), 0 },\n    { &GUID_Key, 122, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(122), 0 },\n    { &GUID_Key, 123, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(123), 0 },\n    { &GUID_Key, 124, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(124), 0 },\n    { &GUID_Key, 125, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(125), 0 },\n    { &GUID_Key, 126, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(126), 0 },\n    { &GUID_Key, 127, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(127), 0 },\n    { &GUID_Key, 128, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(128), 0 },\n    { &GUID_Key, 129, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(129), 0 },\n    { &GUID_Key, 130, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(130), 0 },\n    { &GUID_Key, 131, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(131), 0 },\n    { &GUID_Key, 132, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(132), 0 },\n    { &GUID_Key, 133, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(133), 0 },\n    { &GUID_Key, 134, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(134), 0 },\n    { &GUID_Key, 135, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(135), 0 },\n    { &GUID_Key, 136, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(136), 0 },\n    { &GUID_Key, 137, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(137), 0 },\n    { &GUID_Key, 138, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(138), 0 },\n    { &GUID_Key, 139, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(139), 0 },\n    { &GUID_Key, 140, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(140), 0 },\n    { &GUID_Key, 141, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(141), 0 },\n    { &GUID_Key, 142, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(142), 0 },\n    { &GUID_Key, 143, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(143), 0 },\n    { &GUID_Key, 144, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(144), 0 },\n    { &GUID_Key, 145, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(145), 0 },\n    { &GUID_Key, 146, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(146), 0 },\n    { &GUID_Key, 147, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(147), 0 },\n    { &GUID_Key, 148, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(148), 0 },\n    { &GUID_Key, 149, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(149), 0 },\n    { &GUID_Key, 150, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(150), 0 },\n    { &GUID_Key, 151, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(151), 0 },\n    { &GUID_Key, 152, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(152), 0 },\n    { &GUID_Key, 153, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(153), 0 },\n    { &GUID_Key, 154, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(154), 0 },\n    { &GUID_Key, 155, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(155), 0 },\n    { &GUID_Key, 156, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(156), 0 },\n    { &GUID_Key, 157, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(157), 0 },\n    { &GUID_Key, 158, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(158), 0 },\n    { &GUID_Key, 159, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(159), 0 },\n    { &GUID_Key, 160, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(160), 0 },\n    { &GUID_Key, 161, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(161), 0 },\n    { &GUID_Key, 162, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(162), 0 },\n    { &GUID_Key, 163, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(163), 0 },\n    { &GUID_Key, 164, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(164), 0 },\n    { &GUID_Key, 165, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(165), 0 },\n    { &GUID_Key, 166, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(166), 0 },\n    { &GUID_Key, 167, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(167), 0 },\n    { &GUID_Key, 168, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(168), 0 },\n    { &GUID_Key, 169, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(169), 0 },\n    { &GUID_Key, 170, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(170), 0 },\n    { &GUID_Key, 171, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(171), 0 },\n    { &GUID_Key, 172, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(172), 0 },\n    { &GUID_Key, 173, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(173), 0 },\n    { &GUID_Key, 174, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(174), 0 },\n    { &GUID_Key, 175, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(175), 0 },\n    { &GUID_Key, 176, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(176), 0 },\n    { &GUID_Key, 177, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(177), 0 },\n    { &GUID_Key, 178, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(178), 0 },\n    { &GUID_Key, 179, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(179), 0 },\n    { &GUID_Key, 180, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(180), 0 },\n    { &GUID_Key, 181, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(181), 0 },\n    { &GUID_Key, 182, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(182), 0 },\n    { &GUID_Key, 183, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(183), 0 },\n    { &GUID_Key, 184, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(184), 0 },\n    { &GUID_Key, 185, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(185), 0 },\n    { &GUID_Key, 186, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(186), 0 },\n    { &GUID_Key, 187, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(187), 0 },\n    { &GUID_Key, 188, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(188), 0 },\n    { &GUID_Key, 189, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(189), 0 },\n    { &GUID_Key, 190, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(190), 0 },\n    { &GUID_Key, 191, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(191), 0 },\n    { &GUID_Key, 192, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(192), 0 },\n    { &GUID_Key, 193, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(193), 0 },\n    { &GUID_Key, 194, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(194), 0 },\n    { &GUID_Key, 195, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(195), 0 },\n    { &GUID_Key, 196, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(196), 0 },\n    { &GUID_Key, 197, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(197), 0 },\n    { &GUID_Key, 198, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(198), 0 },\n    { &GUID_Key, 199, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(199), 0 },\n    { &GUID_Key, 200, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(200), 0 },\n    { &GUID_Key, 201, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(201), 0 },\n    { &GUID_Key, 202, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(202), 0 },\n    { &GUID_Key, 203, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(203), 0 },\n    { &GUID_Key, 204, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(204), 0 },\n    { &GUID_Key, 205, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(205), 0 },\n    { &GUID_Key, 206, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(206), 0 },\n    { &GUID_Key, 207, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(207), 0 },\n    { &GUID_Key, 208, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(208), 0 },\n    { &GUID_Key, 209, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(209), 0 },\n    { &GUID_Key, 210, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(210), 0 },\n    { &GUID_Key, 211, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(211), 0 },\n    { &GUID_Key, 212, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(212), 0 },\n    { &GUID_Key, 213, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(213), 0 },\n    { &GUID_Key, 214, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(214), 0 },\n    { &GUID_Key, 215, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(215), 0 },\n    { &GUID_Key, 216, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(216), 0 },\n    { &GUID_Key, 217, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(217), 0 },\n    { &GUID_Key, 218, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(218), 0 },\n    { &GUID_Key, 219, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(219), 0 },\n    { &GUID_Key, 220, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(220), 0 },\n    { &GUID_Key, 221, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(221), 0 },\n    { &GUID_Key, 222, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(222), 0 },\n    { &GUID_Key, 223, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(223), 0 },\n    { &GUID_Key, 224, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(224), 0 },\n    { &GUID_Key, 225, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(225), 0 },\n    { &GUID_Key, 226, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(226), 0 },\n    { &GUID_Key, 227, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(227), 0 },\n    { &GUID_Key, 228, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(228), 0 },\n    { &GUID_Key, 229, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(229), 0 },\n    { &GUID_Key, 230, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(230), 0 },\n    { &GUID_Key, 231, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(231), 0 },\n    { &GUID_Key, 232, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(232), 0 },\n    { &GUID_Key, 233, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(233), 0 },\n    { &GUID_Key, 234, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(234), 0 },\n    { &GUID_Key, 235, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(235), 0 },\n    { &GUID_Key, 236, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(236), 0 },\n    { &GUID_Key, 237, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(237), 0 },\n    { &GUID_Key, 238, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(238), 0 },\n    { &GUID_Key, 239, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(239), 0 },\n    { &GUID_Key, 240, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(240), 0 },\n    { &GUID_Key, 241, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(241), 0 },\n    { &GUID_Key, 242, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(242), 0 },\n    { &GUID_Key, 243, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(243), 0 },\n    { &GUID_Key, 244, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(244), 0 },\n    { &GUID_Key, 245, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(245), 0 },\n    { &GUID_Key, 246, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(246), 0 },\n    { &GUID_Key, 247, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(247), 0 },\n    { &GUID_Key, 248, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(248), 0 },\n    { &GUID_Key, 249, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(249), 0 },\n    { &GUID_Key, 250, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(250), 0 },\n    { &GUID_Key, 251, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(251), 0 },\n    { &GUID_Key, 252, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(252), 0 },\n    { &GUID_Key, 253, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(253), 0 },\n    { &GUID_Key, 254, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(254), 0 },\n    { &GUID_Key, 255, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(255), 0 },\n};\n\n// 0x4FDF20\nstatic const DIDATAFORMAT c_dfDIKeyboard = {\n    sizeof(DIDATAFORMAT),\n    sizeof(DIOBJECTDATAFORMAT),\n    DIDF_RELAXIS,\n    256,\n    sizeof(dfDIKeyboard) / sizeof(dfDIKeyboard[0]),\n    (LPDIOBJECTDATAFORMAT)dfDIKeyboard\n};\n\n// 0x51E458\nstatic LPDIRECTINPUTA lpDirectInput = NULL;\n\n// 0x51E45C\nstatic LPDIRECTINPUTDEVICEA lpDirectInputMouse = NULL;\n\n// 0x51E460\nstatic LPDIRECTINPUTDEVICEA lpDirectInputKeyboard = NULL;\n\n// 0x51E464\nstatic int nDirectInputKeyboardBufferIndex = 0;\n\n// 0x51E468\nstatic int nDirectInputKeyboardBufferCount = 0;\n\n// 0x6B2560\nstatic DIDEVICEOBJECTDATA DirectInputKeyboardBuffer[KEYBOARD_DEVICE_DATA_CAPACITY];\n\n// 0x4E0400\nbool dxinput_init()\n{\n    if (lpDirectInput != NULL) {\n        return false;\n    }\n\n    HRESULT hr = GNW95_DirectInputCreate(GNW95_hInstance, DIRECTINPUT_VERSION, &lpDirectInput, NULL);\n    if (hr != DI_OK) {\n        goto err;\n    }\n\n    if (!dxinput_mouse_init()) {\n        goto err;\n    }\n\n    if (!dxinput_keyboard_init()) {\n        goto err;\n    }\n\n    return true;\n\nerr:\n\n    dxinput_keyboard_exit();\n    dxinput_mouse_exit();\n\n    if (lpDirectInput != NULL) {\n        IDirectInput_Release(lpDirectInput);\n        lpDirectInput = NULL;\n    }\n\n    return false;\n}\n\n// 0x4E0478\nvoid dxinput_exit()\n{\n    // NOTE: Uninline.\n    dxinput_keyboard_exit();\n\n    // NOTE: Uninline.\n    dxinput_mouse_exit();\n\n    if (lpDirectInput != NULL) {\n        IDirectInput_Release(lpDirectInput);\n        lpDirectInput = NULL;\n    }\n}\n\n// 0x4E04E8\nbool dxinput_acquire_mouse()\n{\n    if (lpDirectInputMouse == NULL) {\n        return false;\n    }\n\n    HRESULT hr = IDirectInputDevice_Acquire(lpDirectInputMouse);\n    if (hr != DI_OK && hr != S_FALSE) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4E0514\nbool dxinput_unacquire_mouse()\n{\n    if (lpDirectInputMouse == NULL) {\n        return false;\n    }\n\n    HRESULT hr = IDirectInputDevice_Unacquire(lpDirectInputMouse);\n    if (hr != DI_OK) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4E053C\nbool dxinput_get_mouse_state(dxinput_mouse_state* mouse_state)\n{\n    if (lpDirectInputMouse == NULL) {\n        return false;\n    }\n\n    if (!dxinput_acquire_mouse()) {\n        return false;\n    }\n\n    DIMOUSESTATE dims;\n    HRESULT hr = IDirectInputDevice_GetDeviceState(lpDirectInputMouse, sizeof(dims), &dims);\n    if (hr != DI_OK) {\n        return false;\n    }\n\n    mouse_state->delta_x = dims.lX;\n    mouse_state->delta_y = dims.lY;\n    mouse_state->left_button = (dims.rgbButtons[0] & 0x80) != 0;\n    mouse_state->right_button = (dims.rgbButtons[1] & 0x80) != 0;\n\n    return true;\n}\n\n// 0x4E05A8\nbool dxinput_acquire_keyboard()\n{\n    if (lpDirectInputKeyboard == NULL) {\n        return false;\n    }\n\n    HRESULT hr = IDirectInputDevice_Acquire(lpDirectInputKeyboard);\n    if (hr != DI_OK && hr != S_FALSE) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4E05D4\nbool dxinput_unacquire_keyboard()\n{\n    if (lpDirectInputKeyboard == NULL) {\n        return false;\n    }\n\n    HRESULT hr = IDirectInputDevice_Unacquire(lpDirectInputKeyboard);\n    if (hr != DI_OK) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4E05FC\nbool dxinput_flush_keyboard_buffer()\n{\n    if (lpDirectInputKeyboard == NULL) {\n        return false;\n    }\n\n    if (!dxinput_acquire_keyboard()) {\n        return false;\n    }\n\n    DWORD items = -1;\n    HRESULT hr = IDirectInputDevice_GetDeviceData(lpDirectInputKeyboard, sizeof(DIDEVICEOBJECTDATA), NULL, &items, 0);\n    if (hr != DI_OK && hr != DI_BUFFEROVERFLOW) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4E0650\nbool dxinput_read_keyboard_buffer(dxinput_key_data* key_data)\n{\n    if (lpDirectInputKeyboard == NULL) {\n        return false;\n    }\n\n    if (!dxinput_acquire_keyboard()) {\n        return false;\n    }\n\n    if (nDirectInputKeyboardBufferIndex >= nDirectInputKeyboardBufferCount) {\n        DWORD items = KEYBOARD_DEVICE_DATA_CAPACITY;\n        HRESULT hr = IDirectInputDevice_GetDeviceData(lpDirectInputKeyboard, sizeof(DIDEVICEOBJECTDATA), DirectInputKeyboardBuffer, &items, 0);\n        if (hr == DI_OK || hr == DI_BUFFEROVERFLOW) {\n            nDirectInputKeyboardBufferCount = items;\n            nDirectInputKeyboardBufferIndex = 0;\n        }\n    }\n\n    if (nDirectInputKeyboardBufferIndex < nDirectInputKeyboardBufferCount) {\n        DIDEVICEOBJECTDATA* entry = &(DirectInputKeyboardBuffer[nDirectInputKeyboardBufferIndex]);\n        key_data->code = entry->dwOfs & 0xFF;\n        key_data->state = (entry->dwData & 0x80) != 0;\n        nDirectInputKeyboardBufferIndex++;\n\n        return true;\n    }\n\n    return false;\n}\n\n// 0x4E070C\nstatic bool dxinput_mouse_init()\n{\n    HRESULT hr;\n\n    hr = IDirectInput_CreateDevice(lpDirectInput, &GUID_SysMouse, &lpDirectInputMouse, NULL);\n    if (hr != DI_OK) {\n        goto err;\n    }\n\n    hr = IDirectInputDevice_SetCooperativeLevel(lpDirectInputMouse, GNW95_hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);\n    if (hr != DI_OK) {\n        goto err;\n    }\n\n    hr = IDirectInputDevice_SetDataFormat(lpDirectInputMouse, &c_dfDIMouse);\n    if (hr != DI_OK) {\n        goto err;\n    }\n\n    return true;\n\nerr:\n\n    // NOTE: Uninline.\n    dxinput_mouse_exit();\n\n    return false;\n}\n\n// 0x4E078C\nstatic void dxinput_mouse_exit()\n{\n    if (lpDirectInputMouse != NULL) {\n        IDirectInputDevice_Unacquire(lpDirectInputMouse);\n        IDirectInputDevice_Release(lpDirectInputMouse);\n        lpDirectInputMouse = NULL;\n    }\n}\n\n// 0x4E07B8\nstatic bool dxinput_keyboard_init()\n{\n    HRESULT hr;\n\n    hr = IDirectInput_CreateDevice(lpDirectInput, &GUID_SysKeyboard, &lpDirectInputKeyboard, NULL);\n    if (hr != DI_OK) {\n        goto err;\n    }\n\n    hr = IDirectInputDevice_SetCooperativeLevel(lpDirectInputKeyboard, GNW95_hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);\n    if (hr != DI_OK) {\n        goto err;\n    }\n\n    hr = IDirectInputDevice_SetDataFormat(lpDirectInputKeyboard, &c_dfDIKeyboard);\n    if (hr != DI_OK) {\n        goto err;\n    }\n\n    DIPROPDWORD dipdw;\n    dipdw.diph.dwSize = sizeof(DIPROPDWORD);\n    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);\n    dipdw.diph.dwObj = 0;\n    dipdw.diph.dwHow = DIPH_DEVICE;\n    dipdw.dwData = KEYBOARD_DEVICE_DATA_CAPACITY;\n\n    hr = IDirectInputDevice_SetProperty(lpDirectInputKeyboard, DIPROP_BUFFERSIZE, &(dipdw.diph));\n    if (hr != DI_OK) {\n        goto err;\n    }\n\n    return true;\n\nerr:\n\n    // NOTE: Uninline.\n    dxinput_keyboard_exit();\n\n    return false;\n}\n\n// 0x4E0874\nstatic void dxinput_keyboard_exit()\n{\n    if (lpDirectInputKeyboard != NULL) {\n        IDirectInputDevice_Unacquire(lpDirectInputKeyboard);\n        IDirectInputDevice_Release(lpDirectInputKeyboard);\n        lpDirectInputKeyboard = NULL;\n    }\n}\n"
  },
  {
    "path": "src/plib/gnw/dxinput.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_DXINPUT_H_\n#define FALLOUT_PLIB_GNW_DXINPUT_H_\n\n#include <stdbool.h>\n\ntypedef struct dxinput_mouse_state {\n    int delta_x;\n    int delta_y;\n    unsigned char left_button;\n    unsigned char right_button;\n} dxinput_mouse_state;\n\ntypedef struct dxinput_key_data {\n    unsigned char code;\n    unsigned char state;\n} dxinput_key_data;\n\nbool dxinput_init();\nvoid dxinput_exit();\nbool dxinput_acquire_mouse();\nbool dxinput_unacquire_mouse();\nbool dxinput_get_mouse_state(dxinput_mouse_state* mouse_state);\nbool dxinput_acquire_keyboard();\nbool dxinput_unacquire_keyboard();\nbool dxinput_flush_keyboard_buffer();\nbool dxinput_read_keyboard_buffer(dxinput_key_data* key_data);\n\n#endif /* FALLOUT_PLIB_GNW_DXINPUT_H_ */\n"
  },
  {
    "path": "src/plib/gnw/gnw.c",
    "content": "#include \"plib/gnw/gnw.h\"\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"plib/gnw/memory.h\"\n#include \"game/palette.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/vcr.h\"\n#include \"plib/gnw/intrface.h\"\n#include \"plib/gnw/svga.h\"\n#include \"plib/gnw/winmain.h\"\n\nstatic void win_free(int win);\nstatic void win_clip(Window* window, RectPtr* rectListNodePtr, unsigned char* a3);\nstatic void refresh_all(Rect* rect, unsigned char* a2);\nstatic int colorOpen(const char* path, int flags);\nstatic int colorRead(int fd, void* buf, size_t count);\nstatic int colorClose(int fd);\n\n// 0x51E3D8\nstatic bool GNW95_already_running = false;\n\n// 0x51E3DC\nstatic HANDLE GNW95_title_mutex = INVALID_HANDLE_VALUE;\n\n// 0x51E3E0\nbool GNW_win_init_flag = false;\n\n// 0x51E3E4\nint GNW_wcolor[6] = {\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n};\n\n// 0x51E3FC\nstatic unsigned char* screen_buffer = NULL;\n\n// 0x6ADD90\nstatic int window_index[MAX_WINDOW_COUNT];\n\n// 0x6ADE58\nstatic Window* window[MAX_WINDOW_COUNT];\n\n// 0x6ADF20\nstatic VideoSystemExitProc* video_reset;\n\n// 0x6ADF24\nstatic int num_windows;\n\n// 0x6ADF28\nstatic int window_flags;\n\n// 0x6ADF2C\nstatic bool buffering;\n\n// 0x6ADF30\nstatic int bk_color;\n\n// 0x6ADF34\nstatic VideoSystemInitProc* video_set;\n\n// 0x6ADF38\nstatic int doing_refresh_all;\n\n// 0x6ADF3C\nvoid* GNW_texture;\n\n// 0x4D5C30\nint win_init(VideoSystemInitProc* videoSystemInitProc, VideoSystemExitProc* videoSystemExitProc, int a3)\n{\n    CloseHandle(GNW95_mutex);\n    GNW95_mutex = INVALID_HANDLE_VALUE;\n\n    if (GNW95_already_running) {\n        return WINDOW_MANAGER_ERR_ALREADY_RUNNING;\n    }\n\n    if (GNW95_title_mutex == INVALID_HANDLE_VALUE) {\n        return WINDOW_MANAGER_ERR_TITLE_NOT_SET;\n    }\n\n    if (GNW_win_init_flag) {\n        return WINDOW_MANAGER_ERR_WINDOW_SYSTEM_ALREADY_INITIALIZED;\n    }\n\n    for (int index = 0; index < MAX_WINDOW_COUNT; index++) {\n        window_index[index] = -1;\n    }\n\n    if (db_total() == 0) {\n        if (db_init(NULL, 0, \"\", 1) == -1) {\n            return WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE;\n        }\n    }\n\n    if (GNW_text_init() == -1) {\n        return WINDOW_MANAGER_ERR_INITIALIZING_TEXT_FONTS;\n    }\n\n    reset_mode();\n\n    video_set = videoSystemInitProc;\n    video_reset = GNW95_reset_mode;\n\n    int rc = videoSystemInitProc();\n    if (rc == -1) {\n        if (video_reset != NULL) {\n            video_reset();\n        }\n\n        return WINDOW_MANAGER_ERR_INITIALIZING_VIDEO_MODE;\n    }\n\n    if (rc == 8) {\n        return WINDOW_MANAGER_ERR_8;\n    }\n\n    if (a3 & 1) {\n        screen_buffer = (unsigned char*)mem_malloc((scr_size.lry - scr_size.uly + 1) * (scr_size.lrx - scr_size.ulx + 1));\n        if (screen_buffer == NULL) {\n            if (video_reset != NULL) {\n                video_reset();\n            } else {\n                GNW95_reset_mode();\n            }\n\n            return WINDOW_MANAGER_ERR_NO_MEMORY;\n        }\n    }\n\n    buffering = false;\n    doing_refresh_all = 0;\n\n    colorInitIO(colorOpen, colorRead, colorClose);\n    colorRegisterAlloc(mem_malloc, mem_realloc, mem_free);\n\n    if (!initColors()) {\n        unsigned char* palette = (unsigned char*)mem_malloc(768);\n        if (palette == NULL) {\n            if (video_reset != NULL) {\n                video_reset();\n            } else {\n                GNW95_reset_mode();\n            }\n\n            if (screen_buffer != NULL) {\n                mem_free(screen_buffer);\n            }\n\n            return WINDOW_MANAGER_ERR_NO_MEMORY;\n        }\n\n        buf_fill(palette, 768, 1, 768, 0);\n\n        // TODO: Incomplete.\n        // _colorBuildColorTable(getSystemPalette(), palette);\n\n        mem_free(palette);\n    }\n\n    GNW_debug_init();\n\n    if (GNW_input_init(a3) == -1) {\n        return WINDOW_MANAGER_ERR_INITIALIZING_INPUT;\n    }\n\n    GNW_intr_init();\n\n    Window* w = window[0] = (Window*)mem_malloc(sizeof(*w));\n    if (w == NULL) {\n        if (video_reset != NULL) {\n            video_reset();\n        } else {\n            GNW95_reset_mode();\n        }\n\n        if (screen_buffer != NULL) {\n            mem_free(screen_buffer);\n        }\n\n        return WINDOW_MANAGER_ERR_NO_MEMORY;\n    }\n\n    w->id = 0;\n    w->flags = 0;\n    w->rect.ulx = scr_size.ulx;\n    w->rect.uly = scr_size.uly;\n    w->rect.lrx = scr_size.lrx;\n    w->rect.lry = scr_size.lry;\n    w->width = scr_size.lrx - scr_size.ulx + 1;\n    w->height = scr_size.lry - scr_size.uly + 1;\n    w->field_24 = 0;\n    w->field_28 = 0;\n    w->buffer = NULL;\n    w->buttonListHead = NULL;\n    w->field_34 = NULL;\n    w->field_38 = 0;\n    w->menuBar = NULL;\n\n    num_windows = 1;\n    GNW_win_init_flag = 1;\n    GNW_wcolor[3] = 21140;\n    GNW_wcolor[4] = 32747;\n    GNW_wcolor[5] = 31744;\n    window_index[0] = 0;\n    GNW_texture = NULL;\n    bk_color = 0;\n    GNW_wcolor[0] = 10570;\n    window_flags = a3;\n    GNW_wcolor[2] = 8456;\n    GNW_wcolor[1] = 15855;\n\n    atexit(win_exit);\n\n    return WINDOW_MANAGER_OK;\n}\n\n// 0x4D616C\nvoid win_exit(void)\n{\n    // 0x51E400\n    static bool insideWinExit = false;\n\n    if (!insideWinExit) {\n        insideWinExit = true;\n        if (GNW_win_init_flag) {\n            GNW_intr_exit();\n\n            for (int index = num_windows - 1; index >= 0; index--) {\n                win_free(window[index]->id);\n            }\n\n            if (GNW_texture != NULL) {\n                mem_free(GNW_texture);\n            }\n\n            if (screen_buffer != NULL) {\n                mem_free(screen_buffer);\n            }\n\n            if (video_reset != NULL) {\n                video_reset();\n            }\n\n            GNW_input_exit();\n            GNW_rect_exit();\n            GNW_text_exit();\n            colorsClose();\n\n            GNW_win_init_flag = false;\n\n            CloseHandle(GNW95_title_mutex);\n            GNW95_title_mutex = INVALID_HANDLE_VALUE;\n        }\n        insideWinExit = false;\n    }\n}\n\n// win_add\n// 0x4D6238\nint win_add(int x, int y, int width, int height, int a4, int flags)\n{\n    int v23;\n    int v25, v26;\n    Window* tmp;\n\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (num_windows == MAX_WINDOW_COUNT) {\n        return -1;\n    }\n\n    if (width > rectGetWidth(&scr_size)) {\n        return -1;\n    }\n\n    if (height > rectGetHeight(&scr_size)) {\n        return -1;\n    }\n\n    Window* w = window[num_windows] = (Window*)mem_malloc(sizeof(*w));\n    if (w == NULL) {\n        return -1;\n    }\n\n    w->buffer = (unsigned char*)mem_malloc(width * height);\n    if (w->buffer == NULL) {\n        mem_free(w);\n        return -1;\n    }\n\n    int index = 1;\n    while (GNW_find(index) != NULL) {\n        index++;\n    }\n\n    w->id = index;\n\n    if ((flags & WINDOW_FLAG_0x01) != 0) {\n        flags |= window_flags;\n    }\n\n    w->width = width;\n    w->height = height;\n    w->flags = flags;\n    w->field_24 = rand() & 0xFFFE;\n    w->field_28 = rand() & 0xFFFE;\n\n    if (a4 == 256) {\n        if (GNW_texture == NULL) {\n            a4 = colorTable[GNW_wcolor[0]];\n        }\n    } else if ((a4 & 0xFF00) != 0) {\n        int colorIndex = (a4 & 0xFF) - 1;\n        a4 = (a4 & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]];\n    }\n\n    w->buttonListHead = 0;\n    w->field_34 = 0;\n    w->field_38 = 0;\n    w->menuBar = NULL;\n    w->blitProc = trans_buf_to_buf;\n    w->field_20 = a4;\n    window_index[index] = num_windows;\n    num_windows++;\n\n    win_fill(index, 0, 0, width, height, a4);\n\n    w->flags |= WINDOW_HIDDEN;\n    win_move(index, x, y);\n    w->flags = flags;\n\n    if ((flags & WINDOW_FLAG_0x04) == 0) {\n        v23 = num_windows - 2;\n        while (v23 > 0) {\n            if (!(window[v23]->flags & WINDOW_FLAG_0x04)) {\n                break;\n            }\n            v23--;\n        }\n\n        if (v23 != num_windows - 2) {\n            v25 = v23 + 1;\n            v26 = num_windows - 1;\n            while (v26 > v25) {\n                tmp = window[v26 - 1];\n                window[v26] = tmp;\n                window_index[tmp->id] = v26;\n                v26--;\n            }\n\n            window[v25] = w;\n            window_index[index] = v25;\n        }\n    }\n\n    return index;\n}\n\n// 0x4D6468\nvoid win_delete(int win)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    Rect rect;\n    rectCopy(&rect, &(w->rect));\n\n    int v1 = window_index[w->id];\n    win_free(win);\n\n    window_index[win] = -1;\n\n    for (int index = v1; index < num_windows - 1; index++) {\n        window[index] = window[index + 1];\n        window_index[window[index]->id] = index;\n    }\n\n    num_windows--;\n\n    // NOTE: Uninline.\n    win_refresh_all(&rect);\n}\n\n// 0x4D650C\nstatic void win_free(int win)\n{\n    Window* w = GNW_find(win);\n    if (w == NULL) {\n        return;\n    }\n\n    if (w->buffer != NULL) {\n        mem_free(w->buffer);\n    }\n\n    if (w->menuBar != NULL) {\n        mem_free(w->menuBar);\n    }\n\n    Button* curr = w->buttonListHead;\n    while (curr != NULL) {\n        Button* next = curr->next;\n        GNW_delete_button(curr);\n        curr = next;\n    }\n\n    mem_free(w);\n}\n\n// 0x4D6558\nvoid win_buffering(bool a1)\n{\n    if (screen_buffer != NULL) {\n        buffering = a1;\n    }\n}\n\n// 0x4D6568\nvoid win_border(int win)\n{\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    Window* w = GNW_find(win);\n    if (w == NULL) {\n        return;\n    }\n\n    lighten_buf(w->buffer + 5, w->width - 10, 5, w->width);\n    lighten_buf(w->buffer, 5, w->height, w->width);\n    lighten_buf(w->buffer + w->width - 5, 5, w->height, w->width);\n    lighten_buf(w->buffer + w->width * (w->height - 5) + 5, w->width - 10, 5, w->width);\n\n    draw_box(w->buffer, w->width, 0, 0, w->width - 1, w->height - 1, colorTable[0]);\n\n    draw_shaded_box(w->buffer, w->width, 1, 1, w->width - 2, w->height - 2, colorTable[GNW_wcolor[1]], colorTable[GNW_wcolor[2]]);\n    draw_shaded_box(w->buffer, w->width, 5, 5, w->width - 6, w->height - 6, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]);\n}\n\n// 0x4D684C\nvoid win_print(int win, char* str, int a3, int x, int y, int a6)\n{\n    int v7;\n    int v14;\n    unsigned char* buf;\n    int v27;\n\n    Window* w = GNW_find(win);\n    v7 = a3;\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    if (a3 == 0) {\n        if (a6 & 0x040000) {\n            v7 = text_mono_width(str);\n        } else {\n            v7 = text_width(str);\n        }\n    }\n\n    if (v7 + x > w->width) {\n        if (!(a6 & 0x04000000)) {\n            return;\n        }\n\n        v7 = w->width - x;\n    }\n\n    buf = w->buffer + x + y * w->width;\n\n    v14 = text_height();\n    if (v14 + y > w->height) {\n        return;\n    }\n\n    if (!(a6 & 0x02000000)) {\n        if (w->field_20 == 256 && GNW_texture != NULL) {\n            buf_texture(buf, v7, text_height(), w->width, GNW_texture, w->field_24 + x, w->field_28 + y);\n        } else {\n            buf_fill(buf, v7, text_height(), w->width, w->field_20);\n        }\n    }\n\n    if ((a6 & 0xFF00) != 0) {\n        int colorIndex = (a6 & 0xFF) - 1;\n        v27 = (a6 & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]];\n    } else {\n        v27 = a6;\n    }\n\n    text_to_buf(buf, str, v7, w->width, v27);\n\n    if (a6 & 0x01000000) {\n        // TODO: Check.\n        Rect rect;\n        rect.ulx = w->rect.ulx + x;\n        rect.uly = w->rect.uly + y;\n        rect.lrx = rect.ulx + v7;\n        rect.lry = rect.uly + text_height();\n        GNW_win_refresh(w, &rect, NULL);\n    }\n}\n\n// 0x4D69DC\nvoid win_text(int win, char** fileNameList, int fileNameListLength, int maxWidth, int x, int y, int flags)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    int width = w->width;\n    unsigned char* ptr = w->buffer + y * width + x;\n    int lineHeight = text_height();\n\n    int step = width * lineHeight;\n    int v1 = lineHeight / 2;\n    int v2 = v1 + 1;\n    int v3 = maxWidth - 1;\n\n    for (int index = 0; index < fileNameListLength; index++) {\n        char* fileName = fileNameList[index];\n        if (*fileName != '\\0') {\n            win_print(win, fileName, maxWidth, x, y, flags);\n        } else {\n            if (maxWidth != 0) {\n                draw_line(ptr, width, 0, v1, v3, v1, colorTable[GNW_wcolor[2]]);\n                draw_line(ptr, width, 0, v2, v3, v2, colorTable[GNW_wcolor[1]]);\n            }\n        }\n\n        ptr += step;\n        y += lineHeight;\n    }\n}\n\n// 0x4D6B24\nvoid win_line(int win, int left, int top, int right, int bottom, int color)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    if ((color & 0xFF00) != 0) {\n        int colorIndex = (color & 0xFF) - 1;\n        color = (color & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]];\n    }\n\n    draw_line(w->buffer, w->width, left, top, right, bottom, color);\n}\n\n// 0x4D6B88\nvoid win_box(int win, int left, int top, int right, int bottom, int color)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    if ((color & 0xFF00) != 0) {\n        int colorIndex = (color & 0xFF) - 1;\n        color = (color & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]];\n    }\n\n    if (right < left) {\n        int tmp = left;\n        left = right;\n        right = tmp;\n    }\n\n    if (bottom < top) {\n        int tmp = top;\n        top = bottom;\n        bottom = tmp;\n    }\n\n    draw_box(w->buffer, w->width, left, top, right, bottom, color);\n}\n\n// 0x4D6CC8\nvoid win_fill(int win, int x, int y, int width, int height, int a6)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    if (a6 == 256) {\n        if (GNW_texture != NULL) {\n            buf_texture(w->buffer + w->width * y + x, width, height, w->width, GNW_texture, x + w->field_24, y + w->field_28);\n        } else {\n            a6 = colorTable[GNW_wcolor[0]] & 0xFF;\n        }\n    } else if ((a6 & 0xFF00) != 0) {\n        int colorIndex = (a6 & 0xFF) - 1;\n        a6 = (a6 & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]];\n    }\n\n    if (a6 < 256) {\n        buf_fill(w->buffer + w->width * y + x, width, height, w->width, a6);\n    }\n}\n\n// 0x4D6DAC\nvoid win_show(int win)\n{\n    Window* w;\n    int v3;\n    int v5;\n    int v7;\n    Window* v6;\n\n    w = GNW_find(win);\n    v3 = window_index[w->id];\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w->flags & WINDOW_HIDDEN) {\n        w->flags &= ~WINDOW_HIDDEN;\n        if (v3 == num_windows - 1) {\n            GNW_win_refresh(w, &(w->rect), NULL);\n        }\n    }\n\n    v5 = num_windows - 1;\n    if (v3 < v5 && !(w->flags & WINDOW_FLAG_0x02)) {\n        v7 = v3;\n        while (v3 < v5 && ((w->flags & WINDOW_FLAG_0x04) || !(window[v7 + 1]->flags & WINDOW_FLAG_0x04))) {\n            v6 = window[v7 + 1];\n            window[v7] = v6;\n            v7++;\n            window_index[v6->id] = v3++;\n        }\n\n        window[v3] = w;\n        window_index[w->id] = v3;\n        GNW_win_refresh(w, &(w->rect), NULL);\n    }\n}\n\n// 0x4D6E64\nvoid win_hide(int win)\n{\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    Window* w = GNW_find(win);\n    if (w == NULL) {\n        return;\n    }\n\n    if ((w->flags & WINDOW_HIDDEN) == 0) {\n        w->flags |= WINDOW_HIDDEN;\n        refresh_all(&(w->rect), NULL);\n    }\n}\n\n// 0x4D6EA0\nvoid win_move(int win, int x, int y)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    Rect rect;\n    rectCopy(&rect, &(w->rect));\n\n    if (x < 0) {\n        x = 0;\n    }\n\n    if (y < 0) {\n        y = 0;\n    }\n\n    if ((w->flags & WINDOW_FLAG_0x0100) != 0) {\n        x += 2;\n    }\n\n    if (x + w->width - 1 > scr_size.lrx) {\n        x = scr_size.lrx - w->width + 1;\n    }\n\n    if (y + w->height - 1 > scr_size.lry) {\n        y = scr_size.lry - w->height + 1;\n    }\n\n    if ((w->flags & WINDOW_FLAG_0x0100) != 0) {\n        // TODO: Not sure what this means.\n        x &= ~0x03;\n    }\n\n    w->rect.ulx = x;\n    w->rect.uly = y;\n    w->rect.lrx = w->width + x - 1;\n    w->rect.lry = w->height + y - 1;\n\n    if ((w->flags & WINDOW_HIDDEN) == 0) {\n        GNW_win_refresh(w, &(w->rect), NULL);\n\n        if (GNW_win_init_flag) {\n            refresh_all(&rect, NULL);\n        }\n    }\n}\n\n// 0x4D6F5C\nvoid win_draw(int win)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    GNW_win_refresh(w, &(w->rect), NULL);\n}\n\n// 0x4D6F80\nvoid win_draw_rect(int win, const Rect* rect)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    Rect newRect;\n    rectCopy(&newRect, rect);\n    rectOffset(&newRect, w->rect.ulx, w->rect.uly);\n\n    GNW_win_refresh(w, &newRect, NULL);\n}\n\n// 0x4D6FD8\nvoid GNW_win_refresh(Window* w, Rect* rect, unsigned char* a3)\n{\n    RectPtr v26, v20, v23, v24;\n    int dest_pitch;\n\n    // TODO: Get rid of this.\n    dest_pitch = 0;\n\n    if ((w->flags & WINDOW_HIDDEN) != 0) {\n        return;\n    }\n\n    if ((w->flags & WINDOW_FLAG_0x20) && buffering && !doing_refresh_all) {\n        // TODO: Incomplete.\n    } else {\n        v26 = rect_malloc();\n        if (v26 == NULL) {\n            return;\n        }\n\n        v26->next = NULL;\n\n        v26->rect.ulx = max(w->rect.ulx, rect->ulx);\n        v26->rect.uly = max(w->rect.uly, rect->uly);\n        v26->rect.lrx = min(w->rect.lrx, rect->lrx);\n        v26->rect.lry = min(w->rect.lry, rect->lry);\n\n        if (v26->rect.lrx >= v26->rect.ulx && v26->rect.lry >= v26->rect.uly) {\n            if (a3) {\n                dest_pitch = rect->lrx - rect->ulx + 1;\n            }\n\n            win_clip(w, &v26, a3);\n\n            if (w->id) {\n                v20 = v26;\n                while (v20) {\n                    GNW_button_refresh(w, &(v20->rect));\n\n                    if (a3) {\n                        if (buffering && (w->flags & WINDOW_FLAG_0x20)) {\n                            w->blitProc(w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width,\n                                v20->rect.lrx - v20->rect.ulx + 1,\n                                v20->rect.lry - v20->rect.uly + 1,\n                                w->width,\n                                a3 + dest_pitch * (v20->rect.uly - rect->uly) + v20->rect.ulx - rect->ulx,\n                                dest_pitch);\n                        } else {\n                            buf_to_buf(\n                                w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width,\n                                v20->rect.lrx - v20->rect.ulx + 1,\n                                v20->rect.lry - v20->rect.uly + 1,\n                                w->width,\n                                a3 + dest_pitch * (v20->rect.uly - rect->uly) + v20->rect.ulx - rect->ulx,\n                                dest_pitch);\n                        }\n                    } else {\n                        if (buffering) {\n                            if (w->flags & WINDOW_FLAG_0x20) {\n                                w->blitProc(\n                                    w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width,\n                                    v20->rect.lrx - v20->rect.ulx + 1,\n                                    v20->rect.lry - v20->rect.uly + 1,\n                                    w->width,\n                                    screen_buffer + v20->rect.uly * (scr_size.lrx - scr_size.ulx + 1) + v20->rect.ulx,\n                                    scr_size.lrx - scr_size.ulx + 1);\n                            } else {\n                                buf_to_buf(\n                                    w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width,\n                                    v20->rect.lrx - v20->rect.ulx + 1,\n                                    v20->rect.lry - v20->rect.uly + 1,\n                                    w->width,\n                                    screen_buffer + v20->rect.uly * (scr_size.lrx - scr_size.ulx + 1) + v20->rect.ulx,\n                                    scr_size.lrx - scr_size.ulx + 1);\n                            }\n                        } else {\n                            scr_blit(\n                                w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width,\n                                w->width,\n                                v20->rect.lry - v20->rect.lry + 1,\n                                0,\n                                0,\n                                v20->rect.lrx - v20->rect.ulx + 1,\n                                v20->rect.lry - v20->rect.uly + 1,\n                                v20->rect.ulx,\n                                v20->rect.uly);\n                        }\n                    }\n\n                    v20 = v20->next;\n                }\n            } else {\n                rectdata* v16 = v26;\n                while (v16 != NULL) {\n                    int width = v16->rect.lrx - v16->rect.ulx + 1;\n                    int height = v16->rect.lry - v16->rect.uly + 1;\n                    unsigned char* buf = (unsigned char*)mem_malloc(width * height);\n                    if (buf != NULL) {\n                        buf_fill(buf, width, height, width, bk_color);\n                        if (dest_pitch != 0) {\n                            buf_to_buf(\n                                buf,\n                                width,\n                                height,\n                                width,\n                                a3 + dest_pitch * (v16->rect.uly - rect->uly) + v16->rect.ulx - rect->ulx,\n                                dest_pitch);\n                        } else {\n                            if (buffering) {\n                                buf_to_buf(buf,\n                                    width,\n                                    height,\n                                    width,\n                                    screen_buffer + v16->rect.uly * (scr_size.lrx - scr_size.ulx + 1) + v16->rect.ulx,\n                                    scr_size.lrx - scr_size.ulx + 1);\n                            } else {\n                                scr_blit(buf, width, height, 0, 0, width, height, v16->rect.ulx, v16->rect.uly);\n                            }\n                        }\n\n                        mem_free(buf);\n                    }\n                    v16 = v16->next;\n                }\n            }\n\n            v23 = v26;\n            while (v23) {\n                v24 = v23->next;\n\n                if (buffering && !a3) {\n                    scr_blit(\n                        screen_buffer + v23->rect.ulx + (scr_size.lrx - scr_size.ulx + 1) * v23->rect.uly,\n                        scr_size.lrx - scr_size.ulx + 1,\n                        v23->rect.lry - v23->rect.uly + 1,\n                        0,\n                        0,\n                        v23->rect.lrx - v23->rect.ulx + 1,\n                        v23->rect.lry - v23->rect.uly + 1,\n                        v23->rect.ulx,\n                        v23->rect.uly);\n                }\n\n                rect_free(v23);\n\n                v23 = v24;\n            }\n\n            if (!doing_refresh_all && a3 == NULL && mouse_hidden() == 0) {\n                if (mouse_in(rect->ulx, rect->uly, rect->lrx, rect->lry)) {\n                    mouse_show();\n                }\n            }\n        } else {\n            rect_free(v26);\n        }\n    }\n}\n\n// 0x4D759C\nvoid win_refresh_all(Rect* rect)\n{\n    if (GNW_win_init_flag) {\n        refresh_all(rect, NULL);\n    }\n}\n\n// 0x4D75B0\nstatic void win_clip(Window* w, RectPtr* rectListNodePtr, unsigned char* a3)\n{\n    int win;\n\n    for (win = window_index[w->id] + 1; win < num_windows; win++) {\n        if (*rectListNodePtr == NULL) {\n            break;\n        }\n\n        // TODO: Review.\n        Window* w = window[win];\n        if (!(w->flags & WINDOW_HIDDEN)) {\n            if (!buffering || !(w->flags & WINDOW_FLAG_0x20)) {\n                rect_clip_list(rectListNodePtr, &(w->rect));\n            } else {\n                if (!doing_refresh_all) {\n                    GNW_win_refresh(w, &(w->rect), NULL);\n                    rect_clip_list(rectListNodePtr, &(w->rect));\n                }\n            }\n        }\n    }\n\n    if (a3 == screen_buffer || a3 == NULL) {\n        if (mouse_hidden() == 0) {\n            Rect rect;\n            mouse_get_rect(&rect);\n            rect_clip_list(rectListNodePtr, &rect);\n        }\n    }\n}\n\n// 0x4D765C\nvoid win_drag(int win)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (w == NULL) {\n        return;\n    }\n\n    win_show(win);\n\n    Rect rect;\n    rectCopy(&rect, &(w->rect));\n\n    GNW_do_bk_process();\n\n    if (vcr_update() != 3) {\n        mouse_info();\n    }\n\n    if ((w->flags & WINDOW_FLAG_0x0100) && (w->rect.ulx & 3)) {\n        win_move(w->id, w->rect.ulx, w->rect.uly);\n    }\n}\n\n// 0x4D77F8\nvoid win_get_mouse_buf(unsigned char* a1)\n{\n    Rect rect;\n    mouse_get_rect(&rect);\n    refresh_all(&rect, a1);\n}\n\n// 0x4D7814\nstatic void refresh_all(Rect* rect, unsigned char* a2)\n{\n    doing_refresh_all = 1;\n\n    for (int index = 0; index < num_windows; index++) {\n        GNW_win_refresh(window[index], rect, a2);\n    }\n\n    doing_refresh_all = 0;\n\n    if (a2 == NULL) {\n        if (!mouse_hidden()) {\n            if (mouse_in(rect->ulx, rect->uly, rect->lrx, rect->lry)) {\n                mouse_show();\n            }\n        }\n    }\n}\n\n// 0x4D7888\nWindow* GNW_find(int win)\n{\n    int v0;\n\n    if (win == -1) {\n        return NULL;\n    }\n\n    v0 = window_index[win];\n    if (v0 == -1) {\n        return NULL;\n    }\n\n    return window[v0];\n}\n\n// win_get_buf\n// 0x4D78B0\nunsigned char* win_get_buf(int win)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return NULL;\n    }\n\n    if (w == NULL) {\n        return NULL;\n    }\n\n    return w->buffer;\n}\n\n// 0x4D78CC\nint win_get_top_win(int x, int y)\n{\n    for (int index = num_windows - 1; index >= 0; index--) {\n        Window* w = window[index];\n        if (x >= w->rect.ulx && x <= w->rect.lrx\n            && y >= w->rect.uly && y <= w->rect.lry) {\n            return w->id;\n        }\n    }\n\n    return -1;\n}\n\n// 0x4D7918\nint win_width(int win)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (w == NULL) {\n        return -1;\n    }\n\n    return w->width;\n}\n\n// 0x4D7934\nint win_height(int win)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (w == NULL) {\n        return -1;\n    }\n\n    return w->height;\n}\n\n// 0x4D7950\nint win_get_rect(int win, Rect* rect)\n{\n    Window* w = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (w == NULL) {\n        return -1;\n    }\n\n    rectCopy(rect, &(w->rect));\n\n    return 0;\n}\n\n// 0x4D797C\nint win_check_all_buttons()\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    int v1 = -1;\n    for (int index = num_windows - 1; index >= 1; index--) {\n        if (GNW_check_buttons(window[index], &v1) == 0) {\n            break;\n        }\n\n        if ((window[index]->flags & WINDOW_FLAG_0x10) != 0) {\n            break;\n        }\n    }\n\n    return v1;\n}\n\n// 0x4D79DC\nButton* GNW_find_button(int btn, Window** windowPtr)\n{\n    for (int index = 0; index < num_windows; index++) {\n        Window* w = window[index];\n        Button* button = w->buttonListHead;\n        while (button != NULL) {\n            if (button->id == btn) {\n                if (windowPtr != NULL) {\n                    *windowPtr = w;\n                }\n\n                return button;\n            }\n            button = button->next;\n        }\n    }\n\n    return NULL;\n}\n\n// 0x4D7A34\nint GNW_check_menu_bars(int a1)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    int v1 = a1;\n    for (int index = num_windows - 1; index >= 1; index--) {\n        Window* w = window[index];\n        if (w->menuBar != NULL) {\n            for (int pulldownIndex = 0; pulldownIndex < w->menuBar->pulldownsLength; pulldownIndex++) {\n                if (v1 == w->menuBar->pulldowns[pulldownIndex].keyCode) {\n                    v1 = GNW_process_menu(w->menuBar, pulldownIndex);\n                    break;\n                }\n            }\n        }\n\n        if ((w->flags & 0x10) != 0) {\n            break;\n        }\n    }\n\n    return v1;\n}\n\n// 0x4D80D8\nvoid win_set_minimized_title(const char* title)\n{\n    if (title == NULL) {\n        return;\n    }\n\n    if (GNW95_title_mutex == INVALID_HANDLE_VALUE) {\n        GNW95_title_mutex = CreateMutexA(NULL, TRUE, title);\n        if (GetLastError() != ERROR_SUCCESS) {\n            GNW95_already_running = true;\n            return;\n        }\n    }\n\n    strncpy(GNW95_title, title, 256);\n    GNW95_title[256 - 1] = '\\0';\n\n    if (GNW95_hwnd != NULL) {\n        SetWindowTextA(GNW95_hwnd, GNW95_title);\n    }\n}\n\n// [open] implementation for palette operations backed by [XFile].\n//\n// 0x4D8174\nstatic int colorOpen(const char* path, int flags)\n{\n    char mode[4];\n    memset(mode, 0, sizeof(mode));\n\n    if ((flags & 0x01) != 0) {\n        mode[0] = 'w';\n    } else if ((flags & 0x10) != 0) {\n        mode[0] = 'a';\n    } else {\n        mode[0] = 'r';\n    }\n\n    if ((flags & 0x100) != 0) {\n        mode[1] = 't';\n    } else if ((flags & 0x200) != 0) {\n        mode[1] = 'b';\n    }\n\n    File* stream = db_fopen(path, mode);\n    if (stream != NULL) {\n        return (int)stream;\n    }\n\n    return -1;\n}\n\n// [read] implementation for palette file operations backed by [XFile].\n//\n// 0x4D81E8\nstatic int colorRead(int fd, void* buf, size_t count)\n{\n    return db_fread(buf, 1, count, (File*)fd);\n}\n\n// [close] implementation for palette file operations backed by [XFile].\n//\n// 0x4D81E0\nstatic int colorClose(int fd)\n{\n    return db_fclose((File*)fd);\n}\n\n// 0x4D8200\nbool GNWSystemError(const char* text)\n{\n    HCURSOR cursor = LoadCursorA(GNW95_hInstance, MAKEINTRESOURCEA(IDC_ARROW));\n    HCURSOR prev = SetCursor(cursor);\n    ShowCursor(TRUE);\n    MessageBoxA(NULL, text, NULL, MB_ICONSTOP);\n    ShowCursor(FALSE);\n    SetCursor(prev);\n    return true;\n}\n"
  },
  {
    "path": "src/plib/gnw/gnw.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_GNW_H_\n#define FALLOUT_PLIB_GNW_GNW_H_\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"plib/gnw/gnw_types.h\"\n#include \"plib/gnw/rect.h\"\n\n#define MAX_WINDOW_COUNT (50)\n\n// The maximum number of radio groups.\n#define RADIO_GROUP_LIST_CAPACITY (64)\n\ntypedef enum WindowManagerErr {\n    WINDOW_MANAGER_OK = 0,\n    WINDOW_MANAGER_ERR_INITIALIZING_VIDEO_MODE = 1,\n    WINDOW_MANAGER_ERR_NO_MEMORY = 2,\n    WINDOW_MANAGER_ERR_INITIALIZING_TEXT_FONTS = 3,\n    WINDOW_MANAGER_ERR_WINDOW_SYSTEM_ALREADY_INITIALIZED = 4,\n    WINDOW_MANAGER_ERR_WINDOW_SYSTEM_NOT_INITIALIZED = 5,\n    WINDOW_MANAGER_ERR_CURRENT_WINDOWS_TOO_BIG = 6,\n    WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE = 7,\n\n    // Unknown fatal error.\n    //\n    // NOTE: When this error code returned from window system initialization, the\n    // game simply exits without any debug message. There is no way to figure out\n    // it's meaning.\n    WINDOW_MANAGER_ERR_8 = 8,\n    WINDOW_MANAGER_ERR_ALREADY_RUNNING = 9,\n    WINDOW_MANAGER_ERR_TITLE_NOT_SET = 10,\n    WINDOW_MANAGER_ERR_INITIALIZING_INPUT = 11,\n} WindowManagerErr;\n\ntypedef int(VideoSystemInitProc)();\ntypedef void(VideoSystemExitProc)();\n\nextern bool GNW_win_init_flag;\nextern int GNW_wcolor[6];\nextern unsigned char* screen_buffer;\n\nextern void* GNW_texture;\n\nint win_init(VideoSystemInitProc* videoSystemInitProc, VideoSystemExitProc* videoSystemExitProc, int a3);\nvoid win_exit(void);\nint win_add(int x, int y, int width, int height, int a4, int flags);\nvoid win_delete(int win);\nvoid win_buffering(bool a1);\nvoid win_border(int win);\nvoid win_print(int win, char* str, int a3, int x, int y, int a6);\nvoid win_text(int win, char** fileNameList, int fileNameListLength, int maxWidth, int x, int y, int flags);\nvoid win_line(int win, int left, int top, int right, int bottom, int color);\nvoid win_box(int win, int left, int top, int right, int bottom, int color);\nvoid win_fill(int win, int x, int y, int width, int height, int a6);\nvoid win_show(int win);\nvoid win_hide(int win);\nvoid win_move(int win_index, int x, int y);\nvoid win_draw(int win);\nvoid win_draw_rect(int win, const Rect* rect);\nvoid GNW_win_refresh(Window* window, Rect* rect, unsigned char* a3);\nvoid win_refresh_all(Rect* rect);\nvoid win_drag(int win);\nvoid win_get_mouse_buf(unsigned char* a1);\nWindow* GNW_find(int win);\nunsigned char* win_get_buf(int win);\nint win_get_top_win(int x, int y);\nint win_width(int win);\nint win_height(int win);\nint win_get_rect(int win, Rect* rect);\nint win_check_all_buttons();\nButton* GNW_find_button(int btn, Window** out_win);\nint GNW_check_menu_bars(int a1);\nvoid win_set_minimized_title(const char* title);\nbool GNWSystemError(const char* str);\n\n#endif /* FALLOUT_PLIB_GNW_GNW_H_ */\n"
  },
  {
    "path": "src/plib/gnw/gnw95dx.c",
    "content": "#include \"plib/gnw/gnw95dx.h\"\n\n// 0x53A274\nPFNDDRAWCREATE GNW95_DirectDrawCreate = NULL;\n\n// 0x53A278\nPFNDINPUTCREATE GNW95_DirectInputCreate = NULL;\n\n// 0x53A27C\nPFNDSOUNDCREATE GNW95_DirectSoundCreate = NULL;\n"
  },
  {
    "path": "src/plib/gnw/gnw95dx.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_GNW95DX_H_\n#define FALLOUT_PLIB_GNW_GNW95DX_H_\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#define DIRECTDRAW_VERSION 0x0300\n#include <ddraw.h>\n\n#define DIRECTINPUT_VERSION 0x0300\n#include <dinput.h>\n#include <mmreg.h>\n\n#define DIRECTSOUND_VERSION 0x0300\n#include <dsound.h>\n\ntypedef HRESULT(__stdcall *PFNDDRAWCREATE)(GUID*, LPDIRECTDRAW*, IUnknown*);\ntypedef HRESULT(__stdcall *PFNDINPUTCREATE)(HINSTANCE, DWORD, LPDIRECTINPUTA*, IUnknown*);\ntypedef HRESULT(__stdcall *PFNDSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown*);\n\nextern PFNDDRAWCREATE GNW95_DirectDrawCreate;\nextern PFNDINPUTCREATE GNW95_DirectInputCreate;\nextern PFNDSOUNDCREATE GNW95_DirectSoundCreate;\n\n#endif /* FALLOUT_PLIB_GNW_GNW95DX_H_ */\n"
  },
  {
    "path": "src/plib/gnw/gnw_types.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_GNW_TYPES_H_\n#define FALLOUT_PLIB_GNW_GNW_TYPES_H_\n\n#include \"plib/gnw/rect.h\"\n\n// The maximum number of buttons in one radio group.\n#define RADIO_GROUP_BUTTON_LIST_CAPACITY 64\n\ntypedef enum WindowFlags {\n    WINDOW_FLAG_0x01 = 0x01,\n    WINDOW_FLAG_0x02 = 0x02,\n    WINDOW_FLAG_0x04 = 0x04,\n    WINDOW_HIDDEN = 0x08,\n    WINDOW_FLAG_0x10 = 0x10,\n    WINDOW_FLAG_0x20 = 0x20,\n    WINDOW_FLAG_0x40 = 0x40,\n    WINDOW_FLAG_0x80 = 0x80,\n    WINDOW_FLAG_0x0100 = 0x0100,\n} WindowFlags;\n\ntypedef enum ButtonFlags {\n    BUTTON_FLAG_0x01 = 0x01,\n    BUTTON_FLAG_0x02 = 0x02,\n    BUTTON_FLAG_0x04 = 0x04,\n    BUTTON_FLAG_DISABLED = 0x08,\n    BUTTON_FLAG_0x10 = 0x10,\n    BUTTON_FLAG_TRANSPARENT = 0x20,\n    BUTTON_FLAG_0x40 = 0x40,\n    BUTTON_FLAG_0x010000 = 0x010000,\n    BUTTON_FLAG_0x020000 = 0x020000,\n    BUTTON_FLAG_0x040000 = 0x040000,\n    BUTTON_FLAG_RIGHT_MOUSE_BUTTON_CONFIGURED = 0x080000,\n} ButtonFlags;\n\ntypedef struct Button Button;\ntypedef struct RadioGroup RadioGroup;\n\ntypedef void WindowBlitProc(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch);\ntypedef void ButtonCallback(int btn, int keyCode);\n\ntypedef struct MenuPulldown {\n    Rect rect;\n    int keyCode;\n    int itemsLength;\n    char** items;\n    int field_1C;\n    int field_20;\n} MenuPulldown;\n\ntypedef struct MenuBar {\n    int win;\n    Rect rect;\n    int pulldownsLength;\n    MenuPulldown pulldowns[15];\n    int borderColor;\n    int backgroundColor;\n} MenuBar;\nstatic_assert(sizeof(MenuBar) == 572, \"wrong size\");\n\ntypedef struct Window {\n    int id;\n    int flags;\n    Rect rect;\n    int width;\n    int height;\n    int field_20;\n    // rand\n    int field_24;\n    // rand\n    int field_28;\n    unsigned char* buffer;\n    Button* buttonListHead;\n    Button* field_34;\n    Button* field_38;\n    MenuBar* menuBar;\n    WindowBlitProc* blitProc;\n} Window;\nstatic_assert(sizeof(Window) == 68, \"wrong size\");\n\ntypedef struct Button {\n    int id;\n    int flags;\n    Rect rect;\n    int mouseEnterEventCode;\n    int mouseExitEventCode;\n    int lefMouseDownEventCode;\n    int leftMouseUpEventCode;\n    int rightMouseDownEventCode;\n    int rightMouseUpEventCode;\n    unsigned char* mouseUpImage;\n    unsigned char* mouseDownImage;\n    unsigned char* mouseHoverImage;\n    unsigned char* field_3C;\n    unsigned char* field_40;\n    unsigned char* field_44;\n    unsigned char* currentImage;\n    unsigned char* mask;\n    ButtonCallback* mouseEnterProc;\n    ButtonCallback* mouseExitProc;\n    ButtonCallback* leftMouseDownProc;\n    ButtonCallback* leftMouseUpProc;\n    ButtonCallback* rightMouseDownProc;\n    ButtonCallback* rightMouseUpProc;\n    ButtonCallback* onPressed;\n    ButtonCallback* onUnpressed;\n    RadioGroup* radioGroup;\n    Button* prev;\n    Button* next;\n} Button;\nstatic_assert(sizeof(Button) == 124, \"wrong size\");\n\ntypedef struct RadioGroup {\n    int field_0;\n    int field_4;\n    void (*field_8)(int);\n    int buttonsLength;\n    Button* buttons[RADIO_GROUP_BUTTON_LIST_CAPACITY];\n} RadioGroup;\n\n#endif /* FALLOUT_PLIB_GNW_GNW_TYPES_H_ */\n"
  },
  {
    "path": "src/plib/gnw/grbuf.c",
    "content": "#include \"plib/gnw/grbuf.h\"\n\n#include <string.h>\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"mmx.h\"\n\n// 0x4D2FC0\nvoid draw_line(unsigned char* buf, int pitch, int x1, int y1, int x2, int y2, int color)\n{\n    int temp;\n    int dx;\n    int dy;\n    unsigned char* p1;\n    unsigned char* p2;\n    unsigned char* p3;\n    unsigned char* p4;\n\n    if (x1 == x2) {\n        if (y1 > y2) {\n            temp = y1;\n            y1 = y2;\n            y2 = temp;\n        }\n\n        p1 = buf + pitch * y1 + x1;\n        p2 = buf + pitch * y2 + x2;\n        while (p1 < p2) {\n            *p1 = color;\n            *p2 = color;\n            p1 += pitch;\n            p2 -= pitch;\n        }\n    } else {\n        if (x1 > x2) {\n            temp = x1;\n            x1 = x2;\n            x2 = temp;\n\n            temp = y1;\n            y1 = y2;\n            y2 = temp;\n        }\n\n        p1 = buf + pitch * y1 + x1;\n        p2 = buf + pitch * y2 + x2;\n        if (y1 == y2) {\n            memset(p1, color, p2 - p1);\n        } else {\n            dx = x2 - x1;\n\n            int v23;\n            int v22;\n            int midX = x1 + (x2 - x1) / 2;\n            if (y1 <= y2) {\n                dy = y2 - y1;\n                v23 = pitch;\n                v22 = midX + ((y2 - y1) / 2 + y1) * pitch;\n            } else {\n                dy = y1 - y2;\n                v23 = -pitch;\n                v22 = midX + (y1 - (y1 - y2) / 2) * pitch;\n            }\n\n            p3 = buf + v22;\n            p4 = p3;\n\n            if (dx <= dy) {\n                int v28 = dx - (dy / 2);\n                int v29 = dy / 4;\n                while (true) {\n                    *p1 = color;\n                    *p2 = color;\n                    *p3 = color;\n                    *p4 = color;\n\n                    if (v29 == 0) {\n                        break;\n                    }\n\n                    if (v28 >= 0) {\n                        p3++;\n                        p2--;\n                        p4--;\n                        p1++;\n                        v28 -= dy;\n                    }\n\n                    p3 += v23;\n                    p2 -= v23;\n                    p4 -= v23;\n                    p1 += v23;\n                    v28 += dx;\n\n                    v29--;\n                }\n            } else {\n                int v26 = dy - (dx / 2);\n                int v27 = dx / 4;\n                while (true) {\n                    *p1 = color;\n                    *p2 = color;\n                    *p3 = color;\n                    *p4 = color;\n\n                    if (v27 == 0) {\n                        break;\n                    }\n\n                    if (v26 >= 0) {\n                        p3 += v23;\n                        p2 -= v23;\n                        p4 -= v23;\n                        p1 += v23;\n                        v26 -= dx;\n                    }\n\n                    p3++;\n                    p2--;\n                    p4--;\n                    p1++;\n                    v26 += dy;\n\n                    v27--;\n                }\n            }\n        }\n    }\n}\n\n// 0x4D31A4\nvoid draw_box(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int color)\n{\n    draw_line(buf, pitch, left, top, right, top, color);\n    draw_line(buf, pitch, left, bottom, right, bottom, color);\n    draw_line(buf, pitch, left, top, left, bottom, color);\n    draw_line(buf, pitch, right, top, right, bottom, color);\n}\n\n// 0x4D322C\nvoid draw_shaded_box(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int ltColor, int rbColor)\n{\n    draw_line(buf, pitch, left, top, right, top, ltColor);\n    draw_line(buf, pitch, left, bottom, right, bottom, rbColor);\n    draw_line(buf, pitch, left, top, left, bottom, ltColor);\n    draw_line(buf, pitch, right, top, right, bottom, rbColor);\n}\n\n// 0x4D33F0\nvoid cscale(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch)\n{\n    int heightRatio = (destHeight << 16) / srcHeight;\n    int widthRatio = (destWidth << 16) / srcWidth;\n\n    int v1 = 0;\n    int v2 = heightRatio;\n    for (int srcY = 0; srcY < srcHeight; srcY += 1) {\n        int v3 = widthRatio;\n        int v4 = (heightRatio * srcY) >> 16;\n        int v5 = v2 >> 16;\n        int v6 = 0;\n\n        unsigned char* c = src + v1;\n        for (int srcX = 0; srcX < srcWidth; srcX += 1) {\n            int v7 = v3 >> 16;\n            int v8 = v6 >> 16;\n\n            unsigned char* v9 = dest + destPitch * v4 + v8;\n            for (int destY = v4; destY < v5; destY += 1) {\n                for (int destX = v8; destX < v7; destX += 1) {\n                    *v9++ = *c;\n                }\n                v9 += destPitch;\n            }\n\n            v3 += widthRatio;\n            c++;\n            v6 += widthRatio;\n        }\n        v1 += srcPitch;\n        v2 += heightRatio;\n    }\n}\n\n// 0x4D3560\nvoid trans_cscale(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch)\n{\n    int heightRatio = (destHeight << 16) / srcHeight;\n    int widthRatio = (destWidth << 16) / srcWidth;\n\n    int v1 = 0;\n    int v2 = heightRatio;\n    for (int srcY = 0; srcY < srcHeight; srcY += 1) {\n        int v3 = widthRatio;\n        int v4 = (heightRatio * srcY) >> 16;\n        int v5 = v2 >> 16;\n        int v6 = 0;\n\n        unsigned char* c = src + v1;\n        for (int srcX = 0; srcX < srcWidth; srcX += 1) {\n            int v7 = v3 >> 16;\n            int v8 = v6 >> 16;\n\n            if (*c != 0) {\n                unsigned char* v9 = dest + destPitch * v4 + v8;\n                for (int destY = v4; destY < v5; destY += 1) {\n                    for (int destX = v8; destX < v7; destX += 1) {\n                        *v9++ = *c;\n                    }\n                    v9 += destPitch;\n                }\n            }\n\n            v3 += widthRatio;\n            c++;\n            v6 += widthRatio;\n        }\n        v1 += srcPitch;\n        v2 += heightRatio;\n    }\n}\n\n// 0x4D36D4\nvoid buf_to_buf(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch)\n{\n    mmxBlit(dest, destPitch, src, srcPitch, width, height);\n}\n\n// 0x4D3704\nvoid trans_buf_to_buf(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch)\n{\n    mmxBlitTrans(dest, destPitch, src, srcPitch, width, height);\n}\n\n// 0x4D387C\nvoid buf_fill(unsigned char* buf, int width, int height, int pitch, int a5)\n{\n    int y;\n\n    for (y = 0; y < height; y++) {\n        memset(buf, a5, width);\n        buf += pitch;\n    }\n}\n\n// 0x4D38E0\nvoid buf_texture(unsigned char* buf, int width, int height, int pitch, void* a5, int a6, int a7)\n{\n    // TODO: Incomplete.\n}\n\n// 0x4D3A48\nvoid lighten_buf(unsigned char* buf, int width, int height, int pitch)\n{\n    int skip = pitch - width;\n\n    for (int y = 0; y < height; y++) {\n        for (int x = 0; x < width; x++) {\n            unsigned char p = *buf;\n            *buf++ = intensityColorTable[p][147];\n        }\n        buf += skip;\n    }\n}\n\n// Swaps two colors in the buffer.\n//\n// 0x4D3A8C\nvoid swap_color_buf(unsigned char* buf, int width, int height, int pitch, int color1, int color2)\n{\n    int step = pitch - width;\n    for (int y = 0; y < height; y++) {\n        for (int x = 0; x < width; x++) {\n            int v1 = *buf & 0xFF;\n            if (v1 == color1) {\n                *buf = color2 & 0xFF;\n            } else if (v1 == color2) {\n                *buf = color1 & 0xFF;\n            }\n            buf++;\n        }\n        buf += step;\n    }\n}\n\n// 0x4D3AE0\nvoid buf_outline(unsigned char* buf, int width, int height, int pitch, int color)\n{\n    unsigned char* ptr = buf + pitch;\n\n    bool cycle;\n    for (int y = 0; y < height - 2; y++) {\n        cycle = true;\n\n        for (int x = 0; x < width; x++) {\n            if (*ptr != 0 && cycle) {\n                *(ptr - 1) = color & 0xFF;\n                cycle = false;\n            } else if (*ptr == 0 && !cycle) {\n                *ptr = color & 0xFF;\n                cycle = true;\n            }\n\n            ptr++;\n        }\n\n        ptr += pitch - width;\n    }\n\n    for (int x = 0; x < width; x++) {\n        ptr = buf + x;\n        cycle = true;\n\n        for (int y = 0; y < height; y++) {\n            if (*ptr != 0 && cycle) {\n                // TODO: Check in debugger, might be a bug.\n                *(ptr - pitch) = color & 0xFF;\n                cycle = false;\n            } else if (*ptr == 0 && !cycle) {\n                *ptr = color & 0xFF;\n                cycle = true;\n            }\n\n            ptr += pitch;\n        }\n    }\n}\n"
  },
  {
    "path": "src/plib/gnw/grbuf.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_GRBUF_H_\n#define FALLOUT_PLIB_GNW_GRBUF_H_\n\nvoid draw_line(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int color);\nvoid draw_box(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7);\nvoid draw_shaded_box(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7, int a8);\nvoid cscale(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch);\nvoid trans_cscale(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch);\nvoid buf_to_buf(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch);\nvoid trans_buf_to_buf(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch);\nvoid buf_fill(unsigned char* buf, int width, int height, int pitch, int a5);\nvoid buf_texture(unsigned char* buf, int width, int height, int pitch, void* a5, int a6, int a7);\nvoid lighten_buf(unsigned char* buf, int width, int height, int pitch);\nvoid swap_color_buf(unsigned char* buf, int width, int height, int pitch, int color1, int color2);\nvoid buf_outline(unsigned char* buf, int width, int height, int pitch, int a5);\n\n#endif /* FALLOUT_PLIB_GNW_GRBUF_H_ */\n"
  },
  {
    "path": "src/plib/gnw/input.c",
    "content": "#include \"plib/gnw/input.h\"\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/dxinput.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"plib/gnw/memory.h\"\n#include \"mmx.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/vcr.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/intrface.h\"\n#include \"plib/gnw/svga.h\"\n#include \"plib/gnw/winmain.h\"\n\ntypedef struct GNW95RepeatStruct {\n    // Time when appropriate key was pressed down or -1 if it's up.\n    TOCKS time;\n    unsigned short count;\n} GNW95RepeatStruct;\n\ntypedef struct inputdata {\n    // This is either logical key or input event id, which can be either\n    // character code pressed or some other numbers used throughout the\n    // game interface.\n    int input;\n    int mx;\n    int my;\n} inputdata;\n\ntypedef struct funcdata {\n    unsigned int flags;\n    BackgroundProcess* f;\n    struct funcdata* next;\n} funcdata;\n\ntypedef funcdata* FuncPtr;\n\nstatic int get_input_buffer();\nstatic void pause_game();\nstatic int default_pause_window();\nstatic void buf_blit(unsigned char* src, int src_pitch, int a3, int x, int y, int width, int height, int dest_x, int dest_y);\nstatic void GNW95_build_key_map();\nstatic int GNW95_hook_keyboard(int hook);\nstatic void GNW95_process_key(dxinput_key_data* data);\n\n// NOT USED.\nstatic IdleFunc* idle_func = NULL;\n\n// NOT USED.\nstatic FocusFunc* focus_func = NULL;\n\n// 0x51E23C\nstatic int GNW95_repeat_rate = 80;\n\n// 0x51E240\nstatic int GNW95_repeat_delay = 500;\n\n// A map of DIK_* constants normalized for QWERTY keyboard.\n//\n// 0x6ABC70\nunsigned char GNW95_key_map[256];\n\n// Ring buffer of input events.\n//\n// Looks like this buffer does not support overwriting of values. Once the\n// buffer is full it will not overwrite values until they are dequeued.\n//\n// 0x6ABD70\nstatic inputdata input_buffer[40];\n\n// 0x6ABF50\nGNW95RepeatStruct GNW95_key_time_stamps[256];\n\n// 0x6AC750\nstatic int input_mx;\n\n// 0x6AC754\nstatic int input_my;\n\n// 0x6AC758\nstatic HHOOK GNW95_keyboardHandle;\n\n// 0x6AC75C\nstatic bool game_paused;\n\n// 0x6AC760\nstatic int screendump_key;\n\n// 0x6AC764\nstatic int using_msec_timer;\n\n// 0x6AC768\nstatic int pause_key;\n\n// 0x6AC76C\nstatic ScreenDumpFunc* screendump_func;\n\n// 0x6AC770\nstatic int input_get;\n\n// 0x6AC774\nstatic unsigned char* screendump_buf;\n\n// 0x6AC778\nstatic PauseWinFunc* pause_win_func;\n\n// 0x6AC77C\nstatic int input_put;\n\n// 0x6AC780\nstatic bool bk_disabled;\n\n// 0x6AC784\nstatic FuncPtr bk_list;\n\n// 0x6AC788\nstatic unsigned int bk_process_time;\n\n// 0x4C8A70\nint GNW_input_init(int use_msec_timer)\n{\n    if (!dxinput_init()) {\n        return -1;\n    }\n\n    if (GNW_kb_set() == -1) {\n        return -1;\n    }\n\n    if (GNW_mouse_init() == -1) {\n        return -1;\n    }\n\n    if (GNW95_input_init() == -1) {\n        return -1;\n    }\n\n    GNW95_hook_input(1);\n    GNW95_build_key_map();\n    GNW95_clear_time_stamps();\n\n    using_msec_timer = use_msec_timer;\n    input_put = 0;\n    input_get = -1;\n    input_mx = -1;\n    input_my = -1;\n    bk_disabled = 0;\n    game_paused = false;\n    pause_key = KEY_ALT_P;\n    pause_win_func = default_pause_window;\n    screendump_func = default_screendump;\n    bk_list = NULL;\n    screendump_key = KEY_ALT_C;\n\n    return 0;\n}\n\n// 0x4C8B40\nvoid GNW_input_exit()\n{\n    // NOTE: Uninline.\n    GNW95_input_exit();\n    GNW_mouse_exit();\n    GNW_kb_restore();\n    dxinput_exit();\n\n    FuncPtr curr = bk_list;\n    while (curr != NULL) {\n        FuncPtr next = curr->next;\n        mem_free(curr);\n        curr = next;\n    }\n}\n\n// 0x4C8B78\nint get_input()\n{\n    int v3;\n\n    GNW95_process_message();\n\n    if (!GNW95_isActive) {\n        GNW95_lost_focus();\n    }\n\n    process_bk();\n\n    v3 = get_input_buffer();\n    if (v3 == -1 && mouse_get_buttons() & 0x33) {\n        mouse_get_position(&input_mx, &input_my);\n        return -2;\n    } else {\n        return GNW_check_menu_bars(v3);\n    }\n\n    return -1;\n}\n\n// 0x4C8BDC\nvoid process_bk()\n{\n    int v1;\n\n    GNW_do_bk_process();\n\n    if (vcr_update() != 3) {\n        mouse_info();\n    }\n\n    v1 = win_check_all_buttons();\n    if (v1 != -1) {\n        GNW_add_input_buffer(v1);\n        return;\n    }\n\n    v1 = kb_getch();\n    if (v1 != -1) {\n        GNW_add_input_buffer(v1);\n        return;\n    }\n}\n\n// 0x4C8C04\nvoid GNW_add_input_buffer(int a1)\n{\n    if (a1 == -1) {\n        return;\n    }\n\n    if (a1 == pause_key) {\n        pause_game();\n        return;\n    }\n\n    if (a1 == screendump_key) {\n        dump_screen();\n        return;\n    }\n\n    if (input_put == input_get) {\n        return;\n    }\n\n    inputdata* inputEvent = &(input_buffer[input_put]);\n    inputEvent->input = a1;\n\n    mouse_get_position(&(inputEvent->mx), &(inputEvent->my));\n\n    input_put++;\n\n    if (input_put == 40) {\n        input_put = 0;\n        return;\n    }\n\n    if (input_get == -1) {\n        input_get = 0;\n    }\n}\n\n// 0x4C8C9C\nstatic int get_input_buffer()\n{\n    if (input_get == -1) {\n        return -1;\n    }\n\n    inputdata* inputEvent = &(input_buffer[input_get]);\n    int eventCode = inputEvent->input;\n    input_mx = inputEvent->mx;\n    input_my = inputEvent->my;\n\n    input_get++;\n\n    if (input_get == 40) {\n        input_get = 0;\n    }\n\n    if (input_get == input_put) {\n        input_get = -1;\n        input_put = 0;\n    }\n\n    return eventCode;\n}\n\n// 0x4C8D04\nvoid flush_input_buffer()\n{\n    input_get = -1;\n    input_put = 0;\n}\n\n// 0x4C8D1C\nvoid GNW_do_bk_process()\n{\n    if (game_paused) {\n        return;\n    }\n\n    if (bk_disabled) {\n        return;\n    }\n\n    bk_process_time = get_time();\n\n    FuncPtr curr = bk_list;\n    FuncPtr* currPtr = &(bk_list);\n\n    while (curr != NULL) {\n        FuncPtr next = curr->next;\n        if (curr->flags & 1) {\n            *currPtr = next;\n\n            mem_free(curr);\n        } else {\n            curr->f();\n            currPtr = &(curr->next);\n        }\n        curr = next;\n    }\n}\n\n// 0x4C8D74\nvoid add_bk_process(BackgroundProcess* f)\n{\n    FuncPtr fp;\n\n    fp = bk_list;\n    while (fp != NULL) {\n        if (fp->f == f) {\n            if ((fp->flags & 0x01) != 0) {\n                fp->flags &= ~0x01;\n                return;\n            }\n        }\n        fp = fp->next;\n    }\n\n    fp = (FuncPtr)mem_malloc(sizeof(*fp));\n    fp->flags = 0;\n    fp->f = f;\n    fp->next = bk_list;\n    bk_list = fp;\n}\n\n// 0x4C8DC4\nvoid remove_bk_process(BackgroundProcess* f)\n{\n    FuncPtr fp;\n\n    fp = bk_list;\n    while (fp != NULL) {\n        if (fp->f == f) {\n            fp->flags |= 0x01;\n            return;\n        }\n        fp = fp->next;\n    }\n}\n\n// 0x4C8DE4\nvoid enable_bk()\n{\n    bk_disabled = false;\n}\n\n// 0x4C8DF0\nvoid disable_bk()\n{\n    bk_disabled = true;\n}\n\n// 0x4C8DFC\nstatic void pause_game()\n{\n    if (!game_paused) {\n        game_paused = true;\n\n        int win = pause_win_func();\n\n        while (get_input() != KEY_ESCAPE) {\n        }\n\n        game_paused = false;\n        win_delete(win);\n    }\n}\n\n// 0x4C8E38\nstatic int default_pause_window()\n{\n    int windowWidth = text_width(\"Paused\") + 32;\n    int windowHeight = 3 * text_height() + 16;\n\n    int win = win_add((rectGetWidth(&scr_size) - windowWidth) / 2,\n        (rectGetHeight(&scr_size) - windowHeight) / 2,\n        windowWidth,\n        windowHeight,\n        256,\n        WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (win == -1) {\n        return -1;\n    }\n\n    win_border(win);\n\n    unsigned char* windowBuffer = win_get_buf(win);\n    text_to_buf(windowBuffer + 8 * windowWidth + 16,\n        \"Paused\",\n        windowWidth,\n        windowWidth,\n        colorTable[31744]);\n\n    win_register_text_button(win,\n        (windowWidth - text_width(\"Done\") - 16) / 2,\n        windowHeight - 8 - text_height() - 6,\n        -1,\n        -1,\n        -1,\n        KEY_ESCAPE,\n        \"Done\",\n        0);\n\n    win_draw(win);\n\n    return win;\n}\n\n// 0x4C8F34\nvoid register_pause(int new_pause_key, PauseWinFunc* new_pause_win_func)\n{\n    pause_key = new_pause_key;\n\n    if (new_pause_win_func == NULL) {\n        new_pause_win_func = default_pause_window;\n    }\n\n    pause_win_func = new_pause_win_func;\n}\n\n// 0x4C8F4C\nvoid dump_screen()\n{\n    int width = scr_size.lrx - scr_size.ulx + 1;\n    int height = scr_size.lry - scr_size.uly + 1;\n    screendump_buf = (unsigned char*)mem_malloc(width * height);\n    if (screendump_buf == NULL) {\n        return;\n    }\n\n    ScreenBlitFunc* v0 = scr_blit;\n    scr_blit = buf_blit;\n\n    ScreenBlitFunc* v2 = mouse_blit;\n    mouse_blit = buf_blit;\n\n    ScreenTransBlitFunc* v1 = mouse_blit_trans;\n    mouse_blit_trans = NULL;\n\n    win_refresh_all(&scr_size);\n\n    mouse_blit_trans = v1;\n    mouse_blit = v2;\n    scr_blit = v0;\n\n    unsigned char* palette = getSystemPalette();\n    screendump_func(width, height, screendump_buf, palette);\n    mem_free(screendump_buf);\n}\n\n// 0x4C8FF0\nstatic void buf_blit(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int width, int height, int destX, int destY)\n{\n    int destWidth = scr_size.lrx - scr_size.ulx + 1;\n    buf_to_buf(src + srcPitch * srcY + srcX, width, height, srcPitch, screendump_buf + destWidth * destY + destX, destWidth);\n}\n\n// 0x4C9048\nint default_screendump(int width, int height, unsigned char* data, unsigned char* palette)\n{\n    char fileName[16];\n    FILE* stream;\n    int index;\n    unsigned int intValue;\n    unsigned short shortValue;\n\n    for (index = 0; index < 100000; index++) {\n        sprintf(fileName, \"scr%.5d.bmp\", index);\n\n        stream = fopen(fileName, \"rb\");\n        if (stream == NULL) {\n            break;\n        }\n\n        fclose(stream);\n    }\n\n    if (index == 100000) {\n        return -1;\n    }\n\n    stream = fopen(fileName, \"wb\");\n    if (stream == NULL) {\n        return -1;\n    }\n\n    // bfType\n    shortValue = 0x4D42;\n    fwrite(&shortValue, sizeof(shortValue), 1, stream);\n\n    // bfSize\n    // 14 - sizeof(BITMAPFILEHEADER)\n    // 40 - sizeof(BITMAPINFOHEADER)\n    // 1024 - sizeof(RGBQUAD) * 256\n    intValue = width * height + 14 + 40 + 1024;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // bfReserved1\n    shortValue = 0;\n    fwrite(&shortValue, sizeof(shortValue), 1, stream);\n\n    // bfReserved2\n    shortValue = 0;\n    fwrite(&shortValue, sizeof(shortValue), 1, stream);\n\n    // bfOffBits\n    intValue = 14 + 40 + 1024;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biSize\n    intValue = 40;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biWidth\n    intValue = width;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biHeight\n    intValue = height;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biPlanes\n    shortValue = 1;\n    fwrite(&shortValue, sizeof(shortValue), 1, stream);\n\n    // biBitCount\n    shortValue = 8;\n    fwrite(&shortValue, sizeof(shortValue), 1, stream);\n\n    // biCompression\n    intValue = 0;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biSizeImage\n    intValue = 0;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biXPelsPerMeter\n    intValue = 0;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biYPelsPerMeter\n    intValue = 0;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biClrUsed\n    intValue = 0;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    // biClrImportant\n    intValue = 0;\n    fwrite(&intValue, sizeof(intValue), 1, stream);\n\n    for (int index = 0; index < 256; index++) {\n        unsigned char rgbReserved = 0;\n        unsigned char rgbRed = palette[index * 3] << 2;\n        unsigned char rgbGreen = palette[index * 3 + 1] << 2;\n        unsigned char rgbBlue = palette[index * 3 + 2] << 2;\n\n        fwrite(&rgbBlue, sizeof(rgbBlue), 1, stream);\n        fwrite(&rgbGreen, sizeof(rgbGreen), 1, stream);\n        fwrite(&rgbRed, sizeof(rgbRed), 1, stream);\n        fwrite(&rgbReserved, sizeof(rgbReserved), 1, stream);\n    }\n\n    for (int y = height - 1; y >= 0; y--) {\n        unsigned char* dataPtr = data + y * width;\n        fwrite(dataPtr, 1, width, stream);\n    }\n\n    fflush(stream);\n    fclose(stream);\n\n    return 0;\n}\n\n// 0x4C9358\nvoid register_screendump(int new_screendump_key, ScreenDumpFunc* new_screendump_func)\n{\n    screendump_key = new_screendump_key;\n\n    if (new_screendump_func == NULL) {\n        new_screendump_func = default_screendump;\n    }\n\n    screendump_func = new_screendump_func;\n}\n\n// 0x4C9370\nTOCKS get_time()\n{\n#pragma warning(suppress : 28159)\n    return GetTickCount();\n}\n\n// 0x4C937C\nvoid pause_for_tocks(unsigned int delay)\n{\n    // NOTE: Uninline.\n    unsigned int start = get_time();\n    unsigned int end = get_time();\n\n    // NOTE: Uninline.\n    unsigned int diff = elapsed_tocks(end, start);\n    while (diff < delay) {\n        process_bk();\n\n        end = get_time();\n\n        // NOTE: Uninline.\n        diff = elapsed_tocks(end, start);\n    }\n}\n\n// 0x4C93B8\nvoid block_for_tocks(unsigned int ms)\n{\n#pragma warning(suppress : 28159)\n    unsigned int start = GetTickCount();\n    unsigned int diff;\n    do {\n        // NOTE: Uninline\n        diff = elapsed_time(start);\n    } while (diff < ms);\n}\n\n// 0x4C93E0\nunsigned int elapsed_time(unsigned int start)\n{\n#pragma warning(suppress : 28159)\n    unsigned int end = GetTickCount();\n\n    // NOTE: Uninline.\n    return elapsed_tocks(end, start);\n}\n\n// 0x4C9400\nunsigned int elapsed_tocks(unsigned int end, unsigned int start)\n{\n    if (start > end) {\n        return INT_MAX;\n    } else {\n        return end - start;\n    }\n}\n\n// 0x4C9410\nunsigned int get_bk_time()\n{\n    return bk_process_time;\n}\n\n// 0x4C9490\nstatic void GNW95_build_key_map()\n{\n    unsigned char* keys = GNW95_key_map;\n    int k;\n\n    keys[DIK_ESCAPE] = DIK_ESCAPE;\n    keys[DIK_1] = DIK_1;\n    keys[DIK_2] = DIK_2;\n    keys[DIK_3] = DIK_3;\n    keys[DIK_4] = DIK_4;\n    keys[DIK_5] = DIK_5;\n    keys[DIK_6] = DIK_6;\n    keys[DIK_7] = DIK_7;\n    keys[DIK_8] = DIK_8;\n    keys[DIK_9] = DIK_9;\n    keys[DIK_0] = DIK_0;\n\n    switch (kb_layout) {\n    case 0:\n        k = DIK_MINUS;\n        break;\n    case 1:\n        k = DIK_6;\n        break;\n    default:\n        k = DIK_SLASH;\n        break;\n    }\n    keys[DIK_MINUS] = k;\n\n    switch (kb_layout) {\n    case 1:\n        k = DIK_0;\n        break;\n    default:\n        k = DIK_EQUALS;\n        break;\n    }\n    keys[DIK_EQUALS] = k;\n\n    keys[DIK_BACK] = DIK_BACK;\n    keys[DIK_TAB] = DIK_TAB;\n\n    switch (kb_layout) {\n    case 1:\n        k = DIK_A;\n        break;\n    default:\n        k = DIK_Q;\n        break;\n    }\n    keys[DIK_Q] = k;\n\n    switch (kb_layout) {\n    case 1:\n        k = DIK_Z;\n        break;\n    default:\n        k = DIK_W;\n        break;\n    }\n    keys[DIK_W] = k;\n\n    keys[DIK_E] = DIK_E;\n    keys[DIK_R] = DIK_R;\n    keys[DIK_T] = DIK_T;\n\n    switch (kb_layout) {\n    case 0:\n    case 1:\n    case 3:\n    case 4:\n        k = DIK_Y;\n        break;\n    default:\n        k = DIK_Z;\n        break;\n    }\n    keys[DIK_Y] = k;\n\n    keys[DIK_U] = DIK_U;\n    keys[DIK_I] = DIK_I;\n    keys[DIK_O] = DIK_O;\n    keys[DIK_P] = DIK_P;\n\n    switch (kb_layout) {\n    case 0:\n    case 3:\n    case 4:\n        k = DIK_LBRACKET;\n        break;\n    case 1:\n        k = DIK_5;\n        break;\n    default:\n        k = DIK_8;\n        break;\n    }\n    keys[DIK_LBRACKET] = k;\n\n    switch (kb_layout) {\n    case 0:\n    case 3:\n    case 4:\n        k = DIK_RBRACKET;\n        break;\n    case 1:\n        k = DIK_MINUS;\n        break;\n    default:\n        k = DIK_9;\n        break;\n    }\n    keys[DIK_RBRACKET] = k;\n\n    keys[DIK_RETURN] = DIK_RETURN;\n    keys[DIK_LCONTROL] = DIK_LCONTROL;\n\n    switch (kb_layout) {\n    case 1:\n        k = DIK_Q;\n        break;\n    default:\n        k = DIK_A;\n        break;\n    }\n    keys[DIK_A] = k;\n\n    keys[DIK_S] = DIK_S;\n    keys[DIK_D] = DIK_D;\n    keys[DIK_F] = DIK_F;\n    keys[DIK_G] = DIK_G;\n    keys[DIK_H] = DIK_H;\n    keys[DIK_J] = DIK_J;\n    keys[DIK_K] = DIK_K;\n    keys[DIK_L] = DIK_L;\n\n    switch (kb_layout) {\n    case 0:\n        k = DIK_SEMICOLON;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n    keys[DIK_SEMICOLON] = k;\n\n    switch (kb_layout) {\n    case 0:\n        k = DIK_APOSTROPHE;\n        break;\n    case 1:\n        k = DIK_4;\n        break;\n    default:\n        k = DIK_MINUS;\n        break;\n    }\n    keys[DIK_APOSTROPHE] = k;\n\n    switch (kb_layout) {\n    case 0:\n        k = DIK_GRAVE;\n        break;\n    case 1:\n        k = DIK_2;\n        break;\n    case 3:\n    case 4:\n        k = 0;\n        break;\n    default:\n        k = DIK_RBRACKET;\n        break;\n    }\n    keys[DIK_GRAVE] = k;\n\n    keys[DIK_LSHIFT] = DIK_LSHIFT;\n\n    switch (kb_layout) {\n    case 0:\n        k = DIK_BACKSLASH;\n        break;\n    case 1:\n        k = DIK_8;\n        break;\n    case 3:\n    case 4:\n        k = DIK_GRAVE;\n        break;\n    default:\n        k = DIK_Y;\n        break;\n    }\n    keys[DIK_BACKSLASH] = k;\n\n    switch (kb_layout) {\n    case 0:\n    case 3:\n    case 4:\n        k = DIK_Z;\n        break;\n    case 1:\n        k = DIK_W;\n        break;\n    default:\n        k = DIK_Y;\n        break;\n    }\n    keys[DIK_Z] = k;\n\n    keys[DIK_X] = DIK_X;\n    keys[DIK_C] = DIK_C;\n    keys[DIK_V] = DIK_V;\n    keys[DIK_B] = DIK_B;\n    keys[DIK_N] = DIK_N;\n\n    switch (kb_layout) {\n    case 1:\n        k = DIK_SEMICOLON;\n        break;\n    default:\n        k = DIK_M;\n        break;\n    }\n    keys[DIK_M] = k;\n\n    switch (kb_layout) {\n    case 1:\n        k = DIK_M;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n    keys[DIK_COMMA] = k;\n\n    switch (kb_layout) {\n    case 1:\n        k = DIK_COMMA;\n        break;\n    default:\n        k = DIK_PERIOD;\n        break;\n    }\n    keys[DIK_PERIOD] = k;\n\n    switch (kb_layout) {\n    case 0:\n        k = DIK_SLASH;\n        break;\n    case 1:\n        k = DIK_PERIOD;\n        break;\n    default:\n        k = DIK_7;\n        break;\n    }\n    keys[DIK_SLASH] = k;\n\n    keys[DIK_RSHIFT] = DIK_RSHIFT;\n    keys[DIK_MULTIPLY] = DIK_MULTIPLY;\n    keys[DIK_SPACE] = DIK_SPACE;\n    keys[DIK_LMENU] = DIK_LMENU;\n    keys[DIK_CAPITAL] = DIK_CAPITAL;\n    keys[DIK_F1] = DIK_F1;\n    keys[DIK_F2] = DIK_F2;\n    keys[DIK_F3] = DIK_F3;\n    keys[DIK_F4] = DIK_F4;\n    keys[DIK_F5] = DIK_F5;\n    keys[DIK_F6] = DIK_F6;\n    keys[DIK_F7] = DIK_F7;\n    keys[DIK_F8] = DIK_F8;\n    keys[DIK_F9] = DIK_F9;\n    keys[DIK_F10] = DIK_F10;\n    keys[DIK_NUMLOCK] = DIK_NUMLOCK;\n    keys[DIK_SCROLL] = DIK_SCROLL;\n    keys[DIK_NUMPAD7] = DIK_NUMPAD7;\n    keys[DIK_NUMPAD9] = DIK_NUMPAD9;\n    keys[DIK_NUMPAD8] = DIK_NUMPAD8;\n    keys[DIK_SUBTRACT] = DIK_SUBTRACT;\n    keys[DIK_NUMPAD4] = DIK_NUMPAD4;\n    keys[DIK_NUMPAD5] = DIK_NUMPAD5;\n    keys[DIK_NUMPAD6] = DIK_NUMPAD6;\n    keys[DIK_ADD] = DIK_ADD;\n    keys[DIK_NUMPAD1] = DIK_NUMPAD1;\n    keys[DIK_NUMPAD2] = DIK_NUMPAD2;\n    keys[DIK_NUMPAD3] = DIK_NUMPAD3;\n    keys[DIK_NUMPAD0] = DIK_NUMPAD0;\n    keys[DIK_DECIMAL] = DIK_DECIMAL;\n    keys[DIK_F11] = DIK_F11;\n    keys[DIK_F12] = DIK_F12;\n    keys[DIK_F13] = -1;\n    keys[DIK_F14] = -1;\n    keys[DIK_F15] = -1;\n    keys[DIK_KANA] = -1;\n    keys[DIK_CONVERT] = -1;\n    keys[DIK_NOCONVERT] = -1;\n    keys[DIK_YEN] = -1;\n    keys[DIK_NUMPADEQUALS] = -1;\n    keys[DIK_PREVTRACK] = -1;\n    keys[DIK_AT] = -1;\n    keys[DIK_COLON] = -1;\n    keys[DIK_UNDERLINE] = -1;\n    keys[DIK_KANJI] = -1;\n    keys[DIK_STOP] = -1;\n    keys[DIK_AX] = -1;\n    keys[DIK_UNLABELED] = -1;\n    keys[DIK_NUMPADENTER] = DIK_NUMPADENTER;\n    keys[DIK_RCONTROL] = DIK_RCONTROL;\n    keys[DIK_NUMPADCOMMA] = -1;\n    keys[DIK_DIVIDE] = DIK_DIVIDE;\n    keys[DIK_SYSRQ] = 84;\n    keys[DIK_RMENU] = DIK_RMENU;\n    keys[DIK_HOME] = DIK_HOME;\n    keys[DIK_UP] = DIK_UP;\n    keys[DIK_PRIOR] = DIK_PRIOR;\n    keys[DIK_LEFT] = DIK_LEFT;\n    keys[DIK_RIGHT] = DIK_RIGHT;\n    keys[DIK_END] = DIK_END;\n    keys[DIK_DOWN] = DIK_DOWN;\n    keys[DIK_NEXT] = DIK_NEXT;\n    keys[DIK_INSERT] = DIK_INSERT;\n    keys[DIK_DELETE] = DIK_DELETE;\n    keys[DIK_LWIN] = -1;\n    keys[DIK_RWIN] = -1;\n    keys[DIK_APPS] = -1;\n}\n\n// 0x4C9BB4\nvoid GNW95_hook_input(int hook)\n{\n    GNW95_hook_keyboard(hook);\n\n    if (hook) {\n        dxinput_acquire_mouse();\n    } else {\n        dxinput_unacquire_mouse();\n    }\n}\n\n// 0x4C9C20\nint GNW95_input_init()\n{\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4C9C24\nvoid GNW95_input_exit()\n{\n    GNW95_hook_keyboard(0);\n}\n\n// 0x4C9C28\nstatic int GNW95_hook_keyboard(int hook)\n{\n    // 0x51E244\n    static bool hooked = false;\n\n    if (hook == hooked) {\n        return 0;\n    }\n\n    if (!hook) {\n        dxinput_unacquire_keyboard();\n\n        UnhookWindowsHookEx(GNW95_keyboardHandle);\n\n        kb_clear();\n\n        hooked = hook;\n\n        return 0;\n    }\n\n    if (dxinput_acquire_keyboard()) {\n        GNW95_keyboardHandle = SetWindowsHookExA(WH_KEYBOARD, GNW95_keyboard_hook, 0, GetCurrentThreadId());\n        kb_clear();\n        hooked = hook;\n\n        return 0;\n    }\n\n    return -1;\n}\n\n// 0x4C9C4C\nLRESULT CALLBACK GNW95_keyboard_hook(int nCode, WPARAM wParam, LPARAM lParam)\n{\n    if (nCode >= 0) {\n        if (wParam == VK_DELETE && lParam & 0x20000000 && GetAsyncKeyState(VK_CONTROL) & 0x80000000)\n            return 0;\n\n        if (wParam == VK_ESCAPE && GetAsyncKeyState(VK_CONTROL) & 0x80000000)\n            return 0;\n\n        if (wParam == VK_RETURN && lParam & 0x20000000)\n            return 0;\n\n        if (wParam == VK_NUMLOCK || wParam == VK_CAPITAL || wParam == VK_SCROLL) {\n            // TODO: Get rid of this goto.\n            goto next;\n        }\n\n        return 1;\n    }\n\nnext:\n\n    return CallNextHookEx(GNW95_keyboardHandle, nCode, wParam, lParam);\n}\n\n// 0x4C9CF0\nvoid GNW95_process_message()\n{\n    if (GNW95_isActive && !kb_is_disabled()) {\n        dxinput_key_data data;\n        while (dxinput_read_keyboard_buffer(&data)) {\n            GNW95_process_key(&data);\n        }\n\n        // NOTE: Uninline\n        TOCKS now = get_time();\n\n        for (int key = 0; key < 256; key++) {\n            GNW95RepeatStruct* ptr = &(GNW95_key_time_stamps[key]);\n            if (ptr->time != -1) {\n                int elapsedTime = ptr->time > now ? INT_MAX : now - ptr->time;\n                int delay = ptr->count == 0 ? GNW95_repeat_delay : GNW95_repeat_rate;\n                if (elapsedTime > delay) {\n                    data.code = key;\n                    data.state = 1;\n                    GNW95_process_key(&data);\n\n                    ptr->time = now;\n                    ptr->count++;\n                }\n            }\n        }\n    }\n\n    MSG msg;\n    while (PeekMessageA(&msg, NULL, 0, 0, 0)) {\n        if (GetMessageA(&msg, NULL, 0, 0)) {\n            TranslateMessage(&msg);\n            DispatchMessageA(&msg);\n        }\n    }\n}\n\n// 0x4C9DF0\nvoid GNW95_clear_time_stamps()\n{\n    for (int index = 0; index < 256; index++) {\n        GNW95_key_time_stamps[index].time = -1;\n        GNW95_key_time_stamps[index].count = 0;\n    }\n}\n\n// 0x4C9E14\nstatic void GNW95_process_key(dxinput_key_data* data)\n{\n    short key = data->code & 0xFF;\n\n    switch (key) {\n    case DIK_NUMPADENTER:\n    case DIK_RCONTROL:\n    case DIK_DIVIDE:\n    case DIK_RMENU:\n    case DIK_HOME:\n    case DIK_UP:\n    case DIK_PRIOR:\n    case DIK_LEFT:\n    case DIK_RIGHT:\n    case DIK_END:\n    case DIK_DOWN:\n    case DIK_NEXT:\n    case DIK_INSERT:\n    case DIK_DELETE:\n        key |= 0x0100;\n        break;\n    }\n\n    int qwertyKey = GNW95_key_map[data->code & 0xFF];\n\n    if (vcr_state == VCR_STATE_PLAYING) {\n        if ((vcr_terminate_flags & VCR_TERMINATE_ON_KEY_PRESS) != 0) {\n            vcr_terminated_condition = VCR_PLAYBACK_COMPLETION_REASON_TERMINATED;\n            vcr_stop();\n        }\n    } else {\n        if ((key & 0x0100) != 0) {\n            kb_simulate_key(224);\n            qwertyKey -= 0x80;\n        }\n\n        GNW95RepeatStruct* ptr = &(GNW95_key_time_stamps[data->code & 0xFF]);\n        if (data->state == 1) {\n            ptr->time = get_time();\n            ptr->count = 0;\n        } else {\n            qwertyKey |= 0x80;\n            ptr->time = -1;\n        }\n\n        kb_simulate_key(qwertyKey);\n    }\n}\n\n// 0x4C9EEC\nvoid GNW95_lost_focus()\n{\n    if (focus_func != NULL) {\n        focus_func(0);\n    }\n\n    while (!GNW95_isActive) {\n        GNW95_process_message();\n\n        if (idle_func != NULL) {\n            idle_func();\n        }\n    }\n\n    if (focus_func != NULL) {\n        focus_func(1);\n    }\n}\n"
  },
  {
    "path": "src/plib/gnw/input.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_INPUT_H_\n#define FALLOUT_PLIB_GNW_INPUT_H_\n\n#include <stdbool.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"plib/gnw/kb.h\"\n#include \"plib/gnw/mouse.h\"\n\ntypedef unsigned long TOCKS;\n\ntypedef void(IdleFunc)();\ntypedef void(FocusFunc)(int);\ntypedef void(BackgroundProcess)();\ntypedef int(PauseWinFunc)();\ntypedef int(ScreenDumpFunc)(int width, int height, unsigned char* buffer, unsigned char* palette);\n\nint GNW_input_init(int use_msec_timer);\nvoid GNW_input_exit();\nint get_input();\nvoid process_bk();\nvoid GNW_add_input_buffer(int a1);\nvoid flush_input_buffer();\nvoid GNW_do_bk_process();\nvoid add_bk_process(BackgroundProcess* f);\nvoid remove_bk_process(BackgroundProcess* f);\nvoid enable_bk();\nvoid disable_bk();\nvoid register_pause(int new_pause_key, PauseWinFunc* new_pause_win_func);\nvoid dump_screen();\nint default_screendump(int width, int height, unsigned char* data, unsigned char* palette);\nvoid register_screendump(int new_screendump_key, ScreenDumpFunc* new_screendump_func);\nTOCKS get_time();\nvoid pause_for_tocks(unsigned int ms);\nvoid block_for_tocks(unsigned int ms);\nunsigned int elapsed_time(unsigned int a1);\nunsigned int elapsed_tocks(unsigned int a1, unsigned int a2);\nunsigned int get_bk_time();\nvoid GNW95_hook_input(int hook);\nint GNW95_input_init();\nvoid GNW95_input_exit();\nLRESULT CALLBACK GNW95_keyboard_hook(int nCode, WPARAM wParam, LPARAM lParam);\nvoid GNW95_process_message();\nvoid GNW95_clear_time_stamps();\nvoid GNW95_lost_focus();\n\n#endif /* FALLOUT_PLIB_GNW_INPUT_H_ */\n"
  },
  {
    "path": "src/plib/gnw/intrface.c",
    "content": "#include \"plib/gnw/intrface.h\"\n\n#include <stdio.h>\n#include <string.h>\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"plib/gnw/button.h\"\n#include \"plib/gnw/memory.h\"\n#include \"plib/gnw/text.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/svga.h\"\n\nstatic int create_pull_down(char** stringList, int stringListLength, int x, int y, int a5, int a6, Rect* rect);\nstatic void win_debug_delete(int btn, int keyCode);\nstatic int find_first_letter(int ch, char** stringList, int stringListLength);\nstatic int process_pull_down(int win, Rect* rect, char** items, int itemsLength, int a5, int a6, MenuBar* menuBar, int pulldownIndex);\nstatic int calc_max_field_chars_wcursor(int a1, int a2);\nstatic void tm_watch_msgs();\nstatic void tm_kill_msg();\nstatic void tm_kill_out_of_order(int a1);\nstatic void tm_click_response(int btn);\nstatic int tm_index_active(int a1);\n\n// 0x51E414\nstatic int wd = -1;\n\n// 0x51E41C\nstatic bool tm_watch_active = false;\n\n// 0x6B2340\nstatic struct {\n    int taken;\n    int y;\n} tm_location[5];\n\n// 0x6B2368\nstatic int tm_text_x;\n\n// 0x6B236C\nstatic int tm_h;\n\n// 0x6B2370\nstatic struct {\n    int created;\n    int id;\n    int location;\n} tm_queue[5];\n\n// 0x6B23AC\nstatic unsigned int tm_persistence;\n\n// 0x6B23B0\nstatic int scr_center_x;\n\n// 0x6B23B4\nint tm_text_y;\n\n// 0x6B23B8\nint tm_kill;\n\n// 0x6B23BC\nint tm_add;\n\n// 0x6B23C0\nint curry;\n\n// 0x6B23C4\nint currx;\n\n// 0x4DA6C0\nint win_list_select(const char* title, char** fileList, int fileListLength, SelectFunc* callback, int x, int y, int a7)\n{\n    return win_list_select_at(title, fileList, fileListLength, callback, x, y, a7, 0);\n}\n\n// 0x4DA70C\nint win_list_select_at(const char* title, char** items, int itemsLength, SelectFunc* callback, int x, int y, int a7, int a8)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    int listViewWidth = win_width_needed(items, itemsLength);\n    int windowWidth = listViewWidth + 16;\n\n    int titleWidth = text_width(title);\n    if (titleWidth > windowWidth) {\n        windowWidth = titleWidth;\n        listViewWidth = titleWidth - 16;\n    }\n\n    windowWidth += 20;\n\n    int win;\n    int windowHeight;\n    int listViewCapacity = 10;\n    for (int heightMultiplier = 13; heightMultiplier > 8; heightMultiplier--) {\n        windowHeight = heightMultiplier * text_height() + 22;\n        win = win_add(x, y, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n        if (win != -1) {\n            break;\n        }\n        listViewCapacity--;\n    }\n\n    if (win == -1) {\n        return -1;\n    }\n\n    Window* window = GNW_find(win);\n    Rect* windowRect = &(window->rect);\n    unsigned char* windowBuffer = window->buffer;\n\n    draw_box(windowBuffer,\n        windowWidth,\n        0,\n        0,\n        windowWidth - 1,\n        windowHeight - 1,\n        colorTable[0]);\n    draw_shaded_box(windowBuffer,\n        windowWidth,\n        1,\n        1,\n        windowWidth - 2,\n        windowHeight - 2,\n        colorTable[GNW_wcolor[1]],\n        colorTable[GNW_wcolor[2]]);\n\n    buf_fill(windowBuffer + windowWidth * 5 + 5,\n        windowWidth - 11,\n        text_height() + 3,\n        windowWidth,\n        colorTable[GNW_wcolor[0]]);\n\n    text_to_buf(windowBuffer + windowWidth / 2 + 8 * windowWidth - text_width(title) / 2,\n        title,\n        windowWidth,\n        windowWidth,\n        colorTable[GNW_wcolor[3]]);\n\n    draw_shaded_box(windowBuffer,\n        windowWidth,\n        5,\n        5,\n        windowWidth - 6,\n        text_height() + 8,\n        colorTable[GNW_wcolor[2]],\n        colorTable[GNW_wcolor[1]]);\n\n    int listViewX = 8;\n    int listViewY = text_height() + 16;\n    unsigned char* listViewBuffer = windowBuffer + windowWidth * listViewY + listViewX;\n    int listViewMaxY = listViewCapacity * text_height() + listViewY;\n\n    buf_fill(listViewBuffer + windowWidth * (-2) + (-3),\n        listViewWidth + listViewX - 2,\n        listViewCapacity * text_height() + 2,\n        windowWidth,\n        colorTable[GNW_wcolor[0]]);\n\n    int scrollOffset = a8;\n    if (a8 < 0 || a8 >= itemsLength) {\n        scrollOffset = 0;\n    }\n\n    // Relative to `scrollOffset`.\n    int selectedItemIndex;\n    if (itemsLength - scrollOffset < listViewCapacity) {\n        int newScrollOffset = itemsLength - listViewCapacity;\n        if (newScrollOffset < 0) {\n            newScrollOffset = 0;\n        }\n        int oldScrollOffset = scrollOffset;\n        scrollOffset = newScrollOffset;\n        selectedItemIndex = oldScrollOffset - newScrollOffset;\n    } else {\n        selectedItemIndex = 0;\n    }\n\n    char** itemsTO = items + a8;\n    win_text(win,\n        items + a8,\n        itemsLength < listViewCapacity ? itemsLength : listViewCapacity,\n        listViewWidth,\n        listViewX,\n        listViewY,\n        a7 | 0x2000000);\n\n    lighten_buf(listViewBuffer + windowWidth * selectedItemIndex * text_height(),\n        listViewWidth,\n        text_height(),\n        windowWidth);\n\n    draw_shaded_box(windowBuffer,\n        windowWidth,\n        5,\n        listViewY - 3,\n        listViewWidth + 10,\n        listViewMaxY,\n        colorTable[GNW_wcolor[2]],\n        colorTable[GNW_wcolor[1]]);\n\n    win_register_text_button(win,\n        windowWidth - 25,\n        listViewY - 3,\n        -1,\n        -1,\n        KEY_ARROW_UP,\n        -1,\n        \"\\x18\",\n        0);\n\n    win_register_text_button(win,\n        windowWidth - 25,\n        listViewMaxY - text_height() - 5,\n        -1,\n        -1,\n        KEY_ARROW_DOWN,\n        -1,\n        \"\\x19\",\n        0);\n\n    win_register_text_button(win,\n        windowWidth / 2 - 32,\n        windowHeight - 8 - text_height() - 6,\n        -1,\n        -1,\n        -1,\n        KEY_ESCAPE,\n        \"Done\",\n        0);\n\n    int scrollbarX = windowWidth - 21;\n    int scrollbarY = listViewY + text_height() + 7;\n    int scrollbarKnobSize = 14;\n    int scrollbarHeight = listViewMaxY - scrollbarY;\n    unsigned char* scrollbarBuffer = windowBuffer + windowWidth * scrollbarY + scrollbarX;\n\n    buf_fill(scrollbarBuffer,\n        scrollbarKnobSize + 1,\n        scrollbarHeight - text_height() - 8,\n        windowWidth,\n        colorTable[GNW_wcolor[0]]);\n\n    win_register_button(win,\n        scrollbarX,\n        scrollbarY,\n        scrollbarKnobSize + 1,\n        scrollbarHeight - text_height() - 8,\n        -1,\n        -1,\n        2048,\n        -1,\n        NULL,\n        NULL,\n        NULL,\n        0);\n\n    draw_shaded_box(windowBuffer,\n        windowWidth,\n        windowWidth - 22,\n        scrollbarY - 1,\n        scrollbarX + scrollbarKnobSize + 1,\n        listViewMaxY - text_height() - 9,\n        colorTable[GNW_wcolor[2]],\n        colorTable[GNW_wcolor[1]]);\n    draw_shaded_box(windowBuffer,\n        windowWidth,\n        scrollbarX,\n        scrollbarY,\n        scrollbarX + scrollbarKnobSize,\n        scrollbarY + scrollbarKnobSize,\n        colorTable[GNW_wcolor[1]],\n        colorTable[GNW_wcolor[2]]);\n\n    lighten_buf(scrollbarBuffer, scrollbarKnobSize, scrollbarKnobSize, windowWidth);\n\n    for (int index = 0; index < listViewCapacity; index++) {\n        win_register_button(win,\n            listViewX,\n            listViewY + index * text_height(),\n            listViewWidth,\n            text_height(),\n            512 + index,\n            -1,\n            1024 + index,\n            -1,\n            NULL,\n            NULL,\n            NULL,\n            0);\n    }\n\n    win_register_button(win,\n        0,\n        0,\n        windowWidth,\n        text_height() + 8,\n        -1,\n        -1,\n        -1,\n        -1,\n        NULL,\n        NULL,\n        NULL,\n        BUTTON_FLAG_0x10);\n\n    win_draw(win);\n\n    int absoluteSelectedItemIndex = -1;\n\n    // Relative to `scrollOffset`.\n    int previousSelectedItemIndex = -1;\n    while (1) {\n        int keyCode = get_input();\n        int mouseX;\n        int mouseY;\n        mouse_get_position(&mouseX, &mouseY);\n\n        if (keyCode == KEY_RETURN || (keyCode >= 1024 && keyCode < listViewCapacity + 1024)) {\n            if (selectedItemIndex != -1) {\n                absoluteSelectedItemIndex = scrollOffset + selectedItemIndex;\n                if (absoluteSelectedItemIndex < itemsLength) {\n                    if (callback == NULL) {\n                        break;\n                    }\n\n                    callback(items, absoluteSelectedItemIndex);\n                }\n                absoluteSelectedItemIndex = -1;\n            }\n        } else if (keyCode == 2048) {\n            if (window->rect.uly + scrollbarY > mouseY) {\n                keyCode = KEY_PAGE_UP;\n            } else if (window->rect.uly + scrollbarKnobSize + scrollbarY < mouseY) {\n                keyCode = KEY_PAGE_DOWN;\n            }\n        }\n\n        if (keyCode == KEY_ESCAPE) {\n            break;\n        }\n\n        if (keyCode >= 512 && keyCode < listViewCapacity + 512) {\n            int itemIndex = keyCode - 512;\n            if (itemIndex != selectedItemIndex && itemIndex < itemsLength) {\n                previousSelectedItemIndex = selectedItemIndex;\n                selectedItemIndex = itemIndex;\n                keyCode = -3;\n            } else {\n                continue;\n            }\n        } else {\n            switch (keyCode) {\n            case KEY_HOME:\n                if (scrollOffset > 0) {\n                    keyCode = -4;\n                    scrollOffset = 0;\n                }\n                break;\n            case KEY_ARROW_UP:\n                if (selectedItemIndex > 0) {\n                    keyCode = -3;\n                    previousSelectedItemIndex = selectedItemIndex;\n                    selectedItemIndex -= 1;\n                } else {\n                    if (scrollOffset > 0) {\n                        keyCode = -4;\n                        scrollOffset -= 1;\n                    }\n                }\n                break;\n            case KEY_PAGE_UP:\n                if (scrollOffset > 0) {\n                    scrollOffset -= listViewCapacity;\n                    if (scrollOffset < 0) {\n                        scrollOffset = 0;\n                    }\n                    keyCode = -4;\n                }\n                break;\n            case KEY_END:\n                if (scrollOffset < itemsLength - listViewCapacity) {\n                    keyCode = -4;\n                    scrollOffset = itemsLength - listViewCapacity;\n                }\n                break;\n            case KEY_ARROW_DOWN:\n                if (selectedItemIndex < listViewCapacity - 1 && selectedItemIndex < itemsLength - 1) {\n                    keyCode = -3;\n                    previousSelectedItemIndex = selectedItemIndex;\n                    selectedItemIndex += 1;\n                } else {\n                    if (scrollOffset + listViewCapacity < itemsLength) {\n                        keyCode = -4;\n                        scrollOffset += 1;\n                    }\n                }\n                break;\n            case KEY_PAGE_DOWN:\n                if (scrollOffset < itemsLength - listViewCapacity) {\n                    scrollOffset += listViewCapacity;\n                    if (scrollOffset > itemsLength - listViewCapacity) {\n                        scrollOffset = itemsLength - listViewCapacity;\n                    }\n                    keyCode = -4;\n                }\n                break;\n            default:\n                if (itemsLength > listViewCapacity) {\n                    if ((keyCode >= 'a' && keyCode <= 'z')\n                        || (keyCode >= 'A' && keyCode <= 'Z')) {\n                        int found = find_first_letter(keyCode, items, itemsLength);\n                        if (found != -1) {\n                            scrollOffset = found;\n                            if (scrollOffset > itemsLength - listViewCapacity) {\n                                scrollOffset = itemsLength - listViewCapacity;\n                            }\n                            keyCode = -4;\n                            selectedItemIndex = found - scrollOffset;\n                        }\n                    }\n                }\n                break;\n            }\n        }\n\n        if (keyCode == -4) {\n            buf_fill(listViewBuffer,\n                listViewWidth,\n                listViewMaxY - listViewY,\n                windowWidth,\n                colorTable[GNW_wcolor[0]]);\n\n            win_text(win,\n                items + scrollOffset,\n                itemsLength < listViewCapacity ? itemsLength : listViewCapacity,\n                listViewWidth,\n                listViewX,\n                listViewY,\n                a7 | 0x2000000);\n\n            lighten_buf(listViewBuffer + windowWidth * selectedItemIndex * text_height(),\n                listViewWidth,\n                text_height(),\n                windowWidth);\n\n            if (itemsLength > listViewCapacity) {\n                buf_fill(windowBuffer + windowWidth * scrollbarY + scrollbarX,\n                    scrollbarKnobSize + 1,\n                    scrollbarKnobSize + 1,\n                    windowWidth,\n                    colorTable[GNW_wcolor[0]]);\n\n                scrollbarY = (scrollOffset * (listViewMaxY - listViewY - 2 * text_height() - 16 - scrollbarKnobSize - 1)) / (itemsLength - listViewCapacity)\n                    + listViewY + text_height() + 7;\n\n                draw_shaded_box(windowBuffer,\n                    windowWidth,\n                    scrollbarX,\n                    scrollbarY,\n                    scrollbarX + scrollbarKnobSize,\n                    scrollbarY + scrollbarKnobSize,\n                    colorTable[GNW_wcolor[1]],\n                    colorTable[GNW_wcolor[2]]);\n\n                lighten_buf(windowBuffer + windowWidth * scrollbarY + scrollbarX,\n                    scrollbarKnobSize,\n                    scrollbarKnobSize,\n                    windowWidth);\n\n                GNW_win_refresh(window, windowRect, NULL);\n            }\n        } else if (keyCode == -3) {\n            Rect itemRect;\n            itemRect.ulx = windowRect->ulx + listViewX;\n            itemRect.lrx = itemRect.ulx + listViewWidth;\n\n            if (previousSelectedItemIndex != -1) {\n                itemRect.uly = windowRect->uly + listViewY + previousSelectedItemIndex * text_height();\n                itemRect.lry = itemRect.uly + text_height();\n\n                buf_fill(listViewBuffer + windowWidth * previousSelectedItemIndex * text_height(),\n                    listViewWidth,\n                    text_height(),\n                    windowWidth,\n                    colorTable[GNW_wcolor[0]]);\n\n                int color;\n                if ((a7 & 0xFF00) != 0) {\n                    int colorIndex = (a7 & 0xFF) - 1;\n                    color = (a7 & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]];\n                } else {\n                    color = a7;\n                }\n\n                text_to_buf(listViewBuffer + windowWidth * previousSelectedItemIndex * text_height(),\n                    items[scrollOffset + previousSelectedItemIndex],\n                    windowWidth,\n                    windowWidth,\n                    color);\n\n                GNW_win_refresh(window, &itemRect, NULL);\n            }\n\n            if (selectedItemIndex != -1) {\n                itemRect.uly = windowRect->uly + listViewY + selectedItemIndex * text_height();\n                itemRect.lry = itemRect.uly + text_height();\n\n                lighten_buf(listViewBuffer + windowWidth * selectedItemIndex * text_height(),\n                    listViewWidth,\n                    text_height(),\n                    windowWidth);\n\n                GNW_win_refresh(window, &itemRect, NULL);\n            }\n        }\n    }\n\n    win_delete(win);\n\n    return absoluteSelectedItemIndex;\n}\n\n// 0x4DB478\nint win_get_str(char* dest, int length, const char* title, int x, int y)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    int titleWidth = text_width(title) + 12;\n    if (titleWidth < text_max() * length) {\n        titleWidth = text_max() * length;\n    }\n\n    int windowWidth = titleWidth + 16;\n    if (windowWidth < 160) {\n        windowWidth = 160;\n    }\n\n    int windowHeight = 5 * text_height() + 16;\n\n    int win = win_add(x, y, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (win == -1) {\n        return -1;\n    }\n\n    win_border(win);\n\n    unsigned char* windowBuffer = win_get_buf(win);\n\n    buf_fill(windowBuffer + windowWidth * (text_height() + 14) + 14,\n        windowWidth - 28,\n        text_height() + 2,\n        windowWidth,\n        colorTable[GNW_wcolor[0]]);\n    text_to_buf(windowBuffer + windowWidth * 8 + 8, title, windowWidth, windowWidth, colorTable[GNW_wcolor[4]]);\n\n    draw_shaded_box(windowBuffer,\n        windowWidth,\n        14,\n        text_height() + 14,\n        windowWidth - 14,\n        2 * text_height() + 16,\n        colorTable[GNW_wcolor[2]],\n        colorTable[GNW_wcolor[1]]);\n\n    win_register_text_button(win,\n        windowWidth / 2 - 72,\n        windowHeight - 8 - text_height() - 6,\n        -1,\n        -1,\n        -1,\n        KEY_RETURN,\n        \"Done\",\n        0);\n\n    win_register_text_button(win,\n        windowWidth / 2 + 8,\n        windowHeight - 8 - text_height() - 6,\n        -1,\n        -1,\n        -1,\n        KEY_ESCAPE,\n        \"Cancel\",\n        0);\n\n    win_draw(win);\n\n    win_input_str(win,\n        dest,\n        length,\n        16,\n        text_height() + 16,\n        colorTable[GNW_wcolor[3]],\n        colorTable[GNW_wcolor[0]]);\n\n    win_delete(win);\n\n    return 0;\n}\n\n// 0x4DBA98\nint win_msg(const char* string, int x, int y, int flags)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    int windowHeight = 3 * text_height() + 16;\n\n    int windowWidth = text_width(string) + 16;\n    if (windowWidth < 80) {\n        windowWidth = 80;\n    }\n\n    windowWidth += 16;\n\n    int win = win_add(x, y, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (win == -1) {\n        return -1;\n    }\n\n    win_border(win);\n\n    Window* window = GNW_find(win);\n    unsigned char* windowBuffer = window->buffer;\n\n    int color;\n    if ((flags & 0xFF00) != 0) {\n        int index = (flags & 0xFF) - 1;\n        color = colorTable[GNW_wcolor[index]];\n        color |= flags & ~0xFFFF;\n    } else {\n        color = flags;\n    }\n\n    text_to_buf(windowBuffer + windowWidth * 8 + 16, string, windowWidth, windowWidth, color);\n\n    win_register_text_button(win,\n        windowWidth / 2 - 32,\n        windowHeight - 8 - text_height() - 6,\n        -1,\n        -1,\n        -1,\n        KEY_ESCAPE,\n        \"Done\",\n        0);\n\n    win_draw(win);\n\n    while (get_input() != KEY_ESCAPE) {\n    }\n\n    win_delete(win);\n\n    return 0;\n}\n\n// 0x4DBBC4\nint win_pull_down(char** items, int itemsLength, int x, int y, int a5)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    Rect rect;\n    int win = create_pull_down(items, itemsLength, x, y, a5, colorTable[GNW_wcolor[0]], &rect);\n    if (win == -1) {\n        return -1;\n    }\n\n    return process_pull_down(win, &rect, items, itemsLength, a5, colorTable[GNW_wcolor[0]], NULL, -1);\n}\n\n// 0x4DBC34\nstatic int create_pull_down(char** stringList, int stringListLength, int x, int y, int a5, int a6, Rect* rect)\n{\n    int windowHeight = stringListLength * text_height() + 16;\n    int windowWidth = win_width_needed(stringList, stringListLength) + 4;\n    if (windowHeight < 2 || windowWidth < 2) {\n        return -1;\n    }\n\n    int win = win_add(x, y, windowWidth, windowHeight, a6, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04);\n    if (win == -1) {\n        return -1;\n    }\n\n    win_text(win, stringList, stringListLength, windowWidth - 4, 2, 8, a5);\n    win_box(win, 0, 0, windowWidth - 1, windowHeight - 1, colorTable[0]);\n    win_box(win, 1, 1, windowWidth - 2, windowHeight - 2, a5);\n    win_draw(win);\n    win_get_rect(win, rect);\n\n    return win;\n}\n\n// 0x4DC30C\nint win_debug(char* string)\n{\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    int lineHeight = text_height();\n\n    if (wd == -1) {\n        wd = win_add(80, 80, 300, 192, 256, WINDOW_FLAG_0x04);\n        if (wd == -1) {\n            return -1;\n        }\n\n        win_border(wd);\n\n        Window* window = GNW_find(wd);\n        unsigned char* windowBuffer = window->buffer;\n\n        win_fill(wd, 8, 8, 284, lineHeight, 0x100 | 1);\n\n        win_print(wd,\n            \"Debug\",\n            0,\n            (300 - text_width(\"Debug\")) / 2,\n            8,\n            0x2000000 | 0x100 | 4);\n\n        draw_shaded_box(windowBuffer,\n            300,\n            8,\n            8,\n            291,\n            lineHeight + 8,\n            colorTable[GNW_wcolor[2]],\n            colorTable[GNW_wcolor[1]]);\n\n        win_fill(wd, 9, 26, 282, 135, 0x100 | 1);\n\n        draw_shaded_box(windowBuffer,\n            300,\n            8,\n            25,\n            291,\n            lineHeight + 145,\n            colorTable[GNW_wcolor[2]],\n            colorTable[GNW_wcolor[1]]);\n\n        currx = 9;\n        curry = 26;\n\n        int btn = win_register_text_button(wd,\n            (300 - text_width(\"Close\")) / 2,\n            192 - 8 - lineHeight - 6,\n            -1,\n            -1,\n            -1,\n            -1,\n            \"Close\",\n            0);\n        win_register_button_func(btn, NULL, NULL, NULL, win_debug_delete);\n\n        win_register_button(wd,\n            8,\n            8,\n            284,\n            lineHeight,\n            -1,\n            -1,\n            -1,\n            -1,\n            NULL,\n            NULL,\n            NULL,\n            BUTTON_FLAG_0x10);\n    }\n\n    char temp[2];\n    temp[1] = '\\0';\n\n    char* pch = string;\n    while (*pch != '\\0') {\n        int characterWidth = text_char_width(*pch);\n        if (*pch == '\\n' || currx + characterWidth > 291) {\n            currx = 9;\n            curry += lineHeight;\n        }\n\n        while (160 - curry < lineHeight) {\n            Window* window = GNW_find(wd);\n            unsigned char* windowBuffer = window->buffer;\n            buf_to_buf(windowBuffer + lineHeight * 300 + 300 * 26 + 9,\n                282,\n                134 - lineHeight - 1,\n                300,\n                windowBuffer + 300 * 26 + 9,\n                300);\n            curry -= lineHeight;\n            win_fill(wd, 9, curry, 282, lineHeight, 0x100 | 1);\n        }\n\n        if (*pch != '\\n') {\n            temp[0] = *pch;\n            win_print(wd, temp, 0, currx, curry, 0x2000000 | 0x100 | 4);\n            currx += characterWidth + text_spacing();\n        }\n\n        pch++;\n    }\n\n    win_draw(wd);\n\n    return 0;\n}\n\n// 0x4DC65C\nstatic void win_debug_delete(int btn, int keyCode)\n{\n    win_delete(wd);\n    wd = -1;\n}\n\n// 0x4DC674\nint win_register_menu_bar(int win, int x, int y, int width, int height, int borderColor, int backgroundColor)\n{\n    Window* window = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (window == NULL) {\n        return -1;\n    }\n\n    if (window->menuBar != NULL) {\n        return -1;\n    }\n\n    int right = x + width;\n    if (right > window->width) {\n        return -1;\n    }\n\n    int bottom = y + height;\n    if (bottom > window->height) {\n        return -1;\n    }\n\n    MenuBar* menuBar = window->menuBar = (MenuBar*)mem_malloc(sizeof(MenuBar));\n    if (menuBar == NULL) {\n        return -1;\n    }\n\n    menuBar->win = win;\n    menuBar->rect.ulx = x;\n    menuBar->rect.uly = y;\n    menuBar->rect.lrx = right - 1;\n    menuBar->rect.lry = bottom - 1;\n    menuBar->pulldownsLength = 0;\n    menuBar->borderColor = borderColor;\n    menuBar->backgroundColor = backgroundColor;\n\n    win_fill(win, x, y, width, height, backgroundColor);\n    win_box(win, x, y, right - 1, bottom - 1, borderColor);\n\n    return 0;\n}\n\n// 0x4DC768\nint win_register_menu_pulldown(int win, int x, char* title, int keyCode, int itemsLength, char** items, int a7, int a8)\n{\n    Window* window = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return -1;\n    }\n\n    if (window == NULL) {\n        return -1;\n    }\n\n    MenuBar* menuBar = window->menuBar;\n    if (menuBar == NULL) {\n        return -1;\n    }\n\n    if (window->menuBar->pulldownsLength == 15) {\n        return -1;\n    }\n\n    int titleX = menuBar->rect.ulx + x;\n    int titleY = (menuBar->rect.uly + menuBar->rect.lry - text_height()) / 2;\n    int btn = win_register_button(win,\n        titleX,\n        titleY,\n        text_width(title),\n        text_height(),\n        -1,\n        -1,\n        keyCode,\n        -1,\n        NULL,\n        NULL,\n        NULL,\n        0);\n    if (btn == -1) {\n        return -1;\n    }\n\n    win_print(win, title, 0, titleX, titleY, window->menuBar->borderColor | 0x2000000);\n\n    MenuPulldown* pulldown = &(window->menuBar->pulldowns[window->menuBar->pulldownsLength]);\n    pulldown->rect.ulx = titleX;\n    pulldown->rect.uly = titleY;\n    pulldown->rect.lrx = text_width(title) + titleX - 1;\n    pulldown->rect.lry = text_height() + titleY - 1;\n    pulldown->keyCode = keyCode;\n    pulldown->itemsLength = itemsLength;\n    pulldown->items = items;\n    pulldown->field_1C = a7;\n    pulldown->field_20 = a8;\n\n    window->menuBar->pulldownsLength++;\n\n    return 0;\n}\n\n// 0x4DC8D0\nvoid win_delete_menu_bar(int win)\n{\n    Window* window = GNW_find(win);\n\n    if (!GNW_win_init_flag) {\n        return;\n    }\n\n    if (window == NULL) {\n        return;\n    }\n\n    if (window->menuBar == NULL) {\n        return;\n    }\n\n    win_fill(win,\n        window->menuBar->rect.ulx,\n        window->menuBar->rect.uly,\n        rectGetWidth(&(window->menuBar->rect)),\n        rectGetHeight(&(window->menuBar->rect)),\n        window->field_20);\n\n    mem_free(window->menuBar);\n    window->menuBar = NULL;\n}\n\n// 0x4DC9F0\nstatic int find_first_letter(int ch, char** stringList, int stringListLength)\n{\n    if (ch >= 'A' && ch <= 'Z') {\n        ch += ' ';\n    }\n\n    for (int index = 0; index < stringListLength; index++) {\n        char* string = stringList[index];\n        if (string[0] == ch || string[0] == ch - ' ') {\n            return index;\n        }\n    }\n\n    return -1;\n}\n\n// 0x4DCA30\nint win_width_needed(char** fileNameList, int fileNameListLength)\n{\n    int maxWidth = 0;\n\n    for (int index = 0; index < fileNameListLength; index++) {\n        int width = text_width(fileNameList[index]);\n        if (width > maxWidth) {\n            maxWidth = width;\n        }\n    }\n\n    return maxWidth;\n}\n\n// 0x4DCA5C\nint win_input_str(int win, char* dest, int maxLength, int x, int y, int textColor, int backgroundColor)\n{\n    Window* window = GNW_find(win);\n    unsigned char* buffer = window->buffer + window->width * y + x;\n\n    int cursorPos = strlen(dest);\n    dest[cursorPos] = '_';\n    dest[cursorPos + 1] = '\\0';\n\n    int lineHeight = text_height();\n    int stringWidth = text_width(dest);\n    buf_fill(buffer, stringWidth, lineHeight, window->width, backgroundColor);\n    text_to_buf(buffer, dest, stringWidth, window->width, textColor);\n\n    Rect dirtyRect;\n    dirtyRect.ulx = window->rect.ulx + x;\n    dirtyRect.uly = window->rect.uly + y;\n    dirtyRect.lrx = dirtyRect.ulx + stringWidth;\n    dirtyRect.lry = dirtyRect.uly + lineHeight;\n    GNW_win_refresh(window, &dirtyRect, NULL);\n\n    // NOTE: This loop is slightly different compared to other input handling\n    // loops. Cursor position is managed inside an incrementing loop. Cursor is\n    // decremented in the loop body when key is not handled.\n    bool isFirstKey = true;\n    for (; cursorPos <= maxLength; cursorPos++) {\n        int keyCode = get_input();\n        if (keyCode != -1) {\n            if (keyCode == KEY_ESCAPE) {\n                dest[cursorPos] = '\\0';\n                return -1;\n            }\n\n            if (keyCode == KEY_BACKSPACE) {\n                if (cursorPos > 0) {\n                    stringWidth = text_width(dest);\n\n                    if (isFirstKey) {\n                        buf_fill(buffer, stringWidth, lineHeight, window->width, backgroundColor);\n\n                        dirtyRect.ulx = window->rect.ulx + x;\n                        dirtyRect.uly = window->rect.uly + y;\n                        dirtyRect.lrx = dirtyRect.ulx + stringWidth;\n                        dirtyRect.lry = dirtyRect.uly + lineHeight;\n                        GNW_win_refresh(window, &dirtyRect, NULL);\n\n                        dest[0] = '_';\n                        dest[1] = '\\0';\n                        cursorPos = 1;\n                    } else {\n                        dest[cursorPos] = ' ';\n                        dest[cursorPos - 1] = '_';\n                    }\n\n                    buf_fill(buffer, stringWidth, lineHeight, window->width, backgroundColor);\n                    text_to_buf(buffer, dest, stringWidth, window->width, textColor);\n\n                    dirtyRect.ulx = window->rect.ulx + x;\n                    dirtyRect.uly = window->rect.uly + y;\n                    dirtyRect.lrx = dirtyRect.ulx + stringWidth;\n                    dirtyRect.lry = dirtyRect.uly + lineHeight;\n                    GNW_win_refresh(window, &dirtyRect, NULL);\n\n                    dest[cursorPos] = '\\0';\n                    cursorPos -= 2;\n\n                    isFirstKey = false;\n                } else {\n                    cursorPos--;\n                }\n            } else if (keyCode == KEY_RETURN) {\n                break;\n            } else {\n                if (cursorPos == maxLength) {\n                    cursorPos = maxLength - 1;\n                } else {\n                    if (keyCode > 0 && keyCode < 256) {\n                        dest[cursorPos] = keyCode;\n                        dest[cursorPos + 1] = '_';\n                        dest[cursorPos + 2] = '\\0';\n\n                        int stringWidth = text_width(dest);\n                        buf_fill(buffer, stringWidth, lineHeight, window->width, backgroundColor);\n                        text_to_buf(buffer, dest, stringWidth, window->width, textColor);\n\n                        dirtyRect.ulx = window->rect.ulx + x;\n                        dirtyRect.uly = window->rect.uly + y;\n                        dirtyRect.lrx = dirtyRect.ulx + stringWidth;\n                        dirtyRect.lry = dirtyRect.uly + lineHeight;\n                        GNW_win_refresh(window, &dirtyRect, NULL);\n\n                        isFirstKey = false;\n                    } else {\n                        cursorPos--;\n                    }\n                }\n            }\n        } else {\n            cursorPos--;\n        }\n    }\n\n    dest[cursorPos] = '\\0';\n\n    return 0;\n}\n\n// 0x4DBD04\nstatic int process_pull_down(int win, Rect* rect, char** items, int itemsLength, int a5, int a6, MenuBar* menuBar, int pulldownIndex)\n{\n    // TODO: Incomplete.\n    return -1;\n}\n\n// 0x4DC930\nint GNW_process_menu(MenuBar* menuBar, int pulldownIndex)\n{\n    // 0x51E418\n    static MenuBar* curr_menu = NULL;\n\n    if (curr_menu != NULL) {\n        return -1;\n    }\n\n    curr_menu = menuBar;\n\n    int keyCode;\n    Rect rect;\n    do {\n        MenuPulldown* pulldown = &(menuBar->pulldowns[pulldownIndex]);\n        int win = create_pull_down(pulldown->items,\n            pulldown->itemsLength,\n            pulldown->rect.ulx,\n            menuBar->rect.lry + 1,\n            pulldown->field_1C,\n            pulldown->field_20,\n            &rect);\n        if (win == -1) {\n            curr_menu = NULL;\n            return -1;\n        }\n\n        keyCode = process_pull_down(win, &rect, pulldown->items, pulldown->itemsLength, pulldown->field_1C, pulldown->field_20, menuBar, pulldownIndex);\n        if (keyCode < -1) {\n            pulldownIndex = -2 - keyCode;\n        }\n    } while (keyCode < -1);\n\n    if (keyCode != -1) {\n        flush_input_buffer();\n        GNW_add_input_buffer(keyCode);\n        keyCode = menuBar->pulldowns[pulldownIndex].keyCode;\n    }\n\n    curr_menu = NULL;\n\n    return keyCode;\n}\n\n// Calculates max length of string needed to represent a1 or a2.\n//\n// 0x4DD03C\nstatic int calc_max_field_chars_wcursor(int a1, int a2)\n{\n    char* str = (char*)mem_malloc(17);\n    if (str == NULL) {\n        return -1;\n    }\n\n    sprintf(str, \"%d\", a1);\n    int len1 = strlen(str);\n\n    sprintf(str, \"%d\", a2);\n    int len2 = strlen(str);\n\n    mem_free(str);\n\n    return max(len1, len2) + 1;\n}\n\n// 0x4DD3EC\nvoid GNW_intr_init()\n{\n    int v1, v2;\n    int i;\n\n    tm_persistence = 3000;\n    tm_add = 0;\n    tm_kill = -1;\n    scr_center_x = scr_size.lrx / 2;\n\n    if (scr_size.lry >= 479) {\n        tm_text_y = 16;\n        tm_text_x = 16;\n    } else {\n        tm_text_y = 10;\n        tm_text_x = 10;\n    }\n\n    tm_h = 2 * tm_text_y + text_height();\n\n    v1 = scr_size.lry >> 3;\n    v2 = scr_size.lry >> 2;\n\n    for (i = 0; i < 5; i++) {\n        tm_location[i].y = v1 * i + v2;\n        tm_location[i].taken = 0;\n    }\n}\n\n// 0x4DD4A4\nvoid GNW_intr_exit()\n{\n    remove_bk_process(tm_watch_msgs);\n    while (tm_kill != -1) {\n        tm_kill_msg();\n    }\n}\n\n// 0x4DD66C\nstatic void tm_watch_msgs()\n{\n    if (tm_watch_active) {\n        return;\n    }\n\n    tm_watch_active = 1;\n    while (tm_kill != -1) {\n        if (elapsed_time(tm_queue[tm_kill].created) < tm_persistence) {\n            break;\n        }\n\n        tm_kill_msg();\n    }\n    tm_watch_active = 0;\n}\n\n// 0x4DD6C0\nstatic void tm_kill_msg()\n{\n    int v0;\n\n    v0 = tm_kill;\n    if (v0 != -1) {\n        win_delete(tm_queue[tm_kill].id);\n        tm_location[tm_queue[tm_kill].location].taken = 0;\n\n        if (v0 == 5) {\n            v0 = 0;\n        }\n\n        if (v0 == tm_add) {\n            tm_add = 0;\n            tm_kill = -1;\n            remove_bk_process(tm_watch_msgs);\n            v0 = tm_kill;\n        }\n    }\n\n    tm_kill = v0;\n}\n\n// 0x4DD744\nstatic void tm_kill_out_of_order(int a1)\n{\n    int v7;\n    int v6;\n\n    if (tm_kill == -1) {\n        return;\n    }\n\n    if (!tm_index_active(a1)) {\n        return;\n    }\n\n    win_delete(tm_queue[a1].id);\n\n    tm_location[tm_queue[a1].location].taken = 0;\n\n    if (a1 != tm_kill) {\n        v6 = a1;\n        do {\n            v7 = v6 - 1;\n            if (v7 < 0) {\n                v7 = 4;\n            }\n\n            tm_queue[v6] = tm_queue[v7];\n            v6 = v7;\n        } while (v7 != tm_kill);\n    }\n\n    if (++tm_kill == 5) {\n        tm_kill = 0;\n    }\n\n    if (tm_add == tm_kill) {\n        tm_add = 0;\n        tm_kill = -1;\n        remove_bk_process(tm_watch_msgs);\n    }\n}\n\n// 0x4DD82C\nstatic void tm_click_response(int btn)\n{\n    int win;\n    int v3;\n\n    if (tm_kill == -1) {\n        return;\n    }\n\n    win = win_button_winID(btn);\n    v3 = tm_kill;\n    while (win != tm_queue[v3].id) {\n        v3++;\n        if (v3 == 5) {\n            v3 = 0;\n        }\n\n        if (v3 == tm_kill || !tm_index_active(v3))\n            return;\n    }\n\n    tm_kill_out_of_order(v3);\n}\n\n// 0x4DD870\nstatic int tm_index_active(int a1)\n{\n    if (tm_kill != tm_add) {\n        if (tm_kill >= tm_add) {\n            if (a1 >= tm_add && a1 < tm_kill)\n                return 0;\n        } else if (a1 < tm_kill || a1 >= tm_add) {\n            return 0;\n        }\n    }\n    return 1;\n}\n"
  },
  {
    "path": "src/plib/gnw/intrface.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_INTRFACE_H_\n#define FALLOUT_PLIB_GNW_INTRFACE_H_\n\n#include <stdbool.h>\n\n#include \"plib/gnw/rect.h\"\n\ntypedef struct MenuBar MenuBar;\n\ntypedef void(SelectFunc)(char** items, int index);\n\nint win_list_select(const char* title, char** fileList, int fileListLength, SelectFunc* callback, int x, int y, int a7);\nint win_list_select_at(const char* title, char** fileList, int fileListLength, SelectFunc* callback, int x, int y, int a7, int a8);\nint win_get_str(char* dest, int length, const char* title, int x, int y);\nint win_msg(const char* string, int x, int y, int flags);\nint win_pull_down(char** items, int itemsLength, int x, int y, int a5);\nint win_debug(char* string);\nint win_register_menu_bar(int win, int x, int y, int width, int height, int borderColor, int backgroundColor);\nint win_register_menu_pulldown(int win, int x, char* title, int keyCode, int itemsLength, char** items, int a7, int a8);\nvoid win_delete_menu_bar(int win);\nint win_width_needed(char** fileNameList, int fileNameListLength);\nint win_input_str(int win, char* dest, int maxLength, int x, int y, int textColor, int backgroundColor);\nint GNW_process_menu(MenuBar* menuBar, int pulldownIndex);\nvoid GNW_intr_init();\nvoid GNW_intr_exit();\n\n#endif /* FALLOUT_PLIB_GNW_INTRFACE_H_ */\n"
  },
  {
    "path": "src/plib/gnw/kb.c",
    "content": "#include \"plib/gnw/kb.h\"\n\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/dxinput.h\"\n#include \"plib/gnw/gnw95dx.h\"\n#include \"plib/gnw/vcr.h\"\n\ntypedef struct key_ansi_t {\n    short keys;\n    short normal;\n    short shift;\n    short left_alt;\n    short right_alt;\n    short ctrl;\n} key_ansi_t;\n\ntypedef struct key_data_t {\n    unsigned char scan_code;\n    unsigned short modifiers;\n} key_data_t;\n\nstatic int kb_next_ascii_English_US();\nstatic int kb_next_ascii();\nstatic void kb_map_ascii_English_US();\nstatic void kb_map_ascii_French();\nstatic void kb_map_ascii_German();\nstatic void kb_map_ascii_Italian();\nstatic void kb_map_ascii_Spanish();\nstatic void kb_init_lock_status();\nstatic void kb_toggle_caps();\nstatic void kb_toggle_num();\nstatic void kb_toggle_scroll();\nstatic void kb_buffer_put();\nstatic void kb_buffer_get();\nstatic int kb_buffer_peek(int index, key_data_t** keyboardEventPtr);\n\n// 0x51E2D0\nstatic unsigned char kb_installed = 0;\n\n// 0x51E2D4\nstatic bool kb_disabled = false;\n\n// 0x51E2D8\nstatic bool kb_numpad_disabled = false;\n\n// 0x51E2DC\nstatic bool kb_numlock_disabled = false;\n\n// 0x51E2E0\nstatic int kb_put = 0;\n\n// 0x51E2E4\nstatic int kb_get = 0;\n\n// 0x51E2E8\nstatic short extended_code = 0;\n\n// 0x51E2EA\nstatic int kb_lock_flags = 0;\n\n// 0x51E2EC\nstatic int (*kb_scan_to_ascii)() = kb_next_ascii_English_US;\n\n// Ring buffer of keyboard events.\n//\n// 0x6ACB30\nstatic key_data_t kb_buffer[64];\n\n// A map of logical key configurations for physical scan codes [DIK_*].\n//\n// 0x6ACC30\nstatic key_ansi_t ascii_table[256];\n\n// A state of physical keys [DIK_*] currently pressed.\n//\n// 0 - key is not pressed.\n// 1 - key pressed.\n//\n// 0x6AD830\nunsigned char keys[256];\n\n// 0x6AD930\nstatic unsigned int kb_idle_start_time;\n\n// 0x6AD934\nstatic key_data_t temp;\n\n// 0x6AD938\nint kb_layout;\n\n// The number of keys currently pressed.\n//\n// 0x6AD93C\nunsigned char keynumpress;\n\n// 0x4CBC90\nint GNW_kb_set()\n{\n    if (kb_installed) {\n        return -1;\n    }\n\n    kb_installed = 1;\n\n    // NOTE: Uninline.\n    kb_clear();\n\n    kb_init_lock_status();\n    kb_set_layout(KEYBOARD_LAYOUT_QWERTY);\n\n    kb_idle_start_time = get_time();\n\n    return 0;\n}\n\n// 0x4CBD00\nvoid GNW_kb_restore()\n{\n    if (kb_installed) {\n        kb_installed = 0;\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4CBD14\nvoid kb_wait()\n{\n    if (kb_installed) {\n        // NOTE: Uninline.\n        kb_clear();\n\n        do {\n            GNW95_process_message();\n        } while (keynumpress == 0);\n\n        // NOTE: Uninline.\n        kb_clear();\n    }\n}\n\n// 0x4CBDA8\nvoid kb_clear()\n{\n    int i;\n\n    if (kb_installed) {\n        keynumpress = 0;\n\n        for (i = 0; i < 256; i++) {\n            keys[i] = 0;\n        }\n\n        kb_put = 0;\n        kb_get = 0;\n    }\n\n    dxinput_flush_keyboard_buffer();\n    GNW95_clear_time_stamps();\n}\n\n// 0x4CBDE8\nint kb_getch()\n{\n    int rc = -1;\n\n    if (kb_installed != 0) {\n        rc = kb_scan_to_ascii();\n    }\n\n    return rc;\n}\n\n// 0x4CBE00\nvoid kb_disable()\n{\n    kb_disabled = true;\n}\n\n// 0x4CBE0C\nvoid kb_enable()\n{\n    kb_disabled = false;\n}\n\n// 0x4CBE18\nbool kb_is_disabled()\n{\n    return kb_disabled;\n}\n\n// NOTE: Unused.\n//\n// 0x4CBE20\nvoid kb_disable_numpad()\n{\n    kb_numpad_disabled = true;\n}\n\n// NOTE: Unused.\n//\n// 0x4CBE2C\nvoid kb_enable_numpad()\n{\n    kb_numpad_disabled = false;\n}\n\n// NOTE: Unused.\n//\n// 0x4CBE38\nbool kb_numpad_is_disabled()\n{\n    return kb_numpad_disabled;\n}\n\n// NOTE: Unused.\n//\n// 0x4CBE40\nvoid kb_disable_numlock()\n{\n    kb_numlock_disabled = true;\n}\n\n// NOTE: Unused.\n//\n// 0x4CBE4C\nvoid kb_enable_numlock()\n{\n    kb_numlock_disabled = false;\n}\n\n// NOTE: Unused.\n//\n// 0x4CBE58\nbool kb_numlock_is_disabled()\n{\n    return kb_numlock_disabled;\n}\n\n// 0x4CBE74\nvoid kb_set_layout(int layout)\n{\n    int old_layout = kb_layout;\n    kb_layout = layout;\n\n    switch (layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        kb_scan_to_ascii = kb_next_ascii_English_US;\n        kb_map_ascii_English_US();\n        break;\n    // case KEYBOARD_LAYOUT_FRENCH:\n    //    kb_scan_to_ascii = sub_4CC5BC;\n    //    _kb_map_ascii_French();\n    //    break;\n    // case KEYBOARD_LAYOUT_GERMAN:\n    //    kb_scan_to_ascii = sub_4CC94C;\n    //    _kb_map_ascii_German();\n    //    break;\n    // case KEYBOARD_LAYOUT_ITALIAN:\n    //    kb_scan_to_ascii = sub_4CCE14;\n    //    _kb_map_ascii_Italian();\n    //    break;\n    // case KEYBOARD_LAYOUT_SPANISH:\n    //    kb_scan_to_ascii = sub_4CD0E0;\n    //    _kb_map_ascii_Spanish();\n    //    break;\n    default:\n        kb_layout = old_layout;\n        break;\n    }\n}\n\n// 0x4CBEEC\nint kb_get_layout()\n{\n    return kb_layout;\n}\n\n// NOTE: Unused.\n//\n// 0x4CBEF4\nint kb_ascii_to_scan(int ascii)\n{\n    int k;\n\n    for (k = 0; k < 256; k++) {\n        if (ascii_table[k].normal == k\n            || ascii_table[k].shift == k\n            || ascii_table[k].left_alt == k\n            || ascii_table[k].right_alt == k\n            || ascii_table[k].ctrl == k) {\n            return k;\n        }\n    }\n\n    return -1;\n}\n\n// NOTE: Unused.\n//\n// 0x4CBF50\nunsigned int kb_elapsed_time()\n{\n    return elapsed_time(kb_idle_start_time);\n}\n\n// NOTE: Unused.\n//\n// 0x4CBF5C\nvoid kb_reset_elapsed_time()\n{\n    kb_idle_start_time = get_time();\n}\n\n// TODO: Key type is likely short.\n//\n// 0x4CBF68\nvoid kb_simulate_key(int scan_code)\n{\n    if (vcr_state == 0) {\n        if (vcr_buffer_index != VCR_BUFFER_CAPACITY - 1) {\n            VcrEntry* vcrEntry = &(vcr_buffer[vcr_buffer_index]);\n            vcrEntry->type = VCR_ENTRY_TYPE_KEYBOARD_EVENT;\n            vcrEntry->keyboardEvent.key = scan_code & 0xFFFF;\n            vcrEntry->time = vcr_time;\n            vcrEntry->counter = vcr_counter;\n\n            vcr_buffer_index++;\n        }\n    }\n\n    kb_idle_start_time = get_bk_time();\n\n    if (scan_code == 224) {\n        extended_code = 0x80;\n    } else {\n        int keyState;\n        if (scan_code & 0x80) {\n            scan_code &= ~0x80;\n            keyState = KEY_STATE_UP;\n        } else {\n            keyState = KEY_STATE_DOWN;\n        }\n\n        int physicalKey = scan_code | extended_code;\n\n        if (keyState != KEY_STATE_UP && keys[physicalKey] != KEY_STATE_UP) {\n            keyState = KEY_STATE_REPEAT;\n        }\n\n        if (keys[physicalKey] != keyState) {\n            keys[physicalKey] = keyState;\n            if (keyState == KEY_STATE_DOWN) {\n                keynumpress++;\n            } else if (keyState == KEY_STATE_UP) {\n                keynumpress--;\n            }\n        }\n\n        if (keyState != KEY_STATE_UP) {\n            temp.scan_code = physicalKey & 0xFF;\n            temp.modifiers = 0;\n\n            if (physicalKey == DIK_CAPITAL) {\n                if (keys[DIK_LCONTROL] == KEY_STATE_UP && keys[DIK_RCONTROL] == KEY_STATE_UP) {\n                    // NOTE: Uninline.\n                    kb_toggle_caps();\n                }\n            } else if (physicalKey == DIK_NUMLOCK) {\n                if (keys[DIK_LCONTROL] == KEY_STATE_UP && keys[DIK_RCONTROL] == KEY_STATE_UP) {\n                    // NOTE: Uninline.\n                    kb_toggle_num();\n                }\n            } else if (physicalKey == DIK_SCROLL) {\n                if (keys[DIK_LCONTROL] == KEY_STATE_UP && keys[DIK_RCONTROL] == KEY_STATE_UP) {\n                    // NOTE: Uninline.\n                    kb_toggle_scroll();\n                }\n            } else if ((physicalKey == DIK_LSHIFT || physicalKey == DIK_RSHIFT) && (kb_lock_flags & MODIFIER_KEY_STATE_CAPS_LOCK) != 0 && kb_layout != 0) {\n                if (keys[DIK_LCONTROL] == KEY_STATE_UP && keys[DIK_RCONTROL] == KEY_STATE_UP) {\n                    // NOTE: Uninline.\n                    kb_toggle_caps();\n                }\n            }\n\n            if (kb_lock_flags != 0) {\n                if ((kb_lock_flags & MODIFIER_KEY_STATE_NUM_LOCK) != 0 && !kb_numlock_disabled) {\n                    temp.modifiers |= KEYBOARD_EVENT_MODIFIER_NUM_LOCK;\n                }\n\n                if ((kb_lock_flags & MODIFIER_KEY_STATE_CAPS_LOCK) != 0) {\n                    temp.modifiers |= KEYBOARD_EVENT_MODIFIER_CAPS_LOCK;\n                }\n\n                if ((kb_lock_flags & MODIFIER_KEY_STATE_SCROLL_LOCK) != 0) {\n                    temp.modifiers |= KEYBOARD_EVENT_MODIFIER_SCROLL_LOCK;\n                }\n            }\n\n            if (keys[DIK_LSHIFT] != KEY_STATE_UP) {\n                temp.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT;\n            }\n\n            if (keys[DIK_RSHIFT] != KEY_STATE_UP) {\n                temp.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_SHIFT;\n            }\n\n            if (keys[DIK_LMENU] != KEY_STATE_UP) {\n                temp.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_ALT;\n            }\n\n            if (keys[DIK_RMENU] != KEY_STATE_UP) {\n                temp.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_ALT;\n            }\n\n            if (keys[DIK_LCONTROL] != KEY_STATE_UP) {\n                temp.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_CONTROL;\n            }\n\n            if (keys[DIK_RCONTROL] != KEY_STATE_UP) {\n                temp.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_CONTROL;\n            }\n\n            if (((kb_put + 1) & 0x3F) != kb_get) {\n                kb_buffer[kb_put] = temp;\n                kb_put++;\n                kb_put &= 0x3F;\n            }\n        }\n\n        extended_code = 0;\n    }\n\n    if (keys[198] != KEY_STATE_UP) {\n        // NOTE: Uninline\n        kb_clear();\n    }\n}\n\n// 0x4CC2F0\nstatic int kb_next_ascii_English_US()\n{\n    key_data_t* keyboardEvent;\n    if (kb_buffer_peek(0, &keyboardEvent) != 0) {\n        return -1;\n    }\n\n    if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_CAPS_LOCK) != 0) {\n        unsigned char a = (kb_layout != KEYBOARD_LAYOUT_FRENCH ? DIK_A : DIK_Q);\n        unsigned char m = (kb_layout != KEYBOARD_LAYOUT_FRENCH ? DIK_M : DIK_SEMICOLON);\n        unsigned char q = (kb_layout != KEYBOARD_LAYOUT_FRENCH ? DIK_Q : DIK_A);\n        unsigned char w = (kb_layout != KEYBOARD_LAYOUT_FRENCH ? DIK_W : DIK_Z);\n\n        unsigned char y;\n        switch (kb_layout) {\n        case KEYBOARD_LAYOUT_QWERTY:\n        case KEYBOARD_LAYOUT_FRENCH:\n        case KEYBOARD_LAYOUT_ITALIAN:\n        case KEYBOARD_LAYOUT_SPANISH:\n            y = DIK_Y;\n            break;\n        default:\n            // GERMAN\n            y = DIK_Z;\n            break;\n        }\n\n        unsigned char z;\n        switch (kb_layout) {\n        case KEYBOARD_LAYOUT_QWERTY:\n        case KEYBOARD_LAYOUT_ITALIAN:\n        case KEYBOARD_LAYOUT_SPANISH:\n            z = DIK_Z;\n            break;\n        case KEYBOARD_LAYOUT_FRENCH:\n            z = DIK_W;\n            break;\n        default:\n            // GERMAN\n            z = DIK_Y;\n            break;\n        }\n\n        unsigned char scanCode = keyboardEvent->scan_code;\n        if (scanCode == a\n            || scanCode == DIK_B\n            || scanCode == DIK_C\n            || scanCode == DIK_D\n            || scanCode == DIK_E\n            || scanCode == DIK_F\n            || scanCode == DIK_G\n            || scanCode == DIK_H\n            || scanCode == DIK_I\n            || scanCode == DIK_J\n            || scanCode == DIK_K\n            || scanCode == DIK_L\n            || scanCode == m\n            || scanCode == DIK_N\n            || scanCode == DIK_O\n            || scanCode == DIK_P\n            || scanCode == q\n            || scanCode == DIK_R\n            || scanCode == DIK_S\n            || scanCode == DIK_T\n            || scanCode == DIK_U\n            || scanCode == DIK_V\n            || scanCode == w\n            || scanCode == DIK_X\n            || scanCode == y\n            || scanCode == z) {\n            if (keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_SHIFT) {\n                keyboardEvent->modifiers &= ~KEYBOARD_EVENT_MODIFIER_ANY_SHIFT;\n            } else {\n                keyboardEvent->modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT;\n            }\n        }\n    }\n\n    return kb_next_ascii();\n}\n\n// 0x4CDA4C\nstatic int kb_next_ascii()\n{\n    key_data_t* keyboardEvent;\n    if (kb_buffer_peek(0, &keyboardEvent) != 0) {\n        return -1;\n    }\n\n    switch (keyboardEvent->scan_code) {\n    case DIK_DIVIDE:\n    case DIK_MULTIPLY:\n    case DIK_SUBTRACT:\n    case DIK_ADD:\n    case DIK_NUMPADENTER:\n        if (kb_numpad_disabled) {\n            if (kb_get != kb_put) {\n                kb_get++;\n                kb_get &= (KEY_QUEUE_SIZE - 1);\n            }\n            return -1;\n        }\n        break;\n    case DIK_NUMPAD0:\n    case DIK_NUMPAD1:\n    case DIK_NUMPAD2:\n    case DIK_NUMPAD3:\n    case DIK_NUMPAD4:\n    case DIK_NUMPAD5:\n    case DIK_NUMPAD6:\n    case DIK_NUMPAD7:\n    case DIK_NUMPAD8:\n    case DIK_NUMPAD9:\n        if (kb_numpad_disabled) {\n            if (kb_get != kb_put) {\n                kb_get++;\n                kb_get &= (KEY_QUEUE_SIZE - 1);\n            }\n            return -1;\n        }\n\n        if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_ALT) == 0 && (keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_NUM_LOCK) != 0) {\n            if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_SHIFT) != 0) {\n                keyboardEvent->modifiers &= ~KEYBOARD_EVENT_MODIFIER_ANY_SHIFT;\n            } else {\n                keyboardEvent->modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT;\n            }\n        }\n\n        break;\n    }\n\n    int logicalKey = -1;\n\n    key_ansi_t* logicalKeyDescription = &(ascii_table[keyboardEvent->scan_code]);\n    if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_CONTROL) != 0) {\n        logicalKey = logicalKeyDescription->ctrl;\n    } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_RIGHT_ALT) != 0) {\n        logicalKey = logicalKeyDescription->right_alt;\n    } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_LEFT_ALT) != 0) {\n        logicalKey = logicalKeyDescription->left_alt;\n    } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_SHIFT) != 0) {\n        logicalKey = logicalKeyDescription->shift;\n    } else {\n        logicalKey = logicalKeyDescription->normal;\n    }\n\n    if (kb_get != kb_put) {\n        kb_get++;\n        kb_get &= (KEY_QUEUE_SIZE - 1);\n    }\n\n    return logicalKey;\n}\n\n// 0x4CDC08\nstatic void kb_map_ascii_English_US()\n{\n    int k;\n\n    for (k = 0; k < 256; k++) {\n        ascii_table[k].keys = -1;\n        ascii_table[k].normal = -1;\n        ascii_table[k].shift = -1;\n        ascii_table[k].left_alt = -1;\n        ascii_table[k].right_alt = -1;\n        ascii_table[k].ctrl = -1;\n    }\n\n    ascii_table[DIK_ESCAPE].normal = KEY_ESCAPE;\n    ascii_table[DIK_ESCAPE].shift = KEY_ESCAPE;\n    ascii_table[DIK_ESCAPE].left_alt = KEY_ESCAPE;\n    ascii_table[DIK_ESCAPE].right_alt = KEY_ESCAPE;\n    ascii_table[DIK_ESCAPE].ctrl = KEY_ESCAPE;\n\n    ascii_table[DIK_F1].normal = KEY_F1;\n    ascii_table[DIK_F1].shift = KEY_SHIFT_F1;\n    ascii_table[DIK_F1].left_alt = KEY_ALT_F1;\n    ascii_table[DIK_F1].right_alt = KEY_ALT_F1;\n    ascii_table[DIK_F1].ctrl = KEY_CTRL_F1;\n\n    ascii_table[DIK_F2].normal = KEY_F2;\n    ascii_table[DIK_F2].shift = KEY_SHIFT_F2;\n    ascii_table[DIK_F2].left_alt = KEY_ALT_F2;\n    ascii_table[DIK_F2].right_alt = KEY_ALT_F2;\n    ascii_table[DIK_F2].ctrl = KEY_CTRL_F2;\n\n    ascii_table[DIK_F3].normal = KEY_F3;\n    ascii_table[DIK_F3].shift = KEY_SHIFT_F3;\n    ascii_table[DIK_F3].left_alt = KEY_ALT_F3;\n    ascii_table[DIK_F3].right_alt = KEY_ALT_F3;\n    ascii_table[DIK_F3].ctrl = KEY_CTRL_F3;\n\n    ascii_table[DIK_F4].normal = KEY_F4;\n    ascii_table[DIK_F4].shift = KEY_SHIFT_F4;\n    ascii_table[DIK_F4].left_alt = KEY_ALT_F4;\n    ascii_table[DIK_F4].right_alt = KEY_ALT_F4;\n    ascii_table[DIK_F4].ctrl = KEY_CTRL_F4;\n\n    ascii_table[DIK_F5].normal = KEY_F5;\n    ascii_table[DIK_F5].shift = KEY_SHIFT_F5;\n    ascii_table[DIK_F5].left_alt = KEY_ALT_F5;\n    ascii_table[DIK_F5].right_alt = KEY_ALT_F5;\n    ascii_table[DIK_F5].ctrl = KEY_CTRL_F5;\n\n    ascii_table[DIK_F6].normal = KEY_F6;\n    ascii_table[DIK_F6].shift = KEY_SHIFT_F6;\n    ascii_table[DIK_F6].left_alt = KEY_ALT_F6;\n    ascii_table[DIK_F6].right_alt = KEY_ALT_F6;\n    ascii_table[DIK_F6].ctrl = KEY_CTRL_F6;\n\n    ascii_table[DIK_F7].normal = KEY_F7;\n    ascii_table[DIK_F7].shift = KEY_SHIFT_F7;\n    ascii_table[DIK_F7].left_alt = KEY_ALT_F7;\n    ascii_table[DIK_F7].right_alt = KEY_ALT_F7;\n    ascii_table[DIK_F7].ctrl = KEY_CTRL_F7;\n\n    ascii_table[DIK_F8].normal = KEY_F8;\n    ascii_table[DIK_F8].shift = KEY_SHIFT_F8;\n    ascii_table[DIK_F8].left_alt = KEY_ALT_F8;\n    ascii_table[DIK_F8].right_alt = KEY_ALT_F8;\n    ascii_table[DIK_F8].ctrl = KEY_CTRL_F8;\n\n    ascii_table[DIK_F9].normal = KEY_F9;\n    ascii_table[DIK_F9].shift = KEY_SHIFT_F9;\n    ascii_table[DIK_F9].left_alt = KEY_ALT_F9;\n    ascii_table[DIK_F9].right_alt = KEY_ALT_F9;\n    ascii_table[DIK_F9].ctrl = KEY_CTRL_F9;\n\n    ascii_table[DIK_F10].normal = KEY_F10;\n    ascii_table[DIK_F10].shift = KEY_SHIFT_F10;\n    ascii_table[DIK_F10].left_alt = KEY_ALT_F10;\n    ascii_table[DIK_F10].right_alt = KEY_ALT_F10;\n    ascii_table[DIK_F10].ctrl = KEY_CTRL_F10;\n\n    ascii_table[DIK_F11].normal = KEY_F11;\n    ascii_table[DIK_F11].shift = KEY_SHIFT_F11;\n    ascii_table[DIK_F11].left_alt = KEY_ALT_F11;\n    ascii_table[DIK_F11].right_alt = KEY_ALT_F11;\n    ascii_table[DIK_F11].ctrl = KEY_CTRL_F11;\n\n    ascii_table[DIK_F12].normal = KEY_F12;\n    ascii_table[DIK_F12].shift = KEY_SHIFT_F12;\n    ascii_table[DIK_F12].left_alt = KEY_ALT_F12;\n    ascii_table[DIK_F12].right_alt = KEY_ALT_F12;\n    ascii_table[DIK_F12].ctrl = KEY_CTRL_F12;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_GRAVE;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_2;\n        break;\n    case KEYBOARD_LAYOUT_ITALIAN:\n    case KEYBOARD_LAYOUT_SPANISH:\n        k = 0;\n        break;\n    default:\n        k = DIK_RBRACKET;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_GRAVE;\n    ascii_table[k].shift = KEY_TILDE;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    ascii_table[DIK_1].normal = KEY_1;\n    ascii_table[DIK_1].shift = KEY_EXCLAMATION;\n    ascii_table[DIK_1].left_alt = -1;\n    ascii_table[DIK_1].right_alt = -1;\n    ascii_table[DIK_1].ctrl = -1;\n\n    ascii_table[DIK_2].normal = KEY_2;\n    ascii_table[DIK_2].shift = KEY_AT;\n    ascii_table[DIK_2].left_alt = -1;\n    ascii_table[DIK_2].right_alt = -1;\n    ascii_table[DIK_2].ctrl = -1;\n\n    ascii_table[DIK_3].normal = KEY_3;\n    ascii_table[DIK_3].shift = KEY_NUMBER_SIGN;\n    ascii_table[DIK_3].left_alt = -1;\n    ascii_table[DIK_3].right_alt = -1;\n    ascii_table[DIK_3].ctrl = -1;\n\n    ascii_table[DIK_4].normal = KEY_4;\n    ascii_table[DIK_4].shift = KEY_DOLLAR;\n    ascii_table[DIK_4].left_alt = -1;\n    ascii_table[DIK_4].right_alt = -1;\n    ascii_table[DIK_4].ctrl = -1;\n\n    ascii_table[DIK_5].normal = KEY_5;\n    ascii_table[DIK_5].shift = KEY_PERCENT;\n    ascii_table[DIK_5].left_alt = -1;\n    ascii_table[DIK_5].right_alt = -1;\n    ascii_table[DIK_5].ctrl = -1;\n\n    ascii_table[DIK_6].normal = KEY_6;\n    ascii_table[DIK_6].shift = KEY_CARET;\n    ascii_table[DIK_6].left_alt = -1;\n    ascii_table[DIK_6].right_alt = -1;\n    ascii_table[DIK_6].ctrl = -1;\n\n    ascii_table[DIK_7].normal = KEY_7;\n    ascii_table[DIK_7].shift = KEY_AMPERSAND;\n    ascii_table[DIK_7].left_alt = -1;\n    ascii_table[DIK_7].right_alt = -1;\n    ascii_table[DIK_7].ctrl = -1;\n\n    ascii_table[DIK_8].normal = KEY_8;\n    ascii_table[DIK_8].shift = KEY_ASTERISK;\n    ascii_table[DIK_8].left_alt = -1;\n    ascii_table[DIK_8].right_alt = -1;\n    ascii_table[DIK_8].ctrl = -1;\n\n    ascii_table[DIK_9].normal = KEY_9;\n    ascii_table[DIK_9].shift = KEY_PAREN_LEFT;\n    ascii_table[DIK_9].left_alt = -1;\n    ascii_table[DIK_9].right_alt = -1;\n    ascii_table[DIK_9].ctrl = -1;\n\n    ascii_table[DIK_0].normal = KEY_0;\n    ascii_table[DIK_0].shift = KEY_PAREN_RIGHT;\n    ascii_table[DIK_0].left_alt = -1;\n    ascii_table[DIK_0].right_alt = -1;\n    ascii_table[DIK_0].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_MINUS;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_6;\n        break;\n    default:\n        k = DIK_SLASH;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_MINUS;\n    ascii_table[k].shift = KEY_UNDERSCORE;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_EQUALS;\n        break;\n    default:\n        k = DIK_0;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_EQUAL;\n    ascii_table[k].shift = KEY_PLUS;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    ascii_table[DIK_BACK].normal = KEY_BACKSPACE;\n    ascii_table[DIK_BACK].shift = KEY_BACKSPACE;\n    ascii_table[DIK_BACK].left_alt = KEY_BACKSPACE;\n    ascii_table[DIK_BACK].right_alt = KEY_BACKSPACE;\n    ascii_table[DIK_BACK].ctrl = KEY_DEL;\n\n    ascii_table[DIK_TAB].normal = KEY_TAB;\n    ascii_table[DIK_TAB].shift = KEY_TAB;\n    ascii_table[DIK_TAB].left_alt = KEY_TAB;\n    ascii_table[DIK_TAB].right_alt = KEY_TAB;\n    ascii_table[DIK_TAB].ctrl = KEY_TAB;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_A;\n        break;\n    default:\n        k = DIK_Q;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_LOWERCASE_Q;\n    ascii_table[k].shift = KEY_UPPERCASE_Q;\n    ascii_table[k].left_alt = KEY_ALT_Q;\n    ascii_table[k].right_alt = KEY_ALT_Q;\n    ascii_table[k].ctrl = KEY_CTRL_Q;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_Z;\n        break;\n    default:\n        k = DIK_W;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_LOWERCASE_W;\n    ascii_table[k].shift = KEY_UPPERCASE_W;\n    ascii_table[k].left_alt = KEY_ALT_W;\n    ascii_table[k].right_alt = KEY_ALT_W;\n    ascii_table[k].ctrl = KEY_CTRL_W;\n\n    ascii_table[DIK_E].normal = KEY_LOWERCASE_E;\n    ascii_table[DIK_E].shift = KEY_UPPERCASE_E;\n    ascii_table[DIK_E].left_alt = KEY_ALT_E;\n    ascii_table[DIK_E].right_alt = KEY_ALT_E;\n    ascii_table[DIK_E].ctrl = KEY_CTRL_E;\n\n    ascii_table[DIK_R].normal = KEY_LOWERCASE_R;\n    ascii_table[DIK_R].shift = KEY_UPPERCASE_R;\n    ascii_table[DIK_R].left_alt = KEY_ALT_R;\n    ascii_table[DIK_R].right_alt = KEY_ALT_R;\n    ascii_table[DIK_R].ctrl = KEY_CTRL_R;\n\n    ascii_table[DIK_T].normal = KEY_LOWERCASE_T;\n    ascii_table[DIK_T].shift = KEY_UPPERCASE_T;\n    ascii_table[DIK_T].left_alt = KEY_ALT_T;\n    ascii_table[DIK_T].right_alt = KEY_ALT_T;\n    ascii_table[DIK_T].ctrl = KEY_CTRL_T;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n    case KEYBOARD_LAYOUT_FRENCH:\n    case KEYBOARD_LAYOUT_ITALIAN:\n    case KEYBOARD_LAYOUT_SPANISH:\n        k = DIK_Y;\n        break;\n    default:\n        k = DIK_Z;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_LOWERCASE_Y;\n    ascii_table[k].shift = KEY_UPPERCASE_Y;\n    ascii_table[k].left_alt = KEY_ALT_Y;\n    ascii_table[k].right_alt = KEY_ALT_Y;\n    ascii_table[k].ctrl = KEY_CTRL_Y;\n\n    ascii_table[DIK_U].normal = KEY_LOWERCASE_U;\n    ascii_table[DIK_U].shift = KEY_UPPERCASE_U;\n    ascii_table[DIK_U].left_alt = KEY_ALT_U;\n    ascii_table[DIK_U].right_alt = KEY_ALT_U;\n    ascii_table[DIK_U].ctrl = KEY_CTRL_U;\n\n    ascii_table[DIK_I].normal = KEY_LOWERCASE_I;\n    ascii_table[DIK_I].shift = KEY_UPPERCASE_I;\n    ascii_table[DIK_I].left_alt = KEY_ALT_I;\n    ascii_table[DIK_I].right_alt = KEY_ALT_I;\n    ascii_table[DIK_I].ctrl = KEY_CTRL_I;\n\n    ascii_table[DIK_O].normal = KEY_LOWERCASE_O;\n    ascii_table[DIK_O].shift = KEY_UPPERCASE_O;\n    ascii_table[DIK_O].left_alt = KEY_ALT_O;\n    ascii_table[DIK_O].right_alt = KEY_ALT_O;\n    ascii_table[DIK_O].ctrl = KEY_CTRL_O;\n\n    ascii_table[DIK_P].normal = KEY_LOWERCASE_P;\n    ascii_table[DIK_P].shift = KEY_UPPERCASE_P;\n    ascii_table[DIK_P].left_alt = KEY_ALT_P;\n    ascii_table[DIK_P].right_alt = KEY_ALT_P;\n    ascii_table[DIK_P].ctrl = KEY_CTRL_P;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n    case KEYBOARD_LAYOUT_ITALIAN:\n    case KEYBOARD_LAYOUT_SPANISH:\n        k = DIK_LBRACKET;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_5;\n        break;\n    default:\n        k = DIK_8;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_BRACKET_LEFT;\n    ascii_table[k].shift = KEY_BRACE_LEFT;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n    case KEYBOARD_LAYOUT_ITALIAN:\n    case KEYBOARD_LAYOUT_SPANISH:\n        k = DIK_RBRACKET;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_MINUS;\n        break;\n    default:\n        k = DIK_9;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_BRACKET_RIGHT;\n    ascii_table[k].shift = KEY_BRACE_RIGHT;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_BACKSLASH;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_8;\n        break;\n    case KEYBOARD_LAYOUT_ITALIAN:\n    case KEYBOARD_LAYOUT_SPANISH:\n        k = DIK_GRAVE;\n        break;\n    default:\n        k = DIK_MINUS;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_BACKSLASH;\n    ascii_table[k].shift = KEY_BAR;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = KEY_CTRL_BACKSLASH;\n\n    ascii_table[DIK_CAPITAL].normal = -1;\n    ascii_table[DIK_CAPITAL].shift = -1;\n    ascii_table[DIK_CAPITAL].left_alt = -1;\n    ascii_table[DIK_CAPITAL].right_alt = -1;\n    ascii_table[DIK_CAPITAL].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_Q;\n        break;\n    default:\n        k = DIK_A;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_LOWERCASE_A;\n    ascii_table[k].shift = KEY_UPPERCASE_A;\n    ascii_table[k].left_alt = KEY_ALT_A;\n    ascii_table[k].right_alt = KEY_ALT_A;\n    ascii_table[k].ctrl = KEY_CTRL_A;\n\n    ascii_table[DIK_S].normal = KEY_LOWERCASE_S;\n    ascii_table[DIK_S].shift = KEY_UPPERCASE_S;\n    ascii_table[DIK_S].left_alt = KEY_ALT_S;\n    ascii_table[DIK_S].right_alt = KEY_ALT_S;\n    ascii_table[DIK_S].ctrl = KEY_CTRL_S;\n\n    ascii_table[DIK_D].normal = KEY_LOWERCASE_D;\n    ascii_table[DIK_D].shift = KEY_UPPERCASE_D;\n    ascii_table[DIK_D].left_alt = KEY_ALT_D;\n    ascii_table[DIK_D].right_alt = KEY_ALT_D;\n    ascii_table[DIK_D].ctrl = KEY_CTRL_D;\n\n    ascii_table[DIK_F].normal = KEY_LOWERCASE_F;\n    ascii_table[DIK_F].shift = KEY_UPPERCASE_F;\n    ascii_table[DIK_F].left_alt = KEY_ALT_F;\n    ascii_table[DIK_F].right_alt = KEY_ALT_F;\n    ascii_table[DIK_F].ctrl = KEY_CTRL_F;\n\n    ascii_table[DIK_G].normal = KEY_LOWERCASE_G;\n    ascii_table[DIK_G].shift = KEY_UPPERCASE_G;\n    ascii_table[DIK_G].left_alt = KEY_ALT_G;\n    ascii_table[DIK_G].right_alt = KEY_ALT_G;\n    ascii_table[DIK_G].ctrl = KEY_CTRL_G;\n\n    ascii_table[DIK_H].normal = KEY_LOWERCASE_H;\n    ascii_table[DIK_H].shift = KEY_UPPERCASE_H;\n    ascii_table[DIK_H].left_alt = KEY_ALT_H;\n    ascii_table[DIK_H].right_alt = KEY_ALT_H;\n    ascii_table[DIK_H].ctrl = KEY_CTRL_H;\n\n    ascii_table[DIK_J].normal = KEY_LOWERCASE_J;\n    ascii_table[DIK_J].shift = KEY_UPPERCASE_J;\n    ascii_table[DIK_J].left_alt = KEY_ALT_J;\n    ascii_table[DIK_J].right_alt = KEY_ALT_J;\n    ascii_table[DIK_J].ctrl = KEY_CTRL_J;\n\n    ascii_table[DIK_K].normal = KEY_LOWERCASE_K;\n    ascii_table[DIK_K].shift = KEY_UPPERCASE_K;\n    ascii_table[DIK_K].left_alt = KEY_ALT_K;\n    ascii_table[DIK_K].right_alt = KEY_ALT_K;\n    ascii_table[DIK_K].ctrl = KEY_CTRL_K;\n\n    ascii_table[DIK_L].normal = KEY_LOWERCASE_L;\n    ascii_table[DIK_L].shift = KEY_UPPERCASE_L;\n    ascii_table[DIK_L].left_alt = KEY_ALT_L;\n    ascii_table[DIK_L].right_alt = KEY_ALT_L;\n    ascii_table[DIK_L].ctrl = KEY_CTRL_L;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_SEMICOLON;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_SEMICOLON;\n    ascii_table[k].shift = KEY_COLON;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_APOSTROPHE;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_3;\n        break;\n    default:\n        k = DIK_2;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_SINGLE_QUOTE;\n    ascii_table[k].shift = KEY_QUOTE;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    ascii_table[DIK_RETURN].normal = KEY_RETURN;\n    ascii_table[DIK_RETURN].shift = KEY_RETURN;\n    ascii_table[DIK_RETURN].left_alt = KEY_RETURN;\n    ascii_table[DIK_RETURN].right_alt = KEY_RETURN;\n    ascii_table[DIK_RETURN].ctrl = KEY_CTRL_J;\n\n    ascii_table[DIK_LSHIFT].normal = -1;\n    ascii_table[DIK_LSHIFT].shift = -1;\n    ascii_table[DIK_LSHIFT].left_alt = -1;\n    ascii_table[DIK_LSHIFT].right_alt = -1;\n    ascii_table[DIK_LSHIFT].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n    case KEYBOARD_LAYOUT_ITALIAN:\n    case KEYBOARD_LAYOUT_SPANISH:\n        k = DIK_Z;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_W;\n        break;\n    default:\n        k = DIK_Y;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_LOWERCASE_Z;\n    ascii_table[k].shift = KEY_UPPERCASE_Z;\n    ascii_table[k].left_alt = KEY_ALT_Z;\n    ascii_table[k].right_alt = KEY_ALT_Z;\n    ascii_table[k].ctrl = KEY_CTRL_Z;\n\n    ascii_table[DIK_X].normal = KEY_LOWERCASE_X;\n    ascii_table[DIK_X].shift = KEY_UPPERCASE_X;\n    ascii_table[DIK_X].left_alt = KEY_ALT_X;\n    ascii_table[DIK_X].right_alt = KEY_ALT_X;\n    ascii_table[DIK_X].ctrl = KEY_CTRL_X;\n\n    ascii_table[DIK_C].normal = KEY_LOWERCASE_C;\n    ascii_table[DIK_C].shift = KEY_UPPERCASE_C;\n    ascii_table[DIK_C].left_alt = KEY_ALT_C;\n    ascii_table[DIK_C].right_alt = KEY_ALT_C;\n    ascii_table[DIK_C].ctrl = KEY_CTRL_C;\n\n    ascii_table[DIK_V].normal = KEY_LOWERCASE_V;\n    ascii_table[DIK_V].shift = KEY_UPPERCASE_V;\n    ascii_table[DIK_V].left_alt = KEY_ALT_V;\n    ascii_table[DIK_V].right_alt = KEY_ALT_V;\n    ascii_table[DIK_V].ctrl = KEY_CTRL_V;\n\n    ascii_table[DIK_B].normal = KEY_LOWERCASE_B;\n    ascii_table[DIK_B].shift = KEY_UPPERCASE_B;\n    ascii_table[DIK_B].left_alt = KEY_ALT_B;\n    ascii_table[DIK_B].right_alt = KEY_ALT_B;\n    ascii_table[DIK_B].ctrl = KEY_CTRL_B;\n\n    ascii_table[DIK_N].normal = KEY_LOWERCASE_N;\n    ascii_table[DIK_N].shift = KEY_UPPERCASE_N;\n    ascii_table[DIK_N].left_alt = KEY_ALT_N;\n    ascii_table[DIK_N].right_alt = KEY_ALT_N;\n    ascii_table[DIK_N].ctrl = KEY_CTRL_N;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_SEMICOLON;\n        break;\n    default:\n        k = DIK_M;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_LOWERCASE_M;\n    ascii_table[k].shift = KEY_UPPERCASE_M;\n    ascii_table[k].left_alt = KEY_ALT_M;\n    ascii_table[k].right_alt = KEY_ALT_M;\n    ascii_table[k].ctrl = KEY_CTRL_M;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_M;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_COMMA;\n    ascii_table[k].shift = KEY_LESS;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_COMMA;\n        break;\n    default:\n        k = DIK_PERIOD;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_DOT;\n    ascii_table[k].shift = KEY_GREATER;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_SLASH;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_PERIOD;\n        break;\n    default:\n        k = DIK_7;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_SLASH;\n    ascii_table[k].shift = KEY_QUESTION;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    ascii_table[DIK_RSHIFT].normal = -1;\n    ascii_table[DIK_RSHIFT].shift = -1;\n    ascii_table[DIK_RSHIFT].left_alt = -1;\n    ascii_table[DIK_RSHIFT].right_alt = -1;\n    ascii_table[DIK_RSHIFT].ctrl = -1;\n\n    ascii_table[DIK_LCONTROL].normal = -1;\n    ascii_table[DIK_LCONTROL].shift = -1;\n    ascii_table[DIK_LCONTROL].left_alt = -1;\n    ascii_table[DIK_LCONTROL].right_alt = -1;\n    ascii_table[DIK_LCONTROL].ctrl = -1;\n\n    ascii_table[DIK_LMENU].normal = -1;\n    ascii_table[DIK_LMENU].shift = -1;\n    ascii_table[DIK_LMENU].left_alt = -1;\n    ascii_table[DIK_LMENU].right_alt = -1;\n    ascii_table[DIK_LMENU].ctrl = -1;\n\n    ascii_table[DIK_SPACE].normal = KEY_SPACE;\n    ascii_table[DIK_SPACE].shift = KEY_SPACE;\n    ascii_table[DIK_SPACE].left_alt = KEY_SPACE;\n    ascii_table[DIK_SPACE].right_alt = KEY_SPACE;\n    ascii_table[DIK_SPACE].ctrl = KEY_SPACE;\n\n    ascii_table[DIK_RMENU].normal = -1;\n    ascii_table[DIK_RMENU].shift = -1;\n    ascii_table[DIK_RMENU].left_alt = -1;\n    ascii_table[DIK_RMENU].right_alt = -1;\n    ascii_table[DIK_RMENU].ctrl = -1;\n\n    ascii_table[DIK_RCONTROL].normal = -1;\n    ascii_table[DIK_RCONTROL].shift = -1;\n    ascii_table[DIK_RCONTROL].left_alt = -1;\n    ascii_table[DIK_RCONTROL].right_alt = -1;\n    ascii_table[DIK_RCONTROL].ctrl = -1;\n\n    ascii_table[DIK_INSERT].normal = KEY_INSERT;\n    ascii_table[DIK_INSERT].shift = KEY_INSERT;\n    ascii_table[DIK_INSERT].left_alt = KEY_ALT_INSERT;\n    ascii_table[DIK_INSERT].right_alt = KEY_ALT_INSERT;\n    ascii_table[DIK_INSERT].ctrl = KEY_CTRL_INSERT;\n\n    ascii_table[DIK_HOME].normal = KEY_HOME;\n    ascii_table[DIK_HOME].shift = KEY_HOME;\n    ascii_table[DIK_HOME].left_alt = KEY_ALT_HOME;\n    ascii_table[DIK_HOME].right_alt = KEY_ALT_HOME;\n    ascii_table[DIK_HOME].ctrl = KEY_CTRL_HOME;\n\n    ascii_table[DIK_PRIOR].normal = KEY_PAGE_UP;\n    ascii_table[DIK_PRIOR].shift = KEY_PAGE_UP;\n    ascii_table[DIK_PRIOR].left_alt = KEY_ALT_PAGE_UP;\n    ascii_table[DIK_PRIOR].right_alt = KEY_ALT_PAGE_UP;\n    ascii_table[DIK_PRIOR].ctrl = KEY_CTRL_PAGE_UP;\n\n    ascii_table[DIK_DELETE].normal = KEY_DELETE;\n    ascii_table[DIK_DELETE].shift = KEY_DELETE;\n    ascii_table[DIK_DELETE].left_alt = KEY_ALT_DELETE;\n    ascii_table[DIK_DELETE].right_alt = KEY_ALT_DELETE;\n    ascii_table[DIK_DELETE].ctrl = KEY_CTRL_DELETE;\n\n    ascii_table[DIK_END].normal = KEY_END;\n    ascii_table[DIK_END].shift = KEY_END;\n    ascii_table[DIK_END].left_alt = KEY_ALT_END;\n    ascii_table[DIK_END].right_alt = KEY_ALT_END;\n    ascii_table[DIK_END].ctrl = KEY_CTRL_END;\n\n    ascii_table[DIK_NEXT].normal = KEY_PAGE_DOWN;\n    ascii_table[DIK_NEXT].shift = KEY_PAGE_DOWN;\n    ascii_table[DIK_NEXT].left_alt = KEY_ALT_PAGE_DOWN;\n    ascii_table[DIK_NEXT].right_alt = KEY_ALT_PAGE_DOWN;\n    ascii_table[DIK_NEXT].ctrl = KEY_CTRL_PAGE_DOWN;\n\n    ascii_table[DIK_UP].normal = KEY_ARROW_UP;\n    ascii_table[DIK_UP].shift = KEY_ARROW_UP;\n    ascii_table[DIK_UP].left_alt = KEY_ALT_ARROW_UP;\n    ascii_table[DIK_UP].right_alt = KEY_ALT_ARROW_UP;\n    ascii_table[DIK_UP].ctrl = KEY_CTRL_ARROW_UP;\n\n    ascii_table[DIK_DOWN].normal = KEY_ARROW_DOWN;\n    ascii_table[DIK_DOWN].shift = KEY_ARROW_DOWN;\n    ascii_table[DIK_DOWN].left_alt = KEY_ALT_ARROW_DOWN;\n    ascii_table[DIK_DOWN].right_alt = KEY_ALT_ARROW_DOWN;\n    ascii_table[DIK_DOWN].ctrl = KEY_CTRL_ARROW_DOWN;\n\n    ascii_table[DIK_LEFT].normal = KEY_ARROW_LEFT;\n    ascii_table[DIK_LEFT].shift = KEY_ARROW_LEFT;\n    ascii_table[DIK_LEFT].left_alt = KEY_ALT_ARROW_LEFT;\n    ascii_table[DIK_LEFT].right_alt = KEY_ALT_ARROW_LEFT;\n    ascii_table[DIK_LEFT].ctrl = KEY_CTRL_ARROW_LEFT;\n\n    ascii_table[DIK_RIGHT].normal = KEY_ARROW_RIGHT;\n    ascii_table[DIK_RIGHT].shift = KEY_ARROW_RIGHT;\n    ascii_table[DIK_RIGHT].left_alt = KEY_ALT_ARROW_RIGHT;\n    ascii_table[DIK_RIGHT].right_alt = KEY_ALT_ARROW_RIGHT;\n    ascii_table[DIK_RIGHT].ctrl = KEY_CTRL_ARROW_RIGHT;\n\n    ascii_table[DIK_NUMLOCK].normal = -1;\n    ascii_table[DIK_NUMLOCK].shift = -1;\n    ascii_table[DIK_NUMLOCK].left_alt = -1;\n    ascii_table[DIK_NUMLOCK].right_alt = -1;\n    ascii_table[DIK_NUMLOCK].ctrl = -1;\n\n    ascii_table[DIK_DIVIDE].normal = KEY_SLASH;\n    ascii_table[DIK_DIVIDE].shift = KEY_SLASH;\n    ascii_table[DIK_DIVIDE].left_alt = -1;\n    ascii_table[DIK_DIVIDE].right_alt = -1;\n    ascii_table[DIK_DIVIDE].ctrl = 3;\n\n    ascii_table[DIK_MULTIPLY].normal = KEY_ASTERISK;\n    ascii_table[DIK_MULTIPLY].shift = KEY_ASTERISK;\n    ascii_table[DIK_MULTIPLY].left_alt = -1;\n    ascii_table[DIK_MULTIPLY].right_alt = -1;\n    ascii_table[DIK_MULTIPLY].ctrl = -1;\n\n    ascii_table[DIK_SUBTRACT].normal = KEY_MINUS;\n    ascii_table[DIK_SUBTRACT].shift = KEY_MINUS;\n    ascii_table[DIK_SUBTRACT].left_alt = -1;\n    ascii_table[DIK_SUBTRACT].right_alt = -1;\n    ascii_table[DIK_SUBTRACT].ctrl = -1;\n\n    ascii_table[DIK_NUMPAD7].normal = KEY_HOME;\n    ascii_table[DIK_NUMPAD7].shift = KEY_7;\n    ascii_table[DIK_NUMPAD7].left_alt = KEY_ALT_HOME;\n    ascii_table[DIK_NUMPAD7].right_alt = KEY_ALT_HOME;\n    ascii_table[DIK_NUMPAD7].ctrl = KEY_CTRL_HOME;\n\n    ascii_table[DIK_NUMPAD8].normal = KEY_ARROW_UP;\n    ascii_table[DIK_NUMPAD8].shift = KEY_8;\n    ascii_table[DIK_NUMPAD8].left_alt = KEY_ALT_ARROW_UP;\n    ascii_table[DIK_NUMPAD8].right_alt = KEY_ALT_ARROW_UP;\n    ascii_table[DIK_NUMPAD8].ctrl = KEY_CTRL_ARROW_UP;\n\n    ascii_table[DIK_NUMPAD9].normal = KEY_PAGE_UP;\n    ascii_table[DIK_NUMPAD9].shift = KEY_9;\n    ascii_table[DIK_NUMPAD9].left_alt = KEY_ALT_PAGE_UP;\n    ascii_table[DIK_NUMPAD9].right_alt = KEY_ALT_PAGE_UP;\n    ascii_table[DIK_NUMPAD9].ctrl = KEY_CTRL_PAGE_UP;\n\n    ascii_table[DIK_ADD].normal = KEY_PLUS;\n    ascii_table[DIK_ADD].shift = KEY_PLUS;\n    ascii_table[DIK_ADD].left_alt = -1;\n    ascii_table[DIK_ADD].right_alt = -1;\n    ascii_table[DIK_ADD].ctrl = -1;\n\n    ascii_table[DIK_NUMPAD4].normal = KEY_ARROW_LEFT;\n    ascii_table[DIK_NUMPAD4].shift = KEY_4;\n    ascii_table[DIK_NUMPAD4].left_alt = KEY_ALT_ARROW_LEFT;\n    ascii_table[DIK_NUMPAD4].right_alt = KEY_ALT_ARROW_LEFT;\n    ascii_table[DIK_NUMPAD4].ctrl = KEY_CTRL_ARROW_LEFT;\n\n    ascii_table[DIK_NUMPAD5].normal = KEY_NUMBERPAD_5;\n    ascii_table[DIK_NUMPAD5].shift = KEY_5;\n    ascii_table[DIK_NUMPAD5].left_alt = KEY_ALT_NUMBERPAD_5;\n    ascii_table[DIK_NUMPAD5].right_alt = KEY_ALT_NUMBERPAD_5;\n    ascii_table[DIK_NUMPAD5].ctrl = KEY_CTRL_NUMBERPAD_5;\n\n    ascii_table[DIK_NUMPAD6].normal = KEY_ARROW_RIGHT;\n    ascii_table[DIK_NUMPAD6].shift = KEY_6;\n    ascii_table[DIK_NUMPAD6].left_alt = KEY_ALT_ARROW_RIGHT;\n    ascii_table[DIK_NUMPAD6].right_alt = KEY_ALT_ARROW_RIGHT;\n    ascii_table[DIK_NUMPAD6].ctrl = KEY_CTRL_ARROW_RIGHT;\n\n    ascii_table[DIK_NUMPAD1].normal = KEY_END;\n    ascii_table[DIK_NUMPAD1].shift = KEY_1;\n    ascii_table[DIK_NUMPAD1].left_alt = KEY_ALT_END;\n    ascii_table[DIK_NUMPAD1].right_alt = KEY_ALT_END;\n    ascii_table[DIK_NUMPAD1].ctrl = KEY_CTRL_END;\n\n    ascii_table[DIK_NUMPAD2].normal = KEY_ARROW_DOWN;\n    ascii_table[DIK_NUMPAD2].shift = KEY_2;\n    ascii_table[DIK_NUMPAD2].left_alt = KEY_ALT_ARROW_DOWN;\n    ascii_table[DIK_NUMPAD2].right_alt = KEY_ALT_ARROW_DOWN;\n    ascii_table[DIK_NUMPAD2].ctrl = KEY_CTRL_ARROW_DOWN;\n\n    ascii_table[DIK_NUMPAD3].normal = KEY_PAGE_DOWN;\n    ascii_table[DIK_NUMPAD3].shift = KEY_3;\n    ascii_table[DIK_NUMPAD3].left_alt = KEY_ALT_PAGE_DOWN;\n    ascii_table[DIK_NUMPAD3].right_alt = KEY_ALT_PAGE_DOWN;\n    ascii_table[DIK_NUMPAD3].ctrl = KEY_CTRL_PAGE_DOWN;\n\n    ascii_table[DIK_NUMPADENTER].normal = KEY_RETURN;\n    ascii_table[DIK_NUMPADENTER].shift = KEY_RETURN;\n    ascii_table[DIK_NUMPADENTER].left_alt = -1;\n    ascii_table[DIK_NUMPADENTER].right_alt = -1;\n    ascii_table[DIK_NUMPADENTER].ctrl = -1;\n\n    ascii_table[DIK_NUMPAD0].normal = KEY_INSERT;\n    ascii_table[DIK_NUMPAD0].shift = KEY_0;\n    ascii_table[DIK_NUMPAD0].left_alt = KEY_ALT_INSERT;\n    ascii_table[DIK_NUMPAD0].right_alt = KEY_ALT_INSERT;\n    ascii_table[DIK_NUMPAD0].ctrl = KEY_CTRL_INSERT;\n\n    ascii_table[DIK_DECIMAL].normal = KEY_DELETE;\n    ascii_table[DIK_DECIMAL].shift = KEY_DOT;\n    ascii_table[DIK_DECIMAL].left_alt = -1;\n    ascii_table[DIK_DECIMAL].right_alt = KEY_ALT_DELETE;\n    ascii_table[DIK_DECIMAL].ctrl = KEY_CTRL_DELETE;\n}\n\n// 0x4D0400\nstatic void kb_map_ascii_French()\n{\n    int k;\n\n    kb_map_ascii_English_US();\n\n    ascii_table[DIK_GRAVE].normal = KEY_178;\n    ascii_table[DIK_GRAVE].shift = -1;\n    ascii_table[DIK_GRAVE].left_alt = -1;\n    ascii_table[DIK_GRAVE].right_alt = -1;\n    ascii_table[DIK_GRAVE].ctrl = -1;\n\n    ascii_table[DIK_1].normal = KEY_AMPERSAND;\n    ascii_table[DIK_1].shift = KEY_1;\n    ascii_table[DIK_1].left_alt = -1;\n    ascii_table[DIK_1].right_alt = -1;\n    ascii_table[DIK_1].ctrl = -1;\n\n    ascii_table[DIK_2].normal = KEY_233;\n    ascii_table[DIK_2].shift = KEY_2;\n    ascii_table[DIK_2].left_alt = -1;\n    ascii_table[DIK_2].right_alt = KEY_152;\n    ascii_table[DIK_2].ctrl = -1;\n\n    ascii_table[DIK_3].normal = KEY_QUOTE;\n    ascii_table[DIK_3].shift = KEY_3;\n    ascii_table[DIK_3].left_alt = -1;\n    ascii_table[DIK_3].right_alt = KEY_NUMBER_SIGN;\n    ascii_table[DIK_3].ctrl = -1;\n\n    ascii_table[DIK_4].normal = KEY_SINGLE_QUOTE;\n    ascii_table[DIK_4].shift = KEY_4;\n    ascii_table[DIK_4].left_alt = -1;\n    ascii_table[DIK_4].right_alt = KEY_BRACE_LEFT;\n    ascii_table[DIK_4].ctrl = -1;\n\n    ascii_table[DIK_5].normal = KEY_PAREN_LEFT;\n    ascii_table[DIK_5].shift = KEY_5;\n    ascii_table[DIK_5].left_alt = -1;\n    ascii_table[DIK_5].right_alt = KEY_BRACKET_LEFT;\n    ascii_table[DIK_5].ctrl = -1;\n\n    ascii_table[DIK_6].normal = KEY_150;\n    ascii_table[DIK_6].shift = KEY_6;\n    ascii_table[DIK_6].left_alt = -1;\n    ascii_table[DIK_6].right_alt = KEY_166;\n    ascii_table[DIK_6].ctrl = -1;\n\n    ascii_table[DIK_7].normal = KEY_232;\n    ascii_table[DIK_7].shift = KEY_7;\n    ascii_table[DIK_7].left_alt = -1;\n    ascii_table[DIK_7].right_alt = KEY_GRAVE;\n    ascii_table[DIK_7].ctrl = -1;\n\n    ascii_table[DIK_8].normal = KEY_UNDERSCORE;\n    ascii_table[DIK_8].shift = KEY_8;\n    ascii_table[DIK_8].left_alt = -1;\n    ascii_table[DIK_8].right_alt = KEY_BACKSLASH;\n    ascii_table[DIK_8].ctrl = -1;\n\n    ascii_table[DIK_9].normal = KEY_231;\n    ascii_table[DIK_9].shift = KEY_9;\n    ascii_table[DIK_9].left_alt = -1;\n    ascii_table[DIK_9].right_alt = KEY_136;\n    ascii_table[DIK_9].ctrl = -1;\n\n    ascii_table[DIK_0].normal = KEY_224;\n    ascii_table[DIK_0].shift = KEY_0;\n    ascii_table[DIK_0].left_alt = -1;\n    ascii_table[DIK_0].right_alt = KEY_AT;\n    ascii_table[DIK_0].ctrl = -1;\n\n    ascii_table[DIK_MINUS].normal = KEY_PAREN_RIGHT;\n    ascii_table[DIK_MINUS].shift = KEY_176;\n    ascii_table[DIK_MINUS].left_alt = -1;\n    ascii_table[DIK_MINUS].right_alt = KEY_BRACKET_RIGHT;\n    ascii_table[DIK_MINUS].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_EQUALS;\n        break;\n    default:\n        k = DIK_0;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_EQUAL;\n    ascii_table[k].shift = KEY_PLUS;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = KEY_BRACE_RIGHT;\n    ascii_table[k].ctrl = -1;\n\n    ascii_table[DIK_LBRACKET].normal = KEY_136;\n    ascii_table[DIK_LBRACKET].shift = KEY_168;\n    ascii_table[DIK_LBRACKET].left_alt = -1;\n    ascii_table[DIK_LBRACKET].right_alt = -1;\n    ascii_table[DIK_LBRACKET].ctrl = -1;\n\n    ascii_table[DIK_RBRACKET].normal = KEY_DOLLAR;\n    ascii_table[DIK_RBRACKET].shift = KEY_163;\n    ascii_table[DIK_RBRACKET].left_alt = -1;\n    ascii_table[DIK_RBRACKET].right_alt = KEY_164;\n    ascii_table[DIK_RBRACKET].ctrl = -1;\n\n    ascii_table[DIK_APOSTROPHE].normal = KEY_249;\n    ascii_table[DIK_APOSTROPHE].shift = KEY_PERCENT;\n    ascii_table[DIK_APOSTROPHE].left_alt = -1;\n    ascii_table[DIK_APOSTROPHE].right_alt = -1;\n    ascii_table[DIK_APOSTROPHE].ctrl = -1;\n\n    ascii_table[DIK_BACKSLASH].normal = KEY_ASTERISK;\n    ascii_table[DIK_BACKSLASH].shift = KEY_181;\n    ascii_table[DIK_BACKSLASH].left_alt = -1;\n    ascii_table[DIK_BACKSLASH].right_alt = -1;\n    ascii_table[DIK_BACKSLASH].ctrl = -1;\n\n    ascii_table[DIK_OEM_102].normal = KEY_LESS;\n    ascii_table[DIK_OEM_102].shift = KEY_GREATER;\n    ascii_table[DIK_OEM_102].left_alt = -1;\n    ascii_table[DIK_OEM_102].right_alt = -1;\n    ascii_table[DIK_OEM_102].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_M;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_COMMA;\n    ascii_table[k].shift = KEY_QUESTION;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_SEMICOLON;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_SEMICOLON;\n    ascii_table[k].shift = KEY_DOT;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        // FIXME: Probably error, maps semicolon to colon on QWERTY keyboards.\n        // Semicolon is already mapped above, so I bet it should be DIK_COLON.\n        k = DIK_SEMICOLON;\n        break;\n    default:\n        k = DIK_PERIOD;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_COLON;\n    ascii_table[k].shift = KEY_SLASH;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    ascii_table[DIK_SLASH].normal = KEY_EXCLAMATION;\n    ascii_table[DIK_SLASH].shift = KEY_167;\n    ascii_table[DIK_SLASH].left_alt = -1;\n    ascii_table[DIK_SLASH].right_alt = -1;\n    ascii_table[DIK_SLASH].ctrl = -1;\n}\n\n// 0x4D0C54\nstatic void kb_map_ascii_German()\n{\n    int k;\n\n    kb_map_ascii_English_US();\n\n    ascii_table[DIK_GRAVE].normal = KEY_136;\n    ascii_table[DIK_GRAVE].shift = KEY_186;\n    ascii_table[DIK_GRAVE].left_alt = -1;\n    ascii_table[DIK_GRAVE].right_alt = -1;\n    ascii_table[DIK_GRAVE].ctrl = -1;\n\n    ascii_table[DIK_2].normal = KEY_2;\n    ascii_table[DIK_2].shift = KEY_QUOTE;\n    ascii_table[DIK_2].left_alt = -1;\n    ascii_table[DIK_2].right_alt = KEY_178;\n    ascii_table[DIK_2].ctrl = -1;\n\n    ascii_table[DIK_3].normal = KEY_3;\n    ascii_table[DIK_3].shift = KEY_167;\n    ascii_table[DIK_3].left_alt = -1;\n    ascii_table[DIK_3].right_alt = KEY_179;\n    ascii_table[DIK_3].ctrl = -1;\n\n    ascii_table[DIK_6].normal = KEY_6;\n    ascii_table[DIK_6].shift = KEY_AMPERSAND;\n    ascii_table[DIK_6].left_alt = -1;\n    ascii_table[DIK_6].right_alt = -1;\n    ascii_table[DIK_6].ctrl = -1;\n\n    ascii_table[DIK_7].normal = KEY_7;\n    ascii_table[DIK_7].shift = KEY_166;\n    ascii_table[DIK_7].left_alt = -1;\n    ascii_table[DIK_7].right_alt = KEY_BRACE_LEFT;\n    ascii_table[DIK_7].ctrl = -1;\n\n    ascii_table[DIK_8].normal = KEY_8;\n    ascii_table[DIK_8].shift = KEY_PAREN_LEFT;\n    ascii_table[DIK_8].left_alt = -1;\n    ascii_table[DIK_8].right_alt = KEY_BRACKET_LEFT;\n    ascii_table[DIK_8].ctrl = -1;\n\n    ascii_table[DIK_9].normal = KEY_9;\n    ascii_table[DIK_9].shift = KEY_PAREN_RIGHT;\n    ascii_table[DIK_9].left_alt = -1;\n    ascii_table[DIK_9].right_alt = KEY_BRACKET_RIGHT;\n    ascii_table[DIK_9].ctrl = -1;\n\n    ascii_table[DIK_0].normal = KEY_0;\n    ascii_table[DIK_0].shift = KEY_EQUAL;\n    ascii_table[DIK_0].left_alt = -1;\n    ascii_table[DIK_0].right_alt = KEY_BRACE_RIGHT;\n    ascii_table[DIK_0].ctrl = -1;\n\n    ascii_table[DIK_MINUS].normal = KEY_223;\n    ascii_table[DIK_MINUS].shift = KEY_QUESTION;\n    ascii_table[DIK_MINUS].left_alt = -1;\n    ascii_table[DIK_MINUS].right_alt = KEY_BACKSLASH;\n    ascii_table[DIK_MINUS].ctrl = -1;\n\n    ascii_table[DIK_EQUALS].normal = KEY_180;\n    ascii_table[DIK_EQUALS].shift = KEY_GRAVE;\n    ascii_table[DIK_EQUALS].left_alt = -1;\n    ascii_table[DIK_EQUALS].right_alt = -1;\n    ascii_table[DIK_EQUALS].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_A;\n        break;\n    default:\n        k = DIK_Q;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_LOWERCASE_Q;\n    ascii_table[k].shift = KEY_UPPERCASE_Q;\n    ascii_table[k].left_alt = KEY_ALT_Q;\n    ascii_table[k].right_alt = KEY_AT;\n    ascii_table[k].ctrl = KEY_CTRL_Q;\n\n    ascii_table[DIK_LBRACKET].normal = KEY_252;\n    ascii_table[DIK_LBRACKET].shift = KEY_220;\n    ascii_table[DIK_LBRACKET].left_alt = -1;\n    ascii_table[DIK_LBRACKET].right_alt = -1;\n    ascii_table[DIK_LBRACKET].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_EQUALS;\n        break;\n    default:\n        k = DIK_RBRACKET;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_PLUS;\n    ascii_table[k].shift = KEY_ASTERISK;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = KEY_152;\n    ascii_table[k].ctrl = -1;\n\n    ascii_table[DIK_SEMICOLON].normal = KEY_246;\n    ascii_table[DIK_SEMICOLON].shift = KEY_214;\n    ascii_table[DIK_SEMICOLON].left_alt = -1;\n    ascii_table[DIK_SEMICOLON].right_alt = -1;\n    ascii_table[DIK_SEMICOLON].ctrl = -1;\n\n    ascii_table[DIK_APOSTROPHE].normal = KEY_228;\n    ascii_table[DIK_APOSTROPHE].shift = KEY_196;\n    ascii_table[DIK_APOSTROPHE].left_alt = -1;\n    ascii_table[DIK_APOSTROPHE].right_alt = -1;\n    ascii_table[DIK_APOSTROPHE].ctrl = -1;\n\n    ascii_table[DIK_BACKSLASH].normal = KEY_NUMBER_SIGN;\n    ascii_table[DIK_BACKSLASH].shift = KEY_SINGLE_QUOTE;\n    ascii_table[DIK_BACKSLASH].left_alt = -1;\n    ascii_table[DIK_BACKSLASH].right_alt = -1;\n    ascii_table[DIK_BACKSLASH].ctrl = -1;\n\n    ascii_table[DIK_OEM_102].normal = KEY_LESS;\n    ascii_table[DIK_OEM_102].shift = KEY_GREATER;\n    ascii_table[DIK_OEM_102].left_alt = -1;\n    ascii_table[DIK_OEM_102].right_alt = KEY_166;\n    ascii_table[DIK_OEM_102].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_SEMICOLON;\n        break;\n    default:\n        k = DIK_M;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_LOWERCASE_M;\n    ascii_table[k].shift = KEY_UPPERCASE_M;\n    ascii_table[k].left_alt = KEY_ALT_M;\n    ascii_table[k].right_alt = KEY_181;\n    ascii_table[k].ctrl = KEY_CTRL_M;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_M;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_COMMA;\n    ascii_table[k].shift = KEY_SEMICOLON;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_COMMA;\n        break;\n    default:\n        k = DIK_PERIOD;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_DOT;\n    ascii_table[k].shift = KEY_COLON;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_MINUS;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_6;\n        break;\n    default:\n        k = DIK_SLASH;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_150;\n    ascii_table[k].shift = KEY_151;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    ascii_table[DIK_DIVIDE].normal = KEY_247;\n    ascii_table[DIK_DIVIDE].shift = KEY_247;\n    ascii_table[DIK_DIVIDE].left_alt = -1;\n    ascii_table[DIK_DIVIDE].right_alt = -1;\n    ascii_table[DIK_DIVIDE].ctrl = -1;\n\n    ascii_table[DIK_MULTIPLY].normal = KEY_215;\n    ascii_table[DIK_MULTIPLY].shift = KEY_215;\n    ascii_table[DIK_MULTIPLY].left_alt = -1;\n    ascii_table[DIK_MULTIPLY].right_alt = -1;\n    ascii_table[DIK_MULTIPLY].ctrl = -1;\n\n    ascii_table[DIK_DECIMAL].normal = KEY_DELETE;\n    ascii_table[DIK_DECIMAL].shift = KEY_COMMA;\n    ascii_table[DIK_DECIMAL].left_alt = -1;\n    ascii_table[DIK_DECIMAL].right_alt = KEY_ALT_DELETE;\n    ascii_table[DIK_DECIMAL].ctrl = KEY_CTRL_DELETE;\n}\n\n// 0x4D1758\nstatic void kb_map_ascii_Italian()\n{\n    int k;\n\n    kb_map_ascii_English_US();\n\n    ascii_table[DIK_GRAVE].normal = KEY_BACKSLASH;\n    ascii_table[DIK_GRAVE].shift = KEY_BAR;\n    ascii_table[DIK_GRAVE].left_alt = -1;\n    ascii_table[DIK_GRAVE].right_alt = -1;\n    ascii_table[DIK_GRAVE].ctrl = -1;\n\n    ascii_table[DIK_OEM_102].normal = KEY_LESS;\n    ascii_table[DIK_OEM_102].shift = KEY_GREATER;\n    ascii_table[DIK_OEM_102].left_alt = -1;\n    ascii_table[DIK_OEM_102].right_alt = -1;\n    ascii_table[DIK_OEM_102].ctrl = -1;\n\n    ascii_table[DIK_1].normal = KEY_1;\n    ascii_table[DIK_1].shift = KEY_EXCLAMATION;\n    ascii_table[DIK_1].left_alt = -1;\n    ascii_table[DIK_1].right_alt = -1;\n    ascii_table[DIK_1].ctrl = -1;\n\n    ascii_table[DIK_2].normal = KEY_2;\n    ascii_table[DIK_2].shift = KEY_QUOTE;\n    ascii_table[DIK_2].left_alt = -1;\n    ascii_table[DIK_2].right_alt = -1;\n    ascii_table[DIK_2].ctrl = -1;\n\n    ascii_table[DIK_3].normal = KEY_3;\n    ascii_table[DIK_3].shift = KEY_163;\n    ascii_table[DIK_3].left_alt = -1;\n    ascii_table[DIK_3].right_alt = -1;\n    ascii_table[DIK_3].ctrl = -1;\n\n    ascii_table[DIK_6].normal = KEY_6;\n    ascii_table[DIK_6].shift = KEY_AMPERSAND;\n    ascii_table[DIK_6].left_alt = -1;\n    ascii_table[DIK_6].right_alt = -1;\n    ascii_table[DIK_6].ctrl = -1;\n\n    ascii_table[DIK_7].normal = KEY_7;\n    ascii_table[DIK_7].shift = KEY_SLASH;\n    ascii_table[DIK_7].left_alt = -1;\n    ascii_table[DIK_7].right_alt = -1;\n    ascii_table[DIK_7].ctrl = -1;\n\n    ascii_table[DIK_8].normal = KEY_8;\n    ascii_table[DIK_8].shift = KEY_PAREN_LEFT;\n    ascii_table[DIK_8].left_alt = -1;\n    ascii_table[DIK_8].right_alt = -1;\n    ascii_table[DIK_8].ctrl = -1;\n\n    ascii_table[DIK_9].normal = KEY_9;\n    ascii_table[DIK_9].shift = KEY_PAREN_RIGHT;\n    ascii_table[DIK_9].left_alt = -1;\n    ascii_table[DIK_9].right_alt = -1;\n    ascii_table[DIK_9].ctrl = -1;\n\n    ascii_table[DIK_0].normal = KEY_0;\n    ascii_table[DIK_0].shift = KEY_EQUAL;\n    ascii_table[DIK_0].left_alt = -1;\n    ascii_table[DIK_0].right_alt = -1;\n    ascii_table[DIK_0].ctrl = -1;\n\n    ascii_table[DIK_MINUS].normal = KEY_SINGLE_QUOTE;\n    ascii_table[DIK_MINUS].shift = KEY_QUESTION;\n    ascii_table[DIK_MINUS].left_alt = -1;\n    ascii_table[DIK_MINUS].right_alt = -1;\n    ascii_table[DIK_MINUS].ctrl = -1;\n\n    ascii_table[DIK_LBRACKET].normal = KEY_232;\n    ascii_table[DIK_LBRACKET].shift = KEY_233;\n    ascii_table[DIK_LBRACKET].left_alt = -1;\n    ascii_table[DIK_LBRACKET].right_alt = KEY_BRACKET_LEFT;\n    ascii_table[DIK_LBRACKET].ctrl = -1;\n\n    ascii_table[DIK_RBRACKET].normal = KEY_PLUS;\n    ascii_table[DIK_RBRACKET].shift = KEY_ASTERISK;\n    ascii_table[DIK_RBRACKET].left_alt = -1;\n    ascii_table[DIK_RBRACKET].right_alt = KEY_BRACKET_RIGHT;\n    ascii_table[DIK_RBRACKET].ctrl = -1;\n\n    ascii_table[DIK_BACKSLASH].normal = KEY_249;\n    ascii_table[DIK_BACKSLASH].shift = KEY_167;\n    ascii_table[DIK_BACKSLASH].left_alt = -1;\n    ascii_table[DIK_BACKSLASH].right_alt = KEY_BRACKET_RIGHT;\n    ascii_table[DIK_BACKSLASH].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_M;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_COMMA;\n    ascii_table[k].shift = KEY_SEMICOLON;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_COMMA;\n        break;\n    default:\n        k = DIK_PERIOD;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_DOT;\n    ascii_table[k].shift = KEY_COLON;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_MINUS;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_6;\n        break;\n    default:\n        k = DIK_SLASH;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_MINUS;\n    ascii_table[k].shift = KEY_UNDERSCORE;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n}\n\n// 0x4D1E24\nstatic void kb_map_ascii_Spanish()\n{\n    int k;\n\n    kb_map_ascii_English_US();\n\n    ascii_table[DIK_1].normal = KEY_1;\n    ascii_table[DIK_1].shift = KEY_EXCLAMATION;\n    ascii_table[DIK_1].left_alt = -1;\n    ascii_table[DIK_1].right_alt = KEY_BAR;\n    ascii_table[DIK_1].ctrl = -1;\n\n    ascii_table[DIK_2].normal = KEY_2;\n    ascii_table[DIK_2].shift = KEY_QUOTE;\n    ascii_table[DIK_2].left_alt = -1;\n    ascii_table[DIK_2].right_alt = KEY_AT;\n    ascii_table[DIK_2].ctrl = -1;\n\n    ascii_table[DIK_3].normal = KEY_3;\n    ascii_table[DIK_3].shift = KEY_149;\n    ascii_table[DIK_3].left_alt = -1;\n    ascii_table[DIK_3].right_alt = KEY_NUMBER_SIGN;\n    ascii_table[DIK_3].ctrl = -1;\n\n    ascii_table[DIK_6].normal = KEY_6;\n    ascii_table[DIK_6].shift = KEY_AMPERSAND;\n    ascii_table[DIK_6].left_alt = -1;\n    ascii_table[DIK_6].right_alt = KEY_172;\n    ascii_table[DIK_6].ctrl = -1;\n\n    ascii_table[DIK_7].normal = KEY_7;\n    ascii_table[DIK_7].shift = KEY_SLASH;\n    ascii_table[DIK_7].left_alt = -1;\n    ascii_table[DIK_7].right_alt = -1;\n    ascii_table[DIK_7].ctrl = -1;\n\n    ascii_table[DIK_8].normal = KEY_8;\n    ascii_table[DIK_8].shift = KEY_PAREN_LEFT;\n    ascii_table[DIK_8].left_alt = -1;\n    ascii_table[DIK_8].right_alt = -1;\n    ascii_table[DIK_8].ctrl = -1;\n\n    ascii_table[DIK_9].normal = KEY_9;\n    ascii_table[DIK_9].shift = KEY_PAREN_RIGHT;\n    ascii_table[DIK_9].left_alt = -1;\n    ascii_table[DIK_9].right_alt = -1;\n    ascii_table[DIK_9].ctrl = -1;\n\n    ascii_table[DIK_0].normal = KEY_0;\n    ascii_table[DIK_0].shift = KEY_EQUAL;\n    ascii_table[DIK_0].left_alt = -1;\n    ascii_table[DIK_0].right_alt = -1;\n    ascii_table[DIK_0].ctrl = -1;\n\n    ascii_table[DIK_MINUS].normal = KEY_146;\n    ascii_table[DIK_MINUS].shift = KEY_QUESTION;\n    ascii_table[DIK_MINUS].left_alt = -1;\n    ascii_table[DIK_MINUS].right_alt = -1;\n    ascii_table[DIK_MINUS].ctrl = -1;\n\n    ascii_table[DIK_EQUALS].normal = KEY_161;\n    ascii_table[DIK_EQUALS].shift = KEY_191;\n    ascii_table[DIK_EQUALS].left_alt = -1;\n    ascii_table[DIK_EQUALS].right_alt = -1;\n    ascii_table[DIK_EQUALS].ctrl = -1;\n\n    ascii_table[DIK_GRAVE].normal = KEY_176;\n    ascii_table[DIK_GRAVE].shift = KEY_170;\n    ascii_table[DIK_GRAVE].left_alt = -1;\n    ascii_table[DIK_GRAVE].right_alt = KEY_BACKSLASH;\n    ascii_table[DIK_GRAVE].ctrl = -1;\n\n    ascii_table[DIK_LBRACKET].normal = KEY_GRAVE;\n    ascii_table[DIK_LBRACKET].shift = KEY_CARET;\n    ascii_table[DIK_LBRACKET].left_alt = -1;\n    ascii_table[DIK_LBRACKET].right_alt = KEY_BRACKET_LEFT;\n    ascii_table[DIK_LBRACKET].ctrl = -1;\n\n    ascii_table[DIK_RBRACKET].normal = KEY_PLUS;\n    ascii_table[DIK_RBRACKET].shift = KEY_ASTERISK;\n    ascii_table[DIK_RBRACKET].left_alt = -1;\n    ascii_table[DIK_RBRACKET].right_alt = KEY_BRACKET_RIGHT;\n    ascii_table[DIK_RBRACKET].ctrl = -1;\n\n    ascii_table[DIK_OEM_102].normal = KEY_LESS;\n    ascii_table[DIK_OEM_102].shift = KEY_GREATER;\n    ascii_table[DIK_OEM_102].left_alt = -1;\n    ascii_table[DIK_OEM_102].right_alt = -1;\n    ascii_table[DIK_OEM_102].ctrl = -1;\n\n    ascii_table[DIK_SEMICOLON].normal = KEY_241;\n    ascii_table[DIK_SEMICOLON].shift = KEY_209;\n    ascii_table[DIK_SEMICOLON].left_alt = -1;\n    ascii_table[DIK_SEMICOLON].right_alt = -1;\n    ascii_table[DIK_SEMICOLON].ctrl = -1;\n\n    ascii_table[DIK_APOSTROPHE].normal = KEY_168;\n    ascii_table[DIK_APOSTROPHE].shift = KEY_180;\n    ascii_table[DIK_APOSTROPHE].left_alt = -1;\n    ascii_table[DIK_APOSTROPHE].right_alt = KEY_BRACE_LEFT;\n    ascii_table[DIK_APOSTROPHE].ctrl = -1;\n\n    ascii_table[DIK_BACKSLASH].normal = KEY_231;\n    ascii_table[DIK_BACKSLASH].shift = KEY_199;\n    ascii_table[DIK_BACKSLASH].left_alt = -1;\n    ascii_table[DIK_BACKSLASH].right_alt = KEY_BRACE_RIGHT;\n    ascii_table[DIK_BACKSLASH].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_M;\n        break;\n    default:\n        k = DIK_COMMA;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_COMMA;\n    ascii_table[k].shift = KEY_SEMICOLON;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_COMMA;\n        break;\n    default:\n        k = DIK_PERIOD;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_DOT;\n    ascii_table[k].shift = KEY_COLON;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n\n    switch (kb_layout) {\n    case KEYBOARD_LAYOUT_QWERTY:\n        k = DIK_MINUS;\n        break;\n    case KEYBOARD_LAYOUT_FRENCH:\n        k = DIK_6;\n        break;\n    default:\n        k = DIK_SLASH;\n        break;\n    }\n\n    ascii_table[k].normal = KEY_MINUS;\n    ascii_table[k].shift = KEY_UNDERSCORE;\n    ascii_table[k].left_alt = -1;\n    ascii_table[k].right_alt = -1;\n    ascii_table[k].ctrl = -1;\n}\n\n// 0x4D24F8\nstatic void kb_init_lock_status()\n{\n    if (GetKeyState(VK_CAPITAL) & 1) {\n        kb_lock_flags |= MODIFIER_KEY_STATE_CAPS_LOCK;\n    }\n\n    if (GetKeyState(VK_NUMLOCK) & 1) {\n        kb_lock_flags |= MODIFIER_KEY_STATE_NUM_LOCK;\n    }\n\n    if (GetKeyState(VK_SCROLL) & 1) {\n        kb_lock_flags |= MODIFIER_KEY_STATE_SCROLL_LOCK;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4D2540\nstatic void kb_toggle_caps()\n{\n    if ((kb_lock_flags & MODIFIER_KEY_STATE_CAPS_LOCK) != 0) {\n        kb_lock_flags &= ~MODIFIER_KEY_STATE_CAPS_LOCK;\n    } else {\n        kb_lock_flags |= MODIFIER_KEY_STATE_CAPS_LOCK;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4D255C\nstatic void kb_toggle_num()\n{\n    if ((kb_lock_flags & MODIFIER_KEY_STATE_NUM_LOCK) != 0) {\n        kb_lock_flags &= ~MODIFIER_KEY_STATE_NUM_LOCK;\n    } else {\n        kb_lock_flags |= MODIFIER_KEY_STATE_NUM_LOCK;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4D2578\nstatic void kb_toggle_scroll()\n{\n    if ((kb_lock_flags & MODIFIER_KEY_STATE_SCROLL_LOCK) != 0) {\n        kb_lock_flags &= ~MODIFIER_KEY_STATE_SCROLL_LOCK;\n    } else {\n        kb_lock_flags |= MODIFIER_KEY_STATE_SCROLL_LOCK;\n    }\n}\n\n// Get pointer to pending key event from the queue but do not consume it.\n//\n// 0x4D2614\nint kb_buffer_peek(int index, key_data_t** keyboardEventPtr)\n{\n    int rc = -1;\n\n    if (kb_get != kb_put) {\n        int end;\n        if (kb_put <= kb_get) {\n            end = kb_put + KEY_QUEUE_SIZE - kb_get - 1;\n        } else {\n            end = kb_put - kb_get - 1;\n        }\n\n        if (index <= end) {\n            int eventIndex = (kb_get + index) & (KEY_QUEUE_SIZE - 1);\n            *keyboardEventPtr = &(kb_buffer[eventIndex]);\n            rc = 0;\n        }\n    }\n\n    return rc;\n}\n"
  },
  {
    "path": "src/plib/gnw/kb.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_KB_H_\n#define FALLOUT_PLIB_GNW_KB_H_\n\n#include <stdbool.h>\n\n#define KEY_STATE_UP 0\n#define KEY_STATE_DOWN 1\n#define KEY_STATE_REPEAT 2\n\n#define MODIFIER_KEY_STATE_NUM_LOCK 0x01\n#define MODIFIER_KEY_STATE_CAPS_LOCK 0x02\n#define MODIFIER_KEY_STATE_SCROLL_LOCK 0x04\n\n#define KEYBOARD_EVENT_MODIFIER_CAPS_LOCK 0x0001\n#define KEYBOARD_EVENT_MODIFIER_NUM_LOCK 0x0002\n#define KEYBOARD_EVENT_MODIFIER_SCROLL_LOCK 0x0004\n#define KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT 0x0008\n#define KEYBOARD_EVENT_MODIFIER_RIGHT_SHIFT 0x0010\n#define KEYBOARD_EVENT_MODIFIER_LEFT_ALT 0x0020\n#define KEYBOARD_EVENT_MODIFIER_RIGHT_ALT 0x0040\n#define KEYBOARD_EVENT_MODIFIER_LEFT_CONTROL 0x0080\n#define KEYBOARD_EVENT_MODIFIER_RIGHT_CONTROL 0x0100\n#define KEYBOARD_EVENT_MODIFIER_ANY_SHIFT (KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT | KEYBOARD_EVENT_MODIFIER_RIGHT_SHIFT)\n#define KEYBOARD_EVENT_MODIFIER_ANY_ALT (KEYBOARD_EVENT_MODIFIER_LEFT_ALT | KEYBOARD_EVENT_MODIFIER_RIGHT_ALT)\n#define KEYBOARD_EVENT_MODIFIER_ANY_CONTROL (KEYBOARD_EVENT_MODIFIER_LEFT_CONTROL | KEYBOARD_EVENT_MODIFIER_RIGHT_CONTROL)\n\n#define KEY_QUEUE_SIZE 64\n\ntypedef enum KeyboardLayout {\n    KEYBOARD_LAYOUT_QWERTY,\n    KEYBOARD_LAYOUT_FRENCH,\n    KEYBOARD_LAYOUT_GERMAN,\n    KEYBOARD_LAYOUT_ITALIAN,\n    KEYBOARD_LAYOUT_SPANISH,\n} KeyboardLayout;\n\ntypedef enum Key {\n    KEY_ESCAPE = '\\x1b',\n    KEY_TAB = '\\x09',\n    KEY_BACKSPACE = '\\x08',\n    KEY_RETURN = '\\r',\n\n    KEY_SPACE = ' ',\n    KEY_EXCLAMATION = '!',\n    KEY_QUOTE = '\"',\n    KEY_NUMBER_SIGN = '#',\n    KEY_DOLLAR = '$',\n    KEY_PERCENT = '%',\n    KEY_AMPERSAND = '&',\n    KEY_SINGLE_QUOTE = '\\'',\n    KEY_PAREN_LEFT = '(',\n    KEY_PAREN_RIGHT = ')',\n    KEY_ASTERISK = '*',\n    KEY_PLUS = '+',\n    KEY_COMMA = ',',\n    KEY_MINUS = '-',\n    KEY_DOT = '.',\n    KEY_SLASH = '/',\n    KEY_0 = '0',\n    KEY_1 = '1',\n    KEY_2 = '2',\n    KEY_3 = '3',\n    KEY_4 = '4',\n    KEY_5 = '5',\n    KEY_6 = '6',\n    KEY_7 = '7',\n    KEY_8 = '8',\n    KEY_9 = '9',\n    KEY_COLON = ':',\n    KEY_SEMICOLON = ';',\n    KEY_LESS = '<',\n    KEY_EQUAL = '=',\n    KEY_GREATER = '>',\n    KEY_QUESTION = '?',\n    KEY_AT = '@',\n    KEY_UPPERCASE_A = 'A',\n    KEY_UPPERCASE_B = 'B',\n    KEY_UPPERCASE_C = 'C',\n    KEY_UPPERCASE_D = 'D',\n    KEY_UPPERCASE_E = 'E',\n    KEY_UPPERCASE_F = 'F',\n    KEY_UPPERCASE_G = 'G',\n    KEY_UPPERCASE_H = 'H',\n    KEY_UPPERCASE_I = 'I',\n    KEY_UPPERCASE_J = 'J',\n    KEY_UPPERCASE_K = 'K',\n    KEY_UPPERCASE_L = 'L',\n    KEY_UPPERCASE_M = 'M',\n    KEY_UPPERCASE_N = 'N',\n    KEY_UPPERCASE_O = 'O',\n    KEY_UPPERCASE_P = 'P',\n    KEY_UPPERCASE_Q = 'Q',\n    KEY_UPPERCASE_R = 'R',\n    KEY_UPPERCASE_S = 'S',\n    KEY_UPPERCASE_T = 'T',\n    KEY_UPPERCASE_U = 'U',\n    KEY_UPPERCASE_V = 'V',\n    KEY_UPPERCASE_W = 'W',\n    KEY_UPPERCASE_X = 'X',\n    KEY_UPPERCASE_Y = 'Y',\n    KEY_UPPERCASE_Z = 'Z',\n\n    KEY_BRACKET_LEFT = '[',\n    KEY_BACKSLASH = '\\\\',\n    KEY_BRACKET_RIGHT = ']',\n    KEY_CARET = '^',\n    KEY_UNDERSCORE = '_',\n\n    KEY_GRAVE = '`',\n    KEY_LOWERCASE_A = 'a',\n    KEY_LOWERCASE_B = 'b',\n    KEY_LOWERCASE_C = 'c',\n    KEY_LOWERCASE_D = 'd',\n    KEY_LOWERCASE_E = 'e',\n    KEY_LOWERCASE_F = 'f',\n    KEY_LOWERCASE_G = 'g',\n    KEY_LOWERCASE_H = 'h',\n    KEY_LOWERCASE_I = 'i',\n    KEY_LOWERCASE_J = 'j',\n    KEY_LOWERCASE_K = 'k',\n    KEY_LOWERCASE_L = 'l',\n    KEY_LOWERCASE_M = 'm',\n    KEY_LOWERCASE_N = 'n',\n    KEY_LOWERCASE_O = 'o',\n    KEY_LOWERCASE_P = 'p',\n    KEY_LOWERCASE_Q = 'q',\n    KEY_LOWERCASE_R = 'r',\n    KEY_LOWERCASE_S = 's',\n    KEY_LOWERCASE_T = 't',\n    KEY_LOWERCASE_U = 'u',\n    KEY_LOWERCASE_V = 'v',\n    KEY_LOWERCASE_W = 'w',\n    KEY_LOWERCASE_X = 'x',\n    KEY_LOWERCASE_Y = 'y',\n    KEY_LOWERCASE_Z = 'z',\n    KEY_BRACE_LEFT = '{',\n    KEY_BAR = '|',\n    KEY_BRACE_RIGHT = '}',\n    KEY_TILDE = '~',\n    KEY_DEL = 127,\n\n    KEY_136 = 136,\n    KEY_146 = 146,\n    KEY_149 = 149,\n    KEY_150 = 150,\n    KEY_151 = 151,\n    KEY_152 = 152,\n    KEY_161 = 161,\n    KEY_163 = 163,\n    KEY_164 = 164,\n    KEY_166 = 166,\n    KEY_168 = 168,\n    KEY_167 = 167,\n    KEY_170 = 170,\n    KEY_172 = 172,\n    KEY_176 = 176,\n    KEY_178 = 178,\n    KEY_179 = 179,\n    KEY_180 = 180,\n    KEY_181 = 181,\n    KEY_186 = 186,\n    KEY_191 = 191,\n    KEY_196 = 196,\n    KEY_199 = 199,\n    KEY_209 = 209,\n    KEY_214 = 214,\n    KEY_215 = 215,\n    KEY_220 = 220,\n    KEY_223 = 223,\n    KEY_224 = 224,\n    KEY_228 = 228,\n    KEY_231 = 231,\n    KEY_232 = 232,\n    KEY_233 = 233,\n    KEY_241 = 241,\n    KEY_246 = 246,\n    KEY_247 = 247,\n    KEY_249 = 249,\n    KEY_252 = 252,\n\n    KEY_ALT_Q = 272,\n    KEY_ALT_W = 273,\n    KEY_ALT_E = 274,\n    KEY_ALT_R = 275,\n    KEY_ALT_T = 276,\n    KEY_ALT_Y = 277,\n    KEY_ALT_U = 278,\n    KEY_ALT_I = 279,\n    KEY_ALT_O = 280,\n    KEY_ALT_P = 281,\n    KEY_ALT_A = 286,\n    KEY_ALT_S = 287,\n    KEY_ALT_D = 288,\n    KEY_ALT_F = 289,\n    KEY_ALT_G = 290,\n    KEY_ALT_H = 291,\n    KEY_ALT_J = 292,\n    KEY_ALT_K = 293,\n    KEY_ALT_L = 294,\n    KEY_ALT_Z = 300,\n    KEY_ALT_X = 301,\n    KEY_ALT_C = 302,\n    KEY_ALT_V = 303,\n    KEY_ALT_B = 304,\n    KEY_ALT_N = 305,\n    KEY_ALT_M = 306,\n\n    KEY_CTRL_Q = 17,\n    KEY_CTRL_W = 23,\n    KEY_CTRL_E = 5,\n    KEY_CTRL_R = 18,\n    KEY_CTRL_T = 20,\n    KEY_CTRL_Y = 25,\n    KEY_CTRL_U = 21,\n    KEY_CTRL_I = 9,\n    KEY_CTRL_O = 15,\n    KEY_CTRL_P = 16,\n    KEY_CTRL_A = 1,\n    KEY_CTRL_S = 19,\n    KEY_CTRL_D = 4,\n    KEY_CTRL_F = 6,\n    KEY_CTRL_G = 7,\n    KEY_CTRL_H = 8,\n    KEY_CTRL_J = 10,\n    KEY_CTRL_K = 11,\n    KEY_CTRL_L = 12,\n    KEY_CTRL_Z = 26,\n    KEY_CTRL_X = 24,\n    KEY_CTRL_C = 3,\n    KEY_CTRL_V = 22,\n    KEY_CTRL_B = 2,\n    KEY_CTRL_N = 14,\n    KEY_CTRL_M = 13,\n\n    KEY_F1 = 315,\n    KEY_F2 = 316,\n    KEY_F3 = 317,\n    KEY_F4 = 318,\n    KEY_F5 = 319,\n    KEY_F6 = 320,\n    KEY_F7 = 321,\n    KEY_F8 = 322,\n    KEY_F9 = 323,\n    KEY_F10 = 324,\n    KEY_F11 = 389,\n    KEY_F12 = 390,\n\n    KEY_SHIFT_F1 = 340,\n    KEY_SHIFT_F2 = 341,\n    KEY_SHIFT_F3 = 342,\n    KEY_SHIFT_F4 = 343,\n    KEY_SHIFT_F5 = 344,\n    KEY_SHIFT_F6 = 345,\n    KEY_SHIFT_F7 = 346,\n    KEY_SHIFT_F8 = 347,\n    KEY_SHIFT_F9 = 348,\n    KEY_SHIFT_F10 = 349,\n    KEY_SHIFT_F11 = 391,\n    KEY_SHIFT_F12 = 392,\n\n    KEY_CTRL_F1 = 350,\n    KEY_CTRL_F2 = 351,\n    KEY_CTRL_F3 = 352,\n    KEY_CTRL_F4 = 353,\n    KEY_CTRL_F5 = 354,\n    KEY_CTRL_F6 = 355,\n    KEY_CTRL_F7 = 356,\n    KEY_CTRL_F8 = 357,\n    KEY_CTRL_F9 = 358,\n    KEY_CTRL_F10 = 359,\n    KEY_CTRL_F11 = 393,\n    KEY_CTRL_F12 = 394,\n\n    KEY_ALT_F1 = 360,\n    KEY_ALT_F2 = 361,\n    KEY_ALT_F3 = 362,\n    KEY_ALT_F4 = 363,\n    KEY_ALT_F5 = 364,\n    KEY_ALT_F6 = 365,\n    KEY_ALT_F7 = 366,\n    KEY_ALT_F8 = 367,\n    KEY_ALT_F9 = 368,\n    KEY_ALT_F10 = 369,\n    KEY_ALT_F11 = 395,\n    KEY_ALT_F12 = 396,\n\n    KEY_HOME = 327,\n    KEY_CTRL_HOME = 375,\n    KEY_ALT_HOME = 407,\n\n    KEY_PAGE_UP = 329,\n    KEY_CTRL_PAGE_UP = 388,\n    KEY_ALT_PAGE_UP = 409,\n\n    KEY_INSERT = 338,\n    KEY_CTRL_INSERT = 402,\n    KEY_ALT_INSERT = 418,\n\n    KEY_DELETE = 339,\n    KEY_CTRL_DELETE = 403,\n    KEY_ALT_DELETE = 419,\n\n    KEY_END = 335,\n    KEY_CTRL_END = 373,\n    KEY_ALT_END = 415,\n\n    KEY_PAGE_DOWN = 337,\n    KEY_ALT_PAGE_DOWN = 417,\n    KEY_CTRL_PAGE_DOWN = 374,\n\n    KEY_ARROW_UP = 328,\n    KEY_CTRL_ARROW_UP = 397,\n    KEY_ALT_ARROW_UP = 408,\n\n    KEY_ARROW_DOWN = 336,\n    KEY_CTRL_ARROW_DOWN = 401,\n    KEY_ALT_ARROW_DOWN = 416,\n\n    KEY_ARROW_LEFT = 331,\n    KEY_CTRL_ARROW_LEFT = 371,\n    KEY_ALT_ARROW_LEFT = 411,\n\n    KEY_ARROW_RIGHT = 333,\n    KEY_CTRL_ARROW_RIGHT = 372,\n    KEY_ALT_ARROW_RIGHT = 413,\n\n    KEY_CTRL_BACKSLASH = 192,\n\n    KEY_NUMBERPAD_5 = 332,\n    KEY_CTRL_NUMBERPAD_5 = 399,\n    KEY_ALT_NUMBERPAD_5 = 9999,\n\n    KEY_FIRST_INPUT_CHARACTER = KEY_SPACE,\n    KEY_LAST_INPUT_CHARACTER = KEY_LOWERCASE_Z,\n} Key;\n\nunsigned char keys[256];\nint kb_layout;\nunsigned char keynumpress;\n\nint GNW_kb_set();\nvoid GNW_kb_restore();\nvoid kb_wait();\nvoid kb_clear();\nint kb_getch();\nvoid kb_disable();\nvoid kb_enable();\nbool kb_is_disabled();\nvoid kb_disable_numpad();\nvoid kb_enable_numpad();\nbool kb_numpad_is_disabled();\nvoid kb_disable_numlock();\nvoid kb_enable_numlock();\nbool kb_numlock_is_disabled();\nvoid kb_set_layout(int layout);\nint kb_get_layout();\nint kb_ascii_to_scan(int ascii);\nunsigned int kb_elapsed_time();\nvoid kb_reset_elapsed_time();\nvoid kb_simulate_key(int scan_code);\n\n#endif /* FALLOUT_PLIB_GNW_KB_H_ */\n"
  },
  {
    "path": "src/plib/gnw/memory.c",
    "content": "#include \"plib/gnw/memory.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/gnw/debug.h\"\n#include \"plib/gnw/gnw.h\"\n\n// A special value that denotes a beginning of a memory block data.\n#define MEMORY_BLOCK_HEADER_GUARD 0xFEEDFACE\n\n// A special value that denotes an ending of a memory block data.\n#define MEMORY_BLOCK_FOOTER_GUARD 0xBEEFCAFE\n\n// A header of a memory block.\ntypedef struct MemoryBlockHeader {\n    // Size of the memory block including header and footer.\n    size_t size;\n\n    // See [MEMORY_BLOCK_HEADER_GUARD].\n    int guard;\n} MemoryBlockHeader;\n\n// A footer of a memory block.\ntypedef struct MemoryBlockFooter {\n    // See [MEMORY_BLOCK_FOOTER_GUARD].\n    int guard;\n} MemoryBlockFooter;\n\nstatic void* my_malloc(size_t size);\nstatic void* my_realloc(void* ptr, size_t size);\nstatic void my_free(void* ptr);\nstatic void* mem_prep_block(void* block, size_t size);\nstatic void mem_check_block(void* block);\n\n// 0x51DED0\nstatic MallocProc* p_malloc = my_malloc;\n\n// 0x51DED4\nstatic ReallocProc* p_realloc = my_realloc;\n\n// 0x51DED8\nstatic FreeProc* p_free = my_free;\n\n// 0x51DEDC\nstatic int num_blocks = 0;\n\n// 0x51DEE0\nstatic int max_blocks = 0;\n\n// 0x51DEE4\nstatic size_t mem_allocated = 0;\n\n// 0x51DEE8\nstatic size_t max_allocated = 0;\n\n// 0x4C5A80\nchar* mem_strdup(const char* string)\n{\n    char* copy = NULL;\n    if (string != NULL) {\n        copy = (char*)p_malloc(strlen(string) + 1);\n        strcpy(copy, string);\n    }\n    return copy;\n}\n\n// 0x4C5AD0\nvoid* mem_malloc(size_t size)\n{\n    return p_malloc(size);\n}\n\n// 0x4C5AD8\nstatic void* my_malloc(size_t size)\n{\n    void* ptr = NULL;\n\n    if (size != 0) {\n        size += sizeof(MemoryBlockHeader) + sizeof(MemoryBlockFooter);\n\n        unsigned char* block = (unsigned char*)malloc(size);\n        if (block != NULL) {\n            // NOTE: Uninline.\n            ptr = mem_prep_block(block, size);\n\n            num_blocks++;\n            if (num_blocks > max_blocks) {\n                max_blocks = num_blocks;\n            }\n\n            mem_allocated += size;\n            if (mem_allocated > max_allocated) {\n                max_allocated = mem_allocated;\n            }\n        }\n    }\n\n    return ptr;\n}\n\n// 0x4C5B50\nvoid* mem_realloc(void* ptr, size_t size)\n{\n    return p_realloc(ptr, size);\n}\n\n// 0x4C5B58\nstatic void* my_realloc(void* ptr, size_t size)\n{\n    if (ptr != NULL) {\n        unsigned char* block = (unsigned char*)ptr - sizeof(MemoryBlockHeader);\n\n        MemoryBlockHeader* header = (MemoryBlockHeader*)block;\n        size_t oldSize = header->size;\n\n        mem_allocated -= oldSize;\n\n        mem_check_block(block);\n\n        if (size != 0) {\n            size += sizeof(MemoryBlockHeader) + sizeof(MemoryBlockFooter);\n        }\n\n        unsigned char* newBlock = (unsigned char*)realloc(block, size);\n        if (newBlock != NULL) {\n            mem_allocated += size;\n            if (mem_allocated > max_allocated) {\n                max_allocated = mem_allocated;\n            }\n\n            // NOTE: Uninline.\n            ptr = mem_prep_block(newBlock, size);\n        } else {\n            if (size != 0) {\n                mem_allocated += oldSize;\n\n                debug_printf(\"%s,%u: \", __FILE__, __LINE__); // \"Memory.c\", 195\n                debug_printf(\"Realloc failure.\\n\");\n            } else {\n                num_blocks--;\n            }\n            ptr = NULL;\n        }\n    } else {\n        ptr = p_malloc(size);\n    }\n\n    return ptr;\n}\n\n// 0x4C5C24\nvoid mem_free(void* ptr)\n{\n    p_free(ptr);\n}\n\n// 0x4C5C2C\nstatic void my_free(void* ptr)\n{\n    if (ptr != NULL) {\n        void* block = (unsigned char*)ptr - sizeof(MemoryBlockHeader);\n        MemoryBlockHeader* header = (MemoryBlockHeader*)block;\n\n        mem_check_block(block);\n\n        mem_allocated -= header->size;\n        num_blocks--;\n\n        free(block);\n    }\n}\n\n// NOTE: Not used.\n//\n// 0x4C5C5C\nvoid mem_check()\n{\n    if (p_malloc == my_malloc) {\n        debug_printf(\"Current memory allocated: %6d blocks, %9u bytes total\\n\", num_blocks, mem_allocated);\n        debug_printf(\"Max memory allocated:     %6d blocks, %9u bytes total\\n\", max_blocks, max_allocated);\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4C5CA8\nvoid mem_register_func(MallocProc* mallocFunc, ReallocProc* reallocFunc, FreeProc* freeFunc)\n{\n    if (!GNW_win_init_flag) {\n        p_malloc = mallocFunc;\n        p_realloc = reallocFunc;\n        p_free = freeFunc;\n    }\n}\n\n// NOTE: Inlined.\n//\n// 0x4C5CC4\nstatic void* mem_prep_block(void* block, size_t size)\n{\n    MemoryBlockHeader* header;\n    MemoryBlockFooter* footer;\n\n    header = (MemoryBlockHeader*)block;\n    header->guard = MEMORY_BLOCK_HEADER_GUARD;\n    header->size = size;\n\n    footer = (MemoryBlockFooter*)((unsigned char*)block + size - sizeof(*footer));\n    footer->guard = MEMORY_BLOCK_FOOTER_GUARD;\n\n    return (unsigned char*)block + sizeof(*header);\n}\n\n// Validates integrity of the memory block.\n//\n// [block] is a pointer to the the memory block itself, not it's data.\n//\n// 0x4C5CE4\nstatic void mem_check_block(void* block)\n{\n    MemoryBlockHeader* header = (MemoryBlockHeader*)block;\n    if (header->guard != MEMORY_BLOCK_HEADER_GUARD) {\n        debug_printf(\"Memory header stomped.\\n\");\n    }\n\n    MemoryBlockFooter* footer = (MemoryBlockFooter*)((unsigned char*)block + header->size - sizeof(MemoryBlockFooter));\n    if (footer->guard != MEMORY_BLOCK_FOOTER_GUARD) {\n        debug_printf(\"Memory footer stomped.\\n\");\n    }\n}\n"
  },
  {
    "path": "src/plib/gnw/memory.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_MEMORY_H_\n#define FALLOUT_PLIB_GNW_MEMORY_H_\n\n#include \"memory_defs.h\"\n\nchar* mem_strdup(const char* string);\nvoid* mem_malloc(size_t size);\nvoid* mem_realloc(void* ptr, size_t size);\nvoid mem_free(void* ptr);\nvoid mem_check();\nvoid mem_register_func(MallocProc* mallocFunc, ReallocProc* reallocFunc, FreeProc* freeFunc);\n\n#endif /* FALLOUT_PLIB_GNW_MEMORY_H_ */\n"
  },
  {
    "path": "src/plib/gnw/mouse.c",
    "content": "#include \"plib/gnw/mouse.h\"\n\n#include \"plib/color/color.h\"\n#include \"plib/gnw/dxinput.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/memory.h\"\n#include \"plib/gnw/svga.h\"\n#include \"plib/gnw/vcr.h\"\n\nstatic void mouse_colorize();\nstatic void mouse_anim();\nstatic void mouse_clip();\n\n// The default mouse cursor buffer.\n//\n// Initially it contains color codes, which will be replaced at startup\n// according to loaded palette.\n//\n// Available color codes:\n// - 0: transparent\n// - 1: white\n// - 15:  black\n//\n// 0x51E250\nstatic unsigned char or_mask[MOUSE_DEFAULT_CURSOR_SIZE] = {\n    // clang-format off\n    1,  1,  1,  1,  1,  1,  1, 0,\n    1, 15, 15, 15, 15, 15,  1, 0,\n    1, 15, 15, 15, 15,  1,  1, 0,\n    1, 15, 15, 15, 15,  1,  1, 0,\n    1, 15, 15, 15, 15, 15,  1, 1,\n    1, 15,  1,  1, 15, 15, 15, 1,\n    1,  1,  1,  1,  1, 15, 15, 1,\n    0,  0,  0,  0,  1,  1,  1, 1,\n    // clang-format on\n};\n\n// 0x51E290\nstatic int mouse_idling = 0;\n\n// 0x51E294\nstatic unsigned char* mouse_buf = NULL;\n\n// 0x51E298\nstatic unsigned char* mouse_shape = NULL;\n\n// 0x51E29C\nstatic unsigned char* mouse_fptr = NULL;\n\n// 0x51E2A0\nstatic double mouse_sensitivity = 1.0;\n\n// 0x51E2AC\nstatic int last_buttons = 0;\n\n// 0x6AC790\nstatic bool mouse_is_hidden;\n\n// 0x6AC794\nstatic int raw_x;\n\n// 0x6AC798\nstatic int mouse_length;\n\n// 0x6AC79C\nstatic int raw_y;\n\n// 0x6AC7A0\nstatic int raw_buttons;\n\n// 0x6AC7A4\nstatic int mouse_y;\n\n// 0x6AC7A8\nstatic int mouse_x;\n\n// 0x6AC7AC\nstatic bool mouse_disabled;\n\n// 0x6AC7B0\nstatic int mouse_buttons;\n\n// 0x6AC7B4\nstatic unsigned int mouse_speed;\n\n// 0x6AC7B8\nstatic int mouse_curr_frame;\n\n// 0x6AC7BC\nstatic bool have_mouse;\n\n// 0x6AC7C0\nstatic int mouse_full;\n\n// 0x6AC7C4\nstatic int mouse_width;\n\n// 0x6AC7C8\nstatic int mouse_num_frames;\n\n// 0x6AC7CC\nstatic int mouse_hoty;\n\n// 0x6AC7D0\nstatic int mouse_hotx;\n\n// 0x6AC7D4\nstatic unsigned int mouse_idle_start_time;\n\n// 0x6AC7D8\nScreenTransBlitFunc* mouse_blit_trans;\n\n// 0x6AC7DC\nScreenBlitFunc* mouse_blit;\n\n// 0x6AC7E0\nstatic char mouse_trans;\n\n// 0x4C9F40\nint GNW_mouse_init()\n{\n    have_mouse = false;\n    mouse_disabled = false;\n\n    mouse_is_hidden = true;\n\n    mouse_colorize();\n\n    if (mouse_set_shape(NULL, 0, 0, 0, 0, 0, 0) == -1) {\n        return -1;\n    }\n\n    if (!dxinput_acquire_mouse()) {\n        return -1;\n    }\n\n    have_mouse = true;\n    mouse_x = scr_size.lrx / 2;\n    mouse_y = scr_size.lry / 2;\n    raw_x = scr_size.lrx / 2;\n    raw_y = scr_size.lry / 2;\n    mouse_idle_start_time = get_time();\n\n    return 0;\n}\n\n// 0x4C9FD8\nvoid GNW_mouse_exit()\n{\n    dxinput_unacquire_mouse();\n\n    if (mouse_buf != NULL) {\n        mem_free(mouse_buf);\n        mouse_buf = NULL;\n    }\n\n    if (mouse_fptr != NULL) {\n        remove_bk_process(mouse_anim);\n        mouse_fptr = NULL;\n    }\n}\n\n// 0x4CA01C\nstatic void mouse_colorize()\n{\n    for (int index = 0; index < 64; index++) {\n        switch (or_mask[index]) {\n        case 0:\n            or_mask[index] = colorTable[0];\n            break;\n        case 1:\n            or_mask[index] = colorTable[8456];\n            break;\n        case 15:\n            or_mask[index] = colorTable[32767];\n            break;\n        }\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4CA064\nvoid mouse_get_shape(unsigned char** buf, int* width, int* length, int* full, int* hotx, int* hoty, char* trans)\n{\n    *buf = mouse_shape;\n    *width = mouse_width;\n    *length = mouse_length;\n    *full = mouse_full;\n    *hotx = mouse_hotx;\n    *hoty = mouse_hoty;\n    *trans = mouse_trans;\n}\n\n// 0x4CA0AC\nint mouse_set_shape(unsigned char* buf, int width, int length, int full, int hotx, int hoty, char trans)\n{\n    Rect rect;\n    unsigned char* v9;\n    int v11, v12;\n    int v7, v8;\n\n    v7 = hotx;\n    v8 = hoty;\n    v9 = buf;\n\n    if (buf == NULL) {\n        // NOTE: Original code looks tail recursion optimization.\n        return mouse_set_shape(or_mask, MOUSE_DEFAULT_CURSOR_WIDTH, MOUSE_DEFAULT_CURSOR_HEIGHT, MOUSE_DEFAULT_CURSOR_WIDTH, 1, 1, colorTable[0]);\n    }\n\n    bool cursorWasHidden = mouse_is_hidden;\n    if (!mouse_is_hidden && have_mouse) {\n        mouse_is_hidden = true;\n        mouse_get_rect(&rect);\n        win_refresh_all(&rect);\n    }\n\n    if (width != mouse_width || length != mouse_length) {\n        unsigned char* buf = (unsigned char*)mem_malloc(width * length);\n        if (buf == NULL) {\n            if (!cursorWasHidden) {\n                mouse_show();\n            }\n            return -1;\n        }\n\n        if (mouse_buf != NULL) {\n            mem_free(mouse_buf);\n        }\n\n        mouse_buf = buf;\n    }\n\n    mouse_width = width;\n    mouse_length = length;\n    mouse_full = full;\n    mouse_shape = v9;\n    mouse_trans = trans;\n\n    if (mouse_fptr) {\n        remove_bk_process(mouse_anim);\n        mouse_fptr = NULL;\n    }\n\n    v11 = mouse_hotx - v7;\n    mouse_hotx = v7;\n\n    mouse_x += v11;\n\n    v12 = mouse_hoty - v8;\n    mouse_hoty = v8;\n\n    mouse_y += v12;\n\n    mouse_clip();\n\n    if (!cursorWasHidden) {\n        mouse_show();\n    }\n\n    raw_x = mouse_x;\n    raw_y = mouse_y;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4CA20\nint mouse_get_anim(unsigned char** frames, int* num_frames, int* width, int* length, int* hotx, int* hoty, char* trans, int* speed)\n{\n    if (mouse_fptr == NULL) {\n        return -1;\n    }\n\n    *frames = mouse_fptr;\n    *num_frames = mouse_num_frames;\n    *width = mouse_width;\n    *length = mouse_length;\n    *hotx = mouse_hotx;\n    *hoty = mouse_hoty;\n    *trans = mouse_trans;\n    *speed = mouse_speed;\n\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4CA26C\nint mouse_set_anim_frames(unsigned char* frames, int num_frames, int start_frame, int width, int length, int hotx, int hoty, char trans, int speed)\n{\n    if (mouse_set_shape(frames + start_frame * width * length, width, length, width, hotx, hoty, trans) == -1) {\n        return -1;\n    }\n\n    mouse_fptr = frames;\n    mouse_num_frames = num_frames;\n    mouse_curr_frame = start_frame;\n    mouse_speed = speed;\n\n    add_bk_process(mouse_anim);\n\n    return 0;\n}\n\n// 0x4CA2D0\nstatic void mouse_anim()\n{\n    // 0x51E2A8\n    static unsigned int ticker = 0;\n\n    if (elapsed_time(ticker) >= mouse_speed) {\n        ticker = get_time();\n\n        if (++mouse_curr_frame == mouse_num_frames) {\n            mouse_curr_frame = 0;\n        }\n\n        mouse_shape = mouse_width * mouse_curr_frame * mouse_length + mouse_fptr;\n\n        if (!mouse_is_hidden) {\n            mouse_show();\n        }\n    }\n}\n\n// 0x4CA34C\nvoid mouse_show()\n{\n    int i;\n    unsigned char* v2;\n    int v7, v8;\n    int v9, v10;\n    int v4;\n    unsigned char v6;\n    int v3;\n\n    v2 = mouse_buf;\n    if (have_mouse) {\n        if (!mouse_blit_trans || !mouse_is_hidden) {\n            win_get_mouse_buf(mouse_buf);\n            v2 = mouse_buf;\n            v3 = 0;\n\n            for (i = 0; i < mouse_length; i++) {\n                for (v4 = 0; v4 < mouse_width; v4++) {\n                    v6 = mouse_shape[i * mouse_full + v4];\n                    if (v6 != mouse_trans) {\n                        v2[v3] = v6;\n                    }\n                    v3++;\n                }\n            }\n        }\n\n        if (mouse_x >= scr_size.ulx) {\n            if (mouse_width + mouse_x - 1 <= scr_size.lrx) {\n                v8 = mouse_width;\n                v7 = 0;\n            } else {\n                v7 = 0;\n                v8 = scr_size.lrx - mouse_x + 1;\n            }\n        } else {\n            v7 = scr_size.ulx - mouse_x;\n            v8 = mouse_width - (scr_size.ulx - mouse_x);\n        }\n\n        if (mouse_y >= scr_size.uly) {\n            if (mouse_length + mouse_y - 1 <= scr_size.lry) {\n                v9 = 0;\n                v10 = mouse_length;\n            } else {\n                v9 = 0;\n                v10 = scr_size.lry - mouse_y + 1;\n            }\n        } else {\n            v9 = scr_size.uly - mouse_y;\n            v10 = mouse_length - (scr_size.uly - mouse_y);\n        }\n\n        mouse_buf = v2;\n        if (mouse_blit_trans && mouse_is_hidden) {\n            mouse_blit_trans(mouse_shape, mouse_full, mouse_length, v7, v9, v8, v10, v7 + mouse_x, v9 + mouse_y, mouse_trans);\n        } else {\n            mouse_blit(mouse_buf, mouse_width, mouse_length, v7, v9, v8, v10, v7 + mouse_x, v9 + mouse_y);\n        }\n\n        v2 = mouse_buf;\n        mouse_is_hidden = false;\n    }\n    mouse_buf = v2;\n}\n\n// 0x4CA534\nvoid mouse_hide()\n{\n    Rect rect;\n\n    if (have_mouse) {\n        if (!mouse_is_hidden) {\n            rect.ulx = mouse_x;\n            rect.uly = mouse_y;\n            rect.lrx = mouse_x + mouse_width - 1;\n            rect.lry = mouse_y + mouse_length - 1;\n\n            mouse_is_hidden = true;\n            win_refresh_all(&rect);\n        }\n    }\n}\n\n// 0x4CA59C\nvoid mouse_info()\n{\n    if (!have_mouse) {\n        return;\n    }\n\n    if (mouse_is_hidden) {\n        return;\n    }\n\n    if (mouse_disabled) {\n        return;\n    }\n\n    int x;\n    int y;\n    int buttons = 0;\n\n    dxinput_mouse_state mouseData;\n    if (dxinput_get_mouse_state(&mouseData)) {\n        x = mouseData.delta_x;\n        y = mouseData.delta_y;\n\n        if (mouseData.left_button == 1) {\n            buttons |= MOUSE_STATE_LEFT_BUTTON_DOWN;\n        }\n\n        if (mouseData.right_button == 1) {\n            buttons |= MOUSE_STATE_RIGHT_BUTTON_DOWN;\n        }\n    } else {\n        x = 0;\n        y = 0;\n    }\n\n    // Adjust for mouse senstivity.\n    x = (int)(x * mouse_sensitivity);\n    y = (int)(y * mouse_sensitivity);\n\n    if (vcr_state == VCR_STATE_PLAYING) {\n        if (((vcr_terminate_flags & VCR_TERMINATE_ON_MOUSE_PRESS) != 0 && buttons != 0)\n            || ((vcr_terminate_flags & VCR_TERMINATE_ON_MOUSE_MOVE) != 0 && (x != 0 || y != 0))) {\n            vcr_terminated_condition = VCR_PLAYBACK_COMPLETION_REASON_TERMINATED;\n            vcr_stop();\n            return;\n        }\n        x = 0;\n        y = 0;\n        buttons = last_buttons;\n    }\n\n    mouse_simulate_input(x, y, buttons);\n}\n\n// 0x4CA698\nvoid mouse_simulate_input(int delta_x, int delta_y, int buttons)\n{\n    // 0x6AC7E4\n    static unsigned int right_time;\n\n    // 0x6AC7E8\n    static unsigned int left_time;\n\n    // 0x6AC7EC\n    static int old;\n\n    if (!have_mouse || mouse_is_hidden) {\n        return;\n    }\n\n    if (delta_x || delta_y || buttons != last_buttons) {\n        if (vcr_state == 0) {\n            if (vcr_buffer_index == VCR_BUFFER_CAPACITY - 1) {\n                vcr_dump_buffer();\n            }\n\n            VcrEntry* vcrEntry = &(vcr_buffer[vcr_buffer_index]);\n            vcrEntry->type = VCR_ENTRY_TYPE_MOUSE_EVENT;\n            vcrEntry->time = vcr_time;\n            vcrEntry->counter = vcr_counter;\n            vcrEntry->mouseEvent.dx = delta_x;\n            vcrEntry->mouseEvent.dy = delta_y;\n            vcrEntry->mouseEvent.buttons = buttons;\n\n            vcr_buffer_index++;\n        }\n    } else {\n        if (last_buttons == 0) {\n            if (!mouse_idling) {\n                mouse_idle_start_time = get_time();\n                mouse_idling = 1;\n            }\n\n            last_buttons = 0;\n            raw_buttons = 0;\n            mouse_buttons = 0;\n\n            return;\n        }\n    }\n\n    mouse_idling = 0;\n    last_buttons = buttons;\n    old = mouse_buttons;\n    mouse_buttons = 0;\n\n    if ((old & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) != 0) {\n        if ((buttons & 0x01) != 0) {\n            mouse_buttons |= MOUSE_EVENT_LEFT_BUTTON_REPEAT;\n\n            if (elapsed_time(left_time) > BUTTON_REPEAT_TIME) {\n                mouse_buttons |= MOUSE_EVENT_LEFT_BUTTON_DOWN;\n                left_time = get_time();\n            }\n        } else {\n            mouse_buttons |= MOUSE_EVENT_LEFT_BUTTON_UP;\n        }\n    } else {\n        if ((buttons & 0x01) != 0) {\n            mouse_buttons |= MOUSE_EVENT_LEFT_BUTTON_DOWN;\n            left_time = get_time();\n        }\n    }\n\n    if ((old & MOUSE_EVENT_RIGHT_BUTTON_DOWN_REPEAT) != 0) {\n        if ((buttons & 0x02) != 0) {\n            mouse_buttons |= MOUSE_EVENT_RIGHT_BUTTON_REPEAT;\n            if (elapsed_time(right_time) > BUTTON_REPEAT_TIME) {\n                mouse_buttons |= MOUSE_EVENT_RIGHT_BUTTON_DOWN;\n                right_time = get_time();\n            }\n        } else {\n            mouse_buttons |= MOUSE_EVENT_RIGHT_BUTTON_UP;\n        }\n    } else {\n        if (buttons & 0x02) {\n            mouse_buttons |= MOUSE_EVENT_RIGHT_BUTTON_DOWN;\n            right_time = get_time();\n        }\n    }\n\n    raw_buttons = mouse_buttons;\n\n    if (delta_x != 0 || delta_y != 0) {\n        Rect mouseRect;\n        mouseRect.ulx = mouse_x;\n        mouseRect.uly = mouse_y;\n        mouseRect.lrx = mouse_width + mouse_x - 1;\n        mouseRect.lry = mouse_length + mouse_y - 1;\n\n        mouse_x += delta_x;\n        mouse_y += delta_y;\n        mouse_clip();\n\n        win_refresh_all(&mouseRect);\n\n        mouse_show();\n\n        raw_x = mouse_x;\n        raw_y = mouse_y;\n    }\n}\n\n// 0x4CA8C8\nbool mouse_in(int left, int top, int right, int bottom)\n{\n    if (!have_mouse) {\n        return false;\n    }\n\n    return mouse_length + mouse_y > top\n        && right >= mouse_x\n        && mouse_width + mouse_x > left\n        && bottom >= mouse_y;\n}\n\n// 0x4CA934\nbool mouse_click_in(int left, int top, int right, int bottom)\n{\n    if (!have_mouse) {\n        return false;\n    }\n\n    return mouse_hoty + mouse_y >= top\n        && mouse_hotx + mouse_x <= right\n        && mouse_hotx + mouse_x >= left\n        && mouse_hoty + mouse_y <= bottom;\n}\n\n// 0x4CA9A0\nvoid mouse_get_rect(Rect* rect)\n{\n    rect->ulx = mouse_x;\n    rect->uly = mouse_y;\n    rect->lrx = mouse_width + mouse_x - 1;\n    rect->lry = mouse_length + mouse_y - 1;\n}\n\n// 0x4CA9DC\nvoid mouse_get_position(int* xPtr, int* yPtr)\n{\n    *xPtr = mouse_hotx + mouse_x;\n    *yPtr = mouse_hoty + mouse_y;\n}\n\n// 0x4CAA04\nvoid mouse_set_position(int a1, int a2)\n{\n    mouse_x = a1 - mouse_hotx;\n    mouse_y = a2 - mouse_hoty;\n    raw_y = a2 - mouse_hoty;\n    raw_x = a1 - mouse_hotx;\n    mouse_clip();\n}\n\n// 0x4CAA38\nstatic void mouse_clip()\n{\n    if (mouse_hotx + mouse_x < scr_size.ulx) {\n        mouse_x = scr_size.ulx - mouse_hotx;\n    } else if (mouse_hotx + mouse_x > scr_size.lrx) {\n        mouse_x = scr_size.lrx - mouse_hotx;\n    }\n\n    if (mouse_hoty + mouse_y < scr_size.uly) {\n        mouse_y = scr_size.uly - mouse_hoty;\n    } else if (mouse_hoty + mouse_y > scr_size.lry) {\n        mouse_y = scr_size.lry - mouse_hoty;\n    }\n}\n\n// 0x4CAAA0\nint mouse_get_buttons()\n{\n    return mouse_buttons;\n}\n\n// 0x4CAAA8\nbool mouse_hidden()\n{\n    return mouse_is_hidden;\n}\n\n// NOTE: Unused.\n//\n// 0x4CAAB0\nvoid mouse_get_hotspot(int* hotx, int* hoty)\n{\n    *hotx = mouse_hotx;\n    *hoty = mouse_hoty;\n}\n\n// NOTE: Unused.\n//\n// 0x4CAAC4\nvoid mouse_set_hotspot(int hotx, int hoty)\n{\n    bool mh;\n\n    mh = mouse_is_hidden;\n    if (!mouse_is_hidden) {\n        mouse_hide();\n    }\n\n    mouse_x += mouse_hotx - hotx;\n    mouse_y += mouse_hoty - hoty;\n    mouse_hotx = hotx;\n    mouse_hoty = hoty;\n\n    if (mh) {\n        mouse_show();\n    }\n}\n\n// NOTE: Unused.\n//\n// 0x4CAB54\nbool mouse_query_exist()\n{\n    return have_mouse;\n}\n\n// 0x4CAB5C\nvoid mouse_get_raw_state(int* out_x, int* out_y, int* out_buttons)\n{\n    dxinput_mouse_state mouseData;\n    if (!dxinput_get_mouse_state(&mouseData)) {\n        mouseData.delta_x = 0;\n        mouseData.delta_y = 0;\n        mouseData.left_button = (mouse_buttons & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0;\n        mouseData.right_button = (mouse_buttons & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0;\n    }\n\n    raw_buttons = 0;\n    raw_x += mouseData.delta_x;\n    raw_y += mouseData.delta_y;\n\n    if (mouseData.left_button != 0) {\n        raw_buttons |= MOUSE_EVENT_LEFT_BUTTON_DOWN;\n    }\n\n    if (mouseData.right_button != 0) {\n        raw_buttons |= MOUSE_EVENT_RIGHT_BUTTON_DOWN;\n    }\n\n    *out_x = raw_x;\n    *out_y = raw_y;\n    *out_buttons = raw_buttons;\n}\n\n// NOTE: Unused.\n//\n// 0x4CAC1C\nvoid mouse_disable()\n{\n    mouse_disabled = true;\n}\n\n// NOTE: Unused.\n//\n// 0x4CAC28\nvoid mouse_enable()\n{\n    mouse_disabled = false;\n}\n\n// NOTE: Unused.\n//\n// 0x4CAC34\nbool mouse_is_disabled()\n{\n    return mouse_disabled;\n}\n\n// 0x4CAC3C\nvoid mouse_set_sensitivity(double value)\n{\n    if (value > 0 && value < 2.0) {\n        mouse_sensitivity = value;\n    }\n}\n\n// NOTE: Unused\n//\n// 0x4CAC6C\ndouble mouse_get_sensitivity()\n{\n    return mouse_sensitivity;\n}\n\n// NOTE: Unused.\n//\n// 0x4CAC74\nunsigned int mouse_elapsed_time()\n{\n    if (mouse_idling) {\n        if (have_mouse && !mouse_is_hidden && !mouse_disabled) {\n            return elapsed_time(mouse_idle_start_time);\n        }\n        mouse_idling = false;\n    }\n    return 0;\n}\n\n// NOTE: Unused.\n//\n// 0x4CAC74\nvoid mouse_reset_elapsed_time()\n{\n    if (mouse_idling) {\n        mouse_idling = false;\n    }\n}\n"
  },
  {
    "path": "src/plib/gnw/mouse.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_MOUSE_H_\n#define FALLOUT_PLIB_GNW_MOUSE_H_\n\n#include <stdbool.h>\n\n#include \"plib/gnw/rect.h\"\n#include \"plib/gnw/svga_types.h\"\n\n#define MOUSE_DEFAULT_CURSOR_WIDTH 8\n#define MOUSE_DEFAULT_CURSOR_HEIGHT 8\n#define MOUSE_DEFAULT_CURSOR_SIZE (MOUSE_DEFAULT_CURSOR_WIDTH * MOUSE_DEFAULT_CURSOR_HEIGHT)\n\n#define MOUSE_STATE_LEFT_BUTTON_DOWN 0x01\n#define MOUSE_STATE_RIGHT_BUTTON_DOWN 0x02\n\n#define MOUSE_EVENT_LEFT_BUTTON_DOWN 0x01\n#define MOUSE_EVENT_RIGHT_BUTTON_DOWN 0x02\n#define MOUSE_EVENT_LEFT_BUTTON_REPEAT 0x04\n#define MOUSE_EVENT_RIGHT_BUTTON_REPEAT 0x08\n#define MOUSE_EVENT_LEFT_BUTTON_UP 0x10\n#define MOUSE_EVENT_RIGHT_BUTTON_UP 0x20\n#define MOUSE_EVENT_ANY_BUTTON_DOWN (MOUSE_EVENT_LEFT_BUTTON_DOWN | MOUSE_EVENT_RIGHT_BUTTON_DOWN)\n#define MOUSE_EVENT_ANY_BUTTON_REPEAT (MOUSE_EVENT_LEFT_BUTTON_REPEAT | MOUSE_EVENT_RIGHT_BUTTON_REPEAT)\n#define MOUSE_EVENT_ANY_BUTTON_UP (MOUSE_EVENT_LEFT_BUTTON_UP | MOUSE_EVENT_RIGHT_BUTTON_UP)\n#define MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT (MOUSE_EVENT_LEFT_BUTTON_DOWN | MOUSE_EVENT_LEFT_BUTTON_REPEAT)\n#define MOUSE_EVENT_RIGHT_BUTTON_DOWN_REPEAT (MOUSE_EVENT_RIGHT_BUTTON_DOWN | MOUSE_EVENT_RIGHT_BUTTON_REPEAT)\n\n#define BUTTON_REPEAT_TIME 250\n\nextern ScreenTransBlitFunc* mouse_blit_trans;\nextern ScreenBlitFunc* mouse_blit;\n\nint GNW_mouse_init();\nvoid GNW_mouse_exit();\nvoid mouse_get_shape(unsigned char** buf, int* width, int* length, int* full, int* hotx, int* hoty, char* trans);\nint mouse_set_shape(unsigned char* buf, int width, int length, int full, int hotx, int hoty, char trans);\nint mouse_get_anim(unsigned char** frames, int* num_frames, int* width, int* length, int* hotx, int* hoty, char* trans, int* speed);\nint mouse_set_anim_frames(unsigned char* frames, int num_frames, int start_frame, int width, int length, int hotx, int hoty, char trans, int speed);\nvoid mouse_show();\nvoid mouse_hide();\nvoid mouse_info();\nvoid mouse_simulate_input(int delta_x, int delta_y, int buttons);\nbool mouse_in(int left, int top, int right, int bottom);\nbool mouse_click_in(int left, int top, int right, int bottom);\nvoid mouse_get_rect(Rect* rect);\nvoid mouse_get_position(int* out_x, int* out_y);\nvoid mouse_set_position(int a1, int a2);\nint mouse_get_buttons();\nbool mouse_hidden();\nvoid mouse_get_hotspot(int* hotx, int* hoty);\nvoid mouse_set_hotspot(int hotx, int hoty);\nbool mouse_query_exist();\nvoid mouse_get_raw_state(int* out_x, int* out_y, int* out_buttons);\nvoid mouse_disable();\nvoid mouse_enable();\nbool mouse_is_disabled();\nvoid mouse_set_sensitivity(double value);\ndouble mouse_get_sensitivity();\nunsigned int mouse_elapsed_time();\nvoid mouse_reset_elapsed_time();\n\n#endif /* FALLOUT_PLIB_GNW_MOUSE_H_ */\n"
  },
  {
    "path": "src/plib/gnw/rect.c",
    "content": "#include \"plib/gnw/rect.h\"\n\n#include <stdlib.h>\n\n#include \"plib/gnw/memory.h\"\n\n// 0x51DEF4\nstatic RectPtr rlist = NULL;\n\n// 0x4C6900\nvoid GNW_rect_exit()\n{\n    RectPtr temp;\n\n    while (rlist != NULL) {\n        temp = rlist->next;\n        mem_free(rlist);\n        rlist = temp;\n    }\n}\n\n// 0x4C6924\nvoid rect_clip_list(RectPtr* rectListNodePtr, Rect* rect)\n{\n    Rect v1;\n    rectCopy(&v1, rect);\n\n    // NOTE: Original code is slightly different.\n    while (*rectListNodePtr != NULL) {\n        RectPtr rectListNode = *rectListNodePtr;\n        if (v1.lrx >= rectListNode->rect.ulx\n            && v1.lry >= rectListNode->rect.uly\n            && v1.ulx <= rectListNode->rect.lrx\n            && v1.uly <= rectListNode->rect.lry) {\n            Rect v2;\n            rectCopy(&v2, &(rectListNode->rect));\n\n            *rectListNodePtr = rectListNode->next;\n\n            rectListNode->next = rlist;\n            rlist = rectListNode;\n\n            if (v2.uly < v1.uly) {\n                RectPtr newRectListNode = rect_malloc();\n                if (newRectListNode == NULL) {\n                    return;\n                }\n\n                rectCopy(&(newRectListNode->rect), &v2);\n                newRectListNode->rect.lry = v1.uly - 1;\n                newRectListNode->next = *rectListNodePtr;\n\n                *rectListNodePtr = newRectListNode;\n                rectListNodePtr = &(newRectListNode->next);\n\n                v2.uly = v1.uly;\n            }\n\n            if (v2.lry > v1.lry) {\n                RectPtr newRectListNode = rect_malloc();\n                if (newRectListNode == NULL) {\n                    return;\n                }\n\n                rectCopy(&(newRectListNode->rect), &v2);\n                newRectListNode->rect.uly = v1.lry + 1;\n                newRectListNode->next = *rectListNodePtr;\n\n                *rectListNodePtr = newRectListNode;\n                rectListNodePtr = &(newRectListNode->next);\n\n                v2.lry = v1.lry;\n            }\n\n            if (v2.ulx < v1.ulx) {\n                RectPtr newRectListNode = rect_malloc();\n                if (newRectListNode == NULL) {\n                    return;\n                }\n\n                rectCopy(&(newRectListNode->rect), &v2);\n                newRectListNode->rect.lrx = v1.ulx - 1;\n                newRectListNode->next = *rectListNodePtr;\n\n                *rectListNodePtr = newRectListNode;\n                rectListNodePtr = &(newRectListNode->next);\n            }\n\n            if (v2.lrx > v1.lrx) {\n                RectPtr newRectListNode = rect_malloc();\n                if (newRectListNode == NULL) {\n                    return;\n                }\n\n                rectCopy(&(newRectListNode->rect), &v2);\n                newRectListNode->rect.ulx = v1.lrx + 1;\n                newRectListNode->next = *rectListNodePtr;\n\n                *rectListNodePtr = newRectListNode;\n                rectListNodePtr = &(newRectListNode->next);\n            }\n        } else {\n            rectListNodePtr = &(rectListNode->next);\n        }\n    }\n}\n\n// 0x4C6BB8\nRectPtr rect_malloc()\n{\n    RectPtr temp;\n    int i;\n\n    if (rlist == NULL) {\n        for (i = 0; i < 10; i++) {\n            temp = (RectPtr)mem_malloc(sizeof(*temp));\n            if (temp == NULL) {\n                break;\n            }\n\n            temp->next = rlist;\n            rlist = temp;\n        }\n    }\n\n    if (rlist == NULL) {\n        return NULL;\n    }\n\n    temp = rlist;\n    rlist = rlist->next;\n\n    return temp;\n}\n\n// 0x4C6C04\nvoid rect_free(RectPtr ptr)\n{\n    ptr->next = rlist;\n    rlist = ptr;\n}\n\n// Calculates a union of two source rectangles and places it into result\n// rectangle.\n//\n// 0x4C6C18\nvoid rect_min_bound(const Rect* r1, const Rect* r2, Rect* min_bound)\n{\n    min_bound->ulx = min(r1->ulx, r2->ulx);\n    min_bound->uly = min(r1->uly, r2->uly);\n    min_bound->lrx = max(r1->lrx, r2->lrx);\n    min_bound->lry = max(r1->lry, r2->lry);\n}\n\n// Calculates intersection of two source rectangles and places it into third\n// rectangle and returns 0. If two source rectangles do not have intersection\n// it returns -1 and resulting rectangle is a copy of r1.\n//\n// 0x4C6C68\nint rect_inside_bound(const Rect* r1, const Rect* bound, Rect* r2)\n{\n    r2->ulx = r1->ulx;\n    r2->uly = r1->uly;\n    r2->lrx = r1->lrx;\n    r2->lry = r1->lry;\n\n    if (r1->ulx <= bound->lrx && bound->ulx <= r1->lrx && bound->lry >= r1->uly && bound->uly <= r1->lry) {\n        if (bound->ulx > r1->ulx) {\n            r2->ulx = bound->ulx;\n        }\n\n        if (bound->lrx < r1->lrx) {\n            r2->lrx = bound->lrx;\n        }\n\n        if (bound->uly > r1->uly) {\n            r2->uly = bound->uly;\n        }\n\n        if (bound->lry < r1->lry) {\n            r2->lry = bound->lry;\n        }\n\n        return 0;\n    }\n\n    return -1;\n}\n"
  },
  {
    "path": "src/plib/gnw/rect.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_RECT_H_\n#define FALLOUT_PLIB_GNW_RECT_H_\n\n// TODO: Remove.\ntypedef struct Point {\n    int x;\n    int y;\n} Point;\n\n// TODO: Remove.\ntypedef struct Size {\n    int width;\n    int height;\n} Size;\n\ntypedef struct Rect {\n    int ulx;\n    int uly;\n    int lrx;\n    int lry;\n} Rect;\n\n// TODO: Check.\ntypedef struct rectdata {\n    Rect rect;\n    struct rectdata* next;\n} rectdata;\n\ntypedef rectdata* RectPtr;\n\nvoid GNW_rect_exit();\nvoid rect_clip_list(RectPtr* rectListNodePtr, Rect* rect);\nRectPtr rect_malloc();\nvoid rect_free(RectPtr ptr);\nvoid rect_min_bound(const Rect* r1, const Rect* r2, Rect* min_bound);\nint rect_inside_bound(const Rect* r1, const Rect* bound, Rect* r2);\n\n// TODO: Remove.\nstatic inline void rectCopy(Rect* dest, const Rect* src)\n{\n    dest->ulx = src->ulx;\n    dest->uly = src->uly;\n    dest->lrx = src->lrx;\n    dest->lry = src->lry;\n}\n\n// TODO: Remove.\nstatic inline int rectGetWidth(const Rect* rect)\n{\n    return rect->lrx - rect->ulx + 1;\n}\n\n// TODO: Remove.\nstatic inline int rectGetHeight(const Rect* rect)\n{\n    return rect->lry - rect->uly + 1;\n}\n\n// TODO: Remove.\nstatic inline void rectOffset(Rect* rect, int dx, int dy)\n{\n    rect->ulx += dx;\n    rect->uly += dy;\n    rect->lrx += dx;\n    rect->lry += dy;\n}\n\n#endif /* FALLOUT_PLIB_GNW_RECT_H_ */\n"
  },
  {
    "path": "src/plib/gnw/svga.c",
    "content": "#include \"plib/gnw/svga.h\"\n\n#include \"mmx.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/grbuf.h\"\n#include \"plib/gnw/mouse.h\"\n#include \"plib/gnw/winmain.h\"\n\nstatic int GNW95_init_mode_ex(int width, int height, int bpp);\nstatic int GNW95_init_mode(int width, int height);\nstatic int ffs(int bits);\n\n// 0x51E2B0\nLPDIRECTDRAW GNW95_DDObject = NULL;\n\n// 0x51E2B4\nLPDIRECTDRAWSURFACE GNW95_DDPrimarySurface = NULL;\n\n// 0x51E2B8\nLPDIRECTDRAWSURFACE GNW95_DDRestoreSurface = NULL;\n\n// 0x51E2BC\nLPDIRECTDRAWPALETTE GNW95_DDPrimaryPalette = NULL;\n\n// 0x51E2C4\nUpdatePaletteFunc* update_palette_func = NULL;\n\n// 0x51E2C8\nbool mmxEnabled = true;\n\n// 0x6AC7F0\nunsigned short GNW95_Pal16[256];\n\n// screen rect\nRect scr_size;\n\n// 0x6ACA00\nunsigned int w95gmask;\n\n// 0x6ACA04\nunsigned int w95rmask;\n\n// 0x6ACA08\nunsigned int w95bmask;\n\n// 0x6ACA0C\nint w95bshift;\n\n// 0x6ACA10\nint w95rshift;\n\n// 0x6ACA14\nint w95gshift;\n\n// 0x6ACA18\nScreenBlitFunc* scr_blit = GNW95_ShowRect;\n\n// 0x6ACA1C\nZeroMemFunc* zero_mem = NULL;\n\n// 0x4CACD0\nvoid mmxEnable(bool enable)\n{\n    // 0x51E2CC\n    static bool inited = false;\n\n    // 0x6ACA20\n    static bool mmx;\n\n    if (!inited) {\n        mmx = mmxIsSupported();\n        inited = true;\n    }\n\n    if (mmx) {\n        mmxEnabled = enable;\n    }\n}\n\n// 0x4CAD08\nint init_mode_320_200()\n{\n    return GNW95_init_mode_ex(320, 200, 8);\n}\n\n// 0x4CAD40\nint init_mode_320_400()\n{\n    return GNW95_init_mode_ex(320, 400, 8);\n}\n\n// 0x4CAD5C\nint init_mode_640_480_16()\n{\n    return -1;\n}\n\n// 0x4CAD64\nint init_mode_640_480()\n{\n    return GNW95_init_mode(640, 480);\n}\n\n// 0x4CAD94\nint init_mode_640_400()\n{\n    return GNW95_init_mode(640, 400);\n}\n\n// 0x4CADA8\nint init_mode_800_600()\n{\n    return GNW95_init_mode(800, 600);\n}\n\n// 0x4CADBC\nint init_mode_1024_768()\n{\n    return GNW95_init_mode(1024, 768);\n}\n\n// 0x4CADD0\nint init_mode_1280_1024()\n{\n    return GNW95_init_mode(1280, 1024);\n}\n\n// 0x4CADE4\nint init_vesa_mode(int mode, int width, int height, int half)\n{\n    if (half != 0) {\n        return -1;\n    }\n\n    return GNW95_init_mode_ex(width, height, 8);\n}\n\n// 0x4CADF3\nint get_start_mode()\n{\n    return -1;\n}\n\n// 0x4CADF8\nvoid reset_mode()\n{\n}\n\n// 0x4CADFC\nvoid zero_vid_mem()\n{\n    if (zero_mem) {\n        zero_mem();\n    }\n}\n\n// 0x4CAE1C\nstatic int GNW95_init_mode_ex(int width, int height, int bpp)\n{\n    if (GNW95_init_window() == -1) {\n        return -1;\n    }\n\n    if (GNW95_init_DirectDraw(width, height, bpp) == -1) {\n        return -1;\n    }\n\n    scr_size.ulx = 0;\n    scr_size.uly = 0;\n    scr_size.lrx = width - 1;\n    scr_size.lry = height - 1;\n\n    mmxEnable(true);\n\n    if (bpp == 8) {\n        mouse_blit_trans = NULL;\n        scr_blit = GNW95_ShowRect;\n        zero_mem = GNW95_zero_vid_mem;\n        mouse_blit = GNW95_ShowRect;\n    } else {\n        zero_mem = NULL;\n        mouse_blit = GNW95_MouseShowRect16;\n        mouse_blit_trans = GNW95_MouseShowTransRect16;\n        scr_blit = GNW95_ShowRect16;\n    }\n\n    return 0;\n}\n\n// 0x4CAECC\nstatic int GNW95_init_mode(int width, int height)\n{\n    return GNW95_init_mode_ex(width, height, 8);\n}\n\n// 0x4CAEDC\nint GNW95_init_window()\n{\n    if (GNW95_hwnd == NULL) {\n        int width = GetSystemMetrics(SM_CXSCREEN);\n        int height = GetSystemMetrics(SM_CYSCREEN);\n\n        GNW95_hwnd = CreateWindowExA(WS_EX_TOPMOST, \"GNW95 Class\", GNW95_title, WS_POPUP | WS_VISIBLE | WS_SYSMENU, 0, 0, width, height, NULL, NULL, GNW95_hInstance, NULL);\n        if (GNW95_hwnd == NULL) {\n            return -1;\n        }\n\n        UpdateWindow(GNW95_hwnd);\n        SetFocus(GNW95_hwnd);\n    }\n\n    return 0;\n}\n\n// calculate shift for mask\n// 0x4CAF50\nstatic int ffs(int bits)\n{\n    int shift = 0;\n\n    if ((bits & 0xFFFF0000) != 0) {\n        shift |= 16;\n        bits &= 0xFFFF0000;\n    }\n\n    if ((bits & 0xFF00FF00) != 0) {\n        shift |= 8;\n        bits &= 0xFF00FF00;\n    }\n\n    if ((bits & 0xF0F0F0F0) != 0) {\n        shift |= 4;\n        bits &= 0xF0F0F0F0;\n    }\n\n    if ((bits & 0xCCCCCCCC) != 0) {\n        shift |= 2;\n        bits &= 0xCCCCCCCC;\n    }\n\n    if ((bits & 0xAAAAAAAA) != 0) {\n        shift |= 1;\n    }\n\n    return shift;\n}\n\n// 0x4CAF9C\nint GNW95_init_DirectDraw(int width, int height, int bpp)\n{\n    if (GNW95_DDObject != NULL) {\n        unsigned char* palette = GNW95_GetPalette();\n        GNW95_reset_mode();\n\n        if (GNW95_init_DirectDraw(width, height, bpp) == -1) {\n            return -1;\n        }\n\n        GNW95_SetPalette(palette);\n\n        return 0;\n    }\n\n    if (GNW95_DirectDrawCreate(NULL, &GNW95_DDObject, NULL) != DD_OK) {\n        return -1;\n    }\n\n    if (IDirectDraw_SetCooperativeLevel(GNW95_DDObject, GNW95_hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN) != DD_OK) {\n        return -1;\n    }\n\n    if (IDirectDraw_SetDisplayMode(GNW95_DDObject, width, height, bpp) != DD_OK) {\n        return -1;\n    }\n\n    DDSURFACEDESC ddsd;\n    memset(&ddsd, 0, sizeof(DDSURFACEDESC));\n\n    ddsd.dwSize = sizeof(DDSURFACEDESC);\n    ddsd.dwFlags = DDSD_CAPS;\n    ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;\n\n    if (IDirectDraw_CreateSurface(GNW95_DDObject, &ddsd, &GNW95_DDPrimarySurface, NULL) != DD_OK) {\n        return -1;\n    }\n\n    GNW95_DDRestoreSurface = GNW95_DDPrimarySurface;\n\n    if (bpp == 8) {\n        PALETTEENTRY pe[256];\n        for (int index = 0; index < 256; index++) {\n            pe[index].peRed = index;\n            pe[index].peGreen = index;\n            pe[index].peBlue = index;\n            pe[index].peFlags = 0;\n        }\n\n        if (IDirectDraw_CreatePalette(GNW95_DDObject, DDPCAPS_8BIT | DDPCAPS_ALLOW256, pe, &GNW95_DDPrimaryPalette, NULL) != DD_OK) {\n            return -1;\n        }\n\n        if (IDirectDrawSurface_SetPalette(GNW95_DDPrimarySurface, GNW95_DDPrimaryPalette) != DD_OK) {\n            return -1;\n        }\n\n        return 0;\n    } else {\n        DDPIXELFORMAT ddpf;\n        ddpf.dwSize = sizeof(DDPIXELFORMAT);\n\n        if (IDirectDrawSurface_GetPixelFormat(GNW95_DDPrimarySurface, &ddpf) != DD_OK) {\n            return -1;\n        }\n\n        w95rmask = ddpf.dwRBitMask;\n        w95gmask = ddpf.dwGBitMask;\n        w95bmask = ddpf.dwBBitMask;\n\n        w95rshift = ffs(w95rmask) - 7;\n        w95gshift = ffs(w95gmask) - 7;\n        w95bshift = ffs(w95bmask) - 7;\n\n        return 0;\n    }\n}\n\n// 0x4CB1B0\nvoid GNW95_reset_mode()\n{\n    if (GNW95_DDObject != NULL) {\n        IDirectDraw_RestoreDisplayMode(GNW95_DDObject);\n\n        if (GNW95_DDPrimarySurface != NULL) {\n            IDirectDrawSurface_Release(GNW95_DDPrimarySurface);\n            GNW95_DDPrimarySurface = NULL;\n            GNW95_DDRestoreSurface = NULL;\n        }\n\n        if (GNW95_DDPrimaryPalette != NULL) {\n            IDirectDrawPalette_Release(GNW95_DDPrimaryPalette);\n            GNW95_DDPrimaryPalette = NULL;\n        }\n\n        IDirectDraw_Release(GNW95_DDObject);\n        GNW95_DDObject = NULL;\n    }\n}\n\n// 0x4CB218\nvoid GNW95_SetPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b)\n{\n    PALETTEENTRY tempEntry;\n\n    r <<= 2;\n    g <<= 2;\n    b <<= 2;\n\n    if (GNW95_DDPrimaryPalette != NULL) {\n        tempEntry.peRed = r;\n        tempEntry.peGreen = g;\n        tempEntry.peBlue = b;\n        tempEntry.peFlags = PC_NOCOLLAPSE;\n        IDirectDrawPalette_SetEntries(GNW95_DDPrimaryPalette, 0, entry, 1, &tempEntry);\n    } else {\n        GNW95_Pal16[entry] = ((w95rshift > 0 ? (r << w95rshift) : (r >> -w95rshift)) & w95rmask)\n            | ((w95gshift > 0 ? (g << w95gshift) : (r >> -w95gshift)) & w95gmask)\n            | ((w95bshift > 0 ? (b << w95bshift) : (r >> -w95bshift)) & w95bmask);\n        win_refresh_all(&scr_size);\n    }\n\n    if (update_palette_func != NULL) {\n        update_palette_func();\n    }\n}\n\n// 0x4CB310\nvoid GNW95_SetPaletteEntries(unsigned char* palette, int start, int count)\n{\n    if (GNW95_DDPrimaryPalette != NULL) {\n        PALETTEENTRY entries[256];\n\n        if (count != 0) {\n            for (int index = 0; index < count; index++) {\n                entries[index].peRed = palette[index * 3] << 2;\n                entries[index].peGreen = palette[index * 3 + 1] << 2;\n                entries[index].peBlue = palette[index * 3 + 2] << 2;\n                entries[index].peFlags = PC_NOCOLLAPSE;\n            }\n        }\n\n        IDirectDrawPalette_SetEntries(GNW95_DDPrimaryPalette, 0, start, count, entries);\n    } else {\n        for (int index = start; index < start + count; index++) {\n            unsigned short r = palette[0] << 2;\n            unsigned short g = palette[1] << 2;\n            unsigned short b = palette[2] << 2;\n            palette += 3;\n\n            r = w95rshift > 0 ? (r << w95rshift) : (r >> -w95rshift);\n            r &= w95rmask;\n\n            g = w95gshift > 0 ? (g << w95gshift) : (g >> -w95gshift);\n            g &= w95gmask;\n\n            b = w95bshift > 0 ? (b << w95bshift) : (b >> -w95bshift);\n            b &= w95bmask;\n\n            unsigned short rgb = r | g | b;\n            GNW95_Pal16[index] = rgb;\n        }\n\n        win_refresh_all(&scr_size);\n    }\n\n    if (update_palette_func != NULL) {\n        update_palette_func();\n    }\n}\n\n// 0x4CB568\nvoid GNW95_SetPalette(unsigned char* palette)\n{\n    if (GNW95_DDPrimaryPalette != NULL) {\n        PALETTEENTRY entries[256];\n\n        for (int index = 0; index < 256; index++) {\n            entries[index].peRed = palette[index * 3] << 2;\n            entries[index].peGreen = palette[index * 3 + 1] << 2;\n            entries[index].peBlue = palette[index * 3 + 2] << 2;\n            entries[index].peFlags = PC_NOCOLLAPSE;\n        }\n\n        IDirectDrawPalette_SetEntries(GNW95_DDPrimaryPalette, 0, 0, 256, entries);\n    } else {\n        for (int index = 0; index < 256; index++) {\n            unsigned short r = palette[index * 3] << 2;\n            unsigned short g = palette[index * 3 + 1] << 2;\n            unsigned short b = palette[index * 3 + 2] << 2;\n\n            r = w95rshift > 0 ? (r << w95rshift) : (r >> -w95rshift);\n            r &= w95rmask;\n\n            g = w95gshift > 0 ? (g << w95gshift) : (g >> -w95gshift);\n            g &= w95gmask;\n\n            b = w95bshift > 0 ? (b << w95bshift) : (b >> -w95bshift);\n            b &= w95bmask;\n\n            unsigned short rgb = r | g | b;\n            GNW95_Pal16[index] = rgb;\n        }\n\n        win_refresh_all(&scr_size);\n    }\n\n    if (update_palette_func != NULL) {\n        update_palette_func();\n    }\n}\n\n// 0x4CB68C\nunsigned char* GNW95_GetPalette()\n{\n    // FIXME: This buffer was supposed to be used as temporary place to store\n    // current palette while switching video modes (changing resolution). However\n    // the original game does not have UI to change video mode. Even if it did this\n    // buffer it too small to hold the entire palette, which require 256 * 3 bytes.\n    //\n    // 0x6ACA24\n    static unsigned char cmap[256];\n\n    if (GNW95_DDPrimaryPalette != NULL) {\n        PALETTEENTRY paletteEntries[256];\n        if (IDirectDrawPalette_GetEntries(GNW95_DDPrimaryPalette, 0, 0, 256, paletteEntries) != DD_OK) {\n            return NULL;\n        }\n\n        for (int index = 0; index < 256; index++) {\n            PALETTEENTRY* paletteEntry = &(paletteEntries[index]);\n            cmap[index * 3] = paletteEntry->peRed >> 2;\n            cmap[index * 3 + 1] = paletteEntry->peGreen >> 2;\n            cmap[index * 3 + 2] = paletteEntry->peBlue >> 2;\n        }\n\n        return cmap;\n    }\n\n    int redShift = w95rshift + 2;\n    int greenShift = w95gshift + 2;\n    int blueShift = w95bshift + 2;\n\n    for (int index = 0; index < 256; index++) {\n        unsigned short rgb = GNW95_Pal16[index];\n\n        unsigned short r = redShift > 0 ? ((rgb & w95rmask) >> redShift) : ((rgb & w95rmask) << -redShift);\n        unsigned short g = greenShift > 0 ? ((rgb & w95gmask) >> greenShift) : ((rgb & w95gmask) << -greenShift);\n        unsigned short b = blueShift > 0 ? ((rgb & w95bmask) >> blueShift) : ((rgb & w95bmask) << -blueShift);\n\n        cmap[index * 3] = (r >> 2) & 0xFF;\n        cmap[index * 3 + 1] = (g >> 2) & 0xFF;\n        cmap[index * 3 + 2] = (b >> 2) & 0xFF;\n    }\n\n    return cmap;\n}\n\n// 0x4CB850\nvoid GNW95_ShowRect(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY)\n{\n    DDSURFACEDESC ddsd;\n    HRESULT hr;\n\n    if (!GNW95_isActive) {\n        return;\n    }\n\n    while (1) {\n        ddsd.dwSize = sizeof(DDSURFACEDESC);\n\n        hr = IDirectDrawSurface_Lock(GNW95_DDPrimarySurface, NULL, &ddsd, 1, NULL);\n        if (hr == DD_OK) {\n            break;\n        }\n\n        if (hr == DDERR_SURFACELOST) {\n            if (IDirectDrawSurface_Restore(GNW95_DDRestoreSurface) != DD_OK) {\n                return;\n            }\n        }\n    }\n\n    buf_to_buf(src + srcPitch * srcY + srcX, srcWidth, srcHeight, srcPitch, (unsigned char*)ddsd.lpSurface + ddsd.lPitch * destY + destX, ddsd.lPitch);\n\n    IDirectDrawSurface_Unlock(GNW95_DDPrimarySurface, ddsd.lpSurface);\n}\n\n// 0x4CB93C\nvoid GNW95_MouseShowRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY)\n{\n    DDSURFACEDESC ddsd;\n    HRESULT hr;\n\n    if (!GNW95_isActive) {\n        return;\n    }\n\n    while (1) {\n        ddsd.dwSize = sizeof(ddsd);\n\n        hr = IDirectDrawSurface_Lock(GNW95_DDPrimarySurface, NULL, &ddsd, 1, NULL);\n        if (hr == DD_OK) {\n            break;\n        }\n\n        if (hr == DDERR_SURFACELOST) {\n            if (IDirectDrawSurface_Restore(GNW95_DDRestoreSurface) != DD_OK) {\n                return;\n            }\n        }\n    }\n\n    unsigned char* dest = (unsigned char*)ddsd.lpSurface + ddsd.lPitch * destY + 2 * destX;\n\n    src += srcPitch * srcY + srcX;\n\n    for (int y = 0; y < srcHeight; y++) {\n        unsigned short* destPtr = (unsigned short*)dest;\n        unsigned char* srcPtr = src;\n        for (int x = 0; x < srcWidth; x++) {\n            *destPtr = GNW95_Pal16[*srcPtr];\n            destPtr++;\n            srcPtr++;\n        }\n\n        dest += ddsd.lPitch;\n        src += srcPitch;\n    }\n\n    IDirectDrawSurface_Unlock(GNW95_DDPrimarySurface, ddsd.lpSurface);\n}\n\n// 0x4CBA44\nvoid GNW95_ShowRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY)\n{\n    GNW95_MouseShowRect16(src, srcPitch, a3, srcX, srcY, srcWidth, srcHeight, destX, destY);\n}\n\n// 0x4CBAB0\nvoid GNW95_MouseShowTransRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, unsigned char keyColor)\n{\n    DDSURFACEDESC ddsd;\n    HRESULT hr;\n\n    if (!GNW95_isActive) {\n        return;\n    }\n\n    while (1) {\n        ddsd.dwSize = sizeof(ddsd);\n\n        hr = IDirectDrawSurface_Lock(GNW95_DDPrimarySurface, NULL, &ddsd, 1, NULL);\n        if (hr == DD_OK) {\n            break;\n        }\n\n        if (hr == DDERR_SURFACELOST) {\n            if (IDirectDrawSurface_Restore(GNW95_DDRestoreSurface) != DD_OK) {\n                return;\n            }\n        }\n    }\n\n    unsigned char* dest = (unsigned char*)ddsd.lpSurface + ddsd.lPitch * destY + 2 * destX;\n\n    src += srcPitch * srcY + srcX;\n\n    for (int y = 0; y < srcHeight; y++) {\n        unsigned short* destPtr = (unsigned short*)dest;\n        unsigned char* srcPtr = src;\n        for (int x = 0; x < srcWidth; x++) {\n            if (*srcPtr != keyColor) {\n                *destPtr = GNW95_Pal16[*srcPtr];\n            }\n            destPtr++;\n            srcPtr++;\n        }\n\n        dest += ddsd.lPitch;\n        src += srcPitch;\n    }\n\n    IDirectDrawSurface_Unlock(GNW95_DDPrimarySurface, ddsd.lpSurface);\n}\n\n// Clears drawing surface.\n//\n// 0x4CBBC8\nvoid GNW95_zero_vid_mem()\n{\n    DDSURFACEDESC ddsd;\n    HRESULT hr;\n    unsigned char* surface;\n\n    if (!GNW95_isActive) {\n        return;\n    }\n\n    while (1) {\n        ddsd.dwSize = sizeof(DDSURFACEDESC);\n\n        hr = IDirectDrawSurface_Lock(GNW95_DDPrimarySurface, NULL, &ddsd, 1, NULL);\n        if (hr == DD_OK) {\n            break;\n        }\n\n        if (hr == DDERR_SURFACELOST) {\n            if (IDirectDrawSurface_Restore(GNW95_DDRestoreSurface) != DD_OK) {\n                return;\n            }\n        }\n    }\n\n    surface = (unsigned char*)ddsd.lpSurface;\n    for (unsigned int y = 0; y < ddsd.dwHeight; y++) {\n        memset(surface, 0, ddsd.dwWidth);\n        surface += ddsd.lPitch;\n    }\n\n    IDirectDrawSurface_Unlock(GNW95_DDPrimarySurface, ddsd.lpSurface);\n}\n"
  },
  {
    "path": "src/plib/gnw/svga.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_SVGA_H_\n#define FALLOUT_PLIB_GNW_SVGA_H_\n\n#include <stdbool.h>\n\n#include \"plib/gnw/gnw95dx.h\"\n#include \"plib/gnw/rect.h\"\n#include \"plib/gnw/svga_types.h\"\n\nextern LPDIRECTDRAW GNW95_DDObject;\nextern LPDIRECTDRAWSURFACE GNW95_DDPrimarySurface;\nextern LPDIRECTDRAWSURFACE GNW95_DDRestoreSurface;\nextern LPDIRECTDRAWPALETTE GNW95_DDPrimaryPalette;\nextern UpdatePaletteFunc* update_palette_func;\nextern bool mmxEnabled;\n\nextern unsigned short GNW95_Pal16[256];\nextern Rect scr_size;\nextern unsigned int w95rmask;\nextern unsigned int w95gmask;\nextern unsigned int w95bmask;\nextern int w95bshift;\nextern int w95rshift;\nextern int w95gshift;\nextern ScreenBlitFunc* scr_blit;\nextern ZeroMemFunc* zero_mem;\n\nvoid mmxEnable(bool enable);\nint init_mode_320_200();\nint init_mode_320_400();\nint init_mode_640_480_16();\nint init_mode_640_480();\nint init_mode_640_400();\nint init_mode_800_600();\nint init_mode_1024_768();\nint init_mode_1280_1024();\nint init_vesa_mode(int mode, int width, int height, int half);\nint get_start_mode();\nvoid reset_mode();\nvoid zero_vid_mem();\nint GNW95_init_window();\nint GNW95_init_DirectDraw(int width, int height, int bpp);\nvoid GNW95_reset_mode();\nvoid GNW95_SetPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b);\nvoid GNW95_SetPaletteEntries(unsigned char* a1, int a2, int a3);\nvoid GNW95_SetPalette(unsigned char* palette);\nunsigned char* GNW95_GetPalette();\nvoid GNW95_ShowRect(unsigned char* src, int src_pitch, int a3, int src_x, int src_y, int src_width, int src_height, int dest_x, int dest_y);\nvoid GNW95_MouseShowRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY);\nvoid GNW95_ShowRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY);\nvoid GNW95_MouseShowTransRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, unsigned char keyColor);\nvoid GNW95_zero_vid_mem();\n\n#endif /* FALLOUT_PLIB_GNW_SVGA_H_ */\n"
  },
  {
    "path": "src/plib/gnw/svga_types.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_SVGA_TYPES_H_\n#define FALLOUT_PLIB_GNW_SVGA_TYPES_H_\n\n// NOTE: These typedefs always appear in this order in every implementation file\n// with extended debug info. However `mouse.c` does not have DirectX types\n// implying it does not include `svga.h` which does so to expose primary\n// DirectDraw objects.\n\ntypedef void(UpdatePaletteFunc)();\ntypedef void(ZeroMemFunc)();\ntypedef void(ResetModeFunc)();\ntypedef int(SetModeFunc)();\ntypedef void(ScreenTransBlitFunc)(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, unsigned char a10);\ntypedef void(ScreenBlitFunc)(unsigned char* src, int src_pitch, int a3, int src_x, int src_y, int src_width, int src_height, int dest_x, int dest_y);\n\n#endif /* FALLOUT_PLIB_GNW_SVGA_TYPES_H_ */\n"
  },
  {
    "path": "src/plib/gnw/text.c",
    "content": "#include \"plib/gnw/text.h\"\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include \"plib/color/color.h\"\n#include \"plib/db/db.h\"\n#include \"plib/gnw/memory.h\"\n\n// The maximum number of text fonts.\n#define TEXT_FONT_MAX 10\n\n// The maximum number of font managers.\n#define FONT_MANAGER_MAX 10\n\nstatic int load_font(int n);\nstatic void GNW_text_font(int font_num);\nstatic bool text_font_exists(int font_num, FontMgrPtr* mgr);\nstatic void GNW_text_to_buf(unsigned char* buf, const char* str, int swidth, int fullw, int color);\nstatic int GNW_text_height();\nstatic int GNW_text_width(const char* str);\nstatic int GNW_text_char_width(int c);\nstatic int GNW_text_mono_width(const char* str);\nstatic int GNW_text_spacing();\nstatic int GNW_text_size(const char* str);\nstatic int GNW_text_max();\n\n// 0x51E3B0\nstatic int curr_font_num = -1;\n\n// 0x51E3B4\nstatic int total_managers = 0;\n\n// 0x51E3B8\ntext_to_buf_func* text_to_buf = NULL;\n\n// 0x51E3BC\ntext_height_func* text_height = NULL;\n\n// 0x51E3C0\ntext_width_func* text_width = NULL;\n\n// 0x51E3C4\ntext_char_width_func* text_char_width = NULL;\n\n// 0x51E3C8\ntext_mono_width_func* text_mono_width = NULL;\n\n// 0x51E3CC\ntext_spacing_func* text_spacing = NULL;\n\n// 0x51E3D0\ntext_size_func* text_size = NULL;\n\n// 0x51E3D4\ntext_max_func* text_max = NULL;\n\n// 0x6ADB08\nstatic Font font[TEXT_FONT_MAX];\n\n// 0x6ADBD0\nstatic FontMgr font_managers[FONT_MANAGER_MAX];\n\n// 0x6ADD88\nstatic Font* curr_font;\n\n// 0x4D555C\nint GNW_text_init()\n{\n    // 0x4D5530\n    static FontMgr GNW_font_mgr = {\n        0,\n        9,\n        GNW_text_font,\n        GNW_text_to_buf,\n        GNW_text_height,\n        GNW_text_width,\n        GNW_text_char_width,\n        GNW_text_mono_width,\n        GNW_text_spacing,\n        GNW_text_size,\n        GNW_text_max,\n    };\n\n    int i;\n    int first_font;\n\n    first_font = -1;\n\n    for (i = 0; i < TEXT_FONT_MAX; i++) {\n        if (load_font(i) == -1) {\n            font[i].num = 0;\n        } else {\n            if (first_font == -1) {\n                first_font = i;\n            }\n        }\n    }\n\n    if (first_font == -1) {\n        return -1;\n    }\n\n    if (text_add_manager(&GNW_font_mgr) == -1) {\n        return -1;\n    }\n\n    text_font(first_font);\n\n    return 0;\n}\n\n// 0x4D55CC\nvoid GNW_text_exit()\n{\n    int i;\n\n    for (i = 0; i < TEXT_FONT_MAX; i++) {\n        if (font[i].num != 0) {\n            mem_free(font[i].info);\n            mem_free(font[i].data);\n        }\n    }\n}\n\n// 0x4D55FC\nstatic int load_font(int n)\n{\n    int rc = -1;\n\n    char path[MAX_PATH];\n    sprintf(path, \"font%d.fon\", n);\n\n    // NOTE: Original code is slightly different. It uses deep nesting and\n    // unwinds everything from the point of failure.\n    Font* textFontDescriptor = &(font[n]);\n    textFontDescriptor->data = NULL;\n    textFontDescriptor->info = NULL;\n\n    File* stream = db_fopen(path, \"rb\");\n    int dataSize;\n    if (stream == NULL) {\n        goto out;\n    }\n\n    if (db_fread(textFontDescriptor, sizeof(Font), 1, stream) != 1) {\n        goto out;\n    }\n\n    textFontDescriptor->info = (FontInfo*)mem_malloc(textFontDescriptor->num * sizeof(FontInfo));\n    if (textFontDescriptor->info == NULL) {\n        goto out;\n    }\n\n    if (db_fread(textFontDescriptor->info, sizeof(FontInfo), textFontDescriptor->num, stream) != textFontDescriptor->num) {\n        goto out;\n    }\n\n    dataSize = textFontDescriptor->height * ((textFontDescriptor->info[textFontDescriptor->num - 1].width + 7) >> 3) + textFontDescriptor->info[textFontDescriptor->num - 1].offset;\n    textFontDescriptor->data = (unsigned char*)mem_malloc(dataSize);\n    if (textFontDescriptor->data == NULL) {\n        goto out;\n    }\n\n    if (db_fread(textFontDescriptor->data, 1, dataSize, stream) != dataSize) {\n        goto out;\n    }\n\n    rc = 0;\n\nout:\n\n    if (rc != 0) {\n        if (textFontDescriptor->data != NULL) {\n            mem_free(textFontDescriptor->data);\n            textFontDescriptor->data = NULL;\n        }\n\n        if (textFontDescriptor->info != NULL) {\n            mem_free(textFontDescriptor->info);\n            textFontDescriptor->info = NULL;\n        }\n    }\n\n    if (stream != NULL) {\n        db_fclose(stream);\n    }\n\n    return rc;\n}\n\n// 0x4D5780\nint text_add_manager(FontMgrPtr mgr)\n{\n    int k;\n\n    if (mgr == NULL) {\n        return -1;\n    }\n\n    if (total_managers >= FONT_MANAGER_MAX) {\n        return -1;\n    }\n\n    // Check if a font manager exists for any font in the specified range.\n    for (k = mgr->low_font_num; k < mgr->high_font_num; k++) {\n        FontMgrPtr mgr;\n        if (text_font_exists(k, &mgr)) {\n            return -1;\n        }\n    }\n\n    memcpy(&(font_managers[total_managers]), mgr, sizeof(*mgr));\n    total_managers++;\n\n    return 0;\n}\n\n// 0x4D58AC\nstatic void GNW_text_font(int font_num)\n{\n    if (font_num >= TEXT_FONT_MAX) {\n        return;\n    }\n\n    if (font[font_num].num == 0) {\n        return;\n    }\n\n    curr_font = &(font[font_num]);\n}\n\n// 0x4D58D4\nint text_curr()\n{\n    return curr_font_num;\n}\n\n// 0x4D58DC\nvoid text_font(int font_num)\n{\n    FontMgrPtr mgr;\n\n    if (text_font_exists(font_num, &mgr)) {\n        text_to_buf = mgr->text_to_buf;\n        text_height = mgr->text_height;\n        text_width = mgr->text_width;\n        text_char_width = mgr->text_char_width;\n        text_mono_width = mgr->text_mono_width;\n        text_spacing = mgr->text_spacing;\n        text_size = mgr->text_size;\n        text_max = mgr->text_max;\n\n        curr_font_num = font_num;\n\n        mgr->text_font(font_num);\n    }\n}\n\n// 0x4D595C\nstatic bool text_font_exists(int font_num, FontMgrPtr* mgr)\n{\n    int k;\n\n    for (k = 0; k < total_managers; k++) {\n        if (font_num >= font_managers[k].low_font_num && font_num <= font_managers[k].high_font_num) {\n            *mgr = &(font_managers[k]);\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x4D59B0\nvoid GNW_text_to_buf(unsigned char* buf, const char* str, int swidth, int fullw, int color)\n{\n    if ((color & FONT_SHADOW) != 0) {\n        color &= ~FONT_SHADOW;\n        text_to_buf(buf + fullw + 1, str, swidth, fullw, colorTable[0]);\n    }\n\n    int monospacedCharacterWidth;\n    if ((color & FONT_MONO) != 0) {\n        monospacedCharacterWidth = text_max();\n    }\n\n    unsigned char* ptr = buf;\n    while (*str != '\\0') {\n        char ch = *str++;\n        if (ch < curr_font->num) {\n            FontInfo* glyph = &(curr_font->info[ch & 0xFF]);\n\n            unsigned char* end;\n            if ((color & FONT_MONO) != 0) {\n                end = ptr + monospacedCharacterWidth;\n                ptr += (monospacedCharacterWidth - curr_font->spacing - glyph->width) / 2;\n            } else {\n                end = ptr + glyph->width + curr_font->spacing;\n            }\n\n            if (end - buf > swidth) {\n                break;\n            }\n\n            unsigned char* glyphData = curr_font->data + glyph->offset;\n            for (int y = 0; y < curr_font->height; y++) {\n                int bits = 0x80;\n                for (int x = 0; x < glyph->width; x++) {\n                    if (bits == 0) {\n                        bits = 0x80;\n                        glyphData++;\n                    }\n\n                    if ((*glyphData & bits) != 0) {\n                        *ptr = color & 0xFF;\n                    }\n\n                    bits >>= 1;\n                    ptr++;\n                }\n                glyphData++;\n                ptr += fullw - glyph->width;\n            }\n\n            ptr = end;\n        }\n    }\n\n    if ((color & FONT_UNDERLINE) != 0) {\n        // TODO: Probably additional -1 present, check.\n        int length = ptr - buf;\n        unsigned char* underlinePtr = buf + fullw * (curr_font->height - 1);\n        for (int pix = 0; pix < length; pix++) {\n            *underlinePtr++ = color & 0xFF;\n        }\n    }\n}\n\n// 0x4D5B54\nstatic int GNW_text_height()\n{\n    return curr_font->height;\n}\n\n// 0x4D5B60\nstatic int GNW_text_width(const char* str)\n{\n    int i;\n    int len;\n    FontInfo* fi;\n\n    len = 0;\n\n    for (i = 0; str[i] != '\\0'; i++) {\n        if (str[i] < curr_font->num) {\n            fi = &(curr_font->info[str[i]]);\n            len += curr_font->spacing + fi->width;\n        }\n    }\n\n    return len;\n}\n\n// 0x4D5BA4\nstatic int GNW_text_char_width(int c)\n{\n    return curr_font->info[c].width;\n}\n\n// 0x4D5BB8\nstatic int GNW_text_mono_width(const char* str)\n{\n    return text_max() * strlen(str);\n}\n\n// 0x4D5BD8\nstatic int GNW_text_spacing()\n{\n    return curr_font->spacing;\n}\n\n// 0x4D5BE4\nstatic int GNW_text_size(const char* str)\n{\n    return text_width(str) * text_height();\n}\n\n// 0x4D5BF8\nstatic int GNW_text_max()\n{\n    int i;\n    int len;\n    FontInfo* fi;\n\n    len = 0;\n\n    for (i = 0; i < curr_font->num; i++) {\n        fi = &(curr_font->info[i]);\n        if (len < fi->width) {\n            len = fi->width;\n        }\n    }\n\n    return len + curr_font->spacing;\n}\n"
  },
  {
    "path": "src/plib/gnw/text.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_TEXT_H_\n#define FALLOUT_PLIB_GNW_TEXT_H_\n\n#define FONT_SHADOW 0x10000\n#define FONT_UNDERLINE 0x20000\n#define FONT_MONO 0x40000\n\ntypedef void text_font_func(int font);\ntypedef void text_to_buf_func(unsigned char* buf, const char* str, int swidth, int fullw, int color);\ntypedef int text_height_func();\ntypedef int text_width_func(const char* str);\n// TODO: Convert type to `char`.\ntypedef int text_char_width_func(int ch);\ntypedef int text_mono_width_func(const char* str);\ntypedef int text_spacing_func();\ntypedef int text_size_func(const char* str);\ntypedef int text_max_func();\n\ntypedef struct FontMgr {\n    int low_font_num;\n    int high_font_num;\n    text_font_func* text_font;\n    text_to_buf_func* text_to_buf;\n    text_height_func* text_height;\n    text_width_func* text_width;\n    text_char_width_func* text_char_width;\n    text_mono_width_func* text_mono_width;\n    text_spacing_func* text_spacing;\n    text_size_func* text_size;\n    text_max_func* text_max;\n} FontMgr;\n\nstatic_assert(sizeof(FontMgr) == 44, \"wrong size\");\n\ntypedef FontMgr* FontMgrPtr;\n\ntypedef struct FontInfo {\n    // The width of the glyph in pixels.\n    int width;\n\n    // Data offset into [Font.data].\n    int offset;\n} FontInfo;\n\ntypedef struct Font {\n    // The number of glyphs in the font.\n    int num;\n\n    // The height of the font.\n    int height;\n\n    // Horizontal spacing between characters in pixels.\n    int spacing;\n\n    FontInfo* info;\n    unsigned char* data;\n} Font;\n\nextern text_to_buf_func* text_to_buf;\nextern text_height_func* text_height;\nextern text_width_func* text_width;\nextern text_char_width_func* text_char_width;\nextern text_mono_width_func* text_mono_width;\nextern text_spacing_func* text_spacing;\nextern text_size_func* text_size;\nextern text_max_func* text_max;\n\nint GNW_text_init();\nvoid GNW_text_exit();\nint text_add_manager(FontMgrPtr mgr);\nint text_curr();\nvoid text_font(int font);\n\n#endif /* FALLOUT_PLIB_GNW_TEXT_H_ */\n"
  },
  {
    "path": "src/plib/gnw/vcr.c",
    "content": "#include \"plib/gnw/vcr.h\"\n\n#include <stdlib.h>\n\n#include \"plib/gnw/input.h\"\n#include \"plib/gnw/memory.h\"\n\nstatic bool vcr_create_buffer();\nstatic bool vcr_destroy_buffer();\nstatic bool vcr_clear_buffer();\nstatic bool vcr_load_buffer();\n\n// 0x51E2F0\nVcrEntry* vcr_buffer = NULL;\n\n// number of entries in vcr_buffer\n// 0x51E2F4\nint vcr_buffer_index = 0;\n\n// 0x51E2F8\nunsigned int vcr_state = VCR_STATE_TURNED_OFF;\n\n// 0x51E2FC\nunsigned int vcr_time = 0;\n\n// 0x51E300\nunsigned int vcr_counter = 0;\n\n// 0x51E304\nunsigned int vcr_terminate_flags = 0;\n\n// 0x51E308\nint vcr_terminated_condition = VCR_PLAYBACK_COMPLETION_REASON_NONE;\n\n// 0x51E30C\nstatic unsigned int vcr_start_time = 0;\n\n// 0x51E310\nstatic int vcr_registered_atexit = 0;\n\n// 0x51E314\nstatic File* vcr_file = NULL;\n\n// 0x51E318\nstatic int vcr_buffer_end = 0;\n\n// 0x51E31C\nstatic VcrPlaybackCompletionCallback* vcr_notify_callback = NULL;\n\n// 0x51E320\nstatic unsigned int vcr_temp_terminate_flags = 0;\n\n// 0x51E324\nstatic int vcr_old_layout = 0;\n\n// 0x6AD940\nstatic VcrEntry vcr_last_play_event;\n\n// 0x4D2680\nbool vcr_record(const char* fileName)\n{\n    if (vcr_state != VCR_STATE_TURNED_OFF) {\n        return false;\n    }\n\n    if (fileName == NULL) {\n        return false;\n    }\n\n    // NOTE: Uninline.\n    if (!vcr_create_buffer()) {\n        return false;\n    }\n\n    vcr_file = db_fopen(fileName, \"wb\");\n    if (vcr_file == NULL) {\n        // NOTE: Uninline.\n        vcr_destroy_buffer();\n        return false;\n    }\n\n    if (vcr_registered_atexit == 0) {\n        vcr_registered_atexit = atexit(vcr_stop);\n    }\n\n    VcrEntry* vcrEntry = &(vcr_buffer[vcr_buffer_index]);\n    vcrEntry->type = VCR_ENTRY_TYPE_INITIAL_STATE;\n    vcrEntry->time = 0;\n    vcrEntry->counter = 0;\n    vcrEntry->initial.keyboardLayout = kb_get_layout();\n\n    while (mouse_get_buttons() != 0) {\n        mouse_info();\n    }\n\n    mouse_get_position(&(vcrEntry->initial.mouseX), &(vcrEntry->initial.mouseY));\n\n    vcr_counter = 1;\n    vcr_buffer_index++;\n    vcr_start_time = get_time();\n    kb_clear();\n    vcr_state = VCR_STATE_RECORDING;\n\n    return true;\n}\n\n// 0x4D27EC\nbool vcr_play(const char* fileName, unsigned int terminationFlags, VcrPlaybackCompletionCallback* callback)\n{\n    if (vcr_state != VCR_STATE_TURNED_OFF) {\n        return false;\n    }\n\n    if (fileName == NULL) {\n        return false;\n    }\n\n    // NOTE: Uninline.\n    if (!vcr_create_buffer()) {\n        return false;\n    }\n\n    vcr_file = db_fopen(fileName, \"rb\");\n    if (vcr_file == NULL) {\n        // NOTE: Uninline.\n        vcr_destroy_buffer();\n        return false;\n    }\n\n    if (!vcr_load_buffer()) {\n        db_fclose(vcr_file);\n        // NOTE: Uninline.\n        vcr_destroy_buffer();\n        return false;\n    }\n\n    while (mouse_get_buttons() != 0) {\n        mouse_info();\n    }\n\n    kb_clear();\n\n    vcr_temp_terminate_flags = terminationFlags;\n    vcr_notify_callback = callback;\n    vcr_terminated_condition = VCR_PLAYBACK_COMPLETION_REASON_COMPLETED;\n    vcr_terminate_flags = 0;\n    vcr_counter = 0;\n    vcr_time = 0;\n    vcr_start_time = get_time();\n    vcr_state = VCR_STATE_PLAYING;\n    vcr_last_play_event.time = 0;\n    vcr_last_play_event.counter = 0;\n\n    return true;\n}\n\n// 0x4D28F4\nint vcr_stop(void)\n{\n    if (vcr_state == VCR_STATE_RECORDING || vcr_state == VCR_STATE_PLAYING) {\n        vcr_state |= VCR_STATE_STOP_REQUESTED;\n    }\n\n    kb_clear();\n\n    return 1;\n}\n\n// 0x4D2918\nint vcr_status()\n{\n    return vcr_state;\n}\n\n// 0x4D2930\nint vcr_update()\n{\n    if ((vcr_state & VCR_STATE_STOP_REQUESTED) != 0) {\n        vcr_state &= ~VCR_STATE_STOP_REQUESTED;\n\n        switch (vcr_state) {\n        case VCR_STATE_RECORDING:\n            vcr_dump_buffer();\n\n            db_fclose(vcr_file);\n            vcr_file = NULL;\n\n            // NOTE: Uninline.\n            vcr_destroy_buffer();\n\n            break;\n        case VCR_STATE_PLAYING:\n            db_fclose(vcr_file);\n            vcr_file = NULL;\n\n            // NOTE: Uninline.\n            vcr_destroy_buffer();\n\n            kb_set_layout(vcr_old_layout);\n\n            if (vcr_notify_callback != NULL) {\n                vcr_notify_callback(vcr_terminated_condition);\n            }\n            break;\n        }\n\n        vcr_state = VCR_STATE_TURNED_OFF;\n    }\n\n    switch (vcr_state) {\n    case VCR_STATE_RECORDING:\n        vcr_counter++;\n        vcr_time = elapsed_time(vcr_start_time);\n        if (vcr_buffer_index == VCR_BUFFER_CAPACITY - 1) {\n            vcr_dump_buffer();\n        }\n        break;\n    case VCR_STATE_PLAYING:\n        if (vcr_buffer_index < vcr_buffer_end || vcr_load_buffer()) {\n            VcrEntry* vcrEntry = &(vcr_buffer[vcr_buffer_index]);\n            if (vcr_last_play_event.counter < vcrEntry->counter) {\n                if (vcrEntry->time > vcr_last_play_event.time) {\n                    unsigned int delay = vcr_last_play_event.time;\n                    delay += (vcr_counter - vcr_last_play_event.counter)\n                        * (vcrEntry->time - vcr_last_play_event.time)\n                        / (vcrEntry->counter - vcr_last_play_event.counter);\n\n                    while (elapsed_time(vcr_start_time) < delay) {\n                    }\n                }\n            }\n\n            vcr_counter++;\n\n            int rc = 0;\n            while (vcr_counter >= vcr_buffer[vcr_buffer_index].counter) {\n                vcr_time = elapsed_time(vcr_start_time);\n                if (vcr_time > vcr_buffer[vcr_buffer_index].time + 5\n                    || vcr_time < vcr_buffer[vcr_buffer_index].time - 5) {\n                    vcr_start_time += vcr_time - vcr_buffer[vcr_buffer_index].time;\n                }\n\n                switch (vcr_buffer[vcr_buffer_index].type) {\n                case VCR_ENTRY_TYPE_INITIAL_STATE:\n                    vcr_state = VCR_STATE_TURNED_OFF;\n                    vcr_old_layout = kb_get_layout();\n                    kb_set_layout(vcr_buffer[vcr_buffer_index].initial.keyboardLayout);\n                    while (mouse_get_buttons() != 0) {\n                        mouse_info();\n                    }\n                    vcr_state = VCR_ENTRY_TYPE_INITIAL_STATE;\n                    mouse_hide();\n                    mouse_set_position(vcr_buffer[vcr_buffer_index].initial.mouseX, vcr_buffer[vcr_buffer_index].initial.mouseY);\n                    mouse_show();\n                    kb_clear();\n                    vcr_terminate_flags = vcr_temp_terminate_flags;\n                    vcr_start_time = get_time();\n                    vcr_counter = 0;\n                    break;\n                case VCR_ENTRY_TYPE_KEYBOARD_EVENT:\n                    kb_simulate_key(vcr_buffer[vcr_buffer_index].keyboardEvent.key);\n                    break;\n                case VCR_ENTRY_TYPE_MOUSE_EVENT:\n                    rc = 3;\n                    mouse_simulate_input(vcr_buffer[vcr_buffer_index].mouseEvent.dx, vcr_buffer[vcr_buffer_index].mouseEvent.dy, vcr_buffer[vcr_buffer_index].mouseEvent.buttons);\n                    break;\n                }\n\n                memcpy(&vcr_last_play_event, &(vcr_buffer[vcr_buffer_index]), sizeof(vcr_last_play_event));\n                vcr_buffer_index++;\n            }\n\n            return rc;\n        } else {\n            // NOTE: Uninline.\n            vcr_stop();\n        }\n        break;\n    }\n\n    return 0;\n}\n\n// NOTE: Inlined.\n//\n// 0x4D2C64\nstatic bool vcr_create_buffer()\n{\n    if (vcr_buffer == NULL) {\n        vcr_buffer = (VcrEntry*)mem_malloc(sizeof(*vcr_buffer) * VCR_BUFFER_CAPACITY);\n        if (vcr_buffer == NULL) {\n            return false;\n        }\n    }\n\n    // NOTE: Uninline.\n    vcr_clear_buffer();\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x4D2C98\nstatic bool vcr_destroy_buffer()\n{\n    if (vcr_buffer == NULL) {\n        return false;\n    }\n\n    // NOTE: Uninline.\n    vcr_clear_buffer();\n\n    mem_free(vcr_buffer);\n    vcr_buffer = NULL;\n\n    return true;\n}\n\n// 0x4D2CD0\nstatic bool vcr_clear_buffer()\n{\n    if (vcr_buffer == NULL) {\n        return false;\n    }\n\n    vcr_buffer_index = 0;\n\n    return true;\n}\n\n// 0x4D2CF0\nbool vcr_dump_buffer()\n{\n    if (vcr_buffer == NULL) {\n        return false;\n    }\n\n    if (vcr_file == NULL) {\n        return false;\n    }\n\n    for (int index = 0; index < vcr_buffer_index; index++) {\n        if (!vcr_save_record(&(vcr_buffer[index]), vcr_file)) {\n            return false;\n        }\n    }\n\n    // NOTE: Uninline.\n    if (!vcr_clear_buffer()) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4D2D74\nstatic bool vcr_load_buffer()\n{\n    if (vcr_file == NULL) {\n        return false;\n    }\n\n    // NOTE: Uninline.\n    if (!vcr_clear_buffer()) {\n        return false;\n    }\n\n    for (vcr_buffer_end = 0; vcr_buffer_end < VCR_BUFFER_CAPACITY; vcr_buffer_end++) {\n        if (!vcr_load_record(&(vcr_buffer[vcr_buffer_end]), vcr_file)) {\n            break;\n        }\n    }\n\n    if (vcr_buffer_end == 0) {\n        return false;\n    }\n\n    return true;\n}\n\n// 0x4D2E00\nbool vcr_save_record(VcrEntry* vcrEntry, File* stream)\n{\n    if (db_fwriteLong(stream, vcrEntry->type) == -1) return false;\n    if (db_fwriteLong(stream, vcrEntry->time) == -1) return false;\n    if (db_fwriteLong(stream, vcrEntry->counter) == -1) return false;\n\n    switch (vcrEntry->type) {\n    case VCR_ENTRY_TYPE_INITIAL_STATE:\n        if (db_fwriteLong(stream, vcrEntry->initial.mouseX) == -1) return false;\n        if (db_fwriteLong(stream, vcrEntry->initial.mouseY) == -1) return false;\n        if (db_fwriteLong(stream, vcrEntry->initial.keyboardLayout) == -1) return false;\n        return true;\n    case VCR_ENTRY_TYPE_KEYBOARD_EVENT:\n        if (db_fwriteShort(stream, vcrEntry->keyboardEvent.key) == -1) return false;\n        return true;\n    case VCR_ENTRY_TYPE_MOUSE_EVENT:\n        if (db_fwriteLong(stream, vcrEntry->mouseEvent.dx) == -1) return false;\n        if (db_fwriteLong(stream, vcrEntry->mouseEvent.dy) == -1) return false;\n        if (db_fwriteLong(stream, vcrEntry->mouseEvent.buttons) == -1) return false;\n        return true;\n    }\n\n    return false;\n}\n\n// 0x4D2EE4\nbool vcr_load_record(VcrEntry* vcrEntry, File* stream)\n{\n    if (db_freadLong(stream, &(vcrEntry->type)) == -1) return false;\n    if (db_freadLong(stream, &(vcrEntry->time)) == -1) return false;\n    if (db_freadLong(stream, &(vcrEntry->counter)) == -1) return false;\n\n    switch (vcrEntry->type) {\n    case VCR_ENTRY_TYPE_INITIAL_STATE:\n        if (db_freadLong(stream, &(vcrEntry->initial.mouseX)) == -1) return false;\n        if (db_freadLong(stream, &(vcrEntry->initial.mouseY)) == -1) return false;\n        if (db_freadLong(stream, &(vcrEntry->initial.keyboardLayout)) == -1) return false;\n        return true;\n    case VCR_ENTRY_TYPE_KEYBOARD_EVENT:\n        if (db_freadShort(stream, &(vcrEntry->keyboardEvent.key)) == -1) return false;\n        return true;\n    case VCR_ENTRY_TYPE_MOUSE_EVENT:\n        if (db_freadLong(stream, &(vcrEntry->mouseEvent.dx)) == -1) return false;\n        if (db_freadLong(stream, &(vcrEntry->mouseEvent.dy)) == -1) return false;\n        if (db_freadLong(stream, &(vcrEntry->mouseEvent.buttons)) == -1) return false;\n        return true;\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "src/plib/gnw/vcr.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_VCR_H_\n#define FALLOUT_PLIB_GNW_VCR_H_\n\n#include <stdbool.h>\n\n#include \"plib/db/db.h\"\n\n#define VCR_BUFFER_CAPACITY 4096\n\ntypedef enum VcrState {\n    VCR_STATE_RECORDING,\n    VCR_STATE_PLAYING,\n    VCR_STATE_TURNED_OFF,\n} VcrState;\n\n#define VCR_STATE_STOP_REQUESTED 0x80000000\n\ntypedef enum VcrTerminationFlags {\n    // Specifies that VCR playback should stop if any key is pressed.\n    VCR_TERMINATE_ON_KEY_PRESS = 0x01,\n\n    // Specifies that VCR playback should stop if mouse is mouved.\n    VCR_TERMINATE_ON_MOUSE_MOVE = 0x02,\n\n    // Specifies that VCR playback should stop if any mouse button is pressed.\n    VCR_TERMINATE_ON_MOUSE_PRESS = 0x04,\n} VcrTerminationFlags;\n\ntypedef enum VcrPlaybackCompletionReason {\n    VCR_PLAYBACK_COMPLETION_REASON_NONE = 0,\n\n    // Indicates that VCR playback completed normally.\n    VCR_PLAYBACK_COMPLETION_REASON_COMPLETED = 1,\n\n    // Indicates that VCR playback terminated according to termination flags.\n    VCR_PLAYBACK_COMPLETION_REASON_TERMINATED = 2,\n} VcrPlaybackCompletionReason;\n\ntypedef enum VcrEntryType {\n    VCR_ENTRY_TYPE_NONE = 0,\n    VCR_ENTRY_TYPE_INITIAL_STATE = 1,\n    VCR_ENTRY_TYPE_KEYBOARD_EVENT = 2,\n    VCR_ENTRY_TYPE_MOUSE_EVENT = 3,\n} VcrEntryType;\n\ntypedef struct VcrEntry {\n    unsigned int type;\n    unsigned int time;\n    unsigned int counter;\n    union {\n        struct {\n            int mouseX;\n            int mouseY;\n            int keyboardLayout;\n        } initial;\n        struct {\n            short key;\n        } keyboardEvent;\n        struct {\n            int dx;\n            int dy;\n            int buttons;\n        } mouseEvent;\n    };\n} VcrEntry;\n\nstatic_assert(sizeof(VcrEntry) == 24, \"wrong size\");\n\ntypedef void(VcrPlaybackCompletionCallback)(int reason);\n\nextern VcrEntry* vcr_buffer;\nextern int vcr_buffer_index;\nextern unsigned int vcr_state;\nextern unsigned int vcr_time;\nextern unsigned int vcr_counter;\nextern unsigned int vcr_terminate_flags;\nextern int vcr_terminated_condition;\n\nbool vcr_record(const char* fileName);\nbool vcr_play(const char* fileName, unsigned int terminationFlags, VcrPlaybackCompletionCallback* callback);\nint vcr_stop(void);\nint vcr_status();\nint vcr_update();\nbool vcr_dump_buffer();\nbool vcr_save_record(VcrEntry* ptr, File* stream);\nbool vcr_load_record(VcrEntry* ptr, File* stream);\n\n#endif /* FALLOUT_PLIB_GNW_VCR_H_ */\n"
  },
  {
    "path": "src/plib/gnw/winmain.c",
    "content": "#include \"plib/gnw/winmain.h\"\n\n#include <signal.h>\n\n#include \"plib/gnw/doscmdln.h\"\n#include \"plib/gnw/input.h\"\n#include \"game/main.h\"\n#include \"plib/gnw/gnw.h\"\n#include \"plib/gnw/svga.h\"\n\nstatic BOOL LoadDirectX();\nstatic void UnloadDirectX(void);\n\n// 0x51E434\nHWND GNW95_hwnd = NULL;\n\n// 0x51E438\nHINSTANCE GNW95_hInstance = NULL;\n\n// 0x51E43C\nLPSTR GNW95_lpszCmdLine = NULL;\n\n// 0x51E440\nint GNW95_nCmdShow = 0;\n\n// 0x51E444\nbool GNW95_isActive = false;\n\n// GNW95MUTEX\nHANDLE GNW95_mutex = NULL;\n\n// 0x51E44C\nHMODULE GNW95_hDDrawLib = NULL;\n\n// 0x51E450\nHMODULE GNW95_hDInputLib = NULL;\n\n// 0x51E454\nHMODULE GNW95_hDSoundLib = NULL;\n\n// 0x6B23D0\nchar GNW95_title[256];\n\n// 0x4DE700\nint WINAPI WinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrevInst, _In_ LPSTR lpCmdLine, _In_ int nCmdShow)\n{\n    DOSCmdLine args;\n\n    GNW95_mutex = CreateMutexA(0, TRUE, \"GNW95MUTEX\");\n    if (GetLastError() == ERROR_SUCCESS) {\n        ShowCursor(0);\n        if (InitClass(hInst)) {\n            if (InitInstance()) {\n                if (LoadDirectX()) {\n                    GNW95_hInstance = hInst;\n                    GNW95_lpszCmdLine = lpCmdLine;\n                    GNW95_nCmdShow = nCmdShow;\n                    DOSCmdLineInit(&args);\n                    if (DOSCmdLineCreate(&args, lpCmdLine)) {\n                        signal(1, SignalHandler);\n                        signal(3, SignalHandler);\n                        signal(5, SignalHandler);\n                        GNW95_isActive = true;\n                        RealMain(args.numArgs, args.args);\n                        DOSCmdLineDestroy(&args);\n                        return 1;\n                    }\n                }\n            }\n        }\n        CloseHandle(GNW95_mutex);\n    }\n    return 0;\n}\n\n// 0x4DE7F4\nBOOL InitClass(HINSTANCE hInstance)\n{\n    WNDCLASSA wc;\n    wc.style = 3;\n    wc.lpfnWndProc = WindowProc;\n    wc.cbClsExtra = 0;\n    wc.cbWndExtra = 0;\n    wc.hInstance = hInstance;\n    wc.hIcon = NULL;\n    wc.hCursor = NULL;\n    wc.hbrBackground = NULL;\n    wc.lpszMenuName = NULL;\n    wc.lpszClassName = \"GNW95 Class\";\n\n    return RegisterClassA(&wc);\n}\n\n// 0x4DE864\nBOOL InitInstance()\n{\n    OSVERSIONINFOA osvi;\n    bool result;\n\n    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);\n\n#pragma warning(suppress : 4996 28159)\n    if (!GetVersionExA(&osvi)) {\n        return true;\n    }\n\n    result = true;\n\n    if (osvi.dwPlatformId == 0 || osvi.dwPlatformId == 2 && osvi.dwMajorVersion < 4) {\n        result = false;\n    }\n\n    if (!result) {\n        MessageBoxA(NULL, \"This program requires Windows 95 or Windows NT version 4.0 or greater.\", \"Wrong Windows Version\", MB_ICONSTOP);\n    }\n\n    return result;\n}\n\n// 0x4DE8D0\nstatic BOOL LoadDirectX()\n{\n    GNW95_hDDrawLib = LoadLibraryA(\"DDRAW.DLL\");\n    if (GNW95_hDDrawLib == NULL) {\n        goto err;\n    }\n\n    GNW95_DirectDrawCreate = (PFNDDRAWCREATE)GetProcAddress(GNW95_hDDrawLib, \"DirectDrawCreate\");\n    if (GNW95_DirectDrawCreate == NULL) {\n        goto err;\n    }\n\n    GNW95_hDInputLib = LoadLibraryA(\"DINPUT.DLL\");\n    if (GNW95_hDInputLib == NULL) {\n        goto err;\n    }\n\n    GNW95_DirectInputCreate = (PFNDINPUTCREATE)GetProcAddress(GNW95_hDInputLib, \"DirectInputCreateA\");\n    if (GNW95_DirectInputCreate == NULL) {\n        goto err;\n    }\n\n    GNW95_hDSoundLib = LoadLibraryA(\"DSOUND.DLL\");\n    if (GNW95_hDSoundLib == NULL) {\n        goto err;\n    }\n\n    GNW95_DirectSoundCreate = (PFNDSOUNDCREATE)GetProcAddress(GNW95_hDSoundLib, \"DirectSoundCreate\");\n    if (GNW95_DirectSoundCreate == NULL) {\n        goto err;\n    }\n\n    atexit(UnloadDirectX);\n\n    return TRUE;\n\nerr:\n    UnloadDirectX();\n\n    MessageBoxA(NULL, \"This program requires Windows 95 with DirectX 3.0a or later or Windows NT version 4.0 with Service Pack 3 or greater.\", \"Could not load DirectX\", MB_ICONSTOP);\n\n    return FALSE;\n}\n\n// 0x4DE988\nstatic void UnloadDirectX(void)\n{\n    if (GNW95_hDSoundLib != NULL) {\n        FreeLibrary(GNW95_hDSoundLib);\n        GNW95_hDSoundLib = NULL;\n        GNW95_DirectDrawCreate = NULL;\n    }\n\n    if (GNW95_hDDrawLib != NULL) {\n        FreeLibrary(GNW95_hDDrawLib);\n        GNW95_hDDrawLib = NULL;\n        GNW95_DirectSoundCreate = NULL;\n    }\n\n    if (GNW95_hDInputLib != NULL) {\n        FreeLibrary(GNW95_hDInputLib);\n        GNW95_hDInputLib = NULL;\n        GNW95_DirectInputCreate = NULL;\n    }\n}\n\n// 0x4DE9F4\nvoid SignalHandler(int sig)\n{\n    win_exit();\n}\n\n// 0x4DE9FC\nLRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)\n{\n    switch (uMsg) {\n    case WM_DESTROY:\n        exit(EXIT_SUCCESS);\n    case WM_PAINT:\n        if (1) {\n            RECT updateRect;\n            if (GetUpdateRect(hWnd, &updateRect, FALSE)) {\n                Rect rect;\n                rect.ulx = updateRect.left;\n                rect.uly = updateRect.top;\n                rect.lrx = updateRect.right - 1;\n                rect.lry = updateRect.bottom - 1;\n                win_refresh_all(&rect);\n            }\n        }\n        break;\n    case WM_ERASEBKGND:\n        return 1;\n    case WM_SETCURSOR:\n        if ((HWND)wParam == GNW95_hwnd) {\n            SetCursor(NULL);\n            return 1;\n        }\n        break;\n    case WM_SYSCOMMAND:\n        switch (wParam & 0xFFF0) {\n        case SC_SCREENSAVE:\n        case SC_MONITORPOWER:\n            return 0;\n        }\n        break;\n    case WM_ACTIVATEAPP:\n        GNW95_isActive = wParam;\n        if (wParam) {\n            GNW95_hook_input(1);\n            win_refresh_all(&scr_size);\n        } else {\n            GNW95_hook_input(0);\n        }\n\n        return 0;\n    }\n\n    return DefWindowProc(hWnd, uMsg, wParam, lParam);\n}\n"
  },
  {
    "path": "src/plib/gnw/winmain.h",
    "content": "#ifndef FALLOUT_PLIB_GNW_WINMAIN_H_\n#define FALLOUT_PLIB_GNW_WINMAIN_H_\n\n#include <stdbool.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\nextern HWND GNW95_hwnd;\nextern HINSTANCE GNW95_hInstance;\nextern LPSTR GNW95_lpszCmdLine;\nextern int GNW95_nCmdShow;\nextern bool GNW95_isActive;\nextern HANDLE GNW95_mutex;\nextern HMODULE GNW95_hDDrawLib;\nextern HMODULE GNW95_hDInputLib;\nextern HMODULE GNW95_hDSoundLib;\n\nextern char GNW95_title[256];\n\nBOOL InitClass(HINSTANCE hInstance);\nBOOL InitInstance();\nvoid SignalHandler(int signalID);\nLRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);\n\n#endif /* FALLOUT_PLIB_GNW_WINMAIN_H_ */\n"
  },
  {
    "path": "src/plib/xfile/dfile.c",
    "content": "#include \"plib/xfile/dfile.h\"\n\n#include <assert.h>\n#include <io.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <fpattern.h>\n\nstatic_assert(sizeof(DBase) == 20, \"wrong size\");\nstatic_assert(sizeof(DBaseEntry) == 20, \"wrong size\");\nstatic_assert(sizeof(DFile) == 44, \"wrong size\");\nstatic_assert(sizeof(DFileFindData) == 524, \"wrong size\");\n\nstatic int dinfo_bsearch_compare(const void* a1, const void* a2);\nstatic DFile* dfile_fopen_helper(DBase* dbase, const char* filename, const char* mode, DFile* a4);\nstatic int dfile_fgetc_helper(DFile* stream);\nstatic bool dfile_read_comp_bytes(DFile* stream, void* ptr, size_t size);\nstatic void dfile_zungetc(DFile* stream, int ch);\n\n// Reads .DAT file contents.\n//\n// 0x4E4F58\nDBase* dbase_open(const char* filePath)\n{\n    assert(filePath); // \"filename\", \"dfile.c\", 74\n\n    FILE* stream = fopen(filePath, \"rb\");\n    if (stream == NULL) {\n        return NULL;\n    }\n\n    DBase* dbase = (DBase*)malloc(sizeof(*dbase));\n    if (dbase == NULL) {\n        fclose(stream);\n        return NULL;\n    }\n\n    memset(dbase, 0, sizeof(*dbase));\n\n    // Get file size, and reposition stream to read footer, which contains two\n    // 32-bits ints.\n    int fileSize = filelength(fileno(stream));\n    if (fseek(stream, fileSize - sizeof(int) * 2, SEEK_SET) != 0) {\n        goto err;\n    }\n\n    // Read the size of entries table.\n    int entriesDataSize;\n    if (fread(&entriesDataSize, sizeof(entriesDataSize), 1, stream) != 1) {\n        goto err;\n    }\n\n    // Read the size of entire dbase content.\n    //\n    // NOTE: It appears that this approach allows existence of arbitrary data in\n    // the beginning of the .DAT file.\n    int dbaseDataSize;\n    if (fread(&dbaseDataSize, sizeof(dbaseDataSize), 1, stream) != 1) {\n        goto err;\n    }\n\n    // Reposition stream to the beginning of the entries table.\n    if (fseek(stream, fileSize - entriesDataSize - sizeof(int) * 2, SEEK_SET) != 0) {\n        goto err;\n    }\n\n    if (fread(&(dbase->entriesLength), sizeof(dbase->entriesLength), 1, stream) != 1) {\n        goto err;\n    }\n\n    dbase->entries = (DBaseEntry*)malloc(sizeof(*dbase->entries) * dbase->entriesLength);\n    if (dbase->entries == NULL) {\n        goto err;\n    }\n\n    memset(dbase->entries, 0, sizeof(*dbase->entries) * dbase->entriesLength);\n\n    // Read entries one by one, stopping on any error.\n    int entryIndex;\n    for (entryIndex = 0; entryIndex < dbase->entriesLength; entryIndex++) {\n        DBaseEntry* entry = &(dbase->entries[entryIndex]);\n\n        int pathLength;\n        if (fread(&pathLength, sizeof(pathLength), 1, stream) != 1) {\n            break;\n        }\n\n        entry->path = (char*)malloc(pathLength + 1);\n        if (entry->path == NULL) {\n            break;\n        }\n\n        if (fread(entry->path, pathLength, 1, stream) != 1) {\n            break;\n        }\n\n        entry->path[pathLength] = '\\0';\n\n        if (fread(&(entry->compressed), sizeof(entry->compressed), 1, stream) != 1) {\n            break;\n        }\n\n        if (fread(&(entry->uncompressedSize), sizeof(entry->uncompressedSize), 1, stream) != 1) {\n            break;\n        }\n\n        if (fread(&(entry->dataSize), sizeof(entry->dataSize), 1, stream) != 1) {\n            break;\n        }\n\n        if (fread(&(entry->dataOffset), sizeof(entry->dataOffset), 1, stream) != 1) {\n            break;\n        }\n    }\n\n    if (entryIndex < dbase->entriesLength) {\n        // We haven't reached the end, which means there was an error while\n        // reading entries.\n        goto err;\n    }\n\n    dbase->path = strdup(filePath);\n    dbase->dataOffset = fileSize - dbaseDataSize;\n\n    fclose(stream);\n\n    return dbase;\n\nerr:\n\n    dbase_close(dbase);\n\n    fclose(stream);\n\n    return NULL;\n}\n\n// Closes [dbase], all open file handles, frees all associated resources,\n// including the [dbase] itself.\n//\n// 0x4E5270\nbool dbase_close(DBase* dbase)\n{\n    assert(dbase); // \"dbase\", \"dfile.c\", 173\n\n    DFile* curr = dbase->dfileHead;\n    while (curr != NULL) {\n        DFile* next = curr->next;\n        dfile_fclose(curr);\n        curr = next;\n    }\n\n    if (dbase->entries != NULL) {\n        for (int index = 0; index < dbase->entriesLength; index++) {\n            DBaseEntry* entry = &(dbase->entries[index]);\n            char* entryName = entry->path;\n            if (entryName != NULL) {\n                free(entryName);\n            }\n        }\n        free(dbase->entries);\n    }\n\n    if (dbase->path != NULL) {\n        free(dbase->path);\n    }\n\n    memset(dbase, 0, sizeof(*dbase));\n\n    free(dbase);\n\n    return true;\n}\n\n// 0x4E5308\nbool dbase_findfirst(DBase* dbase, DFileFindData* findFileData, const char* pattern)\n{\n    for (int index = 0; index < dbase->entriesLength; index++) {\n        DBaseEntry* entry = &(dbase->entries[index]);\n        if (fpattern_match(pattern, entry->path)) {\n            strcpy(findFileData->fileName, entry->path);\n            strcpy(findFileData->pattern, pattern);\n            findFileData->index = index;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x4E53A0\nbool dbase_findnext(DBase* dbase, DFileFindData* findFileData)\n{\n    for (int index = findFileData->index + 1; index < dbase->entriesLength; index++) {\n        DBaseEntry* entry = &(dbase->entries[index]);\n        if (fpattern_match(findFileData->pattern, entry->path)) {\n            strcpy(findFileData->fileName, entry->path);\n            findFileData->index = index;\n            return true;\n        }\n    }\n\n    return false;\n}\n\n// 0x4E541C\nbool dbase_findclose(DBase* dbase, DFileFindData* findFileData)\n{\n    return true;\n}\n\n// [filelength].\n//\n// 0x4E5424\nlong dfile_filelength(DFile* stream)\n{\n    return stream->entry->uncompressedSize;\n}\n\n// [fclose].\n//\n// 0x4E542C\nint dfile_fclose(DFile* stream)\n{\n    assert(stream); // \"stream\", \"dfile.c\", 253\n\n    int rc = 0;\n\n    if (stream->entry->compressed == 1) {\n        if (inflateEnd(stream->decompressionStream) != Z_OK) {\n            rc = -1;\n        }\n    }\n\n    if (stream->decompressionStream != NULL) {\n        free(stream->decompressionStream);\n    }\n\n    if (stream->decompressionBuffer != NULL) {\n        free(stream->decompressionBuffer);\n    }\n\n    if (stream->stream != NULL) {\n        fclose(stream->stream);\n    }\n\n    // Loop thru open file handles and find previous to remove current handle\n    // from linked list.\n    //\n    // NOTE: Compiled code is slightly different.\n    DFile* curr = stream->dbase->dfileHead;\n    DFile* prev = NULL;\n    while (curr != NULL) {\n        if (curr == stream) {\n            break;\n        }\n\n        prev = curr;\n        curr = curr->next;\n    }\n\n    if (curr != NULL) {\n        if (prev == NULL) {\n            stream->dbase->dfileHead = stream->next;\n        } else {\n            prev->next = stream->next;\n        }\n    }\n\n    memset(stream, 0, sizeof(*stream));\n\n    free(stream);\n\n    return rc;\n}\n\n// [fopen].\n//\n// 0x4E5504\nDFile* dfile_fopen(DBase* dbase, const char* filePath, const char* mode)\n{\n    assert(dbase); // dfile.c, 295\n    assert(filePath); // dfile.c, 296\n    assert(mode); // dfile.c, 297\n\n    return dfile_fopen_helper(dbase, filePath, mode, 0);\n}\n\n// [vfprintf].\n//\n// 0x4E56C0\nint dfile_vfprintf(DFile* stream, const char* format, va_list args)\n{\n    assert(stream); // \"stream\", \"dfile.c\", 368\n    assert(format); // \"format\", \"dfile.c\", 369\n\n    return -1;\n}\n\n// [fgetc].\n//\n// This function reports \\r\\n sequence as one character \\n, even though it\n// consumes two characters from the underlying stream.\n//\n// 0x4E5700\nint dfile_fgetc(DFile* stream)\n{\n    assert(stream); // \"stream\", \"dfile.c\", 384\n\n    if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) {\n        return -1;\n    }\n\n    if ((stream->flags & DFILE_HAS_UNGETC) != 0) {\n        stream->flags &= ~DFILE_HAS_UNGETC;\n        return stream->ungotten;\n    }\n\n    int ch = dfile_fgetc_helper(stream);\n    if (ch == -1) {\n        stream->flags |= DFILE_EOF;\n    }\n\n    return ch;\n}\n\n// [fgets].\n//\n// Both Windows (\\r\\n) and Unix (\\n) line endings are recognized. Windows\n// line ending is reported as \\n.\n//\n// 0x4E5764\nchar* dfile_fgets(char* string, int size, DFile* stream)\n{\n    assert(string); // \"s\", \"dfile.c\", 407\n    assert(size); // \"n\", \"dfile.c\", 408\n    assert(stream); // \"stream\", \"dfile.c\", 409\n\n    if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) {\n        return NULL;\n    }\n\n    char* pch = string;\n\n    if ((stream->flags & DFILE_HAS_UNGETC) != 0) {\n        *pch++ = stream->ungotten & 0xFF;\n        size--;\n        stream->flags &= ~DFILE_HAS_UNGETC;\n    }\n\n    // Read up to size - 1 characters one by one saving space for the null\n    // terminator.\n    for (int index = 0; index < size - 1; index++) {\n        int ch = dfile_fgetc_helper(stream);\n        if (ch == -1) {\n            break;\n        }\n\n        *pch++ = ch & 0xFF;\n\n        if (ch == '\\n') {\n            break;\n        }\n    }\n\n    if (pch == string) {\n        // No character was set into the buffer.\n        return NULL;\n    }\n\n    *pch = '\\0';\n\n    return string;\n}\n\n// [fputc].\n//\n// 0x4E5830\nint dfile_fputc(int ch, DFile* stream)\n{\n    assert(stream); // \"stream\", \"dfile.c\", 437\n\n    return -1;\n}\n\n// [fputs].\n//\n// 0x4E5854\nint dfile_fputs(const char* string, DFile* stream)\n{\n    assert(string); // \"s\", \"dfile.c\", 448\n    assert(stream); // \"stream\", \"dfile.c\", 449\n\n    return -1;\n}\n\n// [fread].\n//\n// 0x4E58FC\nsize_t dfile_fread(void* ptr, size_t size, size_t count, DFile* stream)\n{\n    assert(ptr); // \"ptr\", \"dfile.c\", 499\n    assert(stream); // \"stream\", dfile.c, 500\n\n    if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) {\n        return 0;\n    }\n\n    size_t remainingSize = stream->entry->uncompressedSize - stream->position;\n    if ((stream->flags & DFILE_HAS_UNGETC) != 0) {\n        remainingSize++;\n    }\n\n    size_t bytesToRead = size * count;\n    if (remainingSize < bytesToRead) {\n        bytesToRead = remainingSize;\n        stream->flags |= DFILE_EOF;\n    }\n\n    size_t extraBytesRead = 0;\n    if ((stream->flags & DFILE_HAS_UNGETC) != 0) {\n        unsigned char* byteBuffer = (unsigned char*)ptr;\n        *byteBuffer++ = stream->ungotten & 0xFF;\n        ptr = byteBuffer;\n\n        bytesToRead--;\n\n        stream->flags &= ~DFILE_HAS_UNGETC;\n        extraBytesRead = 1;\n    }\n\n    size_t bytesRead;\n    if (stream->entry->compressed == 1) {\n        if (!dfile_read_comp_bytes(stream, ptr, bytesToRead)) {\n            stream->flags |= DFILE_ERROR;\n            return false;\n        }\n\n        bytesRead = bytesToRead;\n    } else {\n        bytesRead = fread(ptr, 1, bytesToRead, stream->stream) + extraBytesRead;\n        stream->position += bytesRead;\n    }\n\n    return bytesRead / size;\n}\n\n// [fwrite].\n//\n// 0x4E59F8\nsize_t dfile_fwrite(const void* ptr, size_t size, size_t count, DFile* stream)\n{\n    assert(ptr); // \"ptr\", \"dfile.c\", 538\n    assert(stream); // \"stream\", \"dfile.c\", 539\n\n    return count - 1;\n}\n\n// [fseek].\n//\n// 0x4E5A74\nint dfile_fseek(DFile* stream, long offset, int origin)\n{\n    assert(stream); // \"stream\", \"dfile.c\", 569\n\n    if ((stream->flags & DFILE_ERROR) != 0) {\n        return 1;\n    }\n\n    if ((stream->flags & DFILE_TEXT) != 0) {\n        if (offset != 0 && origin != SEEK_SET) {\n            // NOTE: For unknown reason this function does not allow arbitrary\n            // seeks in text streams, whether compressed or not. It only\n            // supports rewinding. Probably because of reading functions which\n            // handle \\r\\n sequence as \\n.\n            return 1;\n        }\n    }\n\n    long offsetFromBeginning;\n    switch (origin) {\n    case SEEK_SET:\n        offsetFromBeginning = offset;\n        break;\n    case SEEK_CUR:\n        offsetFromBeginning = stream->position + offset;\n        break;\n    case SEEK_END:\n        offsetFromBeginning = stream->entry->uncompressedSize + offset;\n        break;\n    default:\n        return 1;\n    }\n\n    if (offsetFromBeginning >= stream->entry->uncompressedSize) {\n        return 1;\n    }\n\n    long pos = stream->position;\n    if (offsetFromBeginning == pos) {\n        stream->flags &= ~(DFILE_HAS_UNGETC | DFILE_EOF);\n        return 0;\n    }\n\n    if (offsetFromBeginning != 0) {\n        if (stream->entry->compressed == 1) {\n            if (offsetFromBeginning < pos) {\n                // We cannot go backwards in compressed stream, so the only way\n                // is to start from the beginning.\n                dfile_rewind(stream);\n            }\n\n            // Consume characters one by one until we reach specified offset.\n            while (offsetFromBeginning > stream->position) {\n                if (dfile_fgetc_helper(stream) == -1) {\n                    return 1;\n                }\n            }\n        } else {\n            if (fseek(stream->stream, offsetFromBeginning - pos, SEEK_CUR) != 0) {\n                stream->flags |= DFILE_ERROR;\n                return 1;\n            }\n\n            // FIXME: I'm not sure what this assignment means. This field is\n            // only meaningful when reading compressed streams.\n            stream->compressedBytesRead = offsetFromBeginning;\n        }\n\n        stream->flags &= ~(DFILE_HAS_UNGETC | DFILE_EOF);\n        return 0;\n    }\n\n    if (fseek(stream->stream, stream->dbase->dataOffset + stream->entry->dataOffset, SEEK_SET) != 0) {\n        stream->flags |= DFILE_ERROR;\n        return 1;\n    }\n\n    if (inflateEnd(stream->decompressionStream) != Z_OK) {\n        stream->flags |= DFILE_ERROR;\n        return 1;\n    }\n\n    stream->decompressionStream->zalloc = Z_NULL;\n    stream->decompressionStream->zfree = Z_NULL;\n    stream->decompressionStream->opaque = Z_NULL;\n    stream->decompressionStream->next_in = stream->decompressionBuffer;\n    stream->decompressionStream->avail_in = 0;\n\n    if (inflateInit(stream->decompressionStream) != Z_OK) {\n        stream->flags |= DFILE_ERROR;\n        return 1;\n    }\n\n    stream->position = 0;\n    stream->compressedBytesRead = 0;\n    stream->flags &= ~(DFILE_HAS_UNGETC | DFILE_EOF);\n\n    return 0;\n}\n\n// [ftell].\n//\n// 0x4E5C88\nlong dfile_ftell(DFile* stream)\n{\n    assert(stream); // \"stream\", \"dfile.c\", 654\n\n    return stream->position;\n}\n\n// [rewind].\n//\n// 0x4E5CB0\nvoid dfile_rewind(DFile* stream)\n{\n    assert(stream); // \"stream\", \"dfile.c\", 664\n\n    dfile_fseek(stream, 0, SEEK_SET);\n\n    stream->flags &= ~DFILE_ERROR;\n}\n\n// [feof].\n//\n// 0x4E5D10\nint dfile_feof(DFile* stream)\n{\n    assert(stream); // \"stream\", \"dfile.c\", 685\n\n    return stream->flags & DFILE_EOF;\n}\n\n// The [bsearch] comparison callback, which is used to find [DBaseEntry] for\n// specified [filePath].\n//\n// 0x4E5D70\nstatic int dinfo_bsearch_compare(const void* a1, const void* a2)\n{\n    const char* filePath = (const char*)a1;\n    DBaseEntry* entry = (DBaseEntry*)a2;\n\n    return stricmp(filePath, entry->path);\n}\n\n// 0x4E5D9C\nstatic DFile* dfile_fopen_helper(DBase* dbase, const char* filePath, const char* mode, DFile* dfile)\n{\n    DBaseEntry* entry = (DBaseEntry*)bsearch(filePath, dbase->entries, dbase->entriesLength, sizeof(*dbase->entries), dinfo_bsearch_compare);\n    if (entry == NULL) {\n        goto err;\n    }\n\n    if (mode[0] != 'r') {\n        goto err;\n    }\n\n    if (dfile == NULL) {\n        dfile = (DFile*)malloc(sizeof(*dfile));\n        if (dfile == NULL) {\n            return NULL;\n        }\n\n        memset(dfile, 0, sizeof(*dfile));\n        dfile->dbase = dbase;\n        dfile->next = dbase->dfileHead;\n        dbase->dfileHead = dfile;\n    } else {\n        if (dbase != dfile->dbase) {\n            goto err;\n        }\n\n        if (dfile->stream != NULL) {\n            fclose(dfile->stream);\n            dfile->stream = NULL;\n        }\n\n        dfile->compressedBytesRead = 0;\n        dfile->position = 0;\n        dfile->flags = 0;\n    }\n\n    dfile->entry = entry;\n\n    // Open stream to .DAT file.\n    dfile->stream = fopen(dbase->path, \"rb\");\n    if (dfile->stream == NULL) {\n        goto err;\n    }\n\n    // Relocate stream to the beginning of data for specified entry.\n    if (fseek(dfile->stream, dbase->dataOffset + entry->dataOffset, SEEK_SET) != 0) {\n        goto err;\n    }\n\n    if (entry->compressed == 1) {\n        // Entry is compressed, setup decompression stream and decompression\n        // buffer. This step is not needed when previous instance of dfile is\n        // passed via parameter, which might already have stream and\n        // buffer allocated.\n        if (dfile->decompressionStream == NULL) {\n            dfile->decompressionStream = (z_streamp)malloc(sizeof(*dfile->decompressionStream));\n            if (dfile->decompressionStream == NULL) {\n                goto err;\n            }\n\n            dfile->decompressionBuffer = (unsigned char*)malloc(DFILE_DECOMPRESSION_BUFFER_SIZE);\n            if (dfile->decompressionBuffer == NULL) {\n                goto err;\n            }\n        }\n\n        dfile->decompressionStream->zalloc = Z_NULL;\n        dfile->decompressionStream->zfree = Z_NULL;\n        dfile->decompressionStream->opaque = Z_NULL;\n        dfile->decompressionStream->next_in = dfile->decompressionBuffer;\n        dfile->decompressionStream->avail_in = 0;\n\n        if (inflateInit(dfile->decompressionStream) != Z_OK) {\n            goto err;\n        }\n    } else {\n        // Entry is not compressed, there is no need to keep decompression\n        // stream and decompression buffer (in case [dfile] was passed via\n        // parameter).\n        if (dfile->decompressionStream != NULL) {\n            free(dfile->decompressionStream);\n            dfile->decompressionStream = NULL;\n        }\n\n        if (dfile->decompressionBuffer != NULL) {\n            free(dfile->decompressionBuffer);\n            dfile->decompressionBuffer = NULL;\n        }\n    }\n\n    if (mode[1] == 't') {\n        dfile->flags |= DFILE_TEXT;\n    }\n\n    return dfile;\n\nerr:\n\n    if (dfile != NULL) {\n        dfile_fclose(dfile);\n    }\n\n    return NULL;\n}\n\n// 0x4E5F9C\nstatic int dfile_fgetc_helper(DFile* stream)\n{\n    if (stream->entry->compressed == 1) {\n        char ch;\n        if (!dfile_read_comp_bytes(stream, &ch, sizeof(ch))) {\n            return -1;\n        }\n\n        if ((stream->flags & DFILE_TEXT) != 0) {\n            // NOTE: I'm not sure if they are comparing as chars or ints. Since\n            // character literals are ints, let's cast read characters to int as\n            // well.\n            if (ch == '\\r') {\n                char nextCh;\n                if (dfile_read_comp_bytes(stream, &nextCh, sizeof(nextCh))) {\n                    if (nextCh == '\\n') {\n                        ch = nextCh;\n                    } else {\n                        // NOTE: Uninline.\n                        dfile_zungetc(stream, nextCh & 0xFF);\n                    }\n                }\n            }\n        }\n\n        return ch & 0xFF;\n    }\n\n    if (stream->position >= stream->entry->uncompressedSize) {\n        return -1;\n    }\n\n    int ch = fgetc(stream->stream);\n    if (ch != -1) {\n        if ((stream->flags & DFILE_TEXT) != 0) {\n            // This is a text stream, attempt to detect \\r\\n sequence.\n            if (ch == '\\r') {\n                if (stream->position + 1 < stream->entry->uncompressedSize) {\n                    int nextCh = fgetc(stream->stream);\n                    if (nextCh == '\\n') {\n                        ch = nextCh;\n                        stream->position++;\n                    } else {\n                        ungetc(nextCh, stream->stream);\n                    }\n                }\n            }\n        }\n\n        stream->position++;\n    }\n\n    return ch;\n}\n\n// 0x4E6078\nstatic bool dfile_read_comp_bytes(DFile* stream, void* ptr, size_t size)\n{\n    if ((stream->flags & DFILE_HAS_COMPRESSED_UNGETC) != 0) {\n        unsigned char* byteBuffer = (unsigned char*)ptr;\n        *byteBuffer++ = stream->compressedUngotten & 0xFF;\n        ptr = byteBuffer;\n\n        size--;\n\n        stream->flags &= ~DFILE_HAS_COMPRESSED_UNGETC;\n        stream->position++;\n\n        if (size == 0) {\n            return true;\n        }\n    }\n\n    stream->decompressionStream->next_out = (Bytef*)ptr;\n    stream->decompressionStream->avail_out = size;\n\n    do {\n        if (stream->decompressionStream->avail_out == 0) {\n            // Everything was decompressed.\n            break;\n        }\n\n        if (stream->decompressionStream->avail_in == 0) {\n            // No more unprocessed data, request next chunk.\n            size_t bytesToRead = stream->entry->dataSize - stream->compressedBytesRead;\n            if (bytesToRead > DFILE_DECOMPRESSION_BUFFER_SIZE) {\n                bytesToRead = DFILE_DECOMPRESSION_BUFFER_SIZE;\n            }\n\n            if (fread(stream->decompressionBuffer, bytesToRead, 1, stream->stream) != 1) {\n                break;\n            }\n\n            stream->decompressionStream->avail_in = bytesToRead;\n            stream->decompressionStream->next_in = stream->decompressionBuffer;\n\n            stream->compressedBytesRead += bytesToRead;\n        }\n    } while (inflate(stream->decompressionStream, Z_NO_FLUSH) == Z_OK);\n\n    if (stream->decompressionStream->avail_out != 0) {\n        // There are some data still waiting, which means there was in error\n        // during decompression loop above.\n        return false;\n    }\n\n    stream->position += size;\n\n    return true;\n}\n\n// NOTE: Inlined.\n//\n// 0x4E613C\nstatic void dfile_zungetc(DFile* stream, int ch)\n{\n    stream->compressedUngotten = ch;\n    stream->flags |= DFILE_HAS_COMPRESSED_UNGETC;\n    stream->position--;\n}\n"
  },
  {
    "path": "src/plib/xfile/dfile.h",
    "content": "#ifndef FALLOUT_PLIB_XFILE_DFILE_H_\n#define FALLOUT_PLIB_XFILE_DFILE_H_\n\n#include <stdbool.h>\n#include <stdio.h>\n\n#define WIN32_LEAN_AND_MEAN\n#include <windows.h>\n\n#include <zlib.h>\n\n// The size of decompression buffer for reading compressed [DFile]s.\n#define DFILE_DECOMPRESSION_BUFFER_SIZE (0x400)\n\n// Specifies that [DFile] has unget character.\n//\n// NOTE: There is an unused function at 0x4E5894 which ungets one character and\n// stores it in [ungotten]. Since that function is not used, this flag will\n// never be set.\n#define DFILE_HAS_UNGETC (0x01)\n\n// Specifies that [DFile] has reached end of stream.\n#define DFILE_EOF (0x02)\n\n// Specifies that [DFile] is in error state.\n//\n// [dfile_rewind] can be used to clear this flag.\n#define DFILE_ERROR (0x04)\n\n// Specifies that [DFile] was opened in text mode.\n#define DFILE_TEXT (0x08)\n\n// Specifies that [DFile] has unget compressed character.\n#define DFILE_HAS_COMPRESSED_UNGETC (0x10)\n\ntypedef struct DBase DBase;\ntypedef struct DBaseEntry DBaseEntry;\ntypedef struct DFile DFile;\n\n// A representation of .DAT file.\ntypedef struct DBase {\n    // The path of .DAT file that this structure represents.\n    char* path;\n\n    // The offset to the beginning of data section of .DAT file.\n    int dataOffset;\n\n    // The number of entries.\n    int entriesLength;\n\n    // The array of entries.\n    DBaseEntry* entries;\n\n    // The head of linked list of open file handles.\n    DFile* dfileHead;\n} DBase;\n\ntypedef struct DBaseEntry {\n    char* path;\n    unsigned char compressed;\n    int uncompressedSize;\n    int dataSize;\n    int dataOffset;\n} DBaseEntry;\n\n// A handle to open entry in .DAT file.\ntypedef struct DFile {\n    DBase* dbase;\n    DBaseEntry* entry;\n    int flags;\n\n    // The stream of .DAT file opened for reading in binary mode.\n    //\n    // This stream is not shared across open handles. Instead every [DFile]\n    // opens it's own stream via [fopen], which is then closed via [fclose] in\n    // [dfile_fclose].\n    FILE* stream;\n\n    // The inflate stream used to decompress data.\n    //\n    // This value is NULL if entry is not compressed.\n    z_streamp decompressionStream;\n\n    // The decompression buffer of size [DFILE_DECOMPRESSION_BUFFER_SIZE].\n    //\n    // This value is NULL if entry is not compressed.\n    unsigned char* decompressionBuffer;\n\n    // The last ungot character.\n    //\n    // See [DFILE_HAS_UNGETC] notes.\n    int ungotten;\n\n    // The last ungot compressed character.\n    //\n    // This value is used when reading compressed text streams to detect\n    // Windows end of line sequence \\r\\n.\n    int compressedUngotten;\n\n    // The number of bytes read so far from compressed stream.\n    //\n    // This value is only used when reading compressed streams. The range is\n    // 0..entry->dataSize.\n    int compressedBytesRead;\n\n    // The position in read stream.\n    //\n    // This value is tracked in terms of uncompressed data (even in compressed\n    // streams). The range is 0..entry->uncompressedSize.\n    long position;\n\n    // Next [DFile] in linked list.\n    //\n    // [DFile]s are stored in [DBase] in reverse order, so it's actually a\n    // previous opened file, not next.\n    DFile* next;\n} DFile;\n\ntypedef struct DFileFindData {\n    // The name of file that was found during previous search.\n    char fileName[MAX_PATH];\n\n    // The pattern to search.\n    //\n    // This value is set automatically when [dbase_findfirst] succeeds so\n    // that subsequent calls to [dbase_findnext] know what to look for.\n    char pattern[MAX_PATH];\n\n    // The index of entry that was found during previous search.\n    //\n    // This value is set automatically when [dbase_findfirst] and\n    // [dbase_findnext] succeed so that subsequent calls to [dbase_findnext]\n    // knows where to start search from.\n    int index;\n} DFileFindData;\n\nDBase* dbase_open(const char* filename);\nbool dbase_close(DBase* dbase);\nbool dbase_findfirst(DBase* dbase, DFileFindData* findFileData, const char* pattern);\nbool dbase_findnext(DBase* dbase, DFileFindData* findFileData);\nbool dbase_findclose(DBase* dbase, DFileFindData* findFileData);\nlong dfile_filelength(DFile* stream);\nint dfile_fclose(DFile* stream);\nDFile* dfile_fopen(DBase* dbase, const char* filename, const char* mode);\nint dfile_vfprintf(DFile* stream, const char* format, va_list args);\nint dfile_fgetc(DFile* stream);\nchar* dfile_fgets(char* str, int size, DFile* stream);\nint dfile_fputc(int ch, DFile* stream);\nint dfile_fputs(const char* s, DFile* stream);\nsize_t dfile_fread(void* ptr, size_t size, size_t count, DFile* stream);\nsize_t dfile_fwrite(const void* ptr, size_t size, size_t count, DFile* stream);\nint dfile_fseek(DFile* stream, long offset, int origin);\nlong dfile_ftell(DFile* stream);\nvoid dfile_rewind(DFile* stream);\nint dfile_feof(DFile* stream);\n\n#endif /* FALLOUT_PLIB_XFILE_DFILE_H_ */\n"
  },
  {
    "path": "src/plib/xfile/xfile.c",
    "content": "#include \"plib/xfile/xfile.h\"\n\n#include <assert.h>\n#include <direct.h>\n#include <io.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"plib/xfile/xsys_find.h\"\n\nstatic void xclearpath();\nstatic void xpathexit(void);\nstatic bool xlistenumfunc(XListEnumerationContext* context);\n\n// 0x6B24D0\nstatic XBase* paths;\n\n// 0x4DED6C\nint xfclose(XFile* stream)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 112\n\n    int rc;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        rc = dfile_fclose(stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        rc = gzclose(stream->gzfile);\n        break;\n    default:\n        rc = fclose(stream->file);\n        break;\n    }\n\n    memset(stream, 0, sizeof(*stream));\n\n    free(stream);\n\n    return rc;\n}\n\n// 0x4DEE2C\nXFile* xfopen(const char* filePath, const char* mode)\n{\n    assert(filePath); // \"filename\", \"xfile.c\", 162\n    assert(mode); // \"mode\", \"xfile.c\", 163\n\n    XFile* stream = (XFile*)malloc(sizeof(*stream));\n    if (stream == NULL) {\n        return NULL;\n    }\n\n    memset(stream, 0, sizeof(*stream));\n\n    // NOTE: Compiled code uses different lengths.\n    char drive[_MAX_DRIVE];\n    char dir[_MAX_DIR];\n    _splitpath(filePath, drive, dir, NULL, NULL);\n\n    char path[FILENAME_MAX];\n    if (drive[0] != '\\0' || dir[0] == '\\\\' || dir[0] == '/' || dir[0] == '.') {\n        // [filePath] is an absolute path. Attempt to open as plain stream.\n        stream->file = fopen(filePath, mode);\n        if (stream->file == NULL) {\n            free(stream);\n            return NULL;\n        }\n\n        stream->type = XFILE_TYPE_FILE;\n        sprintf(path, filePath);\n    } else {\n        // [filePath] is a relative path. Loop thru open xbases and attempt to\n        // open [filePath] from appropriate xbase.\n        XBase* curr = paths;\n        while (curr != NULL) {\n            if (curr->isDbase) {\n                // Attempt to open dfile stream from dbase.\n                stream->dfile = dfile_fopen(curr->dbase, filePath, mode);\n                if (stream->dfile != NULL) {\n                    stream->type = XFILE_TYPE_DFILE;\n                    sprintf(path, filePath);\n                    break;\n                }\n            } else {\n                // Build path relative to directory-based xbase.\n                sprintf(path, \"%s\\\\%s\", curr->path, filePath);\n\n                // Attempt to open plain stream.\n                stream->file = fopen(path, mode);\n                if (stream->file != NULL) {\n                    stream->type = XFILE_TYPE_FILE;\n                    break;\n                }\n            }\n            curr = curr->next;\n        }\n\n        if (stream->file == NULL) {\n            // File was not opened during the loop above. Attempt to open file\n            // relative to the current working directory.\n            stream->file = fopen(filePath, mode);\n            if (stream->file == NULL) {\n                free(stream);\n                return NULL;\n            }\n\n            stream->type = XFILE_TYPE_FILE;\n            sprintf(path, filePath);\n        }\n    }\n\n    if (stream->type == XFILE_TYPE_FILE) {\n        // Opened file is a plain stream, which might be gzipped. In this case\n        // first two bytes will contain magic numbers.\n        int ch1 = fgetc(stream->file);\n        int ch2 = fgetc(stream->file);\n        if (ch1 == 0x1F && ch2 == 0x8B) {\n            // File is gzipped. Close plain stream and reopen this file as\n            // gzipped stream.\n            fclose(stream->file);\n\n            stream->type = XFILE_TYPE_GZFILE;\n            stream->gzfile = gzopen(path, mode);\n        } else {\n            // File is not gzipped.\n            rewind(stream->file);\n        }\n    }\n\n    return stream;\n}\n\n// 0x4DF11C\nint xfprintf(XFile* stream, const char* format, ...)\n{\n    assert(format); // \"format\", \"xfile.c\", 305\n\n    va_list args;\n    va_start(args, format);\n\n    int rc = xvfprintf(stream, format, args);\n\n    va_end(args);\n\n    return rc;\n}\n\n// [vfprintf].\n//\n// 0x4DF1AC\nint xvfprintf(XFile* stream, const char* format, va_list args)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 332\n    assert(format); // \"format\", \"xfile.c\", 333\n\n    int rc;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        rc = dfile_vfprintf(stream->dfile, format, args);\n        break;\n    case XFILE_TYPE_GZFILE:\n        rc = gzvprintf(stream->gzfile, format, args);\n        break;\n    default:\n        rc = vfprintf(stream->file, format, args);\n        break;\n    }\n\n    return rc;\n}\n\n// 0x4DF22C\nint xfgetc(XFile* stream)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 354\n\n    int ch;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        ch = dfile_fgetc(stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        ch = gzgetc(stream->gzfile);\n        break;\n    default:\n        ch = fgetc(stream->file);\n        break;\n    }\n\n    return ch;\n}\n\n// 0x4DF280\nchar* xfgets(char* string, int size, XFile* stream)\n{\n    assert(string); // \"s\", \"xfile.c\", 375\n    assert(size); // \"n\", \"xfile.c\", 376\n    assert(stream); // \"stream\", \"xfile.c\", 377\n\n    char* result;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        result = dfile_fgets(string, size, stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        result = gzgets(stream->gzfile, string, size);\n        break;\n    default:\n        result = fgets(string, size, stream->file);\n        break;\n    }\n\n    return result;\n}\n\n// 0x4DF320\nint xfputc(int ch, XFile* stream)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 399\n\n    int rc;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        rc = dfile_fputc(ch, stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        rc = gzputc(stream->gzfile, ch);\n        break;\n    default:\n        rc = fputc(ch, stream->file);\n        break;\n    }\n\n    return rc;\n}\n\n// 0x4DF380\nint xfputs(const char* string, XFile* stream)\n{\n    assert(string); // \"s\", \"xfile.c\", 421\n    assert(stream); // \"stream\", \"xfile.c\", 422\n\n    int rc;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        rc = dfile_fputs(string, stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        rc = gzputs(stream->gzfile, string);\n        break;\n    default:\n        rc = fputs(string, stream->file);\n        break;\n    }\n\n    return rc;\n}\n\n// 0x4DF44C\nsize_t xfread(void* ptr, size_t size, size_t count, XFile* stream)\n{\n    assert(ptr); // \"ptr\", \"xfile.c\", 421\n    assert(stream); // \"stream\", \"xfile.c\", 422\n\n    size_t elementsRead;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        elementsRead = dfile_fread(ptr, size, count, stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        // FIXME: There is a bug in the return value. Both [dfile_fread] and\n        // [fread] returns number of elements read, but [gzwrite] have no such\n        // concept, it works with bytes, and returns number of bytes read.\n        // Depending on the [size] and [count] parameters this function can\n        // return wrong result.\n        elementsRead = gzread(stream->gzfile, ptr, size * count);\n        break;\n    default:\n        elementsRead = fread(ptr, size, count, stream->file);\n        break;\n    }\n\n    return elementsRead;\n}\n\n// 0x4DF4E8\nsize_t xfwrite(const void* ptr, size_t size, size_t count, XFile* stream)\n{\n    assert(ptr); // \"ptr\", \"xfile.c\", 504\n    assert(stream); // \"stream\", \"xfile.c\", 505\n\n    size_t elementsWritten;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        elementsWritten = dfile_fwrite(ptr, size, count, stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        // FIXME: There is a bug in the return value. [fwrite] returns number\n        // of elements written (while [dfile_fwrite] does not support writing at\n        // all), but [gzwrite] have no such concept, it works with bytes, and\n        // returns number of bytes written. Depending on the [size] and [count]\n        // parameters this function can return wrong result.\n        elementsWritten = gzwrite(stream->gzfile, ptr, size * count);\n        break;\n    default:\n        elementsWritten = fwrite(ptr, size, count, stream->file);\n        break;\n    }\n\n    return elementsWritten;\n}\n\n// 0x4DF5D8\nint xfseek(XFile* stream, long offset, int origin)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 547\n\n    int result;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        result = dfile_fseek(stream->dfile, offset, origin);\n        break;\n    case XFILE_TYPE_GZFILE:\n        result = gzseek(stream->gzfile, offset, origin);\n        break;\n    default:\n        result = fseek(stream->file, offset, origin);\n        break;\n    }\n\n    return result;\n}\n\n// 0x4DF690\nlong xftell(XFile* stream)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 588\n\n    long pos;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        pos = dfile_ftell(stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        pos = gztell(stream->gzfile);\n        break;\n    default:\n        pos = ftell(stream->file);\n        break;\n    }\n\n    return pos;\n}\n\n// 0x4DF6E4\nvoid xrewind(XFile* stream)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 608\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        dfile_rewind(stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        gzrewind(stream->gzfile);\n        break;\n    default:\n        rewind(stream->file);\n        break;\n    }\n}\n\n// 0x4DF780\nint xfeof(XFile* stream)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 648\n\n    int rc;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        rc = dfile_feof(stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        rc = gzeof(stream->gzfile);\n        break;\n    default:\n        rc = feof(stream->file);\n        break;\n    }\n\n    return rc;\n}\n\n// 0x4DF828\nlong xfilelength(XFile* stream)\n{\n    assert(stream); // \"stream\", \"xfile.c\", 690\n\n    long fileSize;\n\n    switch (stream->type) {\n    case XFILE_TYPE_DFILE:\n        fileSize = dfile_filelength(stream->dfile);\n        break;\n    case XFILE_TYPE_GZFILE:\n        fileSize = 0;\n        break;\n    default:\n        fileSize = filelength(fileno(stream->file));\n        break;\n    }\n\n    return fileSize;\n}\n\n// Closes all open xbases and opens a set of xbases specified by [paths].\n//\n// [paths] is a set of paths separated by semicolon. Can be NULL, in this case\n// all open xbases are simply closed.\n//\n// 0x4DF878\nbool xsetpath(char* paths)\n{\n    // NOTE: Uninline.\n    xclearpath();\n\n    if (paths != NULL) {\n        char* tok = strtok(paths, \";\");\n        while (tok != NULL) {\n            if (!xaddpath(tok)) {\n                return false;\n            }\n            tok = strtok(NULL, \";\");\n        }\n    }\n\n    return true;\n}\n\n// 0x4DF938\nbool xaddpath(const char* path)\n{\n    // 0x6B24D4\n    static bool init;\n\n    assert(path); // \"path\", \"xfile.c\", 747\n\n    // Register atexit handler so that underlying dbase (if any) can be\n    // gracefully closed.\n    if (!init) {\n        atexit(xpathexit);\n        init = true;\n    }\n\n    XBase* curr = paths;\n    XBase* prev = NULL;\n    while (curr != NULL) {\n        if (stricmp(path, curr->path) == 0) {\n            break;\n        }\n\n        prev = curr;\n        curr = curr->next;\n    }\n\n    if (curr != NULL) {\n        if (prev != NULL) {\n            // Move found xbase to the top.\n            prev->next = curr->next;\n            curr->next = paths;\n            paths = curr;\n        }\n        return true;\n    }\n\n    XBase* xbase = (XBase*)malloc(sizeof(*xbase));\n    if (xbase == NULL) {\n        return false;\n    }\n\n    memset(xbase, 0, sizeof(*xbase));\n\n    xbase->path = strdup(path);\n    if (xbase->path == NULL) {\n        free(xbase);\n        return false;\n    }\n\n    DBase* dbase = dbase_open(path);\n    if (dbase != NULL) {\n        xbase->isDbase = true;\n        xbase->dbase = dbase;\n        xbase->next = paths;\n        paths = xbase;\n        return true;\n    }\n\n    char workingDirectory[FILENAME_MAX];\n    if (getcwd(workingDirectory, FILENAME_MAX) == NULL) {\n        // FIXME: Leaking xbase and path.\n        return false;\n    }\n\n    if (chdir(path) == 0) {\n        chdir(workingDirectory);\n        xbase->next = paths;\n        paths = xbase;\n        return true;\n    }\n\n    if (xmkdir(path) != 0) {\n        // FIXME: Leaking xbase and path.\n        return false;\n    }\n\n    chdir(workingDirectory);\n\n    xbase->next = paths;\n    paths = xbase;\n\n    return true;\n}\n\n// 0x4DFB3C\nbool xenumpath(const char* pattern, XListEnumerationHandler* handler, XList* xlist)\n{\n    assert(pattern); // \"filespec\", \"xfile.c\", 845\n    assert(handler); // \"enumfunc\", \"xfile.c\", 846\n\n    DirectoryFileFindData directoryFileFindData;\n    XListEnumerationContext context;\n\n    context.xlist = xlist;\n\n    char drive[_MAX_DRIVE];\n    char dir[_MAX_DIR];\n    char fileName[_MAX_FNAME];\n    char extension[_MAX_EXT];\n    _splitpath(pattern, drive, dir, fileName, extension);\n    if (drive[0] != '\\0' || dir[0] == '\\\\' || dir[0] == '/' || dir[0] == '.') {\n        if (xsys_findfirst(pattern, &directoryFileFindData)) {\n            do {\n                bool isDirectory = fileFindIsDirectory(&directoryFileFindData);\n                char* entryName = fileFindGetName(&directoryFileFindData);\n\n                if (isDirectory) {\n                    if (strcmp(entryName, \"..\") == 0 || strcmp(entryName, \".\") == 0) {\n                        continue;\n                    }\n\n                    context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY;\n                } else {\n                    context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE;\n                }\n\n                _makepath(context.name, drive, dir, entryName, NULL);\n\n                if (!handler(&context)) {\n                    break;\n                }\n            } while (xsys_findnext(&directoryFileFindData));\n        }\n        return xsys_findclose(&directoryFileFindData);\n    }\n\n    XBase* xbase = paths;\n    while (xbase != NULL) {\n        if (xbase->isDbase) {\n            DFileFindData dbaseFindData;\n            if (dbase_findfirst(xbase->dbase, &dbaseFindData, pattern)) {\n                context.type = XFILE_ENUMERATION_ENTRY_TYPE_DFILE;\n\n                do {\n                    strcpy(context.name, dbaseFindData.fileName);\n                    if (!handler(&context)) {\n                        return dbase_findclose(xbase->dbase, &dbaseFindData);\n                    }\n                } while (dbase_findnext(xbase->dbase, &dbaseFindData));\n\n                dbase_findclose(xbase->dbase, &dbaseFindData);\n            }\n        } else {\n            char path[FILENAME_MAX];\n            sprintf(path, \"%s\\\\%s\", xbase->path, pattern);\n\n            if (xsys_findfirst(path, &directoryFileFindData)) {\n                do {\n                    bool isDirectory = fileFindIsDirectory(&directoryFileFindData);\n                    char* entryName = fileFindGetName(&directoryFileFindData);\n\n                    if (isDirectory) {\n                        if (strcmp(entryName, \"..\") == 0 || strcmp(entryName, \".\") == 0) {\n                            continue;\n                        }\n\n                        context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY;\n                    } else {\n                        context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE;\n                    }\n\n                    _makepath(context.name, drive, dir, entryName, NULL);\n\n                    if (!handler(&context)) {\n                        break;\n                    }\n                } while (xsys_findnext(&directoryFileFindData));\n            }\n            xsys_findclose(&directoryFileFindData);\n        }\n        xbase = xbase->next;\n    }\n\n    _splitpath(pattern, drive, dir, fileName, extension);\n    if (xsys_findfirst(pattern, &directoryFileFindData)) {\n        do {\n            bool isDirectory = fileFindIsDirectory(&directoryFileFindData);\n            char* entryName = fileFindGetName(&directoryFileFindData);\n\n            if (isDirectory) {\n                if (strcmp(entryName, \"..\") == 0 || strcmp(entryName, \".\") == 0) {\n                    continue;\n                }\n\n                context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY;\n            } else {\n                context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE;\n            }\n\n            _makepath(context.name, drive, dir, entryName, NULL);\n\n            if (!handler(&context)) {\n                break;\n            }\n        } while (xsys_findnext(&directoryFileFindData));\n    }\n    return xsys_findclose(&directoryFileFindData);\n}\n\n// 0x4DFF28\nbool xbuild_filelist(const char* pattern, XList* xlist)\n{\n    xenumpath(pattern, xlistenumfunc, xlist);\n    return xlist->fileNamesLength != -1;\n}\n\n// 0x4DFF48\nvoid xfree_filelist(XList* xlist)\n{\n    assert(xlist); // \"list\", \"xfile.c\", 949\n\n    for (int index = 0; index < xlist->fileNamesLength; index++) {\n        if (xlist->fileNames[index] != NULL) {\n            free(xlist->fileNames[index]);\n        }\n    }\n\n    free(xlist->fileNames);\n\n    memset(xlist, 0, sizeof(*xlist));\n}\n\n// Recursively creates specified file path.\n//\n// 0x4DFFAC\nint xmkdir(const char* filePath)\n{\n    char workingDirectory[FILENAME_MAX];\n    if (getcwd(workingDirectory, FILENAME_MAX) == NULL) {\n        return -1;\n    }\n\n    char drive[_MAX_DRIVE];\n    char dir[_MAX_DIR];\n    _splitpath(filePath, drive, dir, NULL, NULL);\n\n    char path[FILENAME_MAX];\n    if (drive[0] != '\\0' || dir[0] == '\\\\' || dir[0] == '/' || dir[0] == '.') {\n        // [filePath] is an absolute path.\n        strcpy(path, filePath);\n    } else {\n        // Find first directory-based xbase.\n        XBase* curr = paths;\n        while (curr != NULL) {\n            if (!curr->isDbase) {\n                sprintf(path, \"%s\\\\%s\", curr->path, filePath);\n                break;\n            }\n            curr = curr->next;\n        }\n\n        if (curr == NULL) {\n            // Either there are no directory-based xbase, or there are no open\n            // xbases at all - resolve path against current working directory.\n            sprintf(path, \"%s\\\\%s\", workingDirectory, filePath);\n        }\n    }\n\n    char* pch = path;\n\n    if (*pch == '\\\\' || *pch == '/') {\n        pch++;\n    }\n\n    while (*pch != '\\0') {\n        if (*pch == '\\\\' || *pch == '/') {\n            char temp = *pch;\n            *pch = '\\0';\n\n            if (chdir(path) != 0) {\n                if (mkdir(path) != 0) {\n                    chdir(workingDirectory);\n                    return -1;\n                }\n            } else {\n                chdir(workingDirectory);\n            }\n\n            *pch = temp;\n        }\n        pch++;\n    }\n\n    // Last path component.\n    mkdir(path);\n\n    chdir(workingDirectory);\n\n    return 0;\n}\n\n// Closes all xbases.\n//\n// NOTE: Inlined.\n//\n// 0x4E01F8\nstatic void xclearpath()\n{\n    XBase* curr = paths;\n    paths = NULL;\n\n    while (curr != NULL) {\n        XBase* next = curr->next;\n\n        if (curr->isDbase) {\n            dbase_close(curr->dbase);\n        }\n\n        free(curr->path);\n        free(curr);\n\n        curr = next;\n    }\n}\n\n// xbase atexit\nstatic void xpathexit(void)\n{\n    // NOTE: Uninline.\n    xclearpath();\n}\n\n// 0x4E0278\nstatic bool xlistenumfunc(XListEnumerationContext* context)\n{\n    if (context->type == XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY) {\n        return true;\n    }\n\n    XList* xlist = context->xlist;\n\n    char** fileNames = (char**)realloc(xlist->fileNames, sizeof(*fileNames) * (xlist->fileNamesLength + 1));\n    if (fileNames == NULL) {\n        xfree_filelist(xlist);\n        xlist->fileNamesLength = -1;\n        return false;\n    }\n\n    xlist->fileNames = fileNames;\n\n    fileNames[xlist->fileNamesLength] = strdup(context->name);\n    if (fileNames[xlist->fileNamesLength] == NULL) {\n        xfree_filelist(xlist);\n        xlist->fileNamesLength = -1;\n        return false;\n    }\n\n    xlist->fileNamesLength++;\n\n    return true;\n}\n"
  },
  {
    "path": "src/plib/xfile/xfile.h",
    "content": "#ifndef FALLOUT_PLIB_XFILE_XFILE_H_\n#define FALLOUT_PLIB_XFILE_XFILE_H_\n\n#include <stdbool.h>\n#include <stdio.h>\n\n#include <zlib.h>\n\n#include \"plib/xfile/dfile.h\"\n\ntypedef enum XFileType {\n    XFILE_TYPE_FILE,\n    XFILE_TYPE_DFILE,\n    XFILE_TYPE_GZFILE,\n} XFileType;\n\n// A universal database of files.\ntypedef struct XBase {\n    // The path to directory or .DAT file that this xbase represents.\n    char* path;\n\n    // The [DBase] instance that this xbase represents.\n    DBase* dbase;\n\n    // A flag used to denote that this xbase represents .DAT file (true), or\n    // a directory (false).\n    //\n    // NOTE: Original type is 1 byte, likely unsigned char.\n    bool isDbase;\n\n    // Next [XBase] in linked list.\n    struct XBase* next;\n} XBase;\n\ntypedef struct XFile {\n    XFileType type;\n    union {\n        FILE* file;\n        DFile* dfile;\n        gzFile gzfile;\n    };\n} XFile;\n\ntypedef struct XList {\n    int fileNamesLength;\n    char** fileNames;\n} XList;\n\ntypedef enum XFileEnumerationEntryType {\n    XFILE_ENUMERATION_ENTRY_TYPE_FILE,\n    XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY,\n    XFILE_ENUMERATION_ENTRY_TYPE_DFILE,\n} XFileEnumerationEntryType;\n\ntypedef struct XListEnumerationContext {\n    char name[FILENAME_MAX];\n    unsigned char type;\n    XList* xlist;\n} XListEnumerationContext;\n\ntypedef bool(XListEnumerationHandler)(XListEnumerationContext* context);\n\nint xfclose(XFile* stream);\nXFile* xfopen(const char* filename, const char* mode);\nint xfprintf(XFile* xfile, const char* format, ...);\nint xvfprintf(XFile* stream, const char* format, va_list args);\nint xfgetc(XFile* stream);\nchar* xfgets(char* string, int size, XFile* stream);\nint xfputc(int ch, XFile* stream);\nint xfputs(const char* s, XFile* stream);\nsize_t xfread(void* ptr, size_t size, size_t count, XFile* stream);\nsize_t xfwrite(const void* buf, size_t size, size_t count, XFile* stream);\nint xfseek(XFile* stream, long offset, int origin);\nlong xftell(XFile* stream);\nvoid xrewind(XFile* stream);\nint xfeof(XFile* stream);\nlong xfilelength(XFile* stream);\nbool xsetpath(char* paths);\nbool xaddpath(const char* path);\nbool xenumpath(const char* pattern, XListEnumerationHandler* handler, XList* xlist);\nbool xbuild_filelist(const char* pattern, XList* xlist);\nvoid xfree_filelist(XList* xlist);\nint xmkdir(const char* path);\n\n#endif /* FALLOUT_PLIB_XFILE_XFILE_H_ */\n"
  },
  {
    "path": "src/plib/xfile/xsys_find.c",
    "content": "#include \"plib/xfile/xsys_find.h\"\n\n// 0x4E6380\nbool xsys_findfirst(const char* path, DirectoryFileFindData* findData)\n{\n#if defined(_MSC_VER)\n    findData->hFind = FindFirstFileA(path, &(findData->ffd));\n    if (findData->hFind == INVALID_HANDLE_VALUE) {\n        return false;\n    }\n#elif defined(__WATCOMC__)\n    findData->dir = opendir(path);\n    if (findData->dir == NULL) {\n        return false;\n    }\n\n    findData->entry = readdir(findData->dir);\n    if (findData->entry == NULL) {\n        closedir(findData->dir);\n        return false;\n    }\n#else\n#error Not implemented\n#endif\n\n    return true;\n}\n\n// 0x4E63A8\nbool xsys_findnext(DirectoryFileFindData* findData)\n{\n#if defined(_MSC_VER)\n    if (!FindNextFileA(findData->hFind, &(findData->ffd))) {\n        return false;\n    }\n#elif defined(__WATCOMC__)\n    findData->entry = readdir(findData->dir);\n    if (findData->entry == NULL) {\n        closedir(findData->dir);\n        return false;\n    }\n#else\n#error Not implemented\n#endif\n\n    return true;\n}\n\n// 0x4E63CC\nbool xsys_findclose(DirectoryFileFindData* findData)\n{\n#if defined(_MSC_VER)\n    FindClose(findData->hFind);\n#elif defined(__WATCOMC__)\n    if (closedir(findData->dir) != 0) {\n        return false;\n    }\n#else\n#error Not implemented\n#endif\n\n    return true;\n}\n"
  },
  {
    "path": "src/plib/xfile/xsys_find.h",
    "content": "#ifndef FALLOUT_PLIB_XFILE_XSYS_FIND_H_\n#define FALLOUT_PLIB_XFILE_XSYS_FIND_H_\n\n#include <stdbool.h>\n\n#if defined(_WIN32)\n#define WIN32_LEAN_AND_MEAN\n#define NOMINMAX\n#include <windows.h>\n#else\n#include <dirent.h>\n#endif\n\n// NOTE: This structure is significantly different from what was in the\n// original code. Watcom provides opendir/readdir/closedir implementations,\n// that use Win32 FindFirstFile/FindNextFile under the hood, which in turn\n// is designed to deal with patterns.\n//\n// The first attempt was to use `dirent` implementation by Toni Ronkko\n// (https://github.com/tronkko/dirent), however it appears to be incompatible\n// with what is provided by Watcom. Toni's implementation adds `*` wildcard\n// unconditionally implying `opendir` accepts directory name only, which I\n// guess is fine when your goal is compliance with POSIX implementation.\n// However in Watcom `opendir` can handle file patterns gracefully. The problem\n// can be seen during game startup when cleaning MAPS directory using\n// MAPS\\*.SAV pattern. Toni's implementation tries to convert that to pattern\n// for Win32 API, thus making it MAPS\\*.SAV\\*, which is obviously incorrect\n// path/pattern for any implementation.\n//\n// Eventually I've decided to go with compiler-specific implementation, keeping\n// original implementation for Watcom (not tested). I'm not sure it will work\n// in other compilers, so for now just stick with the error.\ntypedef struct DirectoryFileFindData {\n#if defined(_WIN32)\n    HANDLE hFind;\n    WIN32_FIND_DATAA ffd;\n#else\n    DIR* dir;\n    struct dirent* entry;\n#endif\n} DirectoryFileFindData;\n\nbool xsys_findfirst(const char* path, DirectoryFileFindData* findData);\nbool xsys_findnext(DirectoryFileFindData* findData);\nbool xsys_findclose(DirectoryFileFindData* findData);\n\nstatic inline bool fileFindIsDirectory(DirectoryFileFindData* findData)\n{\n#if defined(_WIN32)\n    return (findData->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;\n#elif defined(__WATCOMC__)\n    return (findData->entry->d_attr & _A_SUBDIR) != 0;\n#else\n#error Not implemented\n#endif\n}\n\nstatic inline char* fileFindGetName(DirectoryFileFindData* findData)\n{\n#if defined(_WIN32)\n    return findData->ffd.cFileName;\n#else\n#error Not implemented\n#endif\n}\n\n#endif /* FALLOUT_PLIB_XFILE_XSYS_FIND_H_ */\n"
  },
  {
    "path": "src/sound_decoder.c",
    "content": "// NOTE: Functions in these module are somewhat different from what can be seen\n// in IDA because of two new helper functions that deal with incoming bits. I\n// bet something like these were implemented via function-like macro in the\n// same manner zlib deals with bits. The pattern is so common in this module so\n// I made an exception and extracted it into separate functions to increase\n// readability.\n\n#include \"sound_decoder.h\"\n\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n\nstatic inline void soundDecoderRequireBits(SoundDecoder* soundDecoder, int bits);\nstatic inline void soundDecoderDropBits(SoundDecoder* soundDecoder, int bits);\n\n// 0x51E328\nint gSoundDecodersCount = 0;\n\n// 0x51E32C\nbool _inited_ = false;\n\n// 0x51E330\nDECODINGPROC _ReadBand_tbl[32] = {\n    _ReadBand_Fmt0_,\n    _ReadBand_Fail_,\n    _ReadBand_Fail_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt3_16_,\n    _ReadBand_Fmt17_,\n    _ReadBand_Fmt18_,\n    _ReadBand_Fmt19_,\n    _ReadBand_Fmt20_,\n    _ReadBand_Fmt21_,\n    _ReadBand_Fmt22_,\n    _ReadBand_Fmt23_,\n    _ReadBand_Fmt24_,\n    _ReadBand_Fail_,\n    _ReadBand_Fmt26_,\n    _ReadBand_Fmt27_,\n    _ReadBand_Fail_,\n    _ReadBand_Fmt29_,\n    _ReadBand_Fail_,\n    _ReadBand_Fail_,\n};\n\n// 0x6AD960\nunsigned char _pack11_2[128];\n\n// 0x6AD9E0\nunsigned char _pack3_3[32];\n\n// 0x6ADA00\nunsigned short word_6ADA00[128];\n\n// 0x6ADB00\nunsigned char* _AudioDecoder_scale0;\n\n// 0x6ADB04\nunsigned char* _AudioDecoder_scale_tbl;\n\n// 0x4D3BB0\nbool soundDecoderPrepare(SoundDecoder* soundDecoder, SoundDecoderReadProc* readProc, int fileHandle)\n{\n    soundDecoder->readProc = readProc;\n    soundDecoder->fd = fileHandle;\n\n    soundDecoder->bufferIn = (unsigned char*)malloc(SOUND_DECODER_IN_BUFFER_SIZE);\n    if (soundDecoder->bufferIn == NULL) {\n        return false;\n    }\n\n    soundDecoder->bufferInSize = SOUND_DECODER_IN_BUFFER_SIZE;\n    soundDecoder->remainingInSize = 0;\n\n    return true;\n}\n\n// 0x4D3BE0\nunsigned char soundDecoderReadNextChunk(SoundDecoder* soundDecoder)\n{\n    soundDecoder->remainingInSize = soundDecoder->readProc(soundDecoder->fd, soundDecoder->bufferIn, soundDecoder->bufferInSize);\n    if (soundDecoder->remainingInSize == 0) {\n        memset(soundDecoder->bufferIn, 0, soundDecoder->bufferInSize);\n        soundDecoder->remainingInSize = soundDecoder->bufferInSize;\n    }\n\n    soundDecoder->nextIn = soundDecoder->bufferIn;\n    soundDecoder->remainingInSize -= 1;\n    return *soundDecoder->nextIn++;\n}\n\n// 0x4D3C78\nvoid _init_pack_tables()\n{\n    int i;\n    int j;\n    int m;\n\n    if (_inited_) {\n        return;\n    }\n\n    for (i = 0; i < 3; i++) {\n        for (j = 0; j < 3; j++) {\n            for (m = 0; m < 3; m++) {\n                _pack3_3[i + j * 3 + m * 9] = i + j * 4 + m * 16;\n            }\n        }\n    }\n\n    for (i = 0; i < 5; i++) {\n        for (j = 0; j < 5; j++) {\n            for (m = 0; m < 5; m++) {\n                word_6ADA00[i + j * 5 + m * 25] = i + j * 8 + m * 64;\n            }\n        }\n    }\n\n    for (i = 0; i < 11; i++) {\n        for (j = 0; j < 11; j++) {\n            _pack11_2[i + j * 11] = i + j * 16;\n        }\n    }\n\n    _inited_ = true;\n}\n\n// 0x4D3D9C\nint _ReadBand_Fail_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    return 0;\n}\n\n// 0x4D3DA0\nint _ReadBand_Fmt0_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        *p = 0;\n        p += soundDecoder->field_24;\n        i--;\n    }\n\n    return 1;\n}\n\n// 0x4D3DC8\nint _ReadBand_Fmt3_16_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    int value;\n    int v14;\n\n    short* base = (short*)_AudioDecoder_scale0;\n    base += UINT_MAX << (bits - 1);\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    v14 = (1 << bits) - 1;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, bits);\n        value = soundDecoder->hold;\n        soundDecoderDropBits(soundDecoder, bits);\n\n        *p = base[v14 & value];\n        p += soundDecoder->field_24;\n\n        i--;\n    }\n\n    return 1;\n}\n\n// 0x4D3E90\nint _ReadBand_Fmt17_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 3);\n\n        int value = soundDecoder->hold & 0xFF;\n        if (!(value & 0x01)) {\n            soundDecoderDropBits(soundDecoder, 1);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else if (!(value & 0x02)) {\n            soundDecoderDropBits(soundDecoder, 2);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else {\n            soundDecoderDropBits(soundDecoder, 3);\n\n            if (value & 0x04) {\n                *p = base[1];\n            } else {\n                *p = base[-1];\n            }\n\n            p += soundDecoder->field_24;\n            i--;\n        }\n    }\n    return 1;\n}\n\n// 0x4D3F98\nint _ReadBand_Fmt18_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 2);\n\n        int value = soundDecoder->hold;\n        if (!(value & 0x01)) {\n            soundDecoderDropBits(soundDecoder, 1);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                return 1;\n            }\n        } else {\n            soundDecoderDropBits(soundDecoder, 2);\n\n            if (value & 0x02) {\n                *p = base[1];\n            } else {\n                *p = base[-1];\n            }\n\n            p += soundDecoder->field_24;\n            i--;\n        }\n    }\n    return 1;\n}\n\n// 0x4D4068\nint _ReadBand_Fmt19_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n    base -= 1;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 5);\n        int value = soundDecoder->hold & 0x1F;\n        soundDecoderDropBits(soundDecoder, 5);\n\n        value = _pack3_3[value];\n\n        *p = base[value & 0x03];\n        p += soundDecoder->field_24;\n        if (--i == 0) {\n            break;\n        }\n\n        *p = base[(value >> 2) & 0x03];\n        p += soundDecoder->field_24;\n        if (--i == 0) {\n            break;\n        }\n\n        *p = base[value >> 4];\n        p += soundDecoder->field_24;\n\n        i--;\n    }\n\n    return 1;\n}\n\n// 0x4D4158\nint _ReadBand_Fmt20_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 4);\n\n        int value = soundDecoder->hold & 0xFF;\n        if (!(value & 0x01)) {\n            soundDecoderDropBits(soundDecoder, 1);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else if (!(value & 0x02)) {\n            soundDecoderDropBits(soundDecoder, 2);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else {\n            soundDecoderDropBits(soundDecoder, 4);\n\n            if (value & 0x08) {\n                if (value & 0x04) {\n                    *p = base[2];\n                } else {\n                    *p = base[1];\n                }\n            } else {\n                if (value & 0x04) {\n                    *p = base[-1];\n                } else {\n                    *p = base[-2];\n                }\n            }\n\n            p += soundDecoder->field_24;\n            i--;\n        }\n    }\n\n    return 1;\n}\n\n// 0x4D4254\nint _ReadBand_Fmt21_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 3);\n\n        int value = soundDecoder->hold & 0xFF;\n        if (!(value & 0x01)) {\n            soundDecoderDropBits(soundDecoder, 1);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else {\n            soundDecoderDropBits(soundDecoder, 3);\n\n            if (value & 0x04) {\n                if (value & 0x02) {\n                    *p = base[2];\n                } else {\n                    *p = base[1];\n                }\n            } else {\n                if (value & 0x02) {\n                    *p = base[-1];\n                } else {\n                    *p = base[-2];\n                }\n            }\n\n            p += soundDecoder->field_24;\n            i--;\n        }\n    }\n\n    return 1;\n}\n\n// 0x4D4338\nint _ReadBand_Fmt22_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n    base -= 2;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 7);\n        int value = soundDecoder->hold & 0x7F;\n        soundDecoderDropBits(soundDecoder, 7);\n\n        value = word_6ADA00[value];\n\n        *p = base[value & 7];\n        p += soundDecoder->field_24;\n\n        if (--i == 0) {\n            break;\n        }\n\n        *p = base[((value >> 3) & 7)];\n        p += soundDecoder->field_24;\n\n        if (--i == 0) {\n            break;\n        }\n\n        *p = base[value >> 6];\n        p += soundDecoder->field_24;\n\n        if (--i == 0) {\n            break;\n        }\n    }\n\n    return 1;\n}\n\n// 0x4D4434\nint _ReadBand_Fmt23_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 5);\n\n        int value = soundDecoder->hold;\n        if (!(value & 0x01)) {\n            soundDecoderDropBits(soundDecoder, 1);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else if (!(value & 0x02)) {\n            soundDecoderDropBits(soundDecoder, 2);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else if (!(value & 0x04)) {\n            soundDecoderDropBits(soundDecoder, 4);\n\n            if (value & 0x08) {\n                *p = base[1];\n            } else {\n                *p = base[-1];\n            }\n\n            p += soundDecoder->field_24;\n            if (--i == 0) {\n                break;\n            }\n        } else {\n            soundDecoderDropBits(soundDecoder, 5);\n\n            value >>= 3;\n            value &= 0x03;\n            if (value >= 2) {\n                value += 3;\n            }\n\n            *p = base[value - 3];\n            p += soundDecoder->field_24;\n            i--;\n        }\n    }\n\n    return 1;\n}\n\n// 0x4D4584\nint _ReadBand_Fmt24_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 4);\n\n        int value = soundDecoder->hold & 0xFF;\n        if (!(value & 0x01)) {\n            soundDecoderDropBits(soundDecoder, 1);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else if (!(value & 0x02)) {\n            soundDecoderDropBits(soundDecoder, 3);\n\n            if (value & 0x04) {\n                *p = base[1];\n            } else {\n                *p = base[-1];\n            }\n\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else {\n            soundDecoderDropBits(soundDecoder, 4);\n\n            value >>= 2;\n            value &= 0x03;\n            if (value >= 2) {\n                value += 3;\n            }\n\n            *p = base[value - 3];\n            p += soundDecoder->field_24;\n            i--;\n        }\n    }\n\n    return 1;\n}\n\n// 0x4D4698\nint _ReadBand_Fmt26_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 5);\n\n        int value = soundDecoder->hold;\n        if (!(value & 0x01)) {\n            soundDecoderDropBits(soundDecoder, 1);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else if (!(value & 0x02)) {\n            soundDecoderDropBits(soundDecoder, 2);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else {\n            soundDecoderDropBits(soundDecoder, 5);\n\n            value >>= 2;\n            value &= 0x07;\n            if (value >= 4) {\n                value += 1;\n            }\n\n            *p = base[value - 4];\n            p += soundDecoder->field_24;\n            i--;\n        }\n    }\n\n    return 1;\n}\n\n// 0x4D47A4\nint _ReadBand_Fmt27_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 4);\n\n        int value = soundDecoder->hold;\n        if (!(value & 0x01)) {\n            soundDecoderDropBits(soundDecoder, 1);\n\n            *p = 0;\n            p += soundDecoder->field_24;\n\n            if (--i == 0) {\n                break;\n            }\n        } else {\n            soundDecoderDropBits(soundDecoder, 4);\n\n            value >>= 1;\n            value &= 0x07;\n            if (value >= 4) {\n                value += 1;\n            }\n\n            *p = base[value - 4];\n            p += soundDecoder->field_24;\n            i--;\n        }\n    }\n\n    return 1;\n}\n\n// 0x4D4870\nint _ReadBand_Fmt29_(SoundDecoder* soundDecoder, int offset, int bits)\n{\n    short* base = (short*)_AudioDecoder_scale0;\n\n    int* p = (int*)soundDecoder->field_34;\n    p += offset;\n\n    int i = soundDecoder->field_28;\n    while (i != 0) {\n        soundDecoderRequireBits(soundDecoder, 7);\n        int value = soundDecoder->hold & 0x7F;\n        soundDecoderDropBits(soundDecoder, 7);\n\n        value = _pack11_2[value];\n\n        *p = base[(value & 0x0F) - 5];\n        p += soundDecoder->field_24;\n        if (--i == 0) {\n            break;\n        }\n\n        *p = base[(value >> 4) - 5];\n        p += soundDecoder->field_24;\n        if (--i == 0) {\n            break;\n        }\n    }\n\n    return 1;\n}\n\n// 0x4D493C\nint _ReadBands_(SoundDecoder* soundDecoder)\n{\n    int v9;\n    int v15;\n    int v17;\n    int v19;\n    unsigned short* v18;\n    int v21;\n    DECODINGPROC fn;\n\n    soundDecoderRequireBits(soundDecoder, 4);\n    v9 = soundDecoder->hold & 0xF;\n    soundDecoderDropBits(soundDecoder, 4);\n\n    soundDecoderRequireBits(soundDecoder, 16);\n    v15 = soundDecoder->hold & 0xFFFF;\n    soundDecoderDropBits(soundDecoder, 16);\n\n    v17 = 1 << v9;\n\n    v18 = (unsigned short*)_AudioDecoder_scale0;\n    v19 = v17;\n    v21 = 0;\n    while (v19--) {\n        *v18++ = v21;\n        v21 += v15;\n    }\n\n    v18 = (unsigned short*)_AudioDecoder_scale0;\n    v19 = v17;\n    v21 = -v15;\n    while (v19--) {\n        v18--;\n        *v18 = v21;\n        v21 -= v15;\n    }\n\n    _init_pack_tables();\n\n    for (int index = 0; index < soundDecoder->field_24; index++) {\n        soundDecoderRequireBits(soundDecoder, 5);\n        int bits = soundDecoder->hold & 0x1F;\n        soundDecoderDropBits(soundDecoder, 5);\n\n        fn = _ReadBand_tbl[bits];\n        if (!fn(soundDecoder, index, bits)) {\n            return 0;\n        }\n    }\n    return 1;\n}\n\n// 0x4D4ADC\nvoid _untransform_subband0(unsigned char* a1, unsigned char* a2, int a3, int a4)\n{\n    short* p;\n\n    p = (short*)a2;\n    p += a3;\n\n    if (a4 == 2) {\n        int i = a3;\n        while (i != 0) {\n            i--;\n        }\n    } else if (a4 == 4) {\n        int v31 = a3;\n        int* v9 = (int*)a2;\n        v9 += a3;\n\n        int* v10 = (int*)a2;\n        v10 += a3 * 3;\n\n        int* v11 = (int*)a2;\n        v11 += a3 * 2;\n\n        while (v31 != 0) {\n            int* v33 = (int*)a2;\n            int* v34 = (int*)a1;\n\n            int v12 = *v34 >> 16;\n\n            int v13 = *v33;\n            *v33 = (int)(*(short*)v34) + 2 * v12 + v13;\n\n            int v14 = *v9;\n            *v9 = 2 * v13 - v12 - v14;\n\n            int v15 = *v11;\n            *v11 = 2 * v14 + v15 + v13;\n\n            int v16 = *v10;\n            *v10 = 2 * v15 - v14 - v16;\n\n            v10++;\n            v11++;\n            v9++;\n\n            *(short*)a1 = v15 & 0xFFFF;\n            *(short*)(a1 + 2) = v16 & 0xFFFF;\n\n            a1 += 4;\n            a2 += 4;\n\n            v31--;\n        }\n    } else {\n        int v30 = a4 >> 1;\n        int v32 = a3;\n        while (v32 != 0) {\n            int* v19 = (int*)a2;\n\n            int v20;\n            int v22;\n            if (v30 & 0x01) {\n\n            } else {\n                v20 = (int)*(short*)a1;\n                v22 = *(int*)a1 >> 16;\n            }\n\n            int v23 = v30 >> 1;\n            while (--v23 != -1) {\n                int v24 = *v19;\n                *v19 += 2 * v22 + v20;\n                v19 += a3;\n\n                int v26 = *v19;\n                *v19 = 2 * v24 - v22 - v26;\n                v19 += a3;\n\n                v20 = *v19;\n                *v19 += 2 * v26 + v24;\n                v19 += a3;\n\n                v22 = *v19;\n                *v19 = 2 * v20 - v26 - v22;\n                v19 += a3;\n            }\n\n            *(short*)a1 = v20 & 0xFFFF;\n            *(short*)(a1 + 2) = v22 & 0xFFFF;\n\n            a1 += 4;\n            a2 += 4;\n            v32--;\n        }\n    }\n}\n\n// 0x4D4D1C\nvoid _untransform_subband(unsigned char* a1, unsigned char* a2, int a3, int a4)\n{\n    int v13;\n    int* v14;\n    int* v25;\n    int* v26;\n    int v15;\n    int v16;\n    int v17;\n\n    int* v18;\n    int v19;\n    int* v20;\n    int* v21;\n\n    v26 = (int*)a1;\n    v25 = (int*)a2;\n\n    if (a4 == 4) {\n        unsigned char* v4 = a2 + 4 * a3;\n        unsigned char* v5 = a2 + 3 * a3;\n        unsigned char* v6 = a2 + 2 * a3;\n        int v7;\n        int v8;\n        int v9;\n        int v10;\n        int v11;\n        while (a3--) {\n            v7 = *(unsigned int*)(v26 + 4);\n            v8 = *(unsigned int*)v25;\n            *(unsigned int*)v25 = *(unsigned int*)v26 + 2 * v7;\n\n            v9 = *(unsigned int*)v4;\n            *(unsigned int*)v4 = 2 * v8 - v7 - v9;\n\n            v10 = *(unsigned int*)v6;\n            v5 += 4;\n            *v6 += 2 * v9 + v8;\n\n            v11 = *(unsigned int*)(v5 - 4);\n            v6 += 4;\n\n            *(unsigned int*)(v5 - 4) = 2 * v10 - v9 - v11;\n            v4 += 4;\n\n            *(unsigned int*)v26 = v10;\n            *(unsigned int*)(v26 + 4) = v11;\n\n            v26 += 2;\n            v25 += 1;\n        }\n    } else {\n        int v24 = a3;\n\n        while (v24 != 0) {\n            v13 = a4 >> 2;\n            v14 = v25;\n            v15 = v26[0];\n            v16 = v26[1];\n\n            while (--v13 != -1) {\n                v17 = *v14;\n                *v14 += 2 * v16 + v15;\n\n                v18 = v14 + a3;\n                v19 = *v18;\n                *v18 = 2 * v17 - v16 - v19;\n\n                v20 = v18 + a3;\n                v15 = *v20;\n                *v20 += 2 * v19 + v17;\n\n                v21 = v20 + a3;\n                v16 = *v21;\n                *v21 = 2 * v15 - v19 - v16;\n\n                v14 = v21 + a3;\n            }\n\n            v26[0] = v15;\n            v26[1] = v16;\n\n            v26 += 2;\n            v25 += 1;\n\n            v24--;\n        }\n    }\n}\n\n// 0x4D4E80\nvoid _untransform_all(SoundDecoder* soundDecoder)\n{\n    int v8;\n    unsigned char* ptr;\n    int v3;\n    int v4;\n    unsigned char* j;\n    int v6;\n    int* v5;\n\n    if (!soundDecoder->field_20) {\n        return;\n    }\n\n    ptr = soundDecoder->field_34;\n\n    v8 = soundDecoder->field_28;\n    while (v8 > 0) {\n        v3 = soundDecoder->field_24 >> 1;\n        v4 = soundDecoder->field_38;\n        if (v4 > v8) {\n            v4 = v8;\n        }\n\n        v4 *= 2;\n\n        _untransform_subband0(soundDecoder->field_30, ptr, v3, v4);\n\n        v5 = (int*)ptr;\n        for (v6 = 0; v6 < v4; v6++) {\n            *v5 += 1;\n            v5 += v3;\n        }\n\n        j = 4 * v3 + soundDecoder->field_30;\n        while (1) {\n            v3 >>= 1;\n            v4 *= 2;\n            if (v3 == 0) {\n                break;\n            }\n            _untransform_subband(j, ptr, v3, v4);\n            j += 8 * v3;\n        }\n\n        ptr += soundDecoder->field_3C * 4;\n        v8 -= soundDecoder->field_38;\n    }\n}\n\n// 0x4D4FA0\nsize_t soundDecoderDecode(SoundDecoder* soundDecoder, void* buffer, size_t size)\n{\n    unsigned char* dest;\n    unsigned char* v5;\n    int v6;\n    int v4;\n\n    dest = (unsigned char*)buffer;\n    v4 = 0;\n    v5 = soundDecoder->field_4C;\n    v6 = soundDecoder->field_50;\n\n    size_t bytesRead;\n    for (bytesRead = 0; bytesRead < size; bytesRead += 2) {\n        if (!v6) {\n            if (!soundDecoder->field_48) {\n                break;\n            }\n\n            if (!_ReadBands_(soundDecoder)) {\n                break;\n            }\n\n            _untransform_all(soundDecoder);\n\n            soundDecoder->field_48 -= soundDecoder->field_2C;\n            soundDecoder->field_4C = soundDecoder->field_34;\n            soundDecoder->field_50 = soundDecoder->field_2C;\n\n            if (soundDecoder->field_48 < 0) {\n                soundDecoder->field_50 += soundDecoder->field_48;\n                soundDecoder->field_48 = 0;\n            }\n\n            v5 = soundDecoder->field_4C;\n            v6 = soundDecoder->field_50;\n        }\n\n        int v13 = *(int*)v5;\n        v5 += 4;\n        *(unsigned short*)(dest + bytesRead) = (v13 >> soundDecoder->field_20) & 0xFFFF;\n        v6--;\n    }\n\n    soundDecoder->field_4C = v5;\n    soundDecoder->field_50 = v6;\n\n    return bytesRead;\n}\n\n// 0x4D5048\nvoid soundDecoderFree(SoundDecoder* soundDecoder)\n{\n    if (soundDecoder->bufferIn != NULL) {\n        free(soundDecoder->bufferIn);\n    }\n\n    if (soundDecoder->field_30 != NULL) {\n        free(soundDecoder->field_30);\n    }\n\n    if (soundDecoder->field_34 != NULL) {\n        free(soundDecoder->field_34);\n    }\n\n    free(soundDecoder);\n\n    gSoundDecodersCount--;\n\n    if (gSoundDecodersCount == 0) {\n        if (_AudioDecoder_scale_tbl != NULL) {\n            free(_AudioDecoder_scale_tbl);\n            _AudioDecoder_scale_tbl = NULL;\n        }\n    }\n}\n\n// 0x4D50A8\nSoundDecoder* soundDecoderInit(SoundDecoderReadProc* readProc, int fileHandle, int* out_a3, int* out_a4, int* out_a5)\n{\n    int v14;\n    int v20;\n    int v73;\n\n    SoundDecoder* soundDecoder = (SoundDecoder*)malloc(sizeof(*soundDecoder));\n    if (soundDecoder == NULL) {\n        return NULL;\n    }\n\n    memset(soundDecoder, 0, sizeof(*soundDecoder));\n\n    gSoundDecodersCount++;\n\n    if (!soundDecoderPrepare(soundDecoder, readProc, fileHandle)) {\n        goto L66;\n    }\n\n    soundDecoder->hold = 0;\n    soundDecoder->bits = 0;\n\n    soundDecoderRequireBits(soundDecoder, 24);\n    v14 = soundDecoder->hold;\n    soundDecoderDropBits(soundDecoder, 24);\n\n    if ((v14 & 0xFFFFFF) != 0x32897) {\n        goto L66;\n    }\n\n    soundDecoderRequireBits(soundDecoder, 8);\n    v20 = soundDecoder->hold;\n    soundDecoderDropBits(soundDecoder, 8);\n\n    if (v20 != 1) {\n        goto L66;\n    }\n\n    soundDecoderRequireBits(soundDecoder, 16);\n    soundDecoder->field_48 = soundDecoder->hold & 0xFFFF;\n    soundDecoderDropBits(soundDecoder, 16);\n\n    soundDecoderRequireBits(soundDecoder, 16);\n    soundDecoder->field_48 |= (soundDecoder->hold & 0xFFFF) << 16;\n    soundDecoderDropBits(soundDecoder, 16);\n\n    soundDecoderRequireBits(soundDecoder, 16);\n    soundDecoder->field_40 = soundDecoder->hold & 0xFFFF;\n    soundDecoderDropBits(soundDecoder, 16);\n\n    soundDecoderRequireBits(soundDecoder, 16);\n    soundDecoder->field_44 = soundDecoder->hold & 0xFFFF;\n    soundDecoderDropBits(soundDecoder, 16);\n\n    soundDecoderRequireBits(soundDecoder, 4);\n    soundDecoder->field_20 = soundDecoder->hold & 0x0F;\n    soundDecoderDropBits(soundDecoder, 4);\n\n    soundDecoderRequireBits(soundDecoder, 12);\n    soundDecoder->field_24 = 1 << soundDecoder->field_20;\n    soundDecoder->field_28 = soundDecoder->hold & 0x0FFF;\n    soundDecoder->field_2C = soundDecoder->field_28 * soundDecoder->field_24;\n    soundDecoderDropBits(soundDecoder, 12);\n\n    if (soundDecoder->field_20 != 0) {\n        v73 = 3 * soundDecoder->field_24 / 2 - 2;\n    } else {\n        v73 = 0;\n    }\n\n    soundDecoder->field_38 = 2048 / soundDecoder->field_24 - 2;\n    if (soundDecoder->field_38 < 1) {\n        soundDecoder->field_38 = 1;\n    }\n\n    soundDecoder->field_3C = soundDecoder->field_38 * soundDecoder->field_24;\n\n    if (v73 != 0) {\n        soundDecoder->field_30 = (unsigned char*)malloc(sizeof(unsigned char*) * v73);\n        if (soundDecoder->field_30 == NULL) {\n            goto L66;\n        }\n\n        memset(soundDecoder->field_30, 0, sizeof(unsigned char*) * v73);\n    }\n\n    soundDecoder->field_34 = (unsigned char*)malloc(sizeof(unsigned char*) * soundDecoder->field_2C);\n    if (soundDecoder->field_34 == NULL) {\n        goto L66;\n    }\n\n    soundDecoder->field_50 = 0;\n\n    if (gSoundDecodersCount == 1) {\n        _AudioDecoder_scale_tbl = (unsigned char*)malloc(0x20000);\n        _AudioDecoder_scale0 = _AudioDecoder_scale_tbl + 0x10000;\n    }\n\n    *out_a3 = soundDecoder->field_40;\n    *out_a4 = soundDecoder->field_44;\n    *out_a5 = soundDecoder->field_48;\n\n    return soundDecoder;\n\nL66:\n\n    soundDecoderFree(soundDecoder);\n\n    *out_a3 = 0;\n    *out_a4 = 0;\n    *out_a5 = 0;\n\n    return 0;\n}\n\nstatic inline void soundDecoderRequireBits(SoundDecoder* soundDecoder, int bits)\n{\n    while (soundDecoder->bits < bits) {\n        soundDecoder->remainingInSize--;\n\n        unsigned char ch;\n        if (soundDecoder->remainingInSize < 0) {\n            ch = soundDecoderReadNextChunk(soundDecoder);\n        } else {\n            ch = *soundDecoder->nextIn++;\n        }\n        soundDecoder->hold |= ch << soundDecoder->bits;\n        soundDecoder->bits += 8;\n    }\n}\n\nstatic inline void soundDecoderDropBits(SoundDecoder* soundDecoder, int bits)\n{\n    soundDecoder->hold >>= bits;\n    soundDecoder->bits -= bits;\n}\n"
  },
  {
    "path": "src/sound_decoder.h",
    "content": "#ifndef SOUND_DECODER_H\n#define SOUND_DECODER_H\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#define SOUND_DECODER_IN_BUFFER_SIZE (512)\n\ntypedef int(SoundDecoderReadProc)(int fileHandle, void* buffer, unsigned int size);\n\ntypedef struct SoundDecoder {\n    SoundDecoderReadProc* readProc;\n    int fd;\n    unsigned char* bufferIn;\n    size_t bufferInSize;\n\n    // Next input byte.\n    unsigned char* nextIn;\n\n    // Number of bytes remaining in the input buffer.\n    int remainingInSize;\n\n    // Bit accumulator.\n    int hold;\n\n    // Number of bits in bit accumulator.\n    int bits;\n    int field_20;\n    int field_24;\n    int field_28;\n    int field_2C;\n    unsigned char* field_30;\n    unsigned char* field_34;\n    int field_38;\n    int field_3C;\n    int field_40;\n    int field_44;\n    int field_48;\n    unsigned char* field_4C;\n    int field_50;\n} SoundDecoder;\n\n#if _WIN32\nstatic_assert(sizeof(SoundDecoder) == 84, \"wrong size\");\n#endif\n\ntypedef int (*DECODINGPROC)(SoundDecoder* soundDecoder, int offset, int bits);\n\nextern int gSoundDecodersCount;\nextern bool _inited_;\nextern DECODINGPROC _ReadBand_tbl[32];\nextern unsigned char _pack11_2[128];\nextern unsigned char _pack3_3[32];\nextern unsigned short word_6ADA00[128];\nextern unsigned char* _AudioDecoder_scale0;\nextern unsigned char* _AudioDecoder_scale_tbl;\n\nbool soundDecoderPrepare(SoundDecoder* a1, SoundDecoderReadProc* readProc, int fileHandle);\nunsigned char soundDecoderReadNextChunk(SoundDecoder* a1);\nvoid _init_pack_tables();\n\nint _ReadBand_Fail_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt0_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt3_16_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt17_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt18_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt19_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt20_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt21_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt22_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt23_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt24_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt26_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt27_(SoundDecoder* soundDecoder, int offset, int bits);\nint _ReadBand_Fmt29_(SoundDecoder* soundDecoder, int offset, int bits);\n\nint _ReadBands_(SoundDecoder* ptr);\nvoid _untransform_subband0(unsigned char* a1, unsigned char* a2, int a3, int a4);\nvoid _untransform_subband(unsigned char* a1, unsigned char* a2, int a3, int a4);\nvoid _untransform_all(SoundDecoder* a1);\nsize_t soundDecoderDecode(SoundDecoder* soundDecoder, void* buffer, size_t size);\nvoid soundDecoderFree(SoundDecoder* soundDecoder);\nSoundDecoder* soundDecoderInit(SoundDecoderReadProc* readProc, int fileHandle, int* out_a3, int* out_a4, int* out_a5);\n\n#endif /* SOUND_DECODER_H */\n"
  },
  {
    "path": "third_party/fpattern/CMakeLists.txt",
    "content": "include(FetchContent)\n\nFetchContent_Declare(fpattern\n    GIT_REPOSITORY \"https://github.com/Loadmaster/fpattern\"\n    GIT_TAG \"v1.9\"\n)\n\nFetchContent_GetProperties(fpattern)\nif (NOT fpattern_POPULATED)\n    FetchContent_Populate(fpattern)\nendif()\n\nadd_library(fpattern STATIC\n    \"${fpattern_SOURCE_DIR}/debug.h\"\n    \"${fpattern_SOURCE_DIR}/fpattern.c\"\n    \"${fpattern_SOURCE_DIR}/fpattern.h\"\n)\n\nset(FPATTERN_LIBRARY \"fpattern\" PARENT_SCOPE)\nset(FPATTERN_INCLUDE_DIR \"${fpattern_SOURCE_DIR}\" PARENT_SCOPE)\n"
  },
  {
    "path": "third_party/fpattern/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright ©1997-2001 by David R Tribble.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "third_party/fpattern/README.md",
    "content": "# fpattern\n\nFallout 2 uses `fpattern` v1.07 (see `0x4EB990`) to find entries in .DAT files. The closest version I was able to find is v1.08 on the author's website ([fpattern.c](http://david.tribble.com/src/fpattern.c), [fpattern.h](http://david.tribble.com/src/fpattern.h)). It's not very safe to use such direct links, and I'm not sure if it's possible to download these files via `FetchContent`. Luckily there is a newer v1.9 on [GitHub](https://github.com/Loadmaster/fpattern).\n"
  },
  {
    "path": "third_party/zlib/CMakeLists.txt",
    "content": "include(FetchContent)\n\nFetchContent_Declare(zlib\n    GIT_REPOSITORY \"https://github.com/madler/zlib\"\n    GIT_TAG \"v1.2.11\"\n)\n\nFetchContent_GetProperties(zlib)\nif (NOT zlib_POPULATED)\n    FetchContent_Populate(zlib)\nendif()\n\nadd_subdirectory(${zlib_SOURCE_DIR} ${zlib_BINARY_DIR} EXCLUDE_FROM_ALL)\n\nset(ZLIB_LIBRARIES zlibstatic PARENT_SCOPE)\nset(ZLIB_INCLUDE_DIRS ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR} PARENT_SCOPE)\n"
  },
  {
    "path": "third_party/zlib/LICENSE",
    "content": "Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler\n\nThis software is provided 'as-is', without any express or implied\nwarranty.  In no event will the authors be held liable for any damages\narising from the use of this software.\n\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must not\n    claim that you wrote the original software. If you use this software\n    in a product, an acknowledgment in the product documentation would be\n    appreciated but is not required.\n2. Altered source versions must be plainly marked as such, and must not be\n    misrepresented as being the original software.\n3. This notice may not be removed or altered from any source distribution.\n\nJean-loup Gailly        Mark Adler\njloup@gzip.org          madler@alumni.caltech.edu\n"
  },
  {
    "path": "third_party/zlib/README.md",
    "content": "# zlib\n\nFallout 2 uses `zlib` v1.1.1 (as seen at `0x4E18A0`, `0x4ED2F0`, and other various places) to decompress data embedded in .DAT files. There is no point in using such old version, because zlib is highlighly optimized, portable, and provides backward compatibility.\n"
  }
]