diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..c12c6e7f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,8 @@
+# Normalize line endings to CRLF on checkout
+* text eol=crlf
+
+# Git kept corrupting binary files
+# by trying to convert them to CRLF
+*.iwi binary
+*.lib binary
+*.ttf binary
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index dbaa467f..88b36f82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,13 +14,21 @@
/build
/ipch
+/.vs
+
+/Debug
+/Release
components/sdk
+components/game_mod/steam
components/*/Debug
components/*/Release
components/shared/*/Debug
components/shared/*/Release
components/D3DBSP_Lib/*/Release
components/D3DBSP_Lib/*/Debug
+components/launcher/imgui.ini
+
+components/gsc_parser/src/cpp/parser
!*.lib
\ No newline at end of file
diff --git a/LinkerMod.sln b/LinkerMod.sln
index 5f93a931..004332a5 100644
--- a/LinkerMod.sln
+++ b/LinkerMod.sln
@@ -6,32 +6,57 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "launcher_ldr", "components\launcher_ldr\launcher_ldr.vcxproj", "{E543772A-4051-499E-8DC6-B5501043DA7B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "linker_pc", "components\linker_pc\linker_pc.vcxproj", "{3A1FA19B-CC11-4725-AC86-6B3CF539E1B2}"
+ ProjectSection(ProjectDependencies) = postProject
+ {E543772A-4051-499E-8DC6-B5501043DA7B} = {E543772A-4051-499E-8DC6-B5501043DA7B}
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601} = {06E30C65-D79A-4FEC-8A60-B36D907E6601}
+ EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "game_mod", "components\game_mod\game_mod.vcxproj", "{69B41EFB-E992-4351-AA29-FDC61F4DBDE0}"
+ ProjectSection(ProjectDependencies) = postProject
+ {E543772A-4051-499E-8DC6-B5501043DA7B} = {E543772A-4051-499E-8DC6-B5501043DA7B}
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601} = {06E30C65-D79A-4FEC-8A60-B36D907E6601}
+ {86C18FD7-1144-440C-B5E3-265194BBC934} = {86C18FD7-1144-440C-B5E3-265194BBC934}
+ EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "radiant_mod", "components\radiant_mod\radiant_mod.vcxproj", "{6A3FB8CD-BEA3-42F1-B551-A0F56ED9B266}"
+ ProjectSection(ProjectDependencies) = postProject
+ {E543772A-4051-499E-8DC6-B5501043DA7B} = {E543772A-4051-499E-8DC6-B5501043DA7B}
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601} = {06E30C65-D79A-4FEC-8A60-B36D907E6601}
+ EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cod2rad", "components\cod2rad\cod2rad.vcxproj", "{B19C7605-5258-457E-883A-1A7896E7BD6C}"
ProjectSection(ProjectDependencies) = postProject
{F9BA1B1B-9BF9-4365-8A18-531F80C8847F} = {F9BA1B1B-9BF9-4365-8A18-531F80C8847F}
+ {E543772A-4051-499E-8DC6-B5501043DA7B} = {E543772A-4051-499E-8DC6-B5501043DA7B}
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601} = {06E30C65-D79A-4FEC-8A60-B36D907E6601}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cod2map", "components\cod2map\cod2map.vcxproj", "{37CF9A1C-7E15-44FA-BA81-A389E617F2D9}"
ProjectSection(ProjectDependencies) = postProject
{F9BA1B1B-9BF9-4365-8A18-531F80C8847F} = {F9BA1B1B-9BF9-4365-8A18-531F80C8847F}
+ {E543772A-4051-499E-8DC6-B5501043DA7B} = {E543772A-4051-499E-8DC6-B5501043DA7B}
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601} = {06E30C65-D79A-4FEC-8A60-B36D907E6601}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "path_mod", "components\path_mod\path_mod.vcxproj", "{B6D3B040-AC50-4BA9-BC91-CE5F5DAAF877}"
ProjectSection(ProjectDependencies) = postProject
{F9BA1B1B-9BF9-4365-8A18-531F80C8847F} = {F9BA1B1B-9BF9-4365-8A18-531F80C8847F}
+ {E543772A-4051-499E-8DC6-B5501043DA7B} = {E543772A-4051-499E-8DC6-B5501043DA7B}
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601} = {06E30C65-D79A-4FEC-8A60-B36D907E6601}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "D3DBSP_Lib", "components\D3DBSP_Lib\D3DBSP_Lib\D3DBSP_Lib.vcxproj", "{F9BA1B1B-9BF9-4365-8A18-531F80C8847F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "asset_viewer", "components\asset_viewer\asset_viewer.vcxproj", "{9934C743-9987-4D7C-B847-0E77DDA520C0}"
+ ProjectSection(ProjectDependencies) = postProject
+ {E543772A-4051-499E-8DC6-B5501043DA7B} = {E543772A-4051-499E-8DC6-B5501043DA7B}
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601} = {06E30C65-D79A-4FEC-8A60-B36D907E6601}
+ EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "asset_util", "components\asset_util\asset_util.vcxproj", "{0045F885-7DD4-4898-B760-ACB4DA32DA6B}"
ProjectSection(ProjectDependencies) = postProject
+ {F9BA1B1B-9BF9-4365-8A18-531F80C8847F} = {F9BA1B1B-9BF9-4365-8A18-531F80C8847F}
+ {C61A8481-11F8-4A98-9776-C5A4E1F13D98} = {C61A8481-11F8-4A98-9776-C5A4E1F13D98}
{D8721F95-955F-4931-9EBA-5C7BA28E6875} = {D8721F95-955F-4931-9EBA-5C7BA28E6875}
{612D8EF6-74FD-4E5E-A5C9-F4E8BF20195A} = {612D8EF6-74FD-4E5E-A5C9-F4E8BF20195A}
EndProjectSection
@@ -40,58 +65,137 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "components\shared\z
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "miniz", "components\shared\miniz\miniz.vcxproj", "{D8721F95-955F-4931-9EBA-5C7BA28E6875}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "launcher", "components\launcher\launcher.vcxproj", "{99C1298E-362E-455B-B938-04CB5B5510BA}"
+ ProjectSection(ProjectDependencies) = postProject
+ {86C18FD7-1144-440C-B5E3-265194BBC934} = {86C18FD7-1144-440C-B5E3-265194BBC934}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imgui", "components\shared\imgui\imgui.vcxproj", "{86C18FD7-1144-440C-B5E3-265194BBC934}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "detours", "components\shared\detours\detours.vcxproj", "{06E30C65-D79A-4FEC-8A60-B36D907E6601}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gsc_parser", "components\gsc_parser\gsc_parser.vcxproj", "{C61A8481-11F8-4A98-9776-C5A4E1F13D98}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "semver", "components\shared\semver\semver.vcxproj", "{5FB372DD-A7E1-4A4B-9A28-3AC02543E9E8}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lib_json", "components\shared\jsoncpp\makefiles\msvc\lib_json.vcxproj", "{1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E543772A-4051-499E-8DC6-B5501043DA7B}.Debug|Win32.ActiveCfg = Debug|Win32
{E543772A-4051-499E-8DC6-B5501043DA7B}.Debug|Win32.Build.0 = Debug|Win32
+ {E543772A-4051-499E-8DC6-B5501043DA7B}.Debug|x64.ActiveCfg = Debug|Win32
{E543772A-4051-499E-8DC6-B5501043DA7B}.Release|Win32.ActiveCfg = Release|Win32
{E543772A-4051-499E-8DC6-B5501043DA7B}.Release|Win32.Build.0 = Release|Win32
+ {E543772A-4051-499E-8DC6-B5501043DA7B}.Release|x64.ActiveCfg = Release|Win32
{3A1FA19B-CC11-4725-AC86-6B3CF539E1B2}.Debug|Win32.ActiveCfg = Debug|Win32
{3A1FA19B-CC11-4725-AC86-6B3CF539E1B2}.Debug|Win32.Build.0 = Debug|Win32
+ {3A1FA19B-CC11-4725-AC86-6B3CF539E1B2}.Debug|x64.ActiveCfg = Debug|Win32
{3A1FA19B-CC11-4725-AC86-6B3CF539E1B2}.Release|Win32.ActiveCfg = Release|Win32
{3A1FA19B-CC11-4725-AC86-6B3CF539E1B2}.Release|Win32.Build.0 = Release|Win32
+ {3A1FA19B-CC11-4725-AC86-6B3CF539E1B2}.Release|x64.ActiveCfg = Release|Win32
{69B41EFB-E992-4351-AA29-FDC61F4DBDE0}.Debug|Win32.ActiveCfg = Debug|Win32
{69B41EFB-E992-4351-AA29-FDC61F4DBDE0}.Debug|Win32.Build.0 = Debug|Win32
+ {69B41EFB-E992-4351-AA29-FDC61F4DBDE0}.Debug|x64.ActiveCfg = Debug|Win32
{69B41EFB-E992-4351-AA29-FDC61F4DBDE0}.Release|Win32.ActiveCfg = Release|Win32
{69B41EFB-E992-4351-AA29-FDC61F4DBDE0}.Release|Win32.Build.0 = Release|Win32
+ {69B41EFB-E992-4351-AA29-FDC61F4DBDE0}.Release|x64.ActiveCfg = Release|Win32
{6A3FB8CD-BEA3-42F1-B551-A0F56ED9B266}.Debug|Win32.ActiveCfg = Debug|Win32
{6A3FB8CD-BEA3-42F1-B551-A0F56ED9B266}.Debug|Win32.Build.0 = Debug|Win32
+ {6A3FB8CD-BEA3-42F1-B551-A0F56ED9B266}.Debug|x64.ActiveCfg = Debug|Win32
{6A3FB8CD-BEA3-42F1-B551-A0F56ED9B266}.Release|Win32.ActiveCfg = Release|Win32
{6A3FB8CD-BEA3-42F1-B551-A0F56ED9B266}.Release|Win32.Build.0 = Release|Win32
+ {6A3FB8CD-BEA3-42F1-B551-A0F56ED9B266}.Release|x64.ActiveCfg = Release|Win32
{B19C7605-5258-457E-883A-1A7896E7BD6C}.Debug|Win32.ActiveCfg = Debug|Win32
{B19C7605-5258-457E-883A-1A7896E7BD6C}.Debug|Win32.Build.0 = Debug|Win32
+ {B19C7605-5258-457E-883A-1A7896E7BD6C}.Debug|x64.ActiveCfg = Debug|Win32
{B19C7605-5258-457E-883A-1A7896E7BD6C}.Release|Win32.ActiveCfg = Release|Win32
{B19C7605-5258-457E-883A-1A7896E7BD6C}.Release|Win32.Build.0 = Release|Win32
+ {B19C7605-5258-457E-883A-1A7896E7BD6C}.Release|x64.ActiveCfg = Release|Win32
{37CF9A1C-7E15-44FA-BA81-A389E617F2D9}.Debug|Win32.ActiveCfg = Debug|Win32
{37CF9A1C-7E15-44FA-BA81-A389E617F2D9}.Debug|Win32.Build.0 = Debug|Win32
+ {37CF9A1C-7E15-44FA-BA81-A389E617F2D9}.Debug|x64.ActiveCfg = Debug|Win32
{37CF9A1C-7E15-44FA-BA81-A389E617F2D9}.Release|Win32.ActiveCfg = Release|Win32
{37CF9A1C-7E15-44FA-BA81-A389E617F2D9}.Release|Win32.Build.0 = Release|Win32
+ {37CF9A1C-7E15-44FA-BA81-A389E617F2D9}.Release|x64.ActiveCfg = Release|Win32
{B6D3B040-AC50-4BA9-BC91-CE5F5DAAF877}.Debug|Win32.ActiveCfg = Debug|Win32
+ {B6D3B040-AC50-4BA9-BC91-CE5F5DAAF877}.Debug|x64.ActiveCfg = Debug|Win32
{B6D3B040-AC50-4BA9-BC91-CE5F5DAAF877}.Release|Win32.ActiveCfg = Release|Win32
+ {B6D3B040-AC50-4BA9-BC91-CE5F5DAAF877}.Release|x64.ActiveCfg = Release|Win32
{F9BA1B1B-9BF9-4365-8A18-531F80C8847F}.Debug|Win32.ActiveCfg = Debug|Win32
{F9BA1B1B-9BF9-4365-8A18-531F80C8847F}.Debug|Win32.Build.0 = Debug|Win32
+ {F9BA1B1B-9BF9-4365-8A18-531F80C8847F}.Debug|x64.ActiveCfg = Debug|Win32
{F9BA1B1B-9BF9-4365-8A18-531F80C8847F}.Release|Win32.ActiveCfg = Release|Win32
{F9BA1B1B-9BF9-4365-8A18-531F80C8847F}.Release|Win32.Build.0 = Release|Win32
+ {F9BA1B1B-9BF9-4365-8A18-531F80C8847F}.Release|x64.ActiveCfg = Release|Win32
{9934C743-9987-4D7C-B847-0E77DDA520C0}.Debug|Win32.ActiveCfg = Debug|Win32
{9934C743-9987-4D7C-B847-0E77DDA520C0}.Debug|Win32.Build.0 = Debug|Win32
+ {9934C743-9987-4D7C-B847-0E77DDA520C0}.Debug|x64.ActiveCfg = Debug|Win32
{9934C743-9987-4D7C-B847-0E77DDA520C0}.Release|Win32.ActiveCfg = Release|Win32
{9934C743-9987-4D7C-B847-0E77DDA520C0}.Release|Win32.Build.0 = Release|Win32
+ {9934C743-9987-4D7C-B847-0E77DDA520C0}.Release|x64.ActiveCfg = Release|Win32
{0045F885-7DD4-4898-B760-ACB4DA32DA6B}.Debug|Win32.ActiveCfg = Debug|Win32
{0045F885-7DD4-4898-B760-ACB4DA32DA6B}.Debug|Win32.Build.0 = Debug|Win32
+ {0045F885-7DD4-4898-B760-ACB4DA32DA6B}.Debug|x64.ActiveCfg = Debug|Win32
{0045F885-7DD4-4898-B760-ACB4DA32DA6B}.Release|Win32.ActiveCfg = Release|Win32
{0045F885-7DD4-4898-B760-ACB4DA32DA6B}.Release|Win32.Build.0 = Release|Win32
+ {0045F885-7DD4-4898-B760-ACB4DA32DA6B}.Release|x64.ActiveCfg = Release|Win32
{612D8EF6-74FD-4E5E-A5C9-F4E8BF20195A}.Debug|Win32.ActiveCfg = Debug|Win32
{612D8EF6-74FD-4E5E-A5C9-F4E8BF20195A}.Debug|Win32.Build.0 = Debug|Win32
+ {612D8EF6-74FD-4E5E-A5C9-F4E8BF20195A}.Debug|x64.ActiveCfg = Debug|Win32
{612D8EF6-74FD-4E5E-A5C9-F4E8BF20195A}.Release|Win32.ActiveCfg = Release|Win32
{612D8EF6-74FD-4E5E-A5C9-F4E8BF20195A}.Release|Win32.Build.0 = Release|Win32
+ {612D8EF6-74FD-4E5E-A5C9-F4E8BF20195A}.Release|x64.ActiveCfg = Release|Win32
{D8721F95-955F-4931-9EBA-5C7BA28E6875}.Debug|Win32.ActiveCfg = Debug|Win32
{D8721F95-955F-4931-9EBA-5C7BA28E6875}.Debug|Win32.Build.0 = Debug|Win32
+ {D8721F95-955F-4931-9EBA-5C7BA28E6875}.Debug|x64.ActiveCfg = Debug|Win32
{D8721F95-955F-4931-9EBA-5C7BA28E6875}.Release|Win32.ActiveCfg = Release|Win32
{D8721F95-955F-4931-9EBA-5C7BA28E6875}.Release|Win32.Build.0 = Release|Win32
+ {D8721F95-955F-4931-9EBA-5C7BA28E6875}.Release|x64.ActiveCfg = Release|Win32
+ {99C1298E-362E-455B-B938-04CB5B5510BA}.Debug|Win32.ActiveCfg = Debug|Win32
+ {99C1298E-362E-455B-B938-04CB5B5510BA}.Debug|Win32.Build.0 = Debug|Win32
+ {99C1298E-362E-455B-B938-04CB5B5510BA}.Debug|x64.ActiveCfg = Debug|Win32
+ {99C1298E-362E-455B-B938-04CB5B5510BA}.Release|Win32.ActiveCfg = Release|Win32
+ {99C1298E-362E-455B-B938-04CB5B5510BA}.Release|Win32.Build.0 = Release|Win32
+ {99C1298E-362E-455B-B938-04CB5B5510BA}.Release|x64.ActiveCfg = Release|Win32
+ {86C18FD7-1144-440C-B5E3-265194BBC934}.Debug|Win32.ActiveCfg = Debug|Win32
+ {86C18FD7-1144-440C-B5E3-265194BBC934}.Debug|Win32.Build.0 = Debug|Win32
+ {86C18FD7-1144-440C-B5E3-265194BBC934}.Debug|x64.ActiveCfg = Debug|Win32
+ {86C18FD7-1144-440C-B5E3-265194BBC934}.Release|Win32.ActiveCfg = Release|Win32
+ {86C18FD7-1144-440C-B5E3-265194BBC934}.Release|Win32.Build.0 = Release|Win32
+ {86C18FD7-1144-440C-B5E3-265194BBC934}.Release|x64.ActiveCfg = Release|Win32
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601}.Debug|Win32.ActiveCfg = Debug|Win32
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601}.Debug|Win32.Build.0 = Debug|Win32
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601}.Debug|x64.ActiveCfg = Debug|Win32
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601}.Release|Win32.ActiveCfg = Release|Win32
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601}.Release|Win32.Build.0 = Release|Win32
+ {06E30C65-D79A-4FEC-8A60-B36D907E6601}.Release|x64.ActiveCfg = Release|Win32
+ {C61A8481-11F8-4A98-9776-C5A4E1F13D98}.Debug|Win32.ActiveCfg = Debug|Win32
+ {C61A8481-11F8-4A98-9776-C5A4E1F13D98}.Debug|Win32.Build.0 = Debug|Win32
+ {C61A8481-11F8-4A98-9776-C5A4E1F13D98}.Debug|x64.ActiveCfg = Debug|Win32
+ {C61A8481-11F8-4A98-9776-C5A4E1F13D98}.Release|Win32.ActiveCfg = Release|Win32
+ {C61A8481-11F8-4A98-9776-C5A4E1F13D98}.Release|Win32.Build.0 = Release|Win32
+ {C61A8481-11F8-4A98-9776-C5A4E1F13D98}.Release|x64.ActiveCfg = Release|Win32
+ {5FB372DD-A7E1-4A4B-9A28-3AC02543E9E8}.Debug|Win32.ActiveCfg = Debug|Win32
+ {5FB372DD-A7E1-4A4B-9A28-3AC02543E9E8}.Debug|Win32.Build.0 = Debug|Win32
+ {5FB372DD-A7E1-4A4B-9A28-3AC02543E9E8}.Debug|x64.ActiveCfg = Debug|Win32
+ {5FB372DD-A7E1-4A4B-9A28-3AC02543E9E8}.Release|Win32.ActiveCfg = Release|Win32
+ {5FB372DD-A7E1-4A4B-9A28-3AC02543E9E8}.Release|Win32.Build.0 = Release|Win32
+ {5FB372DD-A7E1-4A4B-9A28-3AC02543E9E8}.Release|x64.ActiveCfg = Release|Win32
+ {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Debug|Win32.ActiveCfg = Debug|Win32
+ {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Debug|Win32.Build.0 = Debug|Win32
+ {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Debug|x64.ActiveCfg = Debug|x64
+ {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Debug|x64.Build.0 = Debug|x64
+ {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Release|Win32.ActiveCfg = Release|Win32
+ {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Release|Win32.Build.0 = Release|Win32
+ {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Release|x64.ActiveCfg = Release|x64
+ {1E6C2C1C-6453-4129-AE3F-0EE8E6599C89}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/README.md b/README.md
index 34436f40..69e4d4ea 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,6 @@
# LinkerMod
###### Enhancements for Call of Duty: Black Ops' Mod Tools
+#### Join the [Discord Server](https://discord.gg/nukNNsP)!
## Features
- Mod Support
diff --git a/build.bat b/build.bat
new file mode 100644
index 00000000..9404fb6b
--- /dev/null
+++ b/build.bat
@@ -0,0 +1,69 @@
+@echo off
+
+rem
+rem ------- Find Visual Studio's install path -------
+rem
+:find2015
+if "%VS140COMNTOOLS%"=="" (
+ goto find2013
+) else (
+ echo Found: Visual Studio 2015
+ set TMP_VSPATH="%VS140COMNTOOLS%\..\..\vc\vcvarsall.bat"
+ goto compile
+)
+
+:find2013
+if "%VS120COMNTOOLS%"=="" (
+ goto find2012
+) else (
+ echo Found: Visual Studio 2013
+ set TMP_VSPATH="%VS120COMNTOOLS%\..\..\vc\vcvarsall.bat"
+ goto compile
+)
+
+:find2012
+if "%VS110COMNTOOLS%"=="" (
+ goto notfound
+) else (
+ echo Found: Visual Studio 2012
+ set TMP_VSPATH="%VS110COMNTOOLS%\..\..\vc\vcvarsall.bat"
+ goto compile
+)
+
+:notfound
+echo Visual Studio (2012/2013/2015) wasn't found on your computer.
+exit 1
+
+rem
+rem ------ Compile -------
+rem
+:compile
+call %TMP_VSPATH% amd64_x86
+set type=Configuration=Release;Platform=Win32
+
+msbuild.exe LinkerMod.sln /maxcpucount /verbosity:minimal /t:Rebuild /p:%type%
+
+rem
+rem ------ Copy Files -------
+rem
+:docopy
+setlocal ENABLEEXTENSIONS
+set KEY_NAME=HKLM\SOFTWARE\activision\call of duty black ops
+set VALUE_NAME=InstallPath
+
+FOR /F "tokens=2*" %%A IN ('REG.exe query "%KEY_NAME%" /v "%VALUE_NAME%" /reg:32 ') DO (set pInstallDir=%%B)
+
+if NOT "%pInstallDir%" == "" (
+ copy .\build\Release\asset_viewer.dll "%pInstallDir%"\bin\asset_viewer.dll
+ copy .\build\Release\cod2map.dll "%pInstallDir%"\bin\cod2map.dll
+ copy .\build\Release\cod2rad.dll "%pInstallDir%"\bin\cod2rad.dll
+ copy .\build\Release\game_mod.dll "%pInstallDir%"\bin\game_mod.dll
+ copy .\build\Release\linker_pc.dll "%pInstallDir%"\bin\linker_pc.dll
+ copy .\build\Release\path_mod.dll "%pInstallDir%"\bin\path_mod.dll
+ copy .\build\Release\radiant_mod.dll "%pInstallDir%"\bin\radiant_mod.dll
+ copy .\build\Release\launcher_ldr.exe "%pInstallDir%"\bin\launcher_ldr.exe
+ exit 0
+) else (
+ echo InstallPath registry key wasn't found.
+ exit 1
+)
\ No newline at end of file
diff --git a/components/D3DBSP_Lib/D3DBSP_Lib/ConsoleLog.cpp b/components/D3DBSP_Lib/D3DBSP_Lib/ConsoleLog.cpp
index daa1a319..4c3953da 100644
--- a/components/D3DBSP_Lib/D3DBSP_Lib/ConsoleLog.cpp
+++ b/components/D3DBSP_Lib/D3DBSP_Lib/ConsoleLog.cpp
@@ -17,118 +17,120 @@ WORD GetConsoleTextAttribute (HANDLE hCon)
return con_info.wAttributes;
}
-
-int Con_Init()
+namespace D3DBSP_Lib
{
- if(!hConsole)
+ int Con_Init()
{
- hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
- defaultConsoleAttributes = GetConsoleTextAttribute(hConsole);
-
- hLogfile = nullptr;
+ if(!hConsole)
+ {
+ hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
+ defaultConsoleAttributes = GetConsoleTextAttribute(hConsole);
+
+ hLogfile = nullptr;
+ }
+
+ return 0;
}
-
- return 0;
-}
-
-int Con_Init(const char* logfilePath, LOGFILE_MODE mode)
-{
- if(!hConsole)
+
+ int Con_Init(const char* logfilePath, LOGFILE_MODE mode)
{
- hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
- defaultConsoleAttributes = GetConsoleTextAttribute(hConsole);
+ if(!hConsole)
+ {
+ hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
+ defaultConsoleAttributes = GetConsoleTextAttribute(hConsole);
+ }
+
+ return Log_Init(logfilePath, mode);
}
-
- return Log_Init(logfilePath, mode);
-}
-
-int Log_Init(const char* logfilePath, LOGFILE_MODE mode)
-{
- if(!hLogfile)
+
+ int Log_Init(const char* logfilePath, LOGFILE_MODE mode)
{
- int result = fopen_s(&hLogfile,logfilePath,FILEMODE_STRINGS[mode]);
- Log_Printf("Logfile Initialized!\n");
- return result;
+ if(!hLogfile)
+ {
+ int result = fopen_s(&hLogfile,logfilePath,FILEMODE_STRINGS[mode]);
+ Log_Printf("Logfile Initialized!\n");
+ return result;
+ }
+
+ return 0;
+ }
+
+ int Con_Printf(const char* fmt, ...)
+ {
+ SetConsoleTextAttribute(hConsole, defaultConsoleAttributes);
+ va_list ap;
+ va_start(ap, fmt);
+ int out = 1;
+ if(hConsole)
+ out = vprintf(fmt, ap);
+ if(hLogfile)
+ vfprintf(hLogfile,fmt,ap);
+ va_end(ap);
+ return out;
+ }
+
+ int Con_Warning(const char* fmt, ...)
+ {
+ SetConsoleTextAttribute(hConsole, 0xE);
+ printf("WARNING: ");
+ va_list ap;
+ va_start(ap, fmt);
+ int out = 1;
+ if(hConsole)
+ out = vprintf(fmt, ap);
+ if(hLogfile)
+ vfprintf(hLogfile,fmt,ap);
+ va_end(ap);
+ SetConsoleTextAttribute(hConsole, defaultConsoleAttributes);
+ dwWarningCount++;
+ return out;
+ }
+
+ int Con_Error(const char* fmt, ...)
+ {
+ SetConsoleTextAttribute(hConsole, 0xC);
+ printf("ERROR: ");
+ va_list ap;
+ va_start(ap, fmt);
+ int out = 1;
+ if(hConsole)
+ out = vprintf(fmt, ap);
+ if(hLogfile)
+ vfprintf(hLogfile,fmt,ap);
+ va_end(ap);
+ SetConsoleTextAttribute(hConsole, defaultConsoleAttributes);
+ dwErrorCount++;
+ return out;
+ }
+
+ int Log_Printf(const char* fmt, ...)
+ {
+ int out = 1;
+ va_list ap;
+ va_start(ap, fmt);
+ if(hLogfile)
+ out = vfprintf(hLogfile,fmt,ap);
+ va_end(ap);
+ return out;
+ }
+
+ DWORD Con_ErrorCount(void)
+ {
+ return dwErrorCount;
+ }
+
+ DWORD Con_WarningCount(void)
+ {
+ return dwWarningCount;
+ }
+
+ int Con_Restore(void)
+ {
+ if(hLogfile)
+ fclose(hLogfile);
+ if(hConsole)
+ return SetConsoleTextAttribute(hConsole, defaultConsoleAttributes);
+
+ return 0;
}
-
- return 0;
-}
-
-int Con_Printf(const char* fmt, ...)
-{
- SetConsoleTextAttribute(hConsole, defaultConsoleAttributes);
- va_list ap;
- va_start(ap, fmt);
- int out = 1;
- if(hConsole)
- out = vprintf(fmt, ap);
- if(hLogfile)
- vfprintf(hLogfile,fmt,ap);
- va_end(ap);
- return out;
-}
-
-int Con_Warning(const char* fmt, ...)
-{
- SetConsoleTextAttribute(hConsole, 0xE);
- printf("WARNING: ");
- va_list ap;
- va_start(ap, fmt);
- int out = 1;
- if(hConsole)
- out = vprintf(fmt, ap);
- if(hLogfile)
- vfprintf(hLogfile,fmt,ap);
- va_end(ap);
- SetConsoleTextAttribute(hConsole, defaultConsoleAttributes);
- dwWarningCount++;
- return out;
-}
-
-int Con_Error(const char* fmt, ...)
-{
- SetConsoleTextAttribute(hConsole, 0xC);
- printf("ERROR: ");
- va_list ap;
- va_start(ap, fmt);
- int out = 1;
- if(hConsole)
- out = vprintf(fmt, ap);
- if(hLogfile)
- vfprintf(hLogfile,fmt,ap);
- va_end(ap);
- SetConsoleTextAttribute(hConsole, defaultConsoleAttributes);
- dwErrorCount++;
- return out;
-}
-
-int Log_Printf(const char* fmt, ...)
-{
- int out = 1;
- va_list ap;
- va_start(ap, fmt);
- if(hLogfile)
- out = vfprintf(hLogfile,fmt,ap);
- va_end(ap);
- return out;
-}
-
-DWORD Con_ErrorCount(void)
-{
- return dwErrorCount;
-}
-
-DWORD Con_WarningCount(void)
-{
- return dwWarningCount;
}
-
-int Con_Restore(void)
-{
- if(hLogfile)
- fclose(hLogfile);
- if(hConsole)
- return SetConsoleTextAttribute(hConsole, defaultConsoleAttributes);
-
- return 0;
-}
\ No newline at end of file
diff --git a/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP.cpp b/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP.cpp
index 10f6834c..174fdd56 100644
--- a/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP.cpp
+++ b/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP.cpp
@@ -28,8 +28,8 @@ int D3DBSP::Load(const char* filepath)
struct stat* results = new struct stat;
if (stat(filepath, results) != 0)
{
- Con_Printf("\n");
- Con_Error("File not found\n");
+ D3DBSP_Lib::Con_Printf("\n");
+ D3DBSP_Lib::Con_Error("File not found\n");
delete results;
return 1;// ERR_FILE_NOT_FOUND;
}
@@ -85,7 +85,7 @@ int D3DBSP::Load(BYTE* pBuf)
memcpy(&magicValue,pBuf,sizeof(DWORD));
if(magicValue != 'PSBI')
{
- Con_Error("Buffer does not contain D3DBSP data\n");
+ D3DBSP_Lib::Con_Error("Buffer does not contain D3DBSP data\n");
return 2; //ERR_NOT_A_D3DBSP_FILE
}
@@ -159,8 +159,8 @@ int D3DBSP::Write(const char* filepath)
if(!pLump->isEmpty)
{
#if _DEBUG
- Log_Printf("Writing Lump [0x%X] %s\n", this->diskLumpOrder[i], LUMP_NAMES[this->diskLumpOrder[i]]);
- Log_Printf(" Start: 0x%X (Size: 0x%X)\n", (DWORD)ofile.tellp(), pLump->size);
+ D3DBSP_Lib::Log_Printf("Writing Lump [0x%X] %s\n", this->diskLumpOrder[i], LUMP_NAMES[this->diskLumpOrder[i]]);
+ D3DBSP_Lib::Log_Printf(" Start: 0x%X (Size: 0x%X)\n", (DWORD)ofile.tellp(), pLump->size);
#endif
ofile.write((char*)pLump->content, pLump->size);
if(padding_size(pLump->size))
@@ -169,7 +169,7 @@ int D3DBSP::Write(const char* filepath)
ofile.write((char*)&pad, padding_size(pLump->size));
}
#if _DEBUG
- Log_Printf(" End: 0x%X\n", (DWORD)ofile.tellp());
+ D3DBSP_Lib::Log_Printf(" End: 0x%X\n", (DWORD)ofile.tellp());
#endif
}
}
diff --git a/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.h b/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.h
index b81c2f54..bb4ee2e1 100644
--- a/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.h
+++ b/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.h
@@ -11,16 +11,20 @@ enum LOGFILE_MODE
LOGFILE_UPDATE_APPEND
};
-int Con_Init(void);
-int Con_Init(const char* logfilePath, LOGFILE_MODE mode);
-int Log_Init(const char* logfilePath, LOGFILE_MODE mode);
-int Con_Printf(const char* fmt, ...);
-int Con_Warning(const char* fmt, ...);
-int Con_Error(const char* fmt, ...);
-int Log_Printf(const char* fmt, ...);
-DWORD Con_ErrorCount(void);
-DWORD Con_WarningCount(void);
-int Con_Restore(void);
+namespace D3DBSP_Lib
+{
+
+ int Con_Init(void);
+ int Con_Init(const char* logfilePath, LOGFILE_MODE mode);
+ int Log_Init(const char* logfilePath, LOGFILE_MODE mode);
+ int Con_Printf(const char* fmt, ...);
+ int Con_Warning(const char* fmt, ...);
+ int Con_Error(const char* fmt, ...);
+ int Log_Printf(const char* fmt, ...);
+ DWORD Con_ErrorCount(void);
+ DWORD Con_WarningCount(void);
+ int Con_Restore(void);
+}
class Lump
{
diff --git a/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.vcxproj b/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.vcxproj
index 5e216643..452d16e9 100644
--- a/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.vcxproj
+++ b/components/D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.vcxproj
@@ -80,6 +80,7 @@
Disabled
WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)
false
+ false
Windows
@@ -95,6 +96,7 @@
true
true
WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)
+ true
Windows
diff --git a/components/asset_util/arg.cpp b/components/asset_util/arg.cpp
index 040e4817..f7039374 100644
--- a/components/asset_util/arg.cpp
+++ b/components/asset_util/arg.cpp
@@ -5,7 +5,8 @@
#include "cmd.h"
#include "common/llist.h"
#include "platform.h"
-#include "common\io.h"
+#include "common/io.h"
+#include "shared_assert.h"
Argument* g_shortcut[255] = { NULL };
diff --git a/components/asset_util/asset_util.vcxproj b/components/asset_util/asset_util.vcxproj
index 29074159..478a2ea4 100644
--- a/components/asset_util/asset_util.vcxproj
+++ b/components/asset_util/asset_util.vcxproj
@@ -13,9 +13,17 @@
+
+
+
+
+
+
+
+
@@ -37,6 +45,14 @@
+
+
+
+
+
+
+
+
@@ -66,15 +82,15 @@
Application
true
- v120_xp
Unicode
+ v120_xp
Application
false
- v120_xp
true
Unicode
+ v120_xp
@@ -101,6 +117,7 @@
Level3
Disabled
WIN32;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS;
+ false
Console
@@ -121,6 +138,7 @@
true
true
WIN32;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS;
+ true
Console
diff --git a/components/asset_util/asset_util.vcxproj.filters b/components/asset_util/asset_util.vcxproj.filters
index 2cedabe4..6cfaa9cd 100644
--- a/components/asset_util/asset_util.vcxproj.filters
+++ b/components/asset_util/asset_util.vcxproj.filters
@@ -54,6 +54,30 @@
cmds
+
+ cmds
+
+
+ cmds
+
+
+ cmds
+
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds
+
@@ -106,6 +130,30 @@
common
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds\ripper
+
+
+ cmds\search
+
@@ -123,5 +171,11 @@
{f2eaf143-9cec-4b52-b528-477c8590ac00}
+
+ {0b5340fe-2268-4426-9c05-d7a26685c469}
+
+
+ {83e72f22-768f-431b-bdf5-d614ad8a8b54}
+
\ No newline at end of file
diff --git a/components/asset_util/cmd.cpp b/components/asset_util/cmd.cpp
index 3c10fcac..6629ecad 100644
--- a/components/asset_util/cmd.cpp
+++ b/components/asset_util/cmd.cpp
@@ -15,9 +15,16 @@ Command* Command::g_cmds = NULL;
REGISTER_GLOBAL_COMMAND(g_cmd_test, "test", "An empty test command", Cmd_Test_f, CMD_CVARS(&g_var));
#endif
REGISTER_GLOBAL_COMMAND(g_cmd_help, "help", "Print usage information", Cmd_Help_f, CMD_GLOBALCVARS);
-REGISTER_GLOBAL_COMMAND(g_cmd_ents, "ents", "Extract the entity string from a fastfile", Cmd_Ents_f, CMD_GLOBALCVARS);
-REGISTER_GLOBAL_COMMAND(g_cmd_extract_ff, "extract-ff", "Extract assets from *.ff files", Cmd_Extract_FF_f, CMD_CVARS(&g_extractAll, &g_extractSounds,&g_useLocalized));
-REGISTER_GLOBAL_COMMAND(g_cmd_extract_iwd, "extract-iwd", "Extract assets from *.iwd files", Cmd_Extract_IWD_f, CMD_CVARS(&g_extractAll, &g_extractImages, &g_extractSounds));
+
+REGISTER_GLOBAL_COMMAND(g_cmd_extract_ff, "extract-ff", "Extract assets from *.ff files", Cmd_Extract_FF_f, CMD_CVARS(&g_extractAll, &g_extractSounds,&g_useLocalized, &fs_outdir));
+REGISTER_GLOBAL_COMMAND(g_cmd_extract_iwd, "extract-iwd", "Extract assets from *.iwd files", Cmd_Extract_IWD_f, CMD_CVARS(&g_extractAll, &g_extractImages, &g_extractSounds, &fs_outdir));
+REGISTER_GLOBAL_COMMAND(g_cmd_search, "search", "Search IWD's and / or FastFiles for a pattern", Cmd_Search_f, CMD_CVARS(&g_useLocalized));
+REGISTER_GLOBAL_COMMAND(g_cmd_rip, "rip", "Rip assets from the running game process", Cmd_Rip_f, CMD_CVARS(&fs_outdir, &rip_waitForProcess, &rip_waitForMap, &rip_killProcess));
+
+REGISTER_GLOBAL_COMMAND(g_cmd_ents, "ents", "Extract the entity string from a fastfile", Cmd_Ents_f, CMD_CVARS(&ents_useLabels, &ents_genBrushes));
+REGISTER_GLOBAL_COMMAND(g_cmd_csvgen, "csvgen", "Experimental CSV regeneration for some assets", Cmd_CSVGen_f, CMD_CVARS(&csvgen_aitypes, &csvgen_characters, &csvgen_xmodelaliases));
+
+REGISTER_GLOBAL_COMMAND(g_cmd_bsp_info, "bsp_info", "Print lump offsets and sizes for a given bsp", Cmd_BspInfo_f, CMD_GLOBALCVARS);
#undef CMD_GLOBALCVARS
#undef CMD_CVARS
diff --git a/components/asset_util/cmd.h b/components/asset_util/cmd.h
index ee43250d..336e18e1 100644
--- a/components/asset_util/cmd.h
+++ b/components/asset_util/cmd.h
@@ -31,8 +31,15 @@ class Command : public Argument, public LList
#define REGISTER_GLOBAL_COMMAND(IDENTIFIER) extern Command IDENTIFIER;
REGISTER_GLOBAL_COMMAND(g_cmd_help);
-REGISTER_GLOBAL_COMMAND(g_cmd_ents);
+
REGISTER_GLOBAL_COMMAND(g_cmd_extract_ff);
REGISTER_GLOBAL_COMMAND(g_cmd_extract_iwd);
+REGISTER_GLOBAL_COMMAND(g_cmd_search);
+REGISTER_GLOBAL_COMMAND(g_cmd_rip);
+
+REGISTER_GLOBAL_COMMAND(g_cmd_ents);
+REGISTER_GLOBAL_COMMAND(g_cmd_csvgen);
+
+REGISTER_GLOBAL_COMMAND(g_cmd_bsp_info);
#undef REGISTER_GLOBAL_COMMAND
diff --git a/components/asset_util/cmds/cmd_bsp.cpp b/components/asset_util/cmds/cmd_bsp.cpp
new file mode 100644
index 00000000..fab9ac9d
--- /dev/null
+++ b/components/asset_util/cmds/cmd_bsp.cpp
@@ -0,0 +1,129 @@
+#include "../sys/AppInfo.h"
+#include "../common/io.h"
+#include "../common/fs.h"
+
+#include "../cvar.h"
+#include "cmd_common.h"
+
+#include "../D3DBSP_Lib/D3DBSP_Lib/D3DBSP_Lib.h"
+#pragma comment(lib, "D3DBSP_Lib.lib")
+
+static const char *LumpNames[] =
+{
+ "LUMP_MATERIALS",
+ "LUMP_LIGHTBYTES",
+ "LUMP_LIGHTGRIDENTRIES",
+ "LUMP_LIGHTGRIDCOLORS",
+ "LUMP_PLANES",
+ "LUMP_BRUSHSIDES",
+ "LUMP_BRUSHSIDEEDGECOUNTS",
+ "LUMP_BRUSHEDGES",
+ "LUMP_BRUSHES",
+ "LUMP_TRIANGLES",
+ "LUMP_DRAWVERTS",
+ "LUMP_DRAWINDICES",
+ "LUMP_CULLGROUPS",
+ "LUMP_CULLGROUPINDICES",
+ "LUMP_OBSOLETE_1",
+ "LUMP_OBSOLETE_2",
+ "LUMP_OBSOLETE_3",
+ "LUMP_OBSOLETE_4",
+ "LUMP_OBSOLETE_5",
+ "LUMP_PORTALVERTS",
+ "LUMP_OBSOLETE_6",
+ "LUMP_UINDS",
+ "LUMP_BRUSHVERTSCOUNTS",
+ "LUMP_BRUSHVERTS",
+ "LUMP_AABBTREES",
+ "LUMP_CELLS",
+ "LUMP_PORTALS",
+ "LUMP_NODES",
+ "LUMP_LEAFS",
+ "LUMP_LEAFBRUSHES",
+ "LUMP_LEAFSURFACES",
+ "LUMP_COLLISIONVERTS",
+ "LUMP_COLLISIONTRIS",
+ "LUMP_COLLISIONEDGEWALKABLE",
+ "LUMP_COLLISIONBORDERS",
+ "LUMP_COLLISIONPARTITIONS",
+ "LUMP_COLLISIONAABBS",
+ "LUMP_MODELS",
+ "LUMP_VISIBILITY",
+ "LUMP_ENTITIES",
+ "LUMP_PATHCONNECTIONS",
+ "LUMP_REFLECTION_PROBES",
+ "LUMP_VERTEX_LAYER_DATA",
+ "LUMP_PRIMARY_LIGHTS",
+ "LUMP_LIGHTGRIDHEADER",
+ "LUMP_LIGHTGRIDROWS",
+ "LUMP_OBSOLETE_10",
+ "LUMP_UNLAYERED_TRIANGLES",
+ "LUMP_UNLAYERED_DRAWVERTS",
+ "LUMP_UNLAYERED_DRAWINDICES",
+ "LUMP_UNLAYERED_CULLGROUPS",
+ "LUMP_UNLAYERED_AABBTREES",
+ "LUMP_WATERHEADER",
+ "LUMP_WATERCELLS",
+ "LUMP_WATERCELLDATA",
+ "LUMP_BURNABLEHEADER",
+ "LUMP_BURNABLECELLS",
+ "LUMP_BURNABLECELLDATA",
+ "LUMP_SIMPLELIGHTMAPBYTES",
+ "LUMP_LODCHAINS",
+ "LUMP_LODINFOS",
+ "LUMP_LODSURFACES",
+ "LUMP_LIGHTREGIONS",
+ "LUMP_LIGHTREGION_HULLS",
+ "LUMP_LIGHTREGION_AXES",
+ "LUMP_WIILIGHTGRID",
+ "LUMP_LIGHTGRID2D_LIGHTS",
+ "LUMP_LIGHTGRID2D_INDICES",
+ "LUMP_LIGHTGRID2D_POINTS",
+ "LUMP_LIGHTGRID2D_CELLS",
+ "LUMP_LIGHT_CORONAS",
+ "LUMP_SHADOWMAP_VOLUMES",
+ "LUMP_SHADOWMAP_VOLUME_PLANES",
+ "LUMP_EXPOSURE_VOLUMES",
+ "LUMP_EXPOSURE_VOLUME_PLANES",
+ "LUMP_OCCLUDERS",
+ "LUMP_OUTDOORBOUNDS",
+ "LUMP_HERO_ONLY_LIGHTS"
+};
+
+extern unsigned int padded(unsigned int i);
+
+int Cmd_BspInfo_f(int argc, char** argv)
+{
+ for (int i = 1; i < argc; i++)
+ {
+ char* filepath = argv[i];
+
+ D3DBSP iBSP;
+ if (iBSP.Load(argv[i]) != 0)
+ continue;
+
+ if (argc > 2)
+ Con_Print("Info for: '%s'\n\n", FS_GetFilenameSubString(argv[i]));
+
+ Con_Print("%-5s %-28s %-10s %s\n\n", "LUMP", "NAME", "OFFSET", "SIZE");
+
+ unsigned int lumpOffset = sizeof(DWORD) * 3 + 8 * iBSP.diskLumpCount;
+ for (unsigned int j = 0; j < iBSP.diskLumpCount; j++)
+ {
+ LUMP_TYPE type = iBSP.diskLumpOrder[j];
+ unsigned int size = iBSP.lumps[type].size;
+
+ Con_Print("[%2d]: %-28s 0x%08X ( 0x%X bytes )\n", j, LumpNames[j], lumpOffset, size);
+
+ lumpOffset += padded(size);
+ }
+ }
+
+ if (argc < 2)
+ {
+ char* _argv[] = { NULL, "bsp_info" };
+ Cmd_Help_f(ARRAYSIZE(_argv), _argv);
+ return -1;
+ }
+ return 0;
+}
diff --git a/components/asset_util/cmds/cmd_common.h b/components/asset_util/cmds/cmd_common.h
index c14e37b4..f0239ff5 100644
--- a/components/asset_util/cmds/cmd_common.h
+++ b/components/asset_util/cmds/cmd_common.h
@@ -1,12 +1,18 @@
#pragma once
#include "../common/io.h"
+#include "../cvar.h"
int Cmd_Help_f(int argc, char** argv);
int Cmd_Ents_f(int argc, char** argv);
int Cmd_Extract_FF_f(int argc, char** argv);
int Cmd_Extract_IWD_f(int argc, char** argv);
+int Cmd_Search_f(int argc, char** argv);
+int Cmd_Rip_f(int argc, char** argv);
+
+int Cmd_BspInfo_f(int argc, char** argv);
+int Cmd_CSVGen_f(int argc, char** argv);
//
// An empty test command used for debugging
diff --git a/components/asset_util/cmds/cmd_csvgen.cpp b/components/asset_util/cmds/cmd_csvgen.cpp
new file mode 100644
index 00000000..9762d2b0
--- /dev/null
+++ b/components/asset_util/cmds/cmd_csvgen.cpp
@@ -0,0 +1,694 @@
+#include "../sys/AppInfo.h"
+#include "../common/io.h"
+#include "../common/fs.h"
+
+#include "../cvar.h"
+#include "cmd_common.h"
+
+#include "../gsc_parser/gsc_parser.h"
+#pragma comment(lib, "gsc_parser.lib")
+
+#include
+#include
+
+//
+// Enumerate over the children of a symbol, cancels if the callback returns false
+//
+void Symbol_EnumChildren(Symbol* symbol, std::function callback)
+{
+ for (Symbol* c = symbol->Children(); c; c = c->NextElem())
+ {
+ if (!callback(c))
+ {
+ return;
+ }
+ }
+}
+
+//
+// Get the main function from the global AST group
+//
+Function* AST_FindFunction(Symbol* AST, const char* name)
+{
+ for (Symbol* c = AST->Children(); c; c = c->NextElem())
+ {
+ if (c->Type() == S_TYPE_FUNCTION_DECL)
+ {
+ Function* func = (Function*)c;
+ if (strcmp(func->identifier->value, name) == 0)
+ {
+ return func;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+const char* XMA_Expression_ExtractModelName(Expression* expr)
+{
+ if (expr->Operator() != OP_TYPE_ASSIGN)
+ return NULL;
+
+ Group* op1_group = (Group*)expr->Children()->HeadNode()->Owner();
+ Group* op2_group = (Group*)expr->Children()->HeadNode()->PrevNode()->Owner();
+ if (op1_group->Type() != S_TYPE_GROUP || op2_group->Type() != S_TYPE_GROUP)
+ return NULL;
+
+ Literal* op2_val = (Literal*)op2_group->Children()->HeadNode()->Owner();
+ if (op2_val->Type() != S_TYPE_LITERAL_STRING)
+ return NULL;
+
+ //
+ // These checks aren't really necessary as all xmodelaliases scripts follow the same pattern
+ //
+ Member* array_expr = (Member*)op1_group->Children()->HeadNode()->Owner();
+ if (array_expr->Type() != S_TYPE_MEMBER_ARRAY_ELEMENT)
+ return NULL;
+
+ Identifier* identifier = (Identifier*)array_expr->Children()->HeadNode()->Owner();
+ if (identifier->Type() != S_TYPE_IDENTIFIER)
+ return NULL;
+
+ // the model names are always assigned to an array element of a[]
+ if (strcmp(identifier->value, "a") != 0)
+ return NULL;
+
+ return op2_val->value;
+}
+
+void AST_GenCSV_XModelAlias(Symbol* AST, const char* rawfile, FILE* csv)
+{
+ Function* main = AST_FindFunction(AST, "main");
+ if (!main)
+ {
+ Con_Warning("script does not contain main()\n");
+ return;
+ }
+
+ Symbol* statements = main->Children()->HeadNode()->PrevNode()->Owner();
+ if (statements->Size() < 1)
+ {
+ Con_Warning("main() function doesn't contain any statments\n");
+ return;
+ }
+
+ std::set models;
+ Symbol_EnumChildren(statements, [&models](Symbol* statement) -> bool
+ {
+ //
+ // Skip anything that isnt an expression
+ //
+ if (statement->Type() != S_TYPE_EXPRESSION)
+ return true;
+
+ const char* model = XMA_Expression_ExtractModelName((Expression*)statement);
+
+ //
+ // Skip any statements that don't contain valid data for this
+ //
+ if (!model)
+ return true;
+
+ std::string modelname = model + 1;
+ modelname[modelname.size() - 1] = '\0';
+
+ models.insert(modelname);
+ return true;
+ });
+
+ fprintf(csv, "rawfile,xmodelalias/%s\n", rawfile);
+ for (const std::string& model : models)
+ {
+ fprintf(csv, "xmodel,%s\n", model.c_str());
+ }
+}
+
+void AST_GenCSV_AIType(Symbol* AST, const char* rawfile, FILE* csv)
+{
+ Function* main = AST_FindFunction(AST, "main");
+ if (!main)
+ {
+ Con_Warning("script does not contain main()\n");
+ return;
+ }
+
+ Symbol* main_statements = main->Children()->HeadNode()->PrevNode()->Owner();
+ if (main_statements->Size() < 1)
+ {
+ Con_Warning("main() function doesn't contain any statments\n");
+ return;
+ }
+
+ Function* precache = AST_FindFunction(AST, "precache");
+ if (!precache)
+ {
+ Con_Warning("script does not contain precache()\n");
+ return;
+ }
+
+ Symbol* precache_statements = precache->Children()->HeadNode()->PrevNode()->Owner();
+ if (precache_statements->Size() < 1)
+ {
+ Con_Warning("precache() function doesn't contain any statments\n");
+ return;
+ }
+
+ std::set weapons;
+ std::set includes;
+ std::set characters;
+
+ Symbol_EnumChildren(main_statements, [&includes](Symbol* statement) -> bool
+ {
+ Expression* expr = (Expression*)statement;
+ if (expr->Type() != S_TYPE_EXPRESSION)
+ return true;
+
+ if (expr->Operator() != OP_TYPE_ASSIGN)
+ return true;
+
+ Group* op1_group = (Group*)expr->Children()->HeadNode()->Owner();
+ Group* op2_group = (Group*)expr->Children()->HeadNode()->PrevNode()->Owner();
+ if (op1_group->Type() != S_TYPE_GROUP || op2_group->Type() != S_TYPE_GROUP)
+ return true;
+
+ //
+ // Skip if the assigned value isn't a string
+ //
+ Literal* op2_val = (Literal*)op2_group->Children()->HeadNode()->Owner();
+ if (op2_val->Type() != S_TYPE_LITERAL_STRING)
+ return true;
+
+ //
+ // Skip if this expression isn't assigning to a struct property
+ //
+ Member* prop_expr = (Member*)op1_group->Children()->HeadNode()->Owner();
+ if (prop_expr->Type() != S_TYPE_MEMBER_OBJECT_PROPERTY)
+ return true;
+
+ Identifier* prop_expr_args = (Identifier*)prop_expr->Children();
+
+ if (prop_expr_args->Type() != S_TYPE_IDENTIFIER || prop_expr_args->NextElem()->Type() != S_TYPE_IDENTIFIER)
+ return true;
+
+ //
+ // Skip if the we're not assigning to self
+ //
+ if (strcmp(prop_expr_args->value, "self") != 0)
+ return true;
+
+ //
+ // Skip if this expression isn't assigning to the csvInclude property
+ //
+ if (strcmp(static_cast(prop_expr_args->NextElem())->value, "csvInclude") != 0)
+ return true;
+
+ std::string str = op2_val->value + 1;
+
+ //
+ // Skip if the string is empty (The only thing that would be in the string here would be a quote char)
+ //
+ if (str.size() <= 1)
+ return true;
+
+ //
+ // Skip if its not a csv file
+ //
+ auto offset = str.find_last_of(".");
+ if (offset != std::string::npos)
+ str[offset] = '\0'; // Strip the extension and trailing quote
+ else
+ str[str.size() - 1] = '\0'; // Otherwise strip the just trailing quote
+
+ includes.insert(str);
+ return true;
+ });
+
+ Symbol_EnumChildren(precache_statements, [&characters, &weapons](Symbol* statement) -> bool
+ {
+ //
+ // Function calls reside inside groups
+ //
+ if (statement->Type() != S_TYPE_GROUP)
+ return true;
+
+ if (statement->Size() < 1)
+ return true;
+
+ Function* call = (Function*)statement->Children()->HeadNode()->Owner();
+
+ //
+ // Skip anything that isnt an expression
+ //
+ if (call->Type() != S_TYPE_FUNCTION_CALL)
+ return true;
+
+ //
+ // Cheap way to get the weapons
+ //
+ if (call->Children()->HeadNode()->Owner()->Type() == S_TYPE_IDENTIFIER)
+ {
+ Identifier* identifier = (Identifier*)call->Children()->HeadNode()->Owner();
+ Group* arg_group = (Group*)call->Children()->NextElem()->Children();
+
+ if (strcmp(identifier->value, "precacheItem") != 0)
+ return true;
+
+ if (arg_group->Children() == NULL)
+ return true;
+
+ Symbol* args = arg_group->Children();
+
+ int argc = args->Size() + 1; // The size of a linked list never includes the head node
+ if (argc != 1)
+ {
+ Con_Warning("Wrong number of args for precacheItem()\n");
+ return true;
+ }
+
+ if (args->Type() != S_TYPE_LITERAL_STRING)
+ {
+ Con_Warning("Wrong type of args for precacheItem() %d\n", args->Size());
+ return true;
+ }
+
+ Literal* string = (Literal*)args;
+ std::string weapon = string->value + 1;
+ weapon[weapon.size() - 1] = '\0';
+
+ weapons.insert(weapon);
+ return true;
+ }
+
+ if (call->Children()->HeadNode()->Owner()->Type() == S_TYPE_REFERENCE)
+ {
+ Reference* ref = (Reference*)call->Children()->HeadNode()->Owner();
+
+ if (strcmp(ref->identifier->value, "precache") != 0)
+ return true;
+
+ const char* substr = "character\\";
+ if (strstr(ref->file->value, substr) != ref->file->value)
+ {
+ if (strstr(ref->file->value, "character/") != ref->file->value)
+ return true;
+ }
+
+ std::string character = ref->file->value + strlen(substr);
+ characters.insert(character);
+ return true;
+ }
+
+ return true;
+ });
+
+ fprintf(csv, "rawfile,aitype/%s\n", rawfile);
+ for (const std::string& character : characters)
+ {
+ fprintf(csv, "character,%s\n", character.c_str());
+ }
+
+ for (const std::string& weapon : weapons)
+ {
+ fprintf(csv, "weapon,sp/%s\n", weapon.c_str());
+ }
+
+ for (const std::string& include : includes)
+ {
+ fprintf(csv, "include,%s\n", include.c_str());
+ }
+}
+
+void AST_GenCSV_Character(Symbol* AST, const char* rawfile, FILE* csv)
+{
+ std::string csc_path = AppInfo_RawDir();
+ csc_path += "\\character\\clientscripts\\";
+ csc_path += rawfile;
+
+ if (csc_path[csc_path.size() - 3] == 'g')
+ csc_path[csc_path.size() - 3] = 'c';
+ else if (csc_path[csc_path.size() - 3] == 'G')
+ csc_path[csc_path.size() - 3] = 'C';
+
+ if (!FS_FileExists(csc_path.c_str()))
+ csc_path.clear();
+
+ Function* precache = AST_FindFunction(AST, "precache");
+ if (!precache)
+ {
+ Con_Warning("script does not contain precache()\n");
+ return;
+ }
+
+ Symbol* precache_statements = precache->Children()->HeadNode()->PrevNode()->Owner();
+ if (precache_statements->Size() < 1)
+ {
+ Con_Warning("precache() function doesn't contain any statments\n");
+ return;
+ }
+
+ std::set models;
+ std::set xmodelaliases;
+ Symbol_EnumChildren(precache_statements, [&models, &xmodelaliases](Symbol* statement) -> bool
+ {
+ //
+ // Function calls reside inside groups
+ //
+ if (statement->Type() != S_TYPE_GROUP)
+ return true;
+
+ if (!statement->Children() || statement->Children()->Size() + 1 < 1)
+ return true;
+
+ Function* call = (Function*)statement->Children()->HeadNode()->Owner();
+
+ //
+ // Skip anything that isnt an expression
+ //
+ if (call->Type() != S_TYPE_FUNCTION_CALL)
+ return true;
+
+ //
+ // Cheap way to get the models
+ //
+ if (call->Children()->HeadNode()->Owner()->Type() == S_TYPE_IDENTIFIER)
+ {
+ Identifier* identifier = (Identifier*)call->Children()->HeadNode()->Owner();
+ Group* arg_group = (Group*)call->Children()->NextElem()->Children();
+
+ if (strcmp(identifier->value, "precacheModel") != 0)
+ return true;
+
+ if (arg_group->Children() == NULL)
+ return true;
+
+ Symbol* args = arg_group->Children();
+
+ int argc = args->Size() + 1; // The size of a linked list never includes the head node
+ if (argc != 1)
+ {
+ Con_Warning("Wrong number of args for precacheItem()\n");
+ return true;
+ }
+
+ if (args->Type() != S_TYPE_LITERAL_STRING)
+ {
+ Con_Warning("Wrong type of args for precacheItem()\n");
+ return true;
+ }
+
+ Literal* string = (Literal*)args;
+ std::string model = string->value + 1;
+ model[model.size() - 1] = '\0';
+
+ models.insert(model);
+ return true;
+ }
+
+ if (call->Children()->HeadNode()->Owner()->Type() == S_TYPE_REFERENCE)
+ {
+ Reference* ref = (Reference*)call->Children()->HeadNode()->Owner();
+
+ if (strcmp(ref->file->value, "codescripts\\character") != 0)
+ return true;
+
+ if (strcmp(ref->identifier->value, "precacheModelArray") != 0)
+ return true;
+
+ Group* arg_group = (Group*)call->Children()->NextElem()->Children();
+
+ if (arg_group->Children() == NULL)
+ return true;
+
+ Symbol* args = arg_group->Children();
+
+ int argc = args->Size() + 1; // The size of a linked list never includes the head node
+ if (argc != 1)
+ {
+ Con_Warning("Wrong number of args for precacheModelArray()\n");
+ return true;
+ }
+
+ //
+ // The secondary call to the xmodelalias::main()
+ //
+ call = (Function*)args;
+ if (call->Type() != S_TYPE_FUNCTION_CALL)
+ {
+ Con_Warning("Wrong type of args for precacheModelArray()\n");
+ return true;
+ }
+
+ ref = (Reference*)call->Children()->HeadNode()->Owner();
+ if (ref->Type() != S_TYPE_REFERENCE)
+ return true;
+
+ if (strcmp(ref->identifier->value, "main") != 0)
+ return true;
+
+ std::string alias = ref->file->value;
+ if (alias.find("xmodelalias\\") == 0)
+ alias = alias.c_str() + strlen("xmodelalias\\");
+
+ xmodelaliases.insert(alias);
+ return true;
+ }
+
+ return true;
+ });
+
+ fprintf(csv, "rawfile,character/%s\n", rawfile);
+ for (const std::string& model : models)
+ {
+ fprintf(csv, "xmodel,%s\n", model.c_str());
+ }
+
+ for (const std::string& xmodelalias : xmodelaliases)
+ {
+ fprintf(csv, "xmodelalias,%s\n", xmodelalias.c_str());
+ }
+
+ if (csc_path.length() > 0)
+ {
+ fprintf(csv, "rawfile,character/clientscripts/%s", FS_GetFilenameSubString(csc_path.c_str()));
+ }
+}
+
+int CSVGen_Callback(const char* filePath, const char* fileName, void(*ast_callback)(Symbol* AST, const char* rawfile, FILE* csv))
+{
+ std::string csv_path = filePath;
+ csv_path[csv_path.length() - 3] = 'c';
+ csv_path[csv_path.length() - 2] = 's';
+ csv_path[csv_path.length() - 1] = 'v';
+
+ if (!fs_overwrite.ValueBool() && FS_FileExists(csv_path.c_str()))
+ {
+ Con_Print("File '%s' already exists - skipping...\n", FS_GetFilenameSubString(csv_path.c_str()));
+ return 1;
+ }
+
+ FILE* h;
+ fopen_s(&h, filePath, "rb");
+
+ if (!h)
+ {
+ Con_Warning("File '%s' could not be opened - skipping...\n", fileName);
+ return -1;
+ }
+
+ const char* typeString = "";
+ if (ast_callback == AST_GenCSV_AIType)
+ typeString = "AIType";
+ else if (ast_callback == AST_GenCSV_Character)
+ typeString = "Character";
+ else if (ast_callback == AST_GenCSV_XModelAlias)
+ typeString = "XModelAlias";
+
+ Con_Print("%s (%s)\n", fileName, typeString);
+
+ int fileSize = FS_FileSize(filePath);
+ char* buf = new char[fileSize];
+
+ fread(buf, 1, fileSize, h);
+ fclose(h);
+
+ yyscan_t scanner = NULL;
+ yylex_init(&scanner);
+
+ yy_scan_bytes(buf, fileSize, scanner);
+ delete[] buf;
+
+ Symbol* AST = NULL;
+ int err = yyparse(&AST, scanner);
+ yylex_destroy(scanner);
+
+ FILE* csv = fopen(csv_path.c_str(), "w");
+ if (csv)
+ {
+ ast_callback(AST, FS_GetFilenameSubString(filePath), csv);
+ fclose(csv);
+ }
+ else
+ {
+ Con_Error("Could not open '%s' for writing...\n", FS_GetFilenameSubString(csv_path.c_str()));
+ }
+
+ delete AST;
+
+ return 0;
+}
+
+int CSVGen_AIType_Callback(const char* filePath, const char* fileName)
+{
+ return CSVGen_Callback(filePath, fileName, AST_GenCSV_AIType);
+}
+
+int CSVGen_Character_Callback(const char* filePath, const char* fileName)
+{
+ return CSVGen_Callback(filePath, fileName, AST_GenCSV_Character);
+}
+
+int CSVGen_XModelAlias_Callback(const char* filePath, const char* fileName)
+{
+ return CSVGen_Callback(filePath, fileName, AST_GenCSV_XModelAlias);
+}
+
+int Cmd_CSVGen_f(int argc, char** argv)
+{
+ // Automatically iterate over all files for the enabled types
+ bool autoMode = false;
+
+ // True if the user manually defined specific types to handle
+ bool explicitType = (csvgen_aitypes.ValueBool() | csvgen_characters.ValueBool() | csvgen_xmodelaliases.ValueBool());
+
+ //
+ // If no explicit types were given - or an asterisk was the only arg - enable automode for all types
+ // If the user doesnt explicitly provide files to be parsed or
+ // the user provides only an asterisk as the file
+ // we automatically assume that we need to enter autoMode to automatically parse all files for the given types
+ //
+ if (argc == 1 || (argc == 2 && strcmp(argv[1], "*") == 0))
+ {
+ autoMode = true;
+
+ // If no explicit types were given - we enable all types
+ if (!explicitType)
+ {
+ csvgen_aitypes.Enable();
+ csvgen_characters.Enable();
+ csvgen_xmodelaliases.Enable();
+ }
+ }
+
+ //
+ // Automatic file mode logic
+ //
+ if (autoMode)
+ {
+ if (argc < 1 || argc > 2)
+ {
+ char* _argv[] = { NULL, "csvgen" };
+ Cmd_Help_f(ARRAYSIZE(_argv), _argv);
+ return -1;
+ }
+
+ char dir_path[MAX_PATH];
+ if (csvgen_aitypes.ValueBool())
+ {
+ sprintf_s(dir_path, "%s\\aitype", AppInfo_RawDir());
+ FS_FileIterator(dir_path, "*.gsc", CSVGen_AIType_Callback);
+ }
+
+ if (csvgen_characters.ValueBool())
+ {
+ sprintf_s(dir_path, "%s\\character", AppInfo_RawDir());
+ FS_FileIterator(dir_path, "*.gsc", CSVGen_Character_Callback);
+ }
+
+ if (csvgen_xmodelaliases.ValueBool())
+ {
+ sprintf_s(dir_path, "%s\\xmodelalias", AppInfo_RawDir());
+ FS_FileIterator(dir_path, "*.gsc", CSVGen_XModelAlias_Callback);
+ }
+
+ return 0;
+ }
+
+ //
+ // Explicit file mode logic
+ //
+ // We only want to run csv gen for a specific files
+ // If only 1 type is given via args, use that type, otherwise print a warning and attempt to resolve the type automatically
+ //
+ int typeFlags = 0;
+ typeFlags |= csvgen_aitypes.ValueBool() ? 1 << 0 : 0;
+ typeFlags |= csvgen_characters.ValueBool() ? 1 << 1 : 0;
+ typeFlags |= csvgen_xmodelaliases.ValueBool() ? 1 << 2 : 0;
+
+ for (int i = 0, bitCount = 0; i < sizeof(typeFlags) * 8; i++)
+ {
+ bitCount += (typeFlags >> i) & 1;
+ if (bitCount > 1)
+ {
+ Con_Warning("Warning: Multiple type arguments given. Falling back to automatic type resolution mode.\n");
+ explicitType = false;
+ break;
+ }
+ }
+
+ for (int i = 1; i < argc; i++)
+ {
+ const char* filepath = argv[i];
+ const char* filename = FS_GetFilenameSubString(argv[i]);
+
+ //
+ // Automatic Type Resolution
+ //
+ if (!explicitType)
+ {
+ if (!FS_FileExists(filepath))
+ Con_Warning("File '%s' does not exist... skipping\n", filename);
+ else if (csvgen_aitypes.ValueBool() && strstr(argv[i], "aitype") != 0)
+ CSVGen_AIType_Callback(filepath, filename);
+ else if (csvgen_characters.ValueBool() && strstr(argv[i], "character") != 0)
+ CSVGen_Character_Callback(filepath, filename);
+ else if (csvgen_xmodelaliases.ValueBool() && strstr(argv[i], "xmodelalias") != 0)
+ CSVGen_XModelAlias_Callback(filepath, filename);
+ else
+ Con_Warning("Unable to resolve type for file '%s'... skipping\n", filename);
+
+ continue;
+ }
+
+ //
+ // Explicit type logic
+ //
+ switch (typeFlags)
+ {
+ case 1 << 0:
+ CSVGen_AIType_Callback(filepath, filename);
+ break;
+ case 1 << 1:
+ CSVGen_Character_Callback(filepath, filename);
+ break;
+ case 1 << 2:
+ CSVGen_XModelAlias_Callback(filepath, filename);
+ break;
+ default:
+ Con_Warning("Warning: Invalid type flags...");
+ }
+ }
+
+ //
+ // This should never be reached in automatic file mode
+ // Thus - there should always be at least 1 file provided
+ //
+ if (argc < 2)
+ {
+ char* _argv[] = { NULL, "csvgen" };
+ Cmd_Help_f(ARRAYSIZE(_argv), _argv);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/components/asset_util/cmds/cmd_ents.cpp b/components/asset_util/cmds/cmd_ents.cpp
index 8798c8ed..74fc5364 100644
--- a/components/asset_util/cmds/cmd_ents.cpp
+++ b/components/asset_util/cmds/cmd_ents.cpp
@@ -1,6 +1,203 @@
#include "../common/ff.h"
#include "zlib\zlib.h"
#include "../common/io.h"
+#include "cmd_common.h"
+
+#include "../cvar.h"
+
+#include
+#include
+
+enum class ParseEnts_StrType
+{
+ KEY,
+ VALUE,
+
+ INFO, // used for the header - we just drop these for now
+};
+
+struct KeyValuePair
+{
+ std::string key;
+ std::string value;
+};
+
+struct EntityTable
+{
+ std::string header;
+ std::vector> ents;
+};
+
+int ParseEnts(const char* ents_str, EntityTable* ents_table)
+{
+ std::vector stack;
+
+ const char* str_start = NULL;
+ const char* ent_start = NULL;
+ int ent_level = 0; // Its like ESP but for ents!
+
+ ParseEnts_StrType strType = ParseEnts_StrType::INFO;
+
+ std::vector ent;
+ KeyValuePair kv;
+
+ for (const char* c = ents_str; *c; c++)
+ {
+ if (*c == '"')
+ {
+ if (str_start == NULL)
+ {
+ str_start = c + 1;
+ continue;
+ }
+
+ if (strType != ParseEnts_StrType::INFO)
+ {
+ std::string str(str_start, c);
+ if (strType == ParseEnts_StrType::KEY)
+ {
+ kv.key = str;
+ strType = ParseEnts_StrType::VALUE;
+ }
+ else if (strType == ParseEnts_StrType::VALUE)
+ {
+ kv.value = str;
+ ent.push_back(kv);
+ kv.key.clear();
+ kv.value.clear();
+ strType = ParseEnts_StrType::KEY;
+ }
+ }
+ str_start = NULL;
+ }
+ else if (str_start == NULL)
+ {
+ if (*c == '{')
+ {
+ if (ent_level++ == 0)
+ {
+ ent_start = c + 1;
+
+ // Add the header to the ents table as the first entry
+ if (strType == ParseEnts_StrType::INFO)
+ {
+ ents_table->header = std::string(ents_str, c);
+ }
+
+ strType = ParseEnts_StrType::KEY; // The next string we should see is a key
+ }
+ }
+ else if (*c == '}')
+ {
+ if (--ent_level == 0)
+ {
+ ents_table->ents.push_back(ent);
+ ent.clear();
+ }
+ }
+
+ if (ent_level < 0)
+ {
+ return ent_level;
+ }
+ }
+ }
+
+ return ent_level;
+}
+
+//
+// Attempts to get the value for the given key and place it in
+//
+int Ent_GetValueForKey(std::vector* ent, const char* _key, std::string** out)
+{
+ std::string key = _key;
+ for (unsigned int i = 0; i < key.length(); i++)
+ {
+ key[i] = tolower(key[i]);
+ }
+
+ for (unsigned int i = 0; i < ent->size(); i++)
+ {
+ if ((*ent)[i].key == key)
+ {
+ *out = &(*ent)[i].value;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+std::string FormatBrush(float* offset, int size)
+{
+ char buf[2048];
+
+ int r = size / 2;
+ const char* mtl = "trigger";
+
+ std::string out = "";
+
+ if (ents_useLabels.ValueBool())
+ out += "// brush 0\n";
+
+ out += "{\n";
+ sprintf_s(buf, " (%.2f %.2f %.2f) (%.2f %.2f %.2f) (%.2f %.2f %.2f) %s 64 64 -304 48 0 0 lightmap_gray 16383.969 16384 -304 48 0 0\n", offset[0], offset[1] + r, offset[2] - r, offset[0] - r, offset[1] + r, offset[2] - r, offset[0] - r, offset[1], offset[2] - r, mtl);
+ out += buf;
+ sprintf_s(buf, " (%.2f %.2f %.2f) (%.2f %.2f %.2f) (%.2f %.2f %.2f) %s 64 64 -304 48 0 0 lightmap_gray 16383.953 16384 -304 48 0 0\n", offset[0] - r, offset[1], offset[2] + r, offset[0] - r, offset[1] + r, offset[2] + r, offset[0], offset[1] + r, offset[2] + r, mtl);
+ out += buf;
+ sprintf_s(buf, " (%.2f %.2f %.2f) (%.2f %.2f %.2f) (%.2f %.2f %.2f) %s 64 64 -304 0 0 0 lightmap_gray 16383.984 16384 -304 0 0 0\n", offset[0], offset[1] - r, offset[2] + r, offset[0] + r, offset[1] - r, offset[2] + r, offset[0] + r, offset[1] - r, offset[2] - r, mtl);
+ out += buf;
+ sprintf_s(buf, " (%.2f %.2f %.2f) (%.2f %.2f %.2f) (%.2f %.2f %.2f) %s 64 64 -48 0 0 0 lightmap_gray 16384 16384 -48 0 0 0\n", offset[0] + r, offset[1] - r, offset[2] + r, offset[0] + r, offset[1], offset[2] + r, offset[0] + r, offset[1], offset[2] - r, mtl);
+ out += buf;
+ sprintf_s(buf, " (%.2f %.2f %.2f) (%.2f %.2f %.2f) (%.2f %.2f %.2f) %s 64 64 -304 0 0 0 lightmap_gray 16384 16384 -304 0 0 0\n", offset[0], offset[1] + r, offset[2] + r, offset[0] - r, offset[1] + r, offset[2] + r, offset[0] - r, offset[1] + r, offset[2] - r, mtl);
+ out += buf;
+ sprintf_s(buf, " (%.2f %.2f %.2f) (%.2f %.2f %.2f) (%.2f %.2f %.2f) %s 64 64 -48 0 0 0 lightmap_gray 16384 16384 -48 0 0 0\n", offset[0]-r, offset[1] + r, offset[2] + r, offset[0] - r, offset[1], offset[2] + r, offset[0] - r, offset[1], offset[2] - r, mtl);
+ out += buf;
+ out += "}";
+
+ return out;
+}
+
+int AddBrushes(EntityTable* ents_table)
+{
+ std::vector>& ents = ents_table->ents;
+ for (unsigned int i = 0; i < ents.size(); i++)
+ {
+ std::vector& ent = ents[i];
+
+ std::string* classname;
+ if (!Ent_GetValueForKey(&ent, "classname", &classname))
+ continue;
+
+ std::string* model;
+ if (!Ent_GetValueForKey(&ent, "model", &model))
+ continue;
+
+ // Skip any thing that uses a non-builtin brushmodel
+ if ((*model)[0] != '*')
+ continue;
+
+ std::string* origin;
+ if (!Ent_GetValueForKey(&ent, "origin", &origin))
+ continue;
+
+ float offset[3];
+ int r = sscanf_s(origin->c_str(), "%f %f %f", offset, offset + 1, offset + 2);
+ if (r != 3)
+ continue;
+
+ KeyValuePair kv;
+ kv.key = "";
+ std::string value = FormatBrush(offset, 64);
+
+ kv.value = value;
+
+ ent.push_back(kv);
+ }
+
+ return 0;
+}
char* FindEntsString(BYTE* start, BYTE* end)
{
@@ -13,7 +210,7 @@ char* FindEntsString(BYTE* start, BYTE* end)
if (strncmp(pattern, (char*)c, len) == NULL)
{
char* p = (char*)c;
-
+
//
// Automatically retry until the real start of the ents string is found
//
@@ -48,7 +245,7 @@ char* FindEntsString(BYTE* start, BYTE* end)
return p;
}
- Con_Print("Trying different offset... (%d attempts remaining)\n", retryCount - 1);
+ Con_Print_v("Trying different offset... (%d attempts remaining)\n", retryCount - 1);
p--;
}
}
@@ -64,59 +261,96 @@ char* FindEntsString(BYTE* start, BYTE* end)
//
int Cmd_Ents_f(int argc, char** argv)
{
- //
- // Temp arg handler
- //
- _ASSERT(argc > 1);
- char* filepath = argv[1];
+ for (int i = 1; i < argc; i++)
+ {
+ char* filepath = argv[i];
- Con_Print("Extracting ents from \"%s\"...\n", filepath);
+ Con_Print_v("Extracting ents from \"%s\"...\n", filepath);
- FILE* h = nullptr;
- if (fopen_s(&h, filepath, "r+b") != 0)
- {
- Con_Error("ERROR: Fastfile '%s' could not be found\n\n", filepath);
- return FALSE;
- }
- rewind(h);
+ FILE* h = nullptr;
+ if (fopen_s(&h, filepath, "r+b") != 0)
+ {
+ Con_Error("ERROR: Fastfile '%s' could not be found\n", filepath);
+ return FALSE;
+ }
+ rewind(h);
- fseek(h, 0, SEEK_END);
- size_t fileSize = ftell(h);
+ fseek(h, 0, SEEK_END);
+ size_t fileSize = ftell(h);
- // Get Compressed FileSize and Allocate a Storage Buffer for Compressed Data
- size_t cSize = fileSize - 12;
- BYTE* cBuf = new BYTE[cSize | 0x8000];
+ // Get Compressed FileSize and Allocate a Storage Buffer for Compressed Data
+ size_t cSize = fileSize - 12;
+ BYTE* cBuf = new BYTE[cSize | 0x8000];
- fseek(h, 12, SEEK_SET);
- fread(cBuf, 1, cSize, h);
+ fseek(h, 12, SEEK_SET);
+ fread(cBuf, 1, cSize, h);
- XFile ffInfo;
- unsigned long dSize = sizeof(XFile);
- uncompress((BYTE*)&ffInfo, &dSize, cBuf, 0x8000);
+ XFile ffInfo;
+ unsigned long dSize = sizeof(XFile);
+ uncompress((BYTE*)&ffInfo, &dSize, cBuf, 0x8000);
- dSize = ffInfo.size + 36;
- if (dSize >= 1073741824)
- {
- //Any fastfiles that claim they decompress to a file >= 1GB
- //are either corrupt or do not belong to the vanilla game
- Con_Error("ERROR: Skipping %s\n", filepath);
- return 1;
- }
+ dSize = ffInfo.size + 36;
+ if (dSize >= 1073741824)
+ {
+ //Any fastfiles that claim they decompress to a file >= 1GB
+ //are either corrupt or do not belong to the vanilla game
+ Con_Error("ERROR: Skipping %s\n", filepath);
+ return 1;
+ }
- BYTE* dBuf = new BYTE[dSize];
- uncompress(dBuf, &dSize, cBuf, cSize);
- delete[] cBuf;
+ BYTE* dBuf = new BYTE[dSize];
+ uncompress(dBuf, &dSize, cBuf, cSize);
+ delete[] cBuf;
- char* result = FindEntsString((BYTE*)dBuf, dBuf + ffInfo.size + 36);
- if (result == NULL)
- {
- Con_Error("Error: Could not find entity string\n");
+ char* ents_str = FindEntsString((BYTE*)dBuf, dBuf + ffInfo.size + 36);
+ if (ents_str == NULL)
+ {
+ Con_Error("Error: Could not find entity string\n");
+ delete[] dBuf;
+ return -1;
+ }
+
+ EntityTable ents_table;
+ int l = ParseEnts(ents_str, &ents_table);
+ delete[] dBuf; // Free the fastfile buffer as we don't need it anymore
+
+ if (ents_genBrushes.ValueBool())
+ AddBrushes(&ents_table);
+
+ Con_Print_nv( "iwmap 4\n"
+ "\"000_Global\" flags active\n"
+ "\"The Map\" flags\n" );
+
+ Con_Print("%s", ents_table.header.c_str());
+
+#if 1
+ std::vector>& ents = ents_table.ents;
+ for (unsigned int i = 0; i < ents.size(); i++)
+ {
+ std::vector& ent = ents[i];
+
+ if (ents_useLabels.ValueBool())
+ Con_Print("// Entity %d\n", i);
+ Con_Print("{\n");
+ for (unsigned int k = 0; k < ents[i].size(); k++)
+ {
+ KeyValuePair& kv = ent[k];
+ if (kv.key.length() > 0)
+ Con_Print("\"%s\" \"%s\"\n", kv.key.c_str(), kv.value.c_str());
+ else
+ Con_Print("%s\n", kv.value.c_str());
+ }
+ Con_Print("}\n");
+ }
+#endif
}
- else
+
+ if (argc < 2)
{
- Con_Print("%s\n", result);
+ char* _argv[] = { NULL, "ents" };
+ Cmd_Help_f(ARRAYSIZE(_argv), _argv);
+ return -1;
}
- delete[] dBuf;
return 0;
}
\ No newline at end of file
diff --git a/components/asset_util/cmds/cmd_extract.cpp b/components/asset_util/cmds/cmd_extract.cpp
index add0eeb1..52b8baaa 100644
--- a/components/asset_util/cmds/cmd_extract.cpp
+++ b/components/asset_util/cmds/cmd_extract.cpp
@@ -3,18 +3,126 @@
#include "../sys/AppInfo.h"
#include "../common/io.h"
+#include "../common/fs.h"
#include "../cvar.h"
+#include
+#include
+
+enum PriorityFlags
+{
+ PATCH = 1 << 0,
+ MP = 1 << 1,
+ SO = 1 << 2,
+ ZM = 1 << 3,
+
+ CODE_PRE_GFX = 1 << 4,
+ CODE_POST_GFX = 1 << 5,
+ UI = 1 << 6,
+
+ COMMON = 1 << 7,
+ TERMINAL = 1 << 8,
+ LEVEL = 1 << 9,
+
+ LOCALIZED = 1 << 10
+};
+
+// Determine the extraction order priority for a given fastfile
+// Lower numbers get higher priority (are extracted first)
+int FF_GetPriority(const char* name, const char* path)
+{
+ int flags = (PriorityFlags)NULL;
+
+ if (strstr(path, AppInfo_FFDir()) != NULL)
+ flags |= LOCALIZED;
+
+ if (strstr(name, "_patch") != NULL || strstr(name, "patch") == name)
+ flags |= PATCH;
+
+ if (strstr(name, "mp_") != NULL || strstr(name, "_mp") != NULL)
+ flags |= MP;
+
+ if (strstr(name, "so_") != NULL)
+ flags |= SO;
+
+ if (strstr(name, "zombie"))
+ flags |= ZM;
+
+ if (strstr(name, "code_pre_gfx") != NULL)
+ flags |= CODE_PRE_GFX;
+ else if (strstr(name, "code_post_gfx") != NULL)
+ flags |= CODE_POST_GFX;
+ else if (strstr(name, "common") != NULL)
+ flags |= COMMON;
+ else if (strstr(name, "ui") != NULL)
+ flags |= UI;
+ else if (strstr(name, "terminal") != NULL)
+ flags |= TERMINAL;
+ else
+ flags |= LEVEL;
+
+ return flags;
+}
+
+struct FastFileEntry
+{
+ int priority;
+ std::string name;
+ std::string path;
+};
+
int Cmd_Extract_FF_f(int argc, char** argv)
{
- if (g_useLocalized.ValueBool())
+ if (g_extractAll.ValueBool())
{
- FS_FileIterator(AppInfo_ZoneDir(), FS_SEARCHPATTERN_FF, FF_FFExtract);
+ std::vector entries;
+ auto FF_ExtractDefered = [&entries](const char* filePath, const char* fileName) -> int
+ {
+ FastFileEntry entry;
+ entry.priority = FF_GetPriority(fileName, filePath);
+ entry.path = filePath;
+ entry.name = fileName;
+
+ entries.push_back(entry);
+ return 0;
+ };
+
+ // Handle iterating over the fastfiles for each fastfile directory
+ // Used to ensure localized fastfiles are retrieved as well
+ auto FF_IterateFastFileDirectory = [&FF_ExtractDefered](const char* path) -> int
+ {
+ return FS_FileIterator(path, FS_SEARCHPATTERN_FF, FF_ExtractDefered);
+ };
+
+ //
+ // Extract normal fastfiles while deferring patch fastfiles
+ //
+ if (g_useLocalized.ValueBool())
+ FS_DirectoryIterator(AppInfo_ZoneDir(), FF_IterateFastFileDirectory);
+ else
+ FS_FileIterator(AppInfo_FFDir(), FS_SEARCHPATTERN_FF, FF_ExtractDefered);
+
+ //
+ // Sort the fastfiles by priority weight
+ //
+ std::sort(entries.begin(), entries.end(), [](FastFileEntry& a, FastFileEntry& b)->bool
+ {
+ return a.priority < b.priority;
+ });
+
+ for (auto& entry : entries)
+ {
+ FF_FFExtract(entry.path.c_str(), entry.name.c_str());
+ }
}
else
{
- FS_FileIterator(AppInfo_FFDir(), FS_SEARCHPATTERN_FF, FF_FFExtract);
+ for (int i = 1; i < argc; i++)
+ {
+ const char* filename = FS_GetFilenameSubString(argv[i]);
+ FF_FFExtract(argv[i], filename);
+ }
}
return 0;
}
diff --git a/components/asset_util/cmds/cmd_help.cpp b/components/asset_util/cmds/cmd_help.cpp
index 40f9e0c6..1a6fda7d 100644
--- a/components/asset_util/cmds/cmd_help.cpp
+++ b/components/asset_util/cmds/cmd_help.cpp
@@ -18,5 +18,20 @@ int Cmd_Help_f(int argc, char** argv)
}
Arg_PrintUsage(cmd);
+
+ if (cmd == &g_cmd_csvgen)
+ {
+ Con_Print("Info:\n"
+ " csvgen has two 'modes' which control how it runs.\n\n"
+ " auto mode: csvgen [options] [*]\n"
+ " Options determine which types csvgen tries to regenerate csvs for\n"
+ " by default all types are enabled\n\n"
+ " explicit mode: csvgen [option] file1 [file2 ...]\n"
+ " Command specific options define the type of file(s) being passed to csvgen\n"
+ " If no type option is given, or more than one is given csvgen will attempt\n"
+ " to automatically determine the type for each file\n"
+ "\n");
+ }
+
return 0;
}
\ No newline at end of file
diff --git a/components/asset_util/cmds/cmd_rip.cpp b/components/asset_util/cmds/cmd_rip.cpp
new file mode 100644
index 00000000..40e4cf0e
--- /dev/null
+++ b/components/asset_util/cmds/cmd_rip.cpp
@@ -0,0 +1,68 @@
+#include "cmd_common.h"
+
+#include "../sys/process.h"
+#include "ripper\process_info.h"
+
+#include "ripper\snd_ripper.h"
+
+bool kill_process = true;
+bool wait = true;
+
+int Cmd_Rip_f(int argc, char** argv)
+{
+ unsigned int timeoutDelay = rip_waitForProcess.ValueBool() ? UINT_MAX : 0;
+ if (processId_t pid = Process_FindSupportedProcess(timeoutDelay))
+ {
+ if (int err = ProcessInfo_Init(pid))
+ {
+ Con_Error("Couldn't initialize process info\n");
+ return err;
+ }
+
+ if (rip_waitForMap.ValueBool())
+ {
+ DB_WaitForMapToLoad();
+ }
+
+ Process_SuspendThreads(pid);
+
+ snapshots_map_t snapshots_map(0);
+ DB_EnumAssetPoolEx(ASSET_TYPE_SOUND, Rip_SoundBank_GatherSnapshots_Callback_f, NULL, &snapshots_map);
+ DB_EnumAssetPoolEx(ASSET_TYPE_SOUND, Rip_SoundBank_Callback_f, NULL, &snapshots_map);
+
+ for (auto& set : snapshots_map)
+ {
+ std::string name = set.first;
+ Rip_Snapshot_Callback_f(name, set.second);
+ }
+
+ radverbs_map_t radverbs_map(0);
+ DB_EnumAssetPoolEx(ASSET_TYPE_SOUND, Rip_SoundBank_GatherRadverbs_Callback_f, NULL, &radverbs_map);
+
+ for (auto& set : radverbs_map)
+ {
+ std::string name = set.first;
+ Rip_Radverb_Callback_f(name, set.second);
+ }
+
+ Process_ResumeThreads(pid);
+
+ ProcessInfo_Free();
+
+ if (rip_killProcess.ValueBool())
+ {
+ Process_KillProcess(pid);
+ }
+
+ return 0;
+ }
+
+ if (argc < 2)
+ {
+ char* _argv[] = { NULL, "rip" };
+ Cmd_Help_f(ARRAYSIZE(_argv), _argv);
+ return -1;
+ }
+
+ return 1;
+}
\ No newline at end of file
diff --git a/components/asset_util/cmds/cmd_search.cpp b/components/asset_util/cmds/cmd_search.cpp
new file mode 100644
index 00000000..f9464505
--- /dev/null
+++ b/components/asset_util/cmds/cmd_search.cpp
@@ -0,0 +1,228 @@
+#include "../sys/AppInfo.h"
+#include "cmd_common.h"
+
+#include "../common/fs.h"
+#include "../common/ff.h"
+
+#include "zlib\zlib.h"
+#include "shared_assert.h"
+
+#include "search/handler.h"
+
+enum class FileType
+{
+ IWD,
+ FF,
+
+ ERR,
+};
+
+struct FileEntry
+{
+ FileType type;
+
+ std::string name;
+ std::string path;
+
+ void Clear(void)
+ {
+ type = FileType::ERR;
+ path = "";
+ name = "";
+ }
+};
+
+class CachedFile
+{
+private:
+ FileEntry entry;
+
+ BYTE* data;
+ size_t size;
+
+private:
+ void Clear(void)
+ {
+ this->entry.Clear();
+ delete[] this->data;
+ data = NULL;
+ size = NULL;
+ }
+
+public:
+ std::string test;
+
+ CachedFile(void) : data(NULL), size(0)
+ {
+ }
+
+ CachedFile(CachedFile&& arg)
+ {
+ this->entry = arg.entry;
+ this->data = arg.data;
+ this->size = arg.size;
+ arg.data = NULL;
+ arg.size = 0;
+ }
+
+ ~CachedFile(void)
+ {
+ this->Clear();
+ }
+
+ bool isValid()
+ {
+ return data != NULL;
+ }
+
+ void Load(FileEntry& _entry)
+ {
+ Con_Print_v("Load: %s\n", _entry.name.c_str());
+
+ this->entry = _entry;
+
+ FILE* h = nullptr;
+ if (fopen_s(&h, entry.path.c_str(), "r+b") != 0)
+ {
+ Con_Error("ERROR: Fastfile '%s' could not be found\n", entry.path.c_str());
+ entry.Clear();
+ return;
+ }
+ rewind(h);
+
+ fseek(h, 0, SEEK_END);
+ size_t fileSize = ftell(h);
+
+ if (entry.type == FileType::FF)
+ {
+ // Get Compressed FileSize and Allocate a Storage Buffer for Compressed Data
+ size_t cSize = fileSize - 12;
+ BYTE* cBuf = new BYTE[cSize | 0x8000];
+
+ fseek(h, 12, SEEK_SET);
+ fread(cBuf, 1, cSize, h);
+
+ this->data = cBuf;
+ this->size = cSize;
+ }
+ else if (entry.type == FileType::IWD)
+ {
+ this->size = fileSize;
+ this->data = new BYTE[size];
+ fread(data, 1, size, h);
+ }
+
+ fclose(h);
+ }
+
+ void Decompress(void)
+ {
+ Con_Print_v("Decompress: %s\n", entry.name.c_str());
+
+ if (!this->isValid())
+ return;
+
+ if (this->entry.type == FileType::FF)
+ {
+ XFile ffInfo;
+ unsigned long dSize = sizeof(XFile);
+ uncompress((BYTE*)&ffInfo, &dSize, this->data, 0x8000);
+
+ dSize = ffInfo.size + 36;
+ if (dSize >= 1073741824)
+ {
+ //Any fastfiles that claim they decompress to a file >= 1GB
+ //are either corrupt or do not belong to the vanilla game
+ Con_Error("ERROR: Skipping %s\n", entry.name.c_str());
+ this->Clear();
+ return;
+ }
+
+ BYTE* dBuf = new BYTE[dSize];
+ uncompress(dBuf, &dSize, this->data, this->size);
+
+ BYTE* cBuf = this->data;
+ this->data = dBuf;
+ this->size = dSize;
+ delete[] cBuf;
+ }
+ }
+
+ void Search(char* pattern)
+ {
+ Con_Print_v("Search: %s\n", this->entry.name.c_str());
+
+ if (!this->isValid())
+ return;
+
+ unsigned int pattern_len = strlen(pattern);
+ for (unsigned int i = 0; i < this->size - pattern_len + 1; i++)
+ {
+ if (memcmp(&data[i], pattern, pattern_len) == 0)
+ {
+ Con_Print("%s\n", entry.name.c_str());
+ return;
+ }
+ }
+
+ return;
+ }
+};
+
+void FF_Cache(FileEntry& fileEntry, CachedFile& outData)
+{
+ outData.Load(fileEntry);
+}
+
+void FF_Decompress(CachedFile& fileData)
+{
+ fileData.Decompress();
+}
+
+int Cmd_Search_f(int argc, char** argv)
+{
+ if (argc != 2)
+ {
+ char* _argv[] = { NULL, "search" };
+ Cmd_Help_f(ARRAYSIZE(_argv), _argv);
+ return -1;
+ }
+
+ const unsigned int MAX_CACHED_FILES = 2;
+ const unsigned int THREAD_COUNT = 3;
+
+ std::vector entries;
+ auto FF_ExtractDefered = [&entries](const char* filePath, const char* fileName) -> int
+ {
+ FileEntry entry = { FileType::FF, fileName, filePath };
+ entries.push_back(entry);
+ return 0;
+ };
+
+ // Handle iterating over the fastfiles for each fastfile directory
+ // Used to ensure localized fastfiles are retrieved as well
+ auto FF_IterateFastFileDirectory = [&FF_ExtractDefered](const char* path) -> int
+ {
+ return FS_FileIterator(path, FS_SEARCHPATTERN_FF, FF_ExtractDefered);
+ };
+
+ //
+ // Extract normal fastfiles while deferring patch fastfiles
+ //
+ if (g_useLocalized.ValueBool())
+ FS_DirectoryIterator(AppInfo_ZoneDir(), FF_IterateFastFileDirectory);
+ else
+ FS_FileIterator(AppInfo_FFDir(), FS_SEARCHPATTERN_FF, FF_ExtractDefered);
+
+ char* pattern = argv[1];
+ auto FF_Search = [pattern](CachedFile& fileData)
+ {
+ fileData.Search(pattern);
+ };
+
+ Handler ffHandler(entries, MAX_CACHED_FILES, FF_Search, FF_Cache, FF_Decompress);
+
+ ffHandler.RunHandler(THREAD_COUNT);
+
+ return 0;
+}
diff --git a/components/asset_util/cmds/ripper/db_registry.cpp b/components/asset_util/cmds/ripper/db_registry.cpp
new file mode 100644
index 00000000..4475b95a
--- /dev/null
+++ b/components/asset_util/cmds/ripper/db_registry.cpp
@@ -0,0 +1,256 @@
+#include "db_registry.h"
+#include "foreign_ptr.h"
+#include "../../common/io.h"
+
+#include "dvar.h"
+
+static const char* g_assetNames[] =
+{
+ "xmodelpieces",
+ "physpreset",
+ "physconstraints",
+ "destructibledef",
+ "xanim",
+ "xmodel",
+ "material",
+ "techset",
+ "image",
+ "sound",
+ "sound_patch",
+ "col_map_sp",
+ "col_map_mp",
+ "com_map",
+ "game_map_sp",
+ "game_map_mp",
+ "map_ents",
+ "gfx_map",
+ "lightdef",
+ "ui_map",
+ "font",
+ "menufile",
+ "menu",
+ "localize",
+ "weapon",
+ "weapondef",
+ "weaponvariant",
+ "snddriverglobals",
+ "fx",
+ "impactfx",
+ "aitype",
+ "mptype",
+ "mpbody",
+ "mphead",
+ "character",
+ "xmodelalias",
+ "rawfile",
+ "stringtable",
+ "packindex",
+ "xGlobals",
+ "ddl",
+ "glasses",
+ "emblemset"
+};
+
+const char * DB_GetXAssetTypeName(int type)
+{
+ return g_assetNames[type];
+}
+
+unsigned int DB_HashForName(const char *name, XAssetType type)
+{
+ unsigned int hash = type;
+ for (const char * pos = name; *pos; ++pos)
+ {
+ int c = tolower(*pos);
+ if (c == '\\')
+ c = '/';
+ hash = (hash << 16) + c + (hash << 6) - hash;
+ }
+ return hash;
+}
+
+const char* DB_GetXAssetName(XAsset *asset)
+{
+ XAssetHeader header = ForeignPointer(asset)->header;
+ SIZE_T numofbytesread = 0;
+
+ static char str[256];
+
+ //switch (ForeignPointer(asset)->type)
+ //{
+ //default:
+ strcpy_s(str, "unsupported asset type");
+ return str;
+ //};
+}
+
+int DB_EnumAssetPool(XAssetType type, asset_callback_t assetCallback_f, asset_callback_t assetOverrideCallback_f, const char* zone)
+{
+ ForeignPointer& db_hashTable = g_process.db_hashTable;
+ ForeignPointer& db_assetEntryPool = g_process.db_assetEntryPool;
+ ForeignPointer& db_zoneNames = g_process.db_zoneNames;
+
+ SIZE_T numofbytesread = 0;
+
+ unsigned int assetCount = 0;
+ for (unsigned int hashIndex = 0; hashIndex < 0x8000; hashIndex++)
+ {
+ __int16 nextAssetEntryIndex = 0;
+ for (int assetEntryIndex = db_hashTable[hashIndex]; assetEntryIndex; assetEntryIndex = nextAssetEntryIndex)
+ {
+ XAssetEntry assetEntry = db_assetEntryPool[assetEntryIndex].entry;
+
+ nextAssetEntryIndex = assetEntry.nextHash;
+ if (assetEntry.asset.type != type)
+ continue;
+
+ if (assetCallback_f)
+ {
+ XAsset* assetOffset = (XAsset*)(db_assetEntryPool.pAddress + assetEntryIndex);
+ ForeignPointer zoneName(db_zoneNames.pAddress + assetEntry.zoneIndex);
+ if (zone == NULL || (zone != NULL && _stricmp(zoneName->name, zone) == 0))
+ {
+ ForeignPointer asset(assetOffset);
+ if (assetCallback_f(asset, zoneName) == 0)
+ assetCount++;
+ }
+ }
+
+ if (!assetOverrideCallback_f)
+ continue;
+
+ XAssetEntry overrideAssetEntry;
+ for (int overrideAssetEntryIndex = assetEntry.nextOverride; overrideAssetEntryIndex; overrideAssetEntryIndex = overrideAssetEntry.nextOverride)
+ {
+ overrideAssetEntry = db_assetEntryPool[overrideAssetEntryIndex].entry;
+
+ XAsset* overrideAssetOffset = (XAsset*)(db_assetEntryPool.pAddress + overrideAssetEntryIndex);
+ ForeignPointer overrideZoneName(db_zoneNames.pAddress + overrideAssetEntry.zoneIndex);
+ if (zone == NULL || (zone != NULL && _stricmp(overrideZoneName->name, zone) == 0))
+ {
+ ForeignPointer overrideAsset(overrideAssetOffset);
+ if (assetOverrideCallback_f(overrideAsset, overrideZoneName) == 0)
+ assetCount++;
+ }
+ }
+ }
+ }
+
+ return assetCount;
+}
+
+int DB_EnumAssetPoolEx(XAssetType type, asset_callback_ex_t assetCallback_f, asset_callback_ex_t assetOverrideCallback_f, void* data, const char* zone)
+{
+ ForeignPointer& db_hashTable = g_process.db_hashTable;
+ ForeignPointer& db_assetEntryPool = g_process.db_assetEntryPool;
+ ForeignPointer& db_zoneNames = g_process.db_zoneNames;
+
+ SIZE_T numofbytesread = 0;
+
+ unsigned int assetCount = 0;
+ for (unsigned int hashIndex = 0; hashIndex < 0x8000; hashIndex++)
+ {
+ __int16 nextAssetEntryIndex = 0;
+ for (int assetEntryIndex = db_hashTable[hashIndex]; assetEntryIndex; assetEntryIndex = nextAssetEntryIndex)
+ {
+ XAssetEntry assetEntry = db_assetEntryPool[assetEntryIndex].entry;
+
+ nextAssetEntryIndex = assetEntry.nextHash;
+ if (assetEntry.asset.type != type)
+ continue;
+
+ if (assetCallback_f)
+ {
+ XAsset* assetOffset = (XAsset*)(db_assetEntryPool.pAddress + assetEntryIndex);
+ ForeignPointer zoneName(db_zoneNames.pAddress + assetEntry.zoneIndex);
+ if (zone == NULL || (zone != NULL && _stricmp(zoneName->name, zone) == 0))
+ {
+ ForeignPointer asset(assetOffset);
+ if (assetCallback_f(asset, zoneName, data) == 0)
+ assetCount++;
+ }
+ }
+
+ if (!assetOverrideCallback_f)
+ continue;
+
+ XAssetEntry overrideAssetEntry;
+ for (int overrideAssetEntryIndex = assetEntry.nextOverride; overrideAssetEntryIndex; overrideAssetEntryIndex = overrideAssetEntry.nextOverride)
+ {
+ overrideAssetEntry = db_assetEntryPool[overrideAssetEntryIndex].entry;
+
+ XAsset* overrideAssetOffset = (XAsset*)(db_assetEntryPool.pAddress + overrideAssetEntryIndex);
+ ForeignPointer overrideZoneName(db_zoneNames.pAddress + overrideAssetEntry.zoneIndex);
+ if (zone == NULL || (zone != NULL && _stricmp(overrideZoneName->name, zone) == 0))
+ {
+ ForeignPointer overrideAsset(overrideAssetOffset);
+ if (assetOverrideCallback_f(overrideAsset, overrideZoneName, data) == 0)
+ assetCount++;
+ }
+ }
+ }
+ }
+
+ return assetCount;
+}
+
+void* DB_FindSingletonAssetForType(XAssetType type)
+{
+ ForeignPointer& db_hashTable = g_process.db_hashTable;
+ ForeignPointer& db_assetEntryPool = g_process.db_assetEntryPool;
+ ForeignPointer& db_zoneNames = g_process.db_zoneNames;
+
+ SIZE_T numofbytesread = 0;
+
+ unsigned int assetCount = 0;
+ for (unsigned int hashIndex = 0; hashIndex < 0x8000; hashIndex++)
+ {
+ __int16 nextAssetEntryIndex = 0;
+ for (int assetEntryIndex = db_hashTable[hashIndex]; assetEntryIndex; assetEntryIndex = nextAssetEntryIndex)
+ {
+ XAssetEntry assetEntry = db_assetEntryPool[assetEntryIndex].entry;
+
+ nextAssetEntryIndex = assetEntry.nextHash;
+ if (assetEntry.asset.type != type)
+ continue;
+
+ return assetEntry.asset.header.data;
+ }
+ }
+
+ return NULL;
+}
+
+int DB_ListAssetPool_AssetCallback(ForeignPointer& asset, ForeignPointer& zoneName)
+{
+ const char* assetName = DB_GetXAssetName(asset.pAddress);
+ Con_Print("%s,%s\n", assetName, zoneName->name);
+ return 0;
+}
+
+void DB_ListAssetPool(XAssetType type, const char* zone)
+{
+ int count = DB_EnumAssetPool(type, DB_ListAssetPool_AssetCallback, DB_ListAssetPool_AssetCallback, zone);
+ printf("Total of %d assets in %s pool\n", count, DB_GetXAssetTypeName(type));
+}
+
+void DB_WaitForMapToLoad(void)
+{
+ // Delay (ms) to prevent the checks from causing the game process to slow down
+ const unsigned int sleep_delay = 100;
+
+ Con_Print("Waiting for map to load... ");
+
+ while (*g_process.cl_ingame == NULL)
+ {
+ Sleep(sleep_delay);
+ }
+
+ ForeignPointer cl_ingame_val = *g_process.cl_ingame;
+ while (!cl_ingame_val->current.enabled)
+ {
+ Sleep(sleep_delay);
+ }
+
+ Con_Print("Map Loaded!\n");
+}
\ No newline at end of file
diff --git a/components/asset_util/cmds/ripper/db_registry.h b/components/asset_util/cmds/ripper/db_registry.h
new file mode 100644
index 00000000..c17e779f
--- /dev/null
+++ b/components/asset_util/cmds/ripper/db_registry.h
@@ -0,0 +1,125 @@
+#pragma once
+#include "../../sys/process.h"
+#include
+#include
+
+enum XAssetType
+{
+ ASSET_TYPE_XMODELPIECES = 0x0,
+ ASSET_TYPE_PHYSPRESET = 0x1,
+ ASSET_TYPE_PHYSCONSTRAINTS = 0x2,
+ ASSET_TYPE_DESTRUCTIBLEDEF = 0x3,
+ ASSET_TYPE_XANIMPARTS = 0x4,
+ ASSET_TYPE_XMODEL = 0x5,
+ ASSET_TYPE_MATERIAL = 0x6,
+ ASSET_TYPE_TECHNIQUE_SET = 0x7,
+ ASSET_TYPE_IMAGE = 0x8,
+ ASSET_TYPE_SOUND = 0x9,
+ ASSET_TYPE_SOUND_PATCH = 0xA,
+ ASSET_TYPE_CLIPMAP = 0xB,
+ ASSET_TYPE_CLIPMAP_PVS = 0xC,
+ ASSET_TYPE_COMWORLD = 0xD,
+ ASSET_TYPE_GAMEWORLD_SP = 0xE,
+ ASSET_TYPE_GAMEWORLD_MP = 0xF,
+ ASSET_TYPE_MAP_ENTS = 0x10,
+ ASSET_TYPE_GFXWORLD = 0x11,
+ ASSET_TYPE_LIGHT_DEF = 0x12,
+ ASSET_TYPE_UI_MAP = 0x13,
+ ASSET_TYPE_FONT = 0x14,
+ ASSET_TYPE_MENULIST = 0x15,
+ ASSET_TYPE_MENU = 0x16,
+ ASSET_TYPE_LOCALIZE_ENTRY = 0x17,
+ ASSET_TYPE_WEAPON = 0x18,
+ ASSET_TYPE_WEAPONDEF = 0x19,
+ ASSET_TYPE_WEAPON_VARIANT = 0x1A,
+ ASSET_TYPE_SNDDRIVER_GLOBALS = 0x1B,
+ ASSET_TYPE_FX = 0x1C,
+ ASSET_TYPE_IMPACT_FX = 0x1D,
+ ASSET_TYPE_AITYPE = 0x1E,
+ ASSET_TYPE_MPTYPE = 0x1F,
+ ASSET_TYPE_MPBODY = 0x20,
+ ASSET_TYPE_MPHEAD = 0x21,
+ ASSET_TYPE_CHARACTER = 0x22,
+ ASSET_TYPE_XMODELALIAS = 0x23,
+ ASSET_TYPE_RAWFILE = 0x24,
+ ASSET_TYPE_STRINGTABLE = 0x25,
+ ASSET_TYPE_PACK_INDEX = 0x26,
+ ASSET_TYPE_XGLOBALS = 0x27,
+ ASSET_TYPE_DDL = 0x28,
+ ASSET_TYPE_GLASSES = 0x29,
+ ASSET_TYPE_EMBLEMSET = 0x2A,
+ ASSET_TYPE_COUNT = 0x2B,
+ ASSET_TYPE_STRING = 0x2B,
+ ASSET_TYPE_ASSETLIST = 0x2C,
+};
+
+union XAssetHeader
+{
+ struct SndBank *sound;
+ void *data;
+};
+
+struct XAsset
+{
+ XAssetType type;
+ XAssetHeader header;
+};
+
+struct XAssetEntry
+{
+ XAsset asset;
+ char zoneIndex;
+ bool inuse;
+ unsigned __int16 nextHash;
+ unsigned __int16 nextOverride;
+ unsigned __int16 usageFrame;
+};
+
+union XAssetEntryPoolEntry
+{
+ XAssetEntry entry;
+ XAssetEntryPoolEntry *next;
+};
+
+enum FF_DIR
+{
+ FFD_DEFAULT = 0x0,
+ FFD_MOD_DIR = 0x1,
+ FFD_USER_MAP = 0x2,
+};
+
+struct XZoneName
+{
+ char name[64];
+ int flags;
+ int fileSize;
+ FF_DIR dir;
+ bool loaded;
+};
+
+#include "foreign_ptr.h"
+
+const char* DB_GetXAssetTypeName(int type);
+const char* DB_GetXAssetName(XAsset *asset);
+
+//
+// Enumerate over all assets of a given type in the asset pool
+// if assetOverrideCallback_f is NULL, override assets will be ignored
+// the zone argument can be used to filter by a specific zone name
+//
+// The return value is the number of assets that were successfully enumerated (where the callback succeeded)
+//
+typedef int(__cdecl* asset_callback_t)(ForeignPointer& asset, ForeignPointer& zoneName);
+typedef int(__cdecl* asset_callback_ex_t)(ForeignPointer& asset, ForeignPointer& zoneName, void* data);
+
+int DB_EnumAssetPool(XAssetType type, asset_callback_t assetCallback_f, asset_callback_t assetOverrideCallback_f, const char* zone = NULL);
+int DB_EnumAssetPoolEx(XAssetType type, asset_callback_ex_t assetCallback_f, asset_callback_ex_t assetOverrideCallback_f, void* data, const char* zone = NULL);
+
+void* DB_FindSingletonAssetForType(XAssetType type);
+
+//
+// List all assets of a given type in the asset pool
+// the zone argument can be used to filter by a specific zone name
+//
+void DB_ListAssetPool(XAssetType type, const char* zone = NULL);
+void DB_WaitForMapToLoad(void);
diff --git a/components/asset_util/cmds/ripper/dvar.h b/components/asset_util/cmds/ripper/dvar.h
new file mode 100644
index 00000000..957d1ba3
--- /dev/null
+++ b/components/asset_util/cmds/ripper/dvar.h
@@ -0,0 +1,89 @@
+#pragma once
+
+enum dvarType_t
+{
+ DVAR_TYPE_BOOL = 0x0,
+ DVAR_TYPE_FLOAT = 0x1,
+ DVAR_TYPE_FLOAT_2 = 0x2,
+ DVAR_TYPE_FLOAT_3 = 0x3,
+ DVAR_TYPE_FLOAT_4 = 0x4,
+ DVAR_TYPE_INT = 0x5,
+ DVAR_TYPE_ENUM = 0x6,
+ DVAR_TYPE_STRING = 0x7,
+ DVAR_TYPE_COLOR = 0x8,
+ DVAR_TYPE_INT64 = 0x9,
+ DVAR_TYPE_LINEAR_COLOR_RGB = 0xA,
+ DVAR_TYPE_COLOR_XYZ = 0xB,
+ DVAR_TYPE_COUNT = 0xC,
+};
+
+enum DvarSetSource
+{
+ DVAR_SOURCE_INTERNAL = 0x0,
+ DVAR_SOURCE_EXTERNAL = 0x1,
+ DVAR_SOURCE_SCRIPT = 0x2,
+ DVAR_SOURCE_DEVGUI = 0x3,
+};
+
+union DvarValue
+{
+ bool enabled;
+ int integer;
+ unsigned int unsignedInt;
+ __int64 integer64;
+ unsigned __int64 unsignedInt64;
+ float value;
+ float vector[4];
+ const char *string;
+ char color[4];
+};
+
+union DvarLimits
+{
+ struct
+ {
+ int stringCount;
+ const char **strings;
+ } enumeration;
+
+ struct
+ {
+ int min;
+ int max;
+ } integer;
+
+ struct
+ {
+ __int64 min;
+ __int64 max;
+ } integer64;
+
+ struct
+ {
+ float min;
+ float max;
+ } value;
+
+ struct
+ {
+ float min;
+ float max;
+ } vector;
+};
+
+struct dvar_s
+{
+ const char *name;
+ const char *description;
+ int hash;
+ unsigned int flags;
+ dvarType_t type;
+ bool modified;
+ bool loadedFromSaveGame;
+ DvarValue current;
+ DvarValue latched;
+ DvarValue reset;
+ DvarValue saved;
+ DvarLimits domain;
+ dvar_s *hashNext;
+};
\ No newline at end of file
diff --git a/components/asset_util/cmds/ripper/foreign_ptr.h b/components/asset_util/cmds/ripper/foreign_ptr.h
new file mode 100644
index 00000000..87cc99db
--- /dev/null
+++ b/components/asset_util/cmds/ripper/foreign_ptr.h
@@ -0,0 +1,52 @@
+#pragma once
+#include
+#include
+
+template
+class ForeignPointer
+{
+private:
+ T content;
+public:
+ T* pAddress;
+
+ bool operator==(void* address) {return pAddress == address};
+
+ T operator*(void) const;
+ T operator[](int index) const;
+ T* operator->(void) const;
+
+ ForeignPointer(void) {};
+ ForeignPointer(T* address) : pAddress(address) {};
+ //ForeignPointer(ForeignPointer& arg) : pAddress(arg.pAddress) {};
+ //ForeignPointer(void* address) : pAddress(address) {};
+ ~ForeignPointer(void) {};
+};
+
+#include "process_info.h"
+
+template
+T ForeignPointer::operator*(void) const
+{
+ T out;
+ SIZE_T numofbytesread = 0;
+ ReadProcessMemory(g_process.handle, pAddress, &out, sizeof(T), &numofbytesread);
+ return out;
+};
+
+template
+T ForeignPointer::operator[](int index) const
+{
+ T out;
+ SIZE_T numofbytesread = 0;
+ ReadProcessMemory(g_process.handle, pAddress + index, &out, sizeof(T), &numofbytesread);
+ return out;
+};
+
+template
+T* ForeignPointer::operator->(void) const
+{
+ SIZE_T numofbytesread;
+ ReadProcessMemory(g_process.handle, (void*)pAddress, (void*)&content, sizeof(T), &numofbytesread);
+ return (T*)&content;
+}
diff --git a/components/asset_util/cmds/ripper/process_info.cpp b/components/asset_util/cmds/ripper/process_info.cpp
new file mode 100644
index 00000000..3a0c207f
--- /dev/null
+++ b/components/asset_util/cmds/ripper/process_info.cpp
@@ -0,0 +1,42 @@
+#pragma once
+#include "process_info.h"
+
+ProcessInfo g_process = { 0 };
+
+int ProcessInfo_Init(processId_t pid)
+{
+ g_process.handle = OpenProcess(PROCESS_VM_READ, FALSE, pid);
+ g_process.pid = pid;
+
+ switch (PROCESS_TYPE type = Process_GetProcessType(pid))
+ {
+ case PROCESS_BLACK_OPS:
+ g_process.db_hashTable = ForeignPointer((unsigned __int16*)0x00CD81F8);
+ g_process.db_assetEntryPool = ForeignPointer((XAssetEntryPoolEntry*)0x00DE85D8);
+ g_process.db_zoneNames = ForeignPointer((XZoneName*)0x010C6608);
+ g_process.cl_ingame = ForeignPointer((dvar_s**)0x02910158);
+ break;
+ case PROCESS_BLACK_OPS_MP:
+ g_process.db_hashTable = ForeignPointer((unsigned __int16*)0x24365D8);
+ g_process.db_assetEntryPool = ForeignPointer((XAssetEntryPoolEntry*)0x252FC18);
+ g_process.db_zoneNames = ForeignPointer((XZoneName*)0x028C9B08);
+ g_process.cl_ingame = ForeignPointer((dvar_s**)0x00E67B30);
+ break;
+ default:
+ ProcessInfo_Free();
+ return -1;
+ }
+
+ return 0;
+}
+
+void ProcessInfo_Free(void)
+{
+ CloseHandle(g_process.handle);
+ g_process.handle = NULL;
+ g_process.pid = NULL;
+
+ g_process.db_hashTable = nullptr;
+ g_process.db_zoneNames = nullptr;
+ g_process.db_assetEntryPool = nullptr;
+}
diff --git a/components/asset_util/cmds/ripper/process_info.h b/components/asset_util/cmds/ripper/process_info.h
new file mode 100644
index 00000000..879d08ea
--- /dev/null
+++ b/components/asset_util/cmds/ripper/process_info.h
@@ -0,0 +1,27 @@
+/*
+ This header file contains the structure offsets struct, system initializers / destructors, and other process info that is relevent to only the ripper command and not asset_util as a whole
+*/
+
+#pragma once
+#include "../../sys/process.h"
+#include "db_registry.h"
+#include "foreign_ptr.h"
+
+#include "dvar.h"
+
+struct ProcessInfo
+{
+ HANDLE handle;
+ processId_t pid;
+
+ ForeignPointer db_hashTable;
+ ForeignPointer db_assetEntryPool;
+ ForeignPointer db_zoneNames;
+
+ ForeignPointer cl_ingame;
+};
+
+extern ProcessInfo g_process;
+
+int ProcessInfo_Init(processId_t pid);
+void ProcessInfo_Free(void);
diff --git a/components/asset_util/cmds/ripper/snd_alias_db.h b/components/asset_util/cmds/ripper/snd_alias_db.h
new file mode 100644
index 00000000..d14cb8b2
--- /dev/null
+++ b/components/asset_util/cmds/ripper/snd_alias_db.h
@@ -0,0 +1,190 @@
+#pragma once
+
+enum snd_asset_format
+{
+ SND_ASSET_FORMAT_PCMS16 = 0x0,
+ SND_ASSET_FORMAT_PCMS24 = 0x1,
+ SND_ASSET_FORMAT_PCMS32 = 0x2,
+ SND_ASSET_FORMAT_IEEE = 0x3,
+ SND_ASSET_FORMAT_XMA4 = 0x4,
+ SND_ASSET_FORMAT_MP3 = 0x5,
+ SND_ASSET_FORMAT_MSADPCM = 0x6,
+ SND_ASSET_FORMAT_WMA = 0x7,
+};
+
+enum snd_asset_channel
+{
+ SND_ASSET_CHANNEL_L = 0x1,
+ SND_ASSET_CHANNEL_R = 0x2,
+ SND_ASSET_CHANNEL_C = 0x4,
+ SND_ASSET_CHANNEL_LFE = 0x8,
+ SND_ASSET_CHANNEL_LS = 0x10,
+ SND_ASSET_CHANNEL_RS = 0x20,
+ SND_ASSET_CHANNEL_LB = 0x40,
+ SND_ASSET_CHANNEL_RB = 0x80,
+};
+
+enum snd_asset_flags
+{
+ SND_ASSET_FLAG_DEFAULT = 0x0,
+ SND_ASSET_FLAG_LOOPING = 0x1,
+ SND_ASSET_FLAG_PAD_LOOP_BUFFER = 0x2,
+};
+
+struct snd_radverb
+{
+ char name[32];
+ unsigned int id;
+ float smoothing;
+ float earlyTime;
+ float lateTime;
+ float earlyGain;
+ float lateGain;
+ float returnGain;
+ float earlyLpf;
+ float lateLpf;
+ float inputLpf;
+ float dampLpf;
+ float wallReflect;
+ float dryGain;
+ float earlySize;
+ float lateSize;
+ float diffusion;
+};
+
+struct snd_snapshot
+{
+ char name[32];
+ unsigned int id;
+ char occlusionName[32];
+ unsigned int occlusionId;
+ float fadeIn;
+ float fadeOut;
+ float distance;
+ unsigned int fadeInCurve;
+ unsigned int fadeOutCurve;
+ float attenuation[64];
+};
+
+struct snd_asset
+{
+ unsigned int version;
+ unsigned int frame_count;
+ unsigned int frame_rate;
+ unsigned int channel_count;
+ unsigned int header_size;
+ unsigned int block_size;
+ unsigned int buffer_size;
+ snd_asset_format format;
+ snd_asset_channel channel_flags;
+ snd_asset_flags flags;
+ unsigned int seek_table_count;
+ unsigned int *seek_table;
+ unsigned int data_size;
+ char *data;
+};
+
+struct snd_alias_t
+{
+ const char *name;
+ unsigned int id;
+ const char *subtitle;
+ const char *secondaryname;
+ struct SoundFile *soundFile;
+ unsigned int flags;
+ unsigned int duck;
+ unsigned int contextType;
+ unsigned int contextValue;
+ unsigned __int16 fluxTime;
+ unsigned __int16 startDelay;
+ unsigned __int16 reverbSend;
+ unsigned __int16 centerSend;
+ unsigned __int16 volMin;
+ unsigned __int16 volMax;
+ unsigned __int16 teamVolMod;
+ unsigned __int16 pitchMin;
+ unsigned __int16 pitchMax;
+ unsigned __int16 teamPitchMod;
+ unsigned __int16 distMin;
+ unsigned __int16 distMax;
+ unsigned __int16 distReverbMax;
+ unsigned __int16 envelopMin;
+ unsigned __int16 envelopMax;
+ unsigned __int16 envelopPercentage;
+ char minPriorityThreshold;
+ char maxPriorityThreshold;
+ char probability;
+ char occlusionLevel;
+ char occlusionWetDry;
+ char minPriority;
+ char maxPriority;
+ char pan;
+ char dryCurve;
+ char wetCurve;
+ char dryMinCurve;
+ char wetMinCurve;
+ char limitCount;
+ char entityLimitCount;
+ char snapshotGroup;
+};
+
+struct snd_alias_list_t
+{
+ const char *name;
+ unsigned int id;
+ snd_alias_t *head;
+ int count;
+ int sequence;
+};
+
+struct LoadedSound
+{
+ const char *name;
+ snd_asset sound;
+};
+
+struct PrimedSound
+{
+ const char *name;
+ char *buffer;
+ unsigned int size;
+};
+
+struct StreamedSound
+{
+ char *filename;
+ PrimedSound *primeSnd;
+};
+
+union SoundFileRef
+{
+ LoadedSound *loadSnd;
+ StreamedSound *streamSnd;
+};
+
+struct SoundFile
+{
+ SoundFileRef u;
+ char type;
+ char exists;
+};
+
+struct SndIndexEntry
+{
+ unsigned __int16 value;
+ unsigned __int16 next;
+};
+
+struct SndBank
+{
+ const char *name;
+ unsigned int aliasCount;
+ snd_alias_list_t *alias;
+ SndIndexEntry *aliasIndex;
+ unsigned int packHash;
+ unsigned int packLocation;
+ unsigned int radverbCount;
+ snd_radverb *radverbs;
+ unsigned int snapshotCount;
+ snd_snapshot *snapshots;
+};
diff --git a/components/asset_util/cmds/ripper/snd_csv_enum.cpp b/components/asset_util/cmds/ripper/snd_csv_enum.cpp
new file mode 100644
index 00000000..e7f1a2f1
--- /dev/null
+++ b/components/asset_util/cmds/ripper/snd_csv_enum.cpp
@@ -0,0 +1,58 @@
+#include "snd_csv_enum.h"
+#include "snd_alias_db.h"
+#include "foreign_ptr.h"
+
+//
+// Encode an enum's in a larger bitflags variable
+// size is the number of bits to use for the input value
+// shift is the number of bits to shift the masked input value
+//
+// For encoding enums from a snd_csv_entry_t
+// 'size' is (int)entry->minimum
+// 'shift' is (int)entry->maximum
+//
+void Snd_CSV_EncodeEnumBits(unsigned int* bits, unsigned int value, int size, int shift)
+{
+#if 0
+ * bits = ((value & ((1 << size) - 1)) << shift) | ((*bits) & ~(((1 << size) - 1) << shift));
+ return;
+#endif
+
+ unsigned int mask = (1 << size) - 1;
+ unsigned int encoded_value = (value & mask) << shift;
+
+ *bits = ((*bits) & ~(mask)) | encoded_value;
+}
+
+//
+// Extract an encoded enum's value from a larger bitflags variable
+// size is the number of bits to use for the input value
+// shift is the number of bits to shift the masked input value
+//
+unsigned int Snd_CSV_DecodeEnumBits(unsigned int bits, int size, int shift)
+{
+ unsigned int mask = (1 << size) - 1;
+ return (bits >> shift) & mask;
+}
+
+const char* SND_CSV_ResolveEnumBitsString(ForeignPointer& alias, snd_csv_enum_bits_entry_t& enum_bits_entry)
+{
+ _ASSERT(enum_bits_entry.enum_stringtable);
+
+ unsigned int bits = alias->flags;
+ unsigned int enum_val = Snd_CSV_DecodeEnumBits(bits, enum_bits_entry.size, enum_bits_entry.shift);
+
+ unsigned int enum_stringCount = 0;
+ for (; enum_bits_entry.enum_stringtable[enum_stringCount]; enum_stringCount++);
+
+ if (enum_val > enum_stringCount)
+ return NULL;
+
+ return enum_bits_entry.enum_stringtable[enum_val];
+}
+
+unsigned int SND_CSV_ResolveEnumBitsValue(ForeignPointer& alias, snd_csv_enum_bits_entry_t& enum_bits_entry)
+{
+ unsigned int bits = alias->flags;
+ return Snd_CSV_DecodeEnumBits(bits, enum_bits_entry.size, enum_bits_entry.shift);
+}
diff --git a/components/asset_util/cmds/ripper/snd_csv_enum.h b/components/asset_util/cmds/ripper/snd_csv_enum.h
new file mode 100644
index 00000000..08d6cd29
--- /dev/null
+++ b/components/asset_util/cmds/ripper/snd_csv_enum.h
@@ -0,0 +1,147 @@
+#pragma once
+#include
+#include "foreign_ptr.h"
+
+static const char* fields_soundalias[] =
+{
+ "name",
+ "file",
+ "template",
+ "loadspec",
+ "secondary",
+ "group",
+ "vol_min",
+ "vol_max",
+ "team_vol_mod",
+ "dist_min",
+ "dist_max",
+ "dist_reverb_max",
+ "volume_falloff_curve",
+ "reverb_falloff_curve",
+ "volume_min_falloff_curve",
+ "reverb_min_falloff_curve",
+ "limit_count",
+ "limit_type",
+ "entity_limit_count",
+ "entity_limit_type",
+ "pitch_min",
+ "pitch_max",
+ "team_pitch_mod",
+ "min_priority",
+ "max_priority",
+ "min_priority_threshold",
+ "max_priority_threshold",
+ "spatialized",
+ "type",
+ "loop",
+ "randomize_type",
+ "probability",
+ "start_delay",
+ "reverb_send",
+ "duck",
+ "pan",
+ "center_send",
+ "envelop_min",
+ "envelop_max",
+ "envelop_percentage",
+ "occlusion_level",
+ "occlusion_wet_dry",
+ "is_big",
+ "distance_lpf",
+ "move_type",
+ "move_time",
+ "real_delay",
+ "subtitle",
+ "mature",
+ "doppler",
+ "futz",
+ "context_type",
+ "context_value",
+ "compression",
+ "timescale",
+ "music",
+ "fade_in",
+ "fade_out",
+ "pc_format",
+ "pause",
+ "stop_on_death",
+ "bus",
+ "snapshot",
+ "voice_limit",
+ "file_xenon",
+ "file_size_xenon",
+ "file_ps3",
+ "file_size_ps3",
+ "file_pc",
+ "file_size_pc",
+ "file_wii",
+ "file_size_wii",
+ "source_csv",
+ "language",
+};
+
+static const char* fields_snapshot[] =
+{
+ "name",
+ "occlusion",
+ "loadspec",
+ "fadeIn",
+ "fadeInCurve",
+ "fadeOut",
+ "fadeOutCurve",
+ "distance",
+ // ... (Everything after this point is auto generated from snapshotGroups)
+};
+
+static const char* fields_radverb[] =
+{
+ "name",
+ "loadspec",
+ "smoothing",
+ "earlyTime",
+ "lateTime",
+ "earlyGain",
+ "lateGain",
+ "returnGain",
+ "earlyLpf",
+ "lateLpf",
+ "inputLpf",
+ "dampLpf",
+ "wallReflect",
+ "dryGain",
+ "earlySize",
+ "lateSize",
+ "diffusion",
+};
+
+static const char *enum_bus[3] = { "world", "game", "voice" };
+static const char *enum_type[5] = { "unknown", "loaded", "streamed", "primed", NULL };
+static const char *enum_priority[5] = { "none", "oldest", "reject", "priority", NULL };
+
+static const char *enum_move_type[9] =
+{
+ "none",
+ "left_player",
+ "center_player",
+ "right_player",
+ "random_player",
+ "left_shot",
+ "center_shot",
+ "right_shot",
+ NULL
+};
+
+static const char *enum_randomize_type[4] = { "volume", "pitch", "variant", NULL };
+static const char *enum_spatialized[4] = { "2d", "3d", "2.5d", NULL };
+static const char *enum_looping[3] = { "nonlooping", "looping", NULL };
+static const char *enum_yes_no[3] = { "no", "yes", NULL };
+
+struct snd_csv_enum_bits_entry_t
+{
+ int size;
+ int shift;
+ const char** enum_stringtable;
+};
+
+const char* SND_CSV_ResolveEnumBitsString(ForeignPointer& alias, snd_csv_enum_bits_entry_t& enum_bits_entry);
+unsigned int SND_CSV_ResolveEnumBitsValue(ForeignPointer& alias, snd_csv_enum_bits_entry_t& enum_bits_entry);
diff --git a/components/asset_util/cmds/ripper/snd_driver_db.h b/components/asset_util/cmds/ripper/snd_driver_db.h
new file mode 100644
index 00000000..f6b6b5f6
--- /dev/null
+++ b/components/asset_util/cmds/ripper/snd_driver_db.h
@@ -0,0 +1,110 @@
+#pragma once
+
+enum snd_category_t
+{
+ SND_CATEGORY_SFX = 0x0,
+ SND_CATEGORY_MUSIC = 0x1,
+ SND_CATEGORY_VOICE = 0x2,
+ SND_CATEGORY_UI = 0x3,
+ SND_CATEGORY_COUNT = 0x4,
+};
+
+struct snd_group
+{
+ char name[32];
+ char parentName[32];
+ unsigned int id;
+ int parentIndex;
+ snd_category_t category;
+ unsigned __int16 attenuationSp;
+ unsigned __int16 attenuationMp;
+};
+
+struct snd_curve
+{
+ char name[32];
+ unsigned int id;
+ float points[8][2];
+};
+
+struct snd_pan
+{
+ char name[32];
+ unsigned int id;
+ float front;
+ float back;
+ float center;
+ float lfe;
+ float left;
+ float right;
+};
+
+struct snd_snapshot_group
+{
+ char name[32];
+};
+
+struct snd_context
+{
+ unsigned int type;
+ unsigned int valueCount;
+ unsigned int values[8];
+};
+
+struct snd_master
+{
+ char name[32];
+ unsigned int id;
+ float notchE;
+ float notchG;
+ float notchF;
+ float notchQ;
+ float lowE;
+ float lowG;
+ float lowF;
+ float lowQ;
+ float peak1E;
+ float peak1G;
+ float peak1F;
+ float peak1Q;
+ float peak2E;
+ float peak2G;
+ float peak2F;
+ float peak2Q;
+ float hiE;
+ float hiG;
+ float hiF;
+ float hiQ;
+ float eqG;
+ float compE;
+ float compPG;
+ float compMG;
+ float compT;
+ float compR;
+ float compTA;
+ float compTR;
+ float limitE;
+ float limitPG;
+ float limitMG;
+ float limitT;
+ float limitR;
+ float limitTA;
+ float limitTR;
+};
+
+struct SndDriverGlobals
+{
+ const char *name;
+ unsigned int groupCount;
+ snd_group *groups;
+ unsigned int curveCount;
+ snd_curve *curves;
+ unsigned int panCount;
+ snd_pan *pans;
+ unsigned int snapshotGroupCount;
+ snd_snapshot_group *snapshotGroups;
+ unsigned int contextCount;
+ snd_context *contexts;
+ unsigned int masterCount;
+ snd_master *masters;
+};
diff --git a/components/asset_util/cmds/ripper/snd_ripper.cpp b/components/asset_util/cmds/ripper/snd_ripper.cpp
new file mode 100644
index 00000000..7319f5ad
--- /dev/null
+++ b/components/asset_util/cmds/ripper/snd_ripper.cpp
@@ -0,0 +1,621 @@
+#include "snd_ripper.h"
+#include "../../common/fs.h"
+#include "../../common/io.h"
+
+#include "../../sys/AppInfo.h"
+#include "../../cvar.h"
+
+static snd_csv_enum_bits_entry_t ee_limit_type = { 2, 25, enum_priority };
+static snd_csv_enum_bits_entry_t ee_entity_limit_type = { 2, 27, enum_priority };
+static snd_csv_enum_bits_entry_t ee_randomize_type = { 3, 29, enum_randomize_type };
+static snd_csv_enum_bits_entry_t ee_move_type = { 3, 22, enum_move_type };
+static snd_csv_enum_bits_entry_t ee_type = { 2, 14, enum_type };
+static snd_csv_enum_bits_entry_t ee_group = { 6, 16, NULL };
+static snd_csv_enum_bits_entry_t ee_real_delay = { 1, 2, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_distance_lpf = { 1, 3, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_doppler = { 1, 4, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_is_big = { 1, 5, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_loop = { 1, 0, enum_looping };
+static snd_csv_enum_bits_entry_t ee_spatialized = { 1, 1, enum_spatialized };
+static snd_csv_enum_bits_entry_t ee_futz = { 1, 6, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_music = { 1, 8, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_timescale = { 1, 10, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_pause = { 1, 7, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_stop_on_death = { 1, 9, enum_yes_no };
+static snd_csv_enum_bits_entry_t ee_bus = { 2, 12, enum_bus };
+static snd_csv_enum_bits_entry_t ee_voice_limit = { 1, 11, enum_yes_no };
+
+struct snd_csv_context_entry_t
+{
+ const char* type;
+ const char* value[8];
+};
+
+static snd_csv_context_entry_t contexts[] =
+{
+ {"ringoff_plr", {"indoor", "outdoor", NULL, NULL, NULL, NULL, NULL, NULL} },
+ {"mature", {"explicit", "safe", NULL, NULL, NULL, NULL, NULL, NULL} },
+ {"test", {"high", "low", NULL, NULL, NULL, NULL, NULL, NULL} },
+ {"hazmat", {"mask", NULL, NULL, NULL, NULL, NULL, NULL, NULL} },
+};
+
+//
+// Read an external string with max length (len)
+// if len == 0 - the string can be any length
+//
+std::string ReadString(const char* ptr, int len = 0)
+{
+ std::string out = "\0";
+ SIZE_T numofbytesread;
+
+ for (int i = 0; len == 0 || i < len; i++)
+ {
+ char c = 0;
+ ReadProcessMemory(g_process.handle, (void*)ptr, (void*)&c, sizeof(c), &numofbytesread);
+
+ if (c == '\0' || numofbytesread == 0)
+ return out;
+
+ out += c;
+ ptr++;
+ }
+
+ return out;
+}
+
+double __cdecl SND_CSV_CENTS_Encode(double val)
+{
+ double v = val / 1200.0;
+ return pow(2.0, v);
+}
+
+double SND_CSV_CENTS_Decode(double val)
+{
+ double v = log(val) / log(2);
+ return v * 1200;
+}
+
+unsigned int __cdecl SND_HashName(const char *name)
+{
+ int hash = 0;
+
+ if (name && *name)
+ {
+ unsigned int len = strlen(name);
+ hash = 5381;
+ for (unsigned i = 0; i < len; ++i)
+ hash = (hash << 16) + (hash << 6) + tolower(name[i]) - hash;
+ if (!hash)
+ hash = 1;
+ }
+
+ return hash;
+}
+
+void Snd_CSV_PrintHeader_Soundalias(FILE* f)
+{
+ for (int i = 0; i < ARRAYSIZE(fields_soundalias); i++)
+ {
+ fprintf(f, "%s,", fields_soundalias[i]);
+ }
+ fprintf(f, "\n");
+}
+
+void Snd_CSV_PrintHeader_Snapshot(FILE* f, ForeignPointer& globals)
+{
+ for (int i = 0; i < ARRAYSIZE(fields_snapshot); i++)
+ {
+ fprintf(f, "%s,", fields_snapshot[i]);
+ }
+
+ ForeignPointer snapshotGroups(globals->snapshotGroups);
+ for (unsigned int i = 0; i < globals->snapshotGroupCount; i++)
+ {
+ fprintf(f, "%s,", snapshotGroups[i].name);
+ }
+
+ fprintf(f, "\n");
+}
+
+void Snd_CSV_PrintHeader_Radverb(FILE* f)
+{
+ for (int i = 0; i < ARRAYSIZE(fields_radverb); i++)
+ {
+ fprintf(f, "%s,", fields_radverb[i]);
+ }
+ fprintf(f, "\n");
+}
+
+int Rip_Sound_Alias_Callback_f(ForeignPointer& alias, snd_ripper_instance_info_t instance)
+{
+ std::string name = ReadString(alias->name, 256);
+ std::string file = "";
+
+ const char* _template = ""; //template
+ const char* loadspec = ""; // unknown
+ std::string secondaryname = ReadString(alias->secondaryname, 256); //secondary
+
+ unsigned int group_index = SND_CSV_ResolveEnumBitsValue(alias, ee_group);
+ _ASSERT(group_index < instance.globals->groupCount);
+
+ ForeignPointer group((snd_group*)instance.globals->groups + group_index);
+
+ unsigned short vol_min = alias->volMin;
+ unsigned short vol_max = alias->volMax;
+ unsigned short team_vol_mod = alias->teamVolMod;
+ unsigned short dist_min = alias->distMin;
+ unsigned short dist_max = alias->distMax;
+ unsigned short dist_reverb_max = alias->distReverbMax;
+
+ _ASSERT(alias->dryCurve < (int)instance.globals->curveCount);
+ _ASSERT(alias->wetCurve < (int)instance.globals->curveCount);
+ _ASSERT(alias->dryMinCurve < (int)instance.globals->curveCount);
+ _ASSERT(alias->wetMinCurve < (int)instance.globals->curveCount);
+
+ ForeignPointer curves((snd_curve*)instance.globals->curves);
+
+ const char* volume_falloff_curve = curves[alias->dryCurve].name;
+ const char* reverb_falloff_curve = curves[alias->wetCurve].name;
+ const char* volume_min_falloff_curve = curves[alias->dryMinCurve].name;
+ const char* reverb_min_falloff_curve = curves[alias->wetMinCurve].name;
+
+ char limit_count = alias->limitCount;
+ const char* limit_type = SND_CSV_ResolveEnumBitsString(alias, ee_limit_type);
+ char entity_limit_count = alias->entityLimitCount;
+ const char* entity_limit_type = SND_CSV_ResolveEnumBitsString(alias, ee_entity_limit_type);
+ unsigned short pitch_min = alias->pitchMin;
+ unsigned short pitch_max = alias->pitchMax;
+ unsigned short team_pitch_mod = alias->teamPitchMod;
+ char min_priority = alias->minPriority;
+ char max_priority = alias->maxPriority;
+ unsigned char min_priority_threshold = alias->minPriorityThreshold;
+ unsigned char max_priority_threshold = alias->maxPriorityThreshold;
+ const char* spatialized = SND_CSV_ResolveEnumBitsString(alias, ee_spatialized);
+ const char* type = SND_CSV_ResolveEnumBitsString(alias, ee_type);
+ const char* loop = SND_CSV_ResolveEnumBitsString(alias, ee_loop);
+ const char* randomize_type = SND_CSV_ResolveEnumBitsString(alias, ee_randomize_type);
+ unsigned char probability = alias->probability;
+ unsigned short start_delay = alias->startDelay;
+ unsigned short reverb_send = alias->reverbSend;
+
+ float fade_in = 0;
+ float fade_out = 0;
+
+ unsigned int duck_hash = alias->duck;
+ std::string duck = (duck_hash) ? "" : "";
+
+ ForeignPointer snapshots((snd_snapshot*)instance.bank->snapshots);
+ if (duck_hash != 0)
+ {
+ for (auto& set : *instance.snapshots_map)
+ {
+ bool done = false;
+
+ for (auto& snapshot : set.second)
+ {
+ if (duck_hash == SND_HashName(snapshot->name))
+ {
+ duck = snapshot->name;
+ fade_in = snapshot->fadeIn;
+ fade_out = snapshot->fadeOut;
+ done = true;
+ break;
+ }
+ }
+
+ if (done)
+ break;
+ }
+ }
+
+ _ASSERT(duck != "");
+
+ unsigned char pan_index = alias->pan;
+ _ASSERT(pan_index < instance.globals->panCount);
+
+ ForeignPointer pan = ((snd_pan*)instance.globals->pans + pan_index);
+
+ unsigned short center_send = alias->centerSend;
+ unsigned short envelop_min = alias->envelopMin;
+ unsigned short envelop_max = alias->envelopMax;
+ unsigned short envelop_percentage = alias->envelopPercentage;
+ unsigned char occlusion_level = alias->occlusionLevel;
+ unsigned char occlusion_wet_dry = alias->occlusionWetDry;
+ const char* is_big = SND_CSV_ResolveEnumBitsString(alias, ee_is_big);
+ const char* distance_lpf = SND_CSV_ResolveEnumBitsString(alias, ee_distance_lpf);
+ const char* move_type = SND_CSV_ResolveEnumBitsString(alias, ee_move_type);
+ unsigned short move_time = alias->fluxTime;
+ const char* real_delay = SND_CSV_ResolveEnumBitsString(alias, ee_real_delay);
+ std::string subtitle = ReadString(alias->subtitle, 1024);
+ const char* mature = "both"; //mature
+ const char* doppler = SND_CSV_ResolveEnumBitsString(alias, ee_doppler);
+ const char* futz = SND_CSV_ResolveEnumBitsString(alias, ee_futz);
+
+ unsigned int contextType_hash = alias->contextType; // context_type
+ snd_csv_context_entry_t* context = NULL;
+
+ for (int i = 0; i < ARRAYSIZE(contexts); i++)
+ {
+ if (contextType_hash == SND_HashName(contexts[i].type))
+ {
+ context = &contexts[i];
+ break;
+ }
+ }
+
+ _ASSERT(!contextType_hash || (contextType_hash && context));
+
+ unsigned int contextValue_hash = alias->contextValue; //context_value
+ const char* context_value = NULL;
+
+ if (context != NULL)
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ if (context->value[i] == NULL)
+ break;
+
+ if (contextValue_hash = SND_HashName(context->value[i]))
+ {
+ context_value = (*context).value[i];
+ break;
+ }
+ }
+
+ _ASSERT(context_value);
+ }
+
+ unsigned short compression = 0; //compression
+
+ const char* timescale = SND_CSV_ResolveEnumBitsString(alias, ee_timescale);
+ const char* music = SND_CSV_ResolveEnumBitsString(alias, ee_music);
+
+ const char* pc_format = "xwma"; //pc_format - (This appears to be xwma in every existing alias - and theres no way to get it for non-loaded files)
+
+ const char* pause = SND_CSV_ResolveEnumBitsString(alias, ee_pause);
+ const char* stop_on_death = SND_CSV_ResolveEnumBitsString(alias, ee_stop_on_death);
+ const char* bus = SND_CSV_ResolveEnumBitsString(alias, ee_bus);
+
+ unsigned char snapshotGroup_index = alias->snapshotGroup;
+ _ASSERT(snapshotGroup_index < instance.globals->snapshotGroupCount);
+
+ ForeignPointer snapshotgroup = ((snd_snapshot_group*)instance.globals->snapshotGroups + snapshotGroup_index);
+ std::string snapshot = snapshotgroup->name;
+
+ const char* voice_limit = SND_CSV_ResolveEnumBitsString(alias, ee_voice_limit);
+ const char* file_xenon = "";
+ int file_size_xenon = 0;
+ const char* file_ps3 = "";
+ int file_size_ps3 = 0;
+ const char* file_pc = "";
+ int file_size_pc = 0;
+ const char* file_wii = "";
+ int file_size_wii = 0;
+ const char* source_csv = "";
+
+ ForeignPointer soundFile((SoundFile*)alias->soundFile);
+ if (strcmp(type, "loaded") == 0)
+ {
+ ForeignPointer snd((LoadedSound*)soundFile->u.loadSnd);
+ file = ReadString(snd->name);
+ }
+ else if (strcmp(type, "streamed") == 0)
+ {
+ ForeignPointer snd((StreamedSound*)soundFile->u.streamSnd);
+ file = ReadString(snd->filename);
+ }
+ else if (strcmp(type, "primed") == 0)
+ {
+ ForeignPointer streamed_snd((StreamedSound*)soundFile->u.streamSnd);
+ ForeignPointer primed_snd((PrimedSound*)streamed_snd->primeSnd);
+ file = ReadString(primed_snd->name);
+ }
+ else // Try to use loaded since the name is always first
+ {
+ ForeignPointer snd((LoadedSound*)soundFile->u.loadSnd);
+ file = ReadString(snd->name);
+ }
+
+ fprintf(instance.outputFile, "%s,", name.c_str());
+ fprintf(instance.outputFile, "%s,", file.c_str());
+ fprintf(instance.outputFile, "%s,", _template);
+ fprintf(instance.outputFile, "%s,", loadspec);
+ fprintf(instance.outputFile, "%s,", secondaryname.c_str());
+ fprintf(instance.outputFile, "%s,", group->name);
+ fprintf(instance.outputFile, "%.3g,", 100.0 * (double)vol_min / (double)USHRT_MAX);
+ fprintf(instance.outputFile, "%.3g,", 100.0 * (double)vol_max / (double)USHRT_MAX);
+ fprintf(instance.outputFile, "%.3g,", 100.0 * (double)team_vol_mod / (double)USHRT_MAX);
+ fprintf(instance.outputFile, "%d,", dist_min);
+ fprintf(instance.outputFile, "%d,", dist_max);
+ fprintf(instance.outputFile, "%d,", dist_reverb_max);
+ fprintf(instance.outputFile, "%s,", volume_falloff_curve);
+ fprintf(instance.outputFile, "%s,", reverb_falloff_curve);
+ fprintf(instance.outputFile, "%s,", volume_min_falloff_curve);
+ fprintf(instance.outputFile, "%s,", reverb_min_falloff_curve);
+ fprintf(instance.outputFile, "%d,", limit_count);
+ fprintf(instance.outputFile, "%s,", limit_type);
+ fprintf(instance.outputFile, "%d,", entity_limit_count);
+ fprintf(instance.outputFile, "%s,", entity_limit_type);
+ fprintf(instance.outputFile, "%.3g,", SND_CSV_CENTS_Decode((double)pitch_min / (double)SHRT_MAX));
+ fprintf(instance.outputFile, "%.3g,", SND_CSV_CENTS_Decode((double)pitch_max / (double)SHRT_MAX));
+ fprintf(instance.outputFile, "%.3g,", SND_CSV_CENTS_Decode((double)team_pitch_mod / (double)SHRT_MAX));
+ fprintf(instance.outputFile, "%d,", min_priority);
+ fprintf(instance.outputFile, "%d,", max_priority);
+ fprintf(instance.outputFile, "%.3g,", (double)min_priority_threshold / (double)UCHAR_MAX);
+ fprintf(instance.outputFile, "%.3g,", (double)max_priority_threshold / (double)UCHAR_MAX);
+ fprintf(instance.outputFile, "%s,", spatialized);
+ fprintf(instance.outputFile, "%s,", type);
+ fprintf(instance.outputFile, "%s,", loop);
+ fprintf(instance.outputFile, "%s,", randomize_type);
+ fprintf(instance.outputFile, "%.3g,", (double)probability / (double)UCHAR_MAX);
+ fprintf(instance.outputFile, "%d,", start_delay);
+ fprintf(instance.outputFile, "%.3g,", 100.0 * (double)reverb_send / (double)USHRT_MAX);
+ fprintf(instance.outputFile, "%s,", duck.c_str());
+ fprintf(instance.outputFile, "%s,", pan->name);
+ fprintf(instance.outputFile, "%.3g,", 100.0 * (double)center_send / (double)USHRT_MAX);
+ fprintf(instance.outputFile, "%d,", envelop_min);
+ fprintf(instance.outputFile, "%d,", envelop_max);
+ fprintf(instance.outputFile, "%.3g,", 100 * (double)envelop_percentage / (double)USHRT_MAX);
+ fprintf(instance.outputFile, "%.3g,", (double)occlusion_level / (double)UCHAR_MAX);
+ fprintf(instance.outputFile, "%.3g,", (double)occlusion_wet_dry / (double)UCHAR_MAX);
+ fprintf(instance.outputFile, "%s,", is_big);
+ fprintf(instance.outputFile, "%s,", distance_lpf);
+ fprintf(instance.outputFile, "%s,", move_type);
+ fprintf(instance.outputFile, "%d,", move_time);
+ fprintf(instance.outputFile, "%s,", real_delay);
+ fprintf(instance.outputFile, "%s,", subtitle.c_str());
+ fprintf(instance.outputFile, "%s,", mature);
+ fprintf(instance.outputFile, "%s,", doppler);
+ fprintf(instance.outputFile, "%s,", futz);
+ fprintf(instance.outputFile, "%s,", context ? context->type : ""); // , context_type);
+ fprintf(instance.outputFile, "%s,", context_value ? context_value : ""); // , context_value);
+ fprintf(instance.outputFile, "%d,", compression);
+ fprintf(instance.outputFile, "%s,", timescale);
+ fprintf(instance.outputFile, "%s,", music);
+ fprintf(instance.outputFile, "%f,", fade_in);
+ fprintf(instance.outputFile, "%f,", fade_out);
+ fprintf(instance.outputFile, "%s,", pc_format);
+ fprintf(instance.outputFile, "%s,", pause);
+ fprintf(instance.outputFile, "%s,", stop_on_death);
+ fprintf(instance.outputFile, "%s,", bus);
+ fprintf(instance.outputFile, "%s,", snapshot.c_str());
+ fprintf(instance.outputFile, "%s,", voice_limit);
+ fprintf(instance.outputFile, "%s,", file_xenon);
+ fprintf(instance.outputFile, "%d,", file_size_xenon);
+ fprintf(instance.outputFile, "%s,", file_ps3);
+ fprintf(instance.outputFile, "%d,", file_size_ps3);
+ fprintf(instance.outputFile, "%s,", file_pc);
+ fprintf(instance.outputFile, "%d,", file_size_pc);
+ fprintf(instance.outputFile, "%s,", file_wii);
+ fprintf(instance.outputFile, "%d,", file_size_wii);
+ fprintf(instance.outputFile, "%s,", source_csv);
+ fprintf(instance.outputFile, "%s,\n", instance.language.c_str());
+
+ return 0;
+}
+
+std::string Snd_ResolveBaseName(std::string& str)
+{
+ auto offset = str.find(".");
+
+ if (offset != std::string::npos)
+ {
+ return std::string(str, 0, offset);
+ }
+
+ return str;
+}
+
+int Rip_SoundBank_GatherSnapshots_Callback_f(ForeignPointer& asset, ForeignPointer& zoneName, void* data)
+{
+ _ASSERT(data);
+
+ snapshots_map_t* snapshots_map = (snapshots_map_t*)data;
+
+ ForeignPointer bank((SndBank*)asset->header.sound);
+ auto& snapshots = (*snapshots_map)[Snd_ResolveBaseName(ReadString(bank->name))];
+
+ if (bank->snapshots == nullptr || bank->snapshotCount == 0)
+ return 0;
+
+ for (unsigned int i = 0; i < bank->snapshotCount; i++)
+ {
+ snapshots.push_back(ForeignPointer((snd_snapshot*)bank->snapshots + i));
+ }
+
+ return 0;
+}
+
+int Rip_SoundBank_GatherRadverbs_Callback_f(ForeignPointer& asset, ForeignPointer& zoneName, void* data)
+{
+ _ASSERT(data);
+
+ radverbs_map_t* radverbs_map = (radverbs_map_t*)data;
+
+ ForeignPointer bank((SndBank*)asset->header.sound);
+ auto& radverbs = (*radverbs_map)[Snd_ResolveBaseName(ReadString(bank->name))];
+
+ if (bank->radverbs == nullptr || bank->radverbCount == 0)
+ return 0;
+
+ for (unsigned int i = 0; i < bank->radverbCount; i++)
+ {
+ radverbs.push_back(ForeignPointer(bank->radverbs + i));
+ }
+
+ return 0;
+}
+
+int Rip_SoundBank_Soundalias_Callback_f(ForeignPointer& bank, ForeignPointer& globals, snapshots_map_t* snapshots_map)
+{
+ std::string name = ReadString(bank->name, 128);
+
+ Con_Print("Exporting soundalias %s.csv...\n", name.c_str());
+
+ int err = FS_CreatePath("soundaliases\\zones\\");
+ if (err)
+ {
+ return Con_Error("Error: Unable to create output path (0x%X)\n", err);
+ }
+
+ char path[MAX_PATH];
+ sprintf_s(path, "%s/soundaliases\\zones\\%s.csv", AppInfo_OutDir(), name.c_str());
+
+ FS_SanitizePath(path);
+
+ if (FS_FileExists(path) && !fs_overwrite.ValueBool())
+ {
+ Con_Print(" ...skipping (file already exists)\n");
+ return 1;
+ }
+
+ FILE* h = fopen(path, "w");
+
+ if (!h)
+ {
+ return Con_Error("Couldnt open '%s'\n", path);
+ }
+
+ Snd_CSV_PrintHeader_Soundalias(h);
+
+ std::string language = "";
+
+ auto _offset = name.find_last_of(".");
+ if (_offset != std::string::npos && _offset + 1 < name.size())
+ language = std::string(name, _offset + 1);
+ else
+ Con_Warning("Unable to determine language for '%s'\n", name.c_str());
+
+ snd_ripper_instance_info_t instance = { h, language, globals, bank, snapshots_map };
+
+ ForeignPointer alias((snd_alias_list_t*)bank->alias);
+ for (unsigned int i = 0; i < bank->aliasCount; i++)
+ {
+ ForeignPointer entry((snd_alias_t*)alias[i].head);
+ Rip_Sound_Alias_Callback_f(entry, instance);
+ }
+
+ fclose(h);
+ return 0;
+}
+
+int Rip_SoundBank_Callback_f(ForeignPointer& asset, ForeignPointer& zoneName, void* data)
+{
+ ForeignPointer globals((SndDriverGlobals*)DB_FindSingletonAssetForType(ASSET_TYPE_SNDDRIVER_GLOBALS));
+ ForeignPointer bank((SndBank*)asset->header.sound);
+
+ return Rip_SoundBank_Soundalias_Callback_f(bank, globals, (snapshots_map_t*)data);
+}
+
+
+int Rip_Snapshot_Callback_f(std::string name, std::vector>& snapshots)
+{
+ Con_Print("Exporting snapshot %s.snapshot.csv...\n", name.c_str());
+
+ int err = FS_CreatePath("soundaliases\\zones\\snapshots\\");
+ if (err)
+ {
+ return Con_Error("Error: Unable to create output path (0x%X)\n", err);
+ }
+
+ char path[MAX_PATH];
+ sprintf_s(path, "%s/soundaliases\\zones\\snapshots\\%s.csv", AppInfo_OutDir(), name.c_str());
+
+ FS_SanitizePath(path);
+
+ if (FS_FileExists(path) && !fs_overwrite.ValueBool())
+ {
+ Con_Print(" ...skipping (file already exists)\n");
+ return 1;
+ }
+
+ FILE* h = fopen(path, "w");
+
+ if (!h)
+ {
+ return Con_Error("Couldnt open '%s'\n", path);
+ }
+
+ ForeignPointer globals((SndDriverGlobals*)DB_FindSingletonAssetForType(ASSET_TYPE_SNDDRIVER_GLOBALS));
+ Snd_CSV_PrintHeader_Snapshot(h, globals);
+
+ for (auto& snapshot : snapshots)
+ {
+ fprintf(h, "%s,", snapshot->name);
+ fprintf(h, "%s,", snapshot->occlusionName);
+ fprintf(h, ","); // loadspec
+ fprintf(h, "%.3g,", snapshot->fadeIn);
+ fprintf(h, ","); // fadeInCurve
+ fprintf(h, "%.3g,", snapshot->fadeOut);
+ fprintf(h, ","); // fadeOutCurve
+ fprintf(h, "%d,", (int)snapshot->distance);
+
+ for (unsigned int i = 0; i < globals->snapshotGroupCount; i++)
+ {
+ fprintf(h, "%.3g,", snapshot->attenuation[i]);
+ }
+
+ fprintf(h, "\n");
+ }
+
+ fclose(h);
+ return 0;
+}
+
+int Rip_Radverb_Callback_f(std::string name, std::vector>& radverbs)
+{
+ Con_Print("Exporting snapshot %s.radverb.csv...\n", name.c_str());
+
+ int err = FS_CreatePath("soundaliases\\zones\\radverb\\");
+ if (err)
+ {
+ return Con_Error("Error: Unable to create output path (0x%X)\n", err);
+ }
+
+ char path[MAX_PATH];
+ sprintf_s(path, "%s/soundaliases\\zones\\radverb\\%s.csv", AppInfo_OutDir(), name.c_str());
+
+ FS_SanitizePath(path);
+
+ if (FS_FileExists(path) && !fs_overwrite.ValueBool())
+ {
+ Con_Print(" ...skipping (file already exists)\n");
+ return 1;
+ }
+
+ FILE* h = fopen(path, "w");
+
+ if (!h)
+ {
+ return Con_Error("Couldnt open '%s'\n", path);
+ }
+
+ Snd_CSV_PrintHeader_Radverb(h);
+
+ for (auto& radverb : radverbs)
+ {
+ fprintf(h, "%s,", radverb->name);
+ fprintf(h, ","); //loadspec
+ fprintf(h, "%.7g,", radverb->smoothing);
+ fprintf(h, "%.7g,", radverb->earlyTime);
+ fprintf(h, "%.7g,", radverb->lateTime);
+ fprintf(h, "%.7g,", radverb->earlyGain);
+ fprintf(h, "%.7g,", radverb->lateGain);
+ fprintf(h, "%.7g,", radverb->returnGain);
+ fprintf(h, "%.7g,", radverb->earlyLpf);
+ fprintf(h, "%.7g,", radverb->lateLpf);
+ fprintf(h, "%.7g,", radverb->inputLpf);
+ fprintf(h, "%.7g,", radverb->dampLpf);
+ fprintf(h, "%.7g,", radverb->wallReflect);
+ fprintf(h, "%.7g,", radverb->dryGain);
+ fprintf(h, "%.7g,", radverb->earlySize);
+ fprintf(h, "%.7g,", radverb->lateSize);
+ fprintf(h, "%.7g,", radverb->diffusion);
+
+ fprintf(h, "\n");
+ }
+
+ fclose(h);
+ return 0;
+}
+
diff --git a/components/asset_util/cmds/ripper/snd_ripper.h b/components/asset_util/cmds/ripper/snd_ripper.h
new file mode 100644
index 00000000..bdfd910e
--- /dev/null
+++ b/components/asset_util/cmds/ripper/snd_ripper.h
@@ -0,0 +1,40 @@
+#pragma once
+#include "process_info.h"
+#include "snd_alias_db.h"
+#include "snd_csv_enum.h"
+#include "snd_driver_db.h"
+
+#include
+#include
+
+typedef std::unordered_map>> snapshots_map_t;
+typedef std::unordered_map>> radverbs_map_t;
+
+struct snd_ripper_instance_info_t
+{
+ FILE* outputFile;
+ std::string& language;
+ ForeignPointer& globals;
+ ForeignPointer& bank;
+
+ snapshots_map_t* snapshots_map;
+};
+
+struct snd_csv_duck_entry_t
+{
+ unsigned int hash;
+ const char* string;
+};
+
+double __cdecl SND_CSV_CENTS_Encode(double val);
+double SND_CSV_CENTS_Decode(double val);
+unsigned int __cdecl SND_HashName(const char *name);
+
+void Snd_CSV_PrintHeader(FILE* f);
+
+int Rip_SoundBank_GatherSnapshots_Callback_f(ForeignPointer& asset, ForeignPointer& zoneName, void* data);
+int Rip_SoundBank_GatherRadverbs_Callback_f(ForeignPointer& asset, ForeignPointer& zoneName, void* data);
+
+int Rip_SoundBank_Callback_f(ForeignPointer& asset, ForeignPointer& zoneName, void* data);
+int Rip_Snapshot_Callback_f(std::string name, std::vector>& snapshots);
+int Rip_Radverb_Callback_f(std::string name, std::vector>& radverbs);
diff --git a/components/asset_util/cmds/search/handler.h b/components/asset_util/cmds/search/handler.h
new file mode 100644
index 00000000..a669875e
--- /dev/null
+++ b/components/asset_util/cmds/search/handler.h
@@ -0,0 +1,262 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#define UNUSED(x) (void)(x)
+
+template
+class Handler
+{
+ private:
+ enum SlotState
+ {
+ EMPTY = 0,
+ INUSE = -1
+ };
+
+ std::mutex mtx_slots;
+ std::mutex mtx_fs;
+
+ std::mutex mtx_busy;
+ std::condition_variable cv_data;
+
+ _SLOT_T **slots;
+ unsigned int numSlots;
+
+ std::vector<_SRC_T> input_data;
+ unsigned int input_index;
+
+ // Always lock mtx_slots before calling this
+ void FreeSlot(_SLOT_T **slot)
+ {
+ if (*slot != (_SLOT_T *)SlotState::INUSE)
+ delete[] * slot;
+ *slot = (_SLOT_T *)SlotState::EMPTY;
+ }
+
+ typedef std::function _SlotInit_f; // Initializes an empty slot with data
+
+ typedef std::function _SlotPostInit_f; // Executes any post slot init code
+ // returns a pointer to the (new) _SLOT_T data
+ // Useful for decompression, etc.
+
+ typedef std::function _SlotHandleData_f; // Handle loaded slot data
+
+ _SlotInit_f _SlotInit_Callback;
+ _SlotPostInit_f _SlotPostInit_Callback;
+ _SlotHandleData_f _SlotHandleData_Callback;
+
+ static void _SlotInit_DefaultCallback(_SRC_T &_src, _SLOT_T &_outData)
+ {
+ UNUSED(_src);
+ UNUSED(_outData);
+ };
+ static void _SlotPostInit_DefaultCallback(_SLOT_T &_slotData) { UNUSED(_slotData); };
+ static void _SlotHandleData_DefaultCallback(_SLOT_T &_slotData) { UNUSED(_slotData); };
+
+ // Run a single handler iteration
+ // Either loads data, handles loaded data, or waits for data to be loaded (if all slots are busy)
+ int ExecuteLoopIteration()
+ {
+ _SLOT_T **slot = (_SLOT_T **)SlotState::INUSE;
+
+ // Data handler lambda - lock mtx_slots before calling this
+ auto Handle_Data = [&slot, this](void) -> int {
+ _SLOT_T *data = *slot;
+ *slot = (_SLOT_T *)SlotState::INUSE;
+ mtx_slots.unlock();
+
+ // Run the handler callback on the slot data
+ _SlotHandleData_Callback(*data);
+
+ //printf("FREEING DATA 0x%" PRIx64 "\n", (long unsigned int)data);
+ delete data;
+
+ // Free the slot & mark it as empty
+ mtx_slots.lock();
+ FreeSlot(slot);
+ mtx_slots.unlock();
+
+ return 0;
+ };
+
+ mtx_slots.lock();
+
+ //
+ // Attempt to get an EMPTY or Unhandled slot
+ // Otherwise we just get an INUSE handle
+ //
+ for (unsigned int i = 0; i < numSlots; i++)
+ {
+ if (slots[i] == (_SLOT_T *)SlotState::INUSE)
+ continue;
+
+ slot = &slots[i];
+ break;
+ }
+
+ if (slot == (_SLOT_T **)SlotState::INUSE || *slot == (_SLOT_T *)SlotState::INUSE)
+ {
+ // There are no usable slots
+ mtx_slots.unlock();
+
+ //printf("All slots are in-use!\n");
+
+ // Wait for cv_data to be notified before checking for data again
+ std::unique_lock lock(mtx_busy);
+ cv_data.wait(lock);
+
+ return 0;
+ }
+ else if (*slot == (_SLOT_T *)SlotState::EMPTY)
+ {
+ // Slot is empty - needs to be populated with data
+ //printf("Got an empty slot\n");
+
+ // If we've already loaded all source data
+ // We just exit current handle loop
+ // (might be better for the thread to try handling any data thats left?)
+ if (input_index >= input_data.size())
+ {
+ mtx_slots.unlock();
+
+ // Notify any threads that are waiting for data
+ // They'll either find an empty slot - realize theres no data and exit
+ // or they'll handle any unhandled data first
+ cv_data.notify_all();
+
+ return -1;
+ }
+
+ // If another thread is already using the filesytem
+ // we try to find some loaded data that hasn't been handled
+ // and handle it
+ if (!mtx_fs.try_lock())
+ {
+ // printf("Filesystem busy... looking for unhandled data\n");
+ for (unsigned int i = 0; i < numSlots; i++)
+ {
+ if (slots[i] == (_SLOT_T *)SlotState::INUSE || slots[i] == (_SLOT_T *)SlotState::EMPTY)
+ continue;
+
+ slot = &slots[i];
+ break;
+ }
+
+ if (*slot != (_SLOT_T *)SlotState::INUSE && *slot != (_SLOT_T *)SlotState::EMPTY)
+ return Handle_Data();
+
+ mtx_slots.unlock();
+ return 0;
+ }
+
+ // Prevent any other threads from taking the slot while we populate the data
+ *slot = (_SLOT_T *)SlotState::INUSE;
+
+ // Update the active input data index, but grab the current one first
+ unsigned int src_index = input_index++;
+
+ mtx_slots.unlock();
+
+ // Allocate the data for the slot
+ // and call the _SlotInit callback to run the init code for the new data
+ _SLOT_T *data = new _SLOT_T;
+ _SlotInit_Callback(input_data[src_index], *data);
+
+ mtx_fs.unlock();
+
+ // Run any PostInit code after the filesystem mutex has been freed
+ _SlotPostInit_Callback(*data);
+
+ // Set slot to point to the data that we initialized
+ mtx_slots.lock();
+ *slot = data; // Assign the data pointer to the slot
+ // printf("ALLOC DATA: 0x%" PRIx64 "\n", (long unsigned int)data);
+ mtx_slots.unlock();
+
+ // Notify any threads that were waiting for data
+ cv_data.notify_one();
+
+ return 0;
+ }
+ else
+ {
+ // We got a normal slot with data in it
+ // printf("Found data to handle!\n");
+ return Handle_Data();
+ }
+
+ return 1;
+ }
+
+ static int WorkerFunc(Handler* handler)
+ {
+ int result;
+ do
+ {
+ result = handler->ExecuteLoopIteration();
+ } while (result == 0);
+
+ return result;
+ }
+
+ public:
+ Handler(std::vector<_SRC_T> &source_data, unsigned int _slotCount,
+ _SlotHandleData_f HandlerCallback=Handler::_SlotHandleData_DefaultCallback,
+ _SlotInit_f InitCallback = Handler::_SlotInit_DefaultCallback,
+ _SlotPostInit_f PostInitCallback = Handler::_SlotPostInit_DefaultCallback)
+ {
+ numSlots = _slotCount;
+ slots = new _SLOT_T *[numSlots];
+ memset(slots, 0, sizeof(_SLOT_T *) * numSlots);
+
+ input_data = source_data;
+ input_index = 0;
+
+ _SlotInit_Callback = InitCallback;
+ _SlotPostInit_Callback = PostInitCallback;
+ _SlotHandleData_Callback = HandlerCallback;
+ }
+
+ ~Handler()
+ {
+ mtx_slots.lock();
+
+ for (unsigned int i = 0; i < numSlots; i++)
+ {
+ FreeSlot(&slots[i]);
+ }
+
+ _SLOT_T **p = slots;
+ slots = NULL;
+ delete[] p;
+
+ mtx_slots.unlock();
+ }
+
+ void RunHandler(unsigned int workerCount = 1)
+ {
+ workerCount -= 1;
+
+ std::thread* threads = new std::thread[workerCount];
+ for(unsigned int i = 0; i < workerCount; i++)
+ {
+ threads[i] = std::thread(Handler::WorkerFunc, this);
+ }
+
+ Handler::WorkerFunc(this);
+
+ for(unsigned int i = 0; i < workerCount; i++)
+ {
+ threads[i].join();
+ }
+
+ delete[] threads;
+ }
+};
\ No newline at end of file
diff --git a/components/asset_util/common/ff.cpp b/components/asset_util/common/ff.cpp
index b807c3a8..122a8faa 100644
--- a/components/asset_util/common/ff.cpp
+++ b/components/asset_util/common/ff.cpp
@@ -6,24 +6,56 @@
#include
#include "../sys/AppInfo.h"
#include "zlib\zlib.h"
+#include
+#include
+#include
-char* FindRawfileString(BYTE* start, BYTE* end)
+enum class RAWFILE_TYPE
{
+ COMPRESSED,
+ UNCOMPRESSED,
+ SOUND,
+
+ NONE = -1,
+};
+
+RAWFILE_TYPE FindRawfileString(BYTE* start, BYTE* end, char** result)
+{
+ RAWFILE_TYPE type = RAWFILE_TYPE::NONE;
+
while (start < end - 5)
{
- if (strncmp(".atr", (char*)start, 4) == 0 ||
- strncmp(".gsc", (char*)start, 4) == 0 ||
- strncmp(".csc", (char*)start, 4) == 0 ||
- strncmp(".vision", (char*)start, 4) == 0 ||
- strncmp(".wav", (char*)start, 4) == 0)
+ if (strncmp(".gsc", (char*)start, 4) == 0 ||
+ strncmp(".csc", (char*)start, 4) == 0)
+ {
+ type = RAWFILE_TYPE::COMPRESSED;
+ }
+ else if (strncmp(".atr", (char*)start, 4) == 0 ||
+ strncmp(".sun", (char*)start, 4) == 0 ||
+ strncmp(".xpo", (char*)start, 4) == 0 )
+ {
+ type = RAWFILE_TYPE::UNCOMPRESSED;
+ }
+ else if (strncmp(".wav", (char*)start, 4) == 0)
+ {
+ type = RAWFILE_TYPE::SOUND;
+ }
+ else if (start < end - 8 && strncmp(".vision", (char*)start, 7) == 0)
{
- return (char*)start;
+ type = RAWFILE_TYPE::UNCOMPRESSED;
+ }
+
+ if (type != RAWFILE_TYPE::NONE)
+ {
+ *result = (char*)start;
+ return type;
}
start++;
}
- return nullptr;
+ *result = nullptr;
+ return RAWFILE_TYPE::NONE;
}
char* FindRawfileStringReverseLookup(BYTE* start)
@@ -49,7 +81,7 @@ int FF_FFExtractCompressedRawfile(XAssetRawfileHeader* rawfileHeader, const char
Con_Print_v("Extracting file: \"%s\"... ", rawfilePath);
char qpath[1024] = "";
- sprintf_s(qpath, "%s/%s", AppInfo_RawDir(), rawfilePath);
+ sprintf_s(qpath, "%s/%s", AppInfo_OutDir(), rawfilePath);
//
// If not in overwrite mode AND the file exists
@@ -57,11 +89,9 @@ int FF_FFExtractCompressedRawfile(XAssetRawfileHeader* rawfileHeader, const char
//
if (!fs_overwrite.ValueBool())
{
- if (FILE* h = fopen(qpath, "r"))
+ if (FS_FileExists(qpath))
{
Con_Print_v("SKIPPED\n");
-
- fclose(h);
return 0;
}
}
@@ -112,7 +142,19 @@ int FF_FFExtractUncompressedRawfile(char* rawfileData, const char* rawfilePath)
Con_Print_v("Extracting file: \"%s\"... ", rawfilePath);
char qpath[1024] = "";
- sprintf_s(qpath, "%s/%s", AppInfo_RawDir(), rawfilePath);
+ sprintf_s(qpath, "%s/%s", AppInfo_OutDir(), rawfilePath);
+
+ int* pHeader = (int*)rawfilePath;
+ int len = pHeader[-2];
+
+ //
+ // Catch incorrect rawfile data to prevent massive allocations
+ //
+ if (len > 1024 * 1024 * 16)
+ {
+ Con_Print_v("IGNORED\n");
+ return 0;
+ }
//
// If not in overwrite mode AND the file exists
@@ -131,26 +173,18 @@ int FF_FFExtractUncompressedRawfile(char* rawfileData, const char* rawfilePath)
if (FS_CreatePath(rawfilePath) != 0)
{
+ printf("%s\n", rawfilePath);
Con_Error_v("PATH ERROR\n");
return 0;
}
-
- //
- // Catch incorrect rawfile data to prevent massive allocations
- //
- if (strlen(rawfileData) > 1024 * 1024 * 16)
- {
- Con_Print_v("IGNORED\n");
- return 0;
- }
-
+
if (FILE* h = fopen(qpath, "wb"))
{
- fwrite(rawfileData, 1, strlen(rawfileData), h);
+ fwrite(rawfileData, 1, len, h);
fclose(h);
Con_Print_v("SUCCESS\n");
- return strlen(rawfileData);
+ return len;
}
Con_Error_v("ERROR\n");
@@ -169,7 +203,7 @@ int FF_FFExtractSoundFile(Snd_Header* snd_header, const char* sndfilePath)
#if _DEBUG
sprintf_s(qpath, "%s", sndfilePath);
#else
- sprintf_s(qpath, "%s/%s", AppInfo_RawDir(), sndfilePath);
+ sprintf_s(qpath, "%s/%s", AppInfo_OutDir(), sndfilePath);
#endif
@@ -200,7 +234,7 @@ int FF_FFExtractSoundFile(Snd_Header* snd_header, const char* sndfilePath)
//
if (snd_header->format != 6 && snd_header->format != 7)
{
- Con_Print_v("IGNORED\n");
+ Con_Print_v("IGNORED (fmt %d)\n", snd_header->format);
return 0;
}
@@ -244,91 +278,105 @@ int FF_FFExtractSoundFile(Snd_Header* snd_header, const char* sndfilePath)
return 0;
}
-int FF_FFExtractFiles(BYTE* searchData, DWORD searchSize)
+void PerformLookup(BYTE *Buffer, std::vector& Data, std::vector& Signature)
{
- int extractedFileCount = 0;
-
- BYTE* endofBuffer = searchData + searchSize;
-
- BYTE* lastSearchLoc = 0;
- while (searchData < searchData + searchSize)
+ for (auto scanStart = Data.begin();;)
{
- char* rawfileString = FindRawfileString(searchData, endofBuffer);
+ // Search for the pattern
+ auto ret = std::search(scanStart, Data.end(), Signature.begin(), Signature.end());
- if (!rawfileString)
- {
- return extractedFileCount;
- }
+ // If we didn't find a match, exit
+ if (ret == Data.end())
+ break;
+
+ // Convert match index to a real address
+ BYTE *asset = std::distance(Data.begin(), ret) + Buffer;
+ // Parse the asset (reverse string scan for the name)
+ char *rawfileString = (char *)asset;
char* tmpString = FindRawfileStringReverseLookup((BYTE*)rawfileString);
+ int moveLen = 0;
if (!tmpString)
- {
- return extractedFileCount;
- }
+ return;
- if ((BYTE*)tmpString < searchData || !IsCharAlphaNumericA(*tmpString))
+ if ((BYTE*)tmpString < Buffer || !IsCharAlphaNumericA(*tmpString))
{
- searchData += strlen(rawfileString) + 1;
+ scanStart = ret + strlen(rawfileString) + 1;
continue;
}
rawfileString = tmpString;
- if (Str_EndsWith(rawfileString, ".wav"))
- {
- if (!g_extractSounds.ValueBool())
- {
- searchData = (BYTE*)rawfileString + strlen(rawfileString) + 1;
- continue;
- }
+ char assetExt[32];
+ memset(assetExt, 0, sizeof(assetExt));
+ memcpy(assetExt, asset, Signature.size());
- Snd_Header* snd_info = (Snd_Header*)(rawfileString - sizeof(Snd_Header));
- FF_FFExtractSoundFile(snd_info, rawfileString);
- searchData = (BYTE*)rawfileString + strlen(rawfileString) + 1;
- }
-
- //
- // ARG_FLAG_FF Should never been false if this function is running
- //
- /*
- if (!ARG_FLAG_FF)
+ // Dump
+ if (strstr(assetExt, ".gsc") ||
+ strstr(assetExt, ".csc"))
{
- searchData = (BYTE*)rawfileString + strlen(rawfileString) + 1;
- continue;
+ // GameScripts are compressed
+ XAssetRawfileHeader* rawfileHeader = (XAssetRawfileHeader*)(rawfileString + strlen(rawfileString) + 1);
+ if (FF_FFExtractCompressedRawfile(rawfileHeader, rawfileString))
+ moveLen = rawfileHeader->compressedSize;
}
- */
-
- if (Str_EndsWith(rawfileString, ".vision"))
+ else if (strstr(assetExt, ".atr") ||
+ strstr(assetExt, ".sun") ||
+ strstr(assetExt, ".xpo") ||
+ strstr(assetExt, ".txt") ||
+ strstr(assetExt, ".cfg") ||
+ strstr(assetExt, ".vision"))
{
- char* rawfileData = rawfileString + strlen(rawfileString) + 1;
- int fileLen = FF_FFExtractUncompressedRawfile(rawfileData, rawfileString);
-
- if (!fileLen)
+ // Ignore menu .txt files stored in /ui/ or /ui_mp/
+ if (!(strstr(assetExt, ".txt") && (!_strnicmp(rawfileString, "ui/", 3) || !_strnicmp(rawfileString, "ui_mp/", 6))))
{
- searchData = (BYTE*)rawfileString + strlen(rawfileString) + 1;
- continue;
+ // Random uncompressed types
+ char* rawfileData = rawfileString + strlen(rawfileString) + 1;
+ moveLen = FF_FFExtractUncompressedRawfile(rawfileData, rawfileString);
}
-
- searchData = (BYTE*)rawfileData + fileLen + 1;
}
- else
+ else if (strstr(assetExt, ".wav"))
{
- XAssetRawfileHeader* rawfileHeader = (XAssetRawfileHeader*)(rawfileString + strlen(rawfileString) + 1);
- if (!FF_FFExtractCompressedRawfile(rawfileHeader, rawfileString))
- {
- searchData = (BYTE*)rawfileString + strlen(rawfileString) + 1;
- continue;
- }
-
-
- searchData = (BYTE*)rawfileHeader + rawfileHeader->compressedSize;
+ // Audio files
+ Snd_Header* snd_info = (Snd_Header*)(rawfileString - sizeof(Snd_Header));
+ moveLen = FF_FFExtractSoundFile(snd_info, rawfileString);
+ }
+ else if (strstr(assetExt, ".hlsl"))
+ {
+ // Shaders (ignored for now)
}
- extractedFileCount++;
+ scanStart = (ret + moveLen + 1);
}
+}
- return extractedFileCount;
+int FF_FFExtractFiles(BYTE* searchData, DWORD searchSize)
+{
+ auto data = std::vector(searchData, searchData + searchSize);
+ auto scanList = std::vector>();
+
+ scanList.push_back(std::vector({ '.', 'g', 's', 'c' }));
+ scanList.push_back(std::vector({ '.', 'c', 's', 'c' }));
+ scanList.push_back(std::vector({ '.', 'a', 't', 'r' }));
+ scanList.push_back(std::vector({ '.', 's', 'u', 'n' }));
+ scanList.push_back(std::vector({ '.', 'x', 'p', 'o' }));
+ scanList.push_back(std::vector({ '.', 'w', 'a', 'v' }));
+ scanList.push_back(std::vector({ '.', 'c', 'f', 'g' }));
+ scanList.push_back(std::vector({ '.', 't', 'x', 't' }));
+ scanList.push_back(std::vector({ '.', 'v', 'i', 's', 'i', 'o', 'n' }));
+ // scanList.push_back(std::vector({ '.', 'h', 'l', 's', 'l' }));
+
+ if (g_extractSounds.ValueBool())
+ scanList.push_back(std::vector({ '.', 'w', 'a', 'v' }));
+
+ concurrency::parallel_for(size_t(0), scanList.size(), [&](size_t i)
+ {
+ PerformLookup(searchData, data, scanList[i]);
+ });
+
+ // Return value is never used as of this time
+ return 0;
}
int FF_FFExtract(const char* filepath, const char* filename)
@@ -338,7 +386,7 @@ int FF_FFExtract(const char* filepath, const char* filename)
FILE* h = nullptr;
if (fopen_s(&h, filepath, "r+b") != 0)
{
- Con_Error("ERROR: Fastfile '%s' could not be found\n\n", filepath);
+ Con_Error("ERROR: Fastfile '%s' could not be found\n", filepath);
return FALSE;
}
rewind(h);
diff --git a/components/asset_util/common/fs.cpp b/components/asset_util/common/fs.cpp
index e42af46c..ac618bf7 100644
--- a/components/asset_util/common/fs.cpp
+++ b/components/asset_util/common/fs.cpp
@@ -28,12 +28,16 @@ bool FS_FileExists(const char* qpath)
return false;
}
-size_t FS_FileSize(const char* qpath)
+/*
+// This is now defined in gsc_lib
+
+long int FS_FileSize(const char* qpath)
{
struct stat st_buf;
int r = stat(qpath, &st_buf);
return r == 0 ? st_buf.st_size : -1;
}
+*/
const char* FS_GetExtensionSubString(const char* filename)
{
@@ -76,6 +80,26 @@ const char* FS_GetFilenameSubString(const char* pathname)
return last;
}
+const wchar_t* FS_GetFilenameSubStringW(wchar_t* pathname)
+{
+ wchar_t* last = pathname;
+ while (*pathname)
+ {
+ if (*pathname == '/' || *pathname == '\\')
+ last = pathname + 1;
+ ++pathname;
+ }
+ return last;
+}
+
+void FS_StripFilename(const char* in, char* out)
+{
+ const char* extension = FS_GetFilenameSubString(in);
+ while (in != extension)
+ *out++ = *in++;
+ *out = 0;
+}
+
int FS_FileCount(const char* path, const char* pattern)
{
HANDLE dir;
@@ -83,7 +107,6 @@ int FS_FileCount(const char* path, const char* pattern)
char spath[MAX_PATH];
sprintf_s(spath, "%s/%s", path, pattern);
- Con_Print("%s\n", spath);
if ((dir = FindFirstFileA(spath, &file_data)) == INVALID_HANDLE_VALUE)
{
@@ -105,7 +128,7 @@ int FS_FileCount(const char* path, const char* pattern)
return count;
}
-int FS_FileIterator(const char* path, const char* pattern, int(__cdecl* FS_FileHandlerCallback)(const char* filePath, const char* fileName))
+int FS_FileIterator(const char* path, const char* pattern, FS_FileHandlerCallback_t FS_FileHandlerCallback)
{
HANDLE dir;
WIN32_FIND_DATAA file_data;
@@ -137,7 +160,7 @@ int FS_FileIterator(const char* path, const char* pattern, int(__cdecl* FS_FileH
return count;
}
-int FS_DirectoryIterator(const char* path, int(__cdecl* FS_DirectoryHandlerCallback)(const char* path))
+int FS_DirectoryIterator(const char* path, FS_DirectoryHandlerCallback_t FS_DirectoryHandlerCallback)
{
HANDLE dir;
WIN32_FIND_DATAA file_data;
@@ -172,25 +195,38 @@ int FS_DirectoryIterator(const char* path, int(__cdecl* FS_DirectoryHandlerCallb
return count;
}
-int FS_CreatePath(const char* targetPath)
+int FS_CreatePath(const char* _targetPath)
{
+ char targetPath[1024];
+ if (strchr(_targetPath, ':') != NULL)
+ strcpy_s(targetPath, _targetPath);
+ else
+ sprintf_s(targetPath, "%s/%s", AppInfo_OutDir(), _targetPath);
+
int len = strlen(targetPath);
for (int i = 0; i < len; i++)
{
- if (targetPath[i] == '/' || targetPath[i] == '\\')
+ bool skip = false;
+ if (i > 0 && targetPath[i-1] == ':')
+ skip = true;
+
+ if (!skip && (targetPath[i] == '/' || targetPath[i] == '\\'))
{
char buf[1024] = "";
strncpy(buf + strlen(buf), targetPath, i);
- char qpath[1024] = "";
- sprintf_s(qpath, "%s/%s/", AppInfo_RawDir(), buf);
+ char* qpath = buf;
+ if (strlen(qpath) == 0)
+ continue;
+ FS_SanitizePath(qpath);
#if _DEBUG
if (!CreateDirectoryA(buf, 0) && GetLastError() != ERROR_ALREADY_EXISTS)
#else
if (!CreateDirectoryA(qpath, 0) && GetLastError() != ERROR_ALREADY_EXISTS)
#endif
{
+
return GetLastError();
}
}
@@ -245,3 +281,28 @@ int FS_CopyDirectory(char* srcPath, char* destPath, bool overwriteFiles)
return 0;
}
+void FS_SanitizePath(char* path)
+{
+ if (path[0] == '/')
+ path[0] = '\\';
+
+ //
+ // Cleanup double / or \
+ //
+ int len = strlen(path);
+ for (int i = 1; i < len; i++)
+ {
+ if (path[i] == '/')
+ path[i] = '\\';
+
+ if (path[i] == '\\' && path[i - 1] == '\\')
+ strcpy(&path[i - 1], &path[i]);
+ }
+
+ //
+ // Trim any leading / or \
+ //
+ const char* c = path;
+ for (; *c == '/' || *c == '\\'; c++);
+ strcpy(path, c);
+}
diff --git a/components/asset_util/common/fs.h b/components/asset_util/common/fs.h
index 26716e89..17ccc255 100644
--- a/components/asset_util/common/fs.h
+++ b/components/asset_util/common/fs.h
@@ -1,5 +1,6 @@
#pragma once
#include
+#include
#define FS_SEARCHPATTERN_IWD "*.IWD"
#define FS_SEARCHPATTERN_FF "*.FF"
@@ -8,17 +9,24 @@
const char* FS_Cwd(void);
bool FS_FileExists(const char* qpath);
-size_t FS_FileSize(const char* qpath);
+//size_t FS_FileSize(const char* qpath);
+long int FS_FileSize(const char* qpath);
const char* FS_GetExtensionSubString(const char* filename);
void FS_StripExtension(const char* in, char* out);
const char* FS_GetFilenameSubString(const char* pathname);
+const wchar_t* FS_GetFilenameSubStringW(wchar_t* pathname);
+void FS_StripFilename(const char* in, char* out);
+
+typedef std::function FS_FileHandlerCallback_t;
+typedef std::function FS_DirectoryHandlerCallback_t;
int FS_FileCount(const char* path, const char* pattern);
-int FS_FileIterator(const char* path, const char* pattern, int(__cdecl* FS_FileHandlerCallback)(const char* filePath, const char* fileName));
-int FS_DirectoryIterator(const char* path, int(__cdecl* FS_DirectoryHandlerCallback)(const char* path));
+int FS_FileIterator(const char* path, const char* pattern, FS_FileHandlerCallback_t FS_FileHandlerCallback);
+int FS_DirectoryIterator(const char* path, FS_DirectoryHandlerCallback_t FS_DirectoryHandlerCallback);
int FS_CreatePath(const char* targetPath);
int FS_CopyDirectory(char* srcPath, char* destPath, bool overwriteFiles = false);
+void FS_SanitizePath(char* path);
diff --git a/components/asset_util/common/iwd.cpp b/components/asset_util/common/iwd.cpp
index 7ea359df..7e4f0a6f 100644
--- a/components/asset_util/common/iwd.cpp
+++ b/components/asset_util/common/iwd.cpp
@@ -44,7 +44,8 @@ int __cdecl IWD_IWDHandler(const char* iwdPath, const char* iwdName)
int __cdecl IWD_IWDExtractFile(mz_zip_archive* iwd, const char* filepath)
{
char outPath[MAX_PATH];
- sprintf_s(outPath, "%s/%s", AppInfo_RawDir(), filepath);
+ sprintf_s(outPath, "%s/%s", AppInfo_OutDir(), filepath);
+ FS_SanitizePath(outPath);
if (fs_overwrite.ValueBool())
{
@@ -53,19 +54,19 @@ int __cdecl IWD_IWDExtractFile(mz_zip_archive* iwd, const char* filepath)
return 0;
}
- FILE* h = NULL;
- if (fopen_s(&h, outPath, "r"))
+ if (FS_FileExists(outPath))
{
Con_Print_v("Skipping file: \"%s\"\n", filepath);
- fclose(h);
return 1;
}
else
{
Con_Print_v("Extracting file: \"%s\"... ", filepath);
- if (FS_CreatePath(filepath) != 0)
+
+ int err = FS_CreatePath(filepath);
+ if (err != 0)
{
- Con_Error_v("DIR ERROR\n");
+ Con_Error_v("DIR ERROR (%d)\n", err);
return 1;
}
@@ -75,7 +76,7 @@ int __cdecl IWD_IWDExtractFile(mz_zip_archive* iwd, const char* filepath)
return 1;
}
- Con_Print("SUCCESS\n");
+ Con_Print_v("SUCCESS\n");
return 0;
}
diff --git a/components/asset_util/common/llist.h b/components/asset_util/common/llist.h
index 7c34fce6..ca0f420b 100644
--- a/components/asset_util/common/llist.h
+++ b/components/asset_util/common/llist.h
@@ -1,5 +1,8 @@
#pragma once
+#include "../gsc_parser/src/cpp/util/llist.h"
+
+#if 0
template
class LList
{
@@ -192,3 +195,4 @@ LList* LList::NextNode(void) const
{
return (this->next == head) ? NULL : this->next;
}
+#endif
\ No newline at end of file
diff --git a/components/asset_util/cvar.cpp b/components/asset_util/cvar.cpp
index 3b87267a..95052ce3 100644
--- a/components/asset_util/cvar.cpp
+++ b/components/asset_util/cvar.cpp
@@ -36,7 +36,6 @@ REGISTER_GLOBAL_CVAR(fs_overwrite, "overwrite", 'o', "Overwrite existing files",
#if _DEBUG
REGISTER_GLOBAL_CVAR(g_dumpCVars, "dumpCVars", 'd', "Print all cvar values to the console", false);
#endif
-REGISTER_GLOBAL_CVAR(g_outPath, "outPath", NULL, "Target directory for file output", "/");
//
// Register Standard CVars
@@ -47,6 +46,19 @@ REGISTER_CVAR(g_extractImages, "images", NULL, "Extract image files", false);
REGISTER_CVAR(g_extractSounds, "sounds", NULL, "Extract audio files", false);
REGISTER_CVAR(g_useLocalized, "includeLocalized", NULL, "Extract from localized files as well", false);
+REGISTER_CVAR(ents_useLabels, "labels", NULL, "Write entity number in a comment above each entity", false);
+REGISTER_CVAR(ents_genBrushes, "dummyBrushes", NULL, "Generate dummy brushes for script/trigger brush models", true);
+
+REGISTER_CVAR(csvgen_aitypes, "aitype", NULL, "Regenerate all aitype CSVs", false);
+REGISTER_CVAR(csvgen_characters, "character", NULL, "Regenerate all character CSVs", false);
+REGISTER_CVAR(csvgen_xmodelaliases, "xmodelalias", NULL, "Regenerate all xmodelalias CSVs", false);
+
+REGISTER_CVAR(rip_waitForProcess, "waitForProcess", NULL, "Wait for a supported process to launch", false);
+REGISTER_CVAR(rip_waitForMap, "waitForMap", NULL, "Wait for a map to load before continuing", false);
+REGISTER_CVAR(rip_killProcess, "killProcess", NULL, "Terminate the game process when ripping is compeleted", false);
+
+REGISTER_CVAR(fs_outdir, "outdir", NULL, "Target directory for file output (default is the game's raw directory)", "");
+
#undef REGISTER_GLOBAL_CVAR
#undef REGISTER_CVAR
diff --git a/components/asset_util/cvar.h b/components/asset_util/cvar.h
index d346bf6d..176eafb6 100644
--- a/components/asset_util/cvar.h
+++ b/components/asset_util/cvar.h
@@ -92,6 +92,7 @@ class CVar : public Argument
REGISTER_GLOBAL_CVAR(g_verbose);
REGISTER_GLOBAL_CVAR(g_logfile);
REGISTER_GLOBAL_CVAR(fs_overwrite);
+
#if _DEBUG
REGISTER_GLOBAL_CVAR(g_dumpCVars);
#endif
@@ -105,5 +106,18 @@ REGISTER_CVAR(g_extractImages);
REGISTER_CVAR(g_extractSounds);
REGISTER_CVAR(g_useLocalized);
+REGISTER_CVAR(ents_useLabels);
+REGISTER_CVAR(ents_genBrushes);
+
+REGISTER_CVAR(csvgen_aitypes);
+REGISTER_CVAR(csvgen_characters);
+REGISTER_CVAR(csvgen_xmodelaliases);
+
+REGISTER_CVAR(rip_waitForProcess);
+REGISTER_CVAR(rip_waitForMap);
+REGISTER_CVAR(rip_killProcess);
+
+REGISTER_CVAR(fs_outdir);
+
#undef REGISTER_GLOBAL_CVAR
#undef REGISTER_CVAR
diff --git a/components/asset_util/gdt/gsc.cpp b/components/asset_util/gdt/gsc.cpp
index 10bd74a6..87e20d05 100644
--- a/components/asset_util/gdt/gsc.cpp
+++ b/components/asset_util/gdt/gsc.cpp
@@ -1,6 +1,6 @@
-#include "../shared/utility.h"
+#include "../shared/shared_utility.h"
#include "gsc.h"
-#include "assettype\character.h"
+#include "assettype/character.h"
#include "../common/io.h"
#include
diff --git a/components/asset_util/main.cpp b/components/asset_util/main.cpp
index 9033391e..5cfefe02 100644
--- a/components/asset_util/main.cpp
+++ b/components/asset_util/main.cpp
@@ -68,7 +68,6 @@ int app_main(int argc, char** argv)
}
else
#endif
- Con_Print("\n");
AppInfo_Init();
return cmd_info.Cmd()->Exec(cmd_info.Argc(), cmd_info.Argv());
diff --git a/components/asset_util/setup.h b/components/asset_util/setup.h
index f64bb10b..0b60693d 100644
--- a/components/asset_util/setup.h
+++ b/components/asset_util/setup.h
@@ -1,6 +1,6 @@
#pragma once
-#include "../shared/utility.h"
+#include "../shared/shared_utility.h"
int Setup_Init(void);
int Setup_Execute(void);
\ No newline at end of file
diff --git a/components/asset_util/sys/AppInfo.cpp b/components/asset_util/sys/AppInfo.cpp
index d7f29bd9..7b35c5ac 100644
--- a/components/asset_util/sys/AppInfo.cpp
+++ b/components/asset_util/sys/AppInfo.cpp
@@ -2,6 +2,9 @@
#include "../setup.h"
#include "AppInfo.h"
#include "../common/io.h"
+#include "../common/fs.h"
+
+#include "../cvar.h"
char g_GameDirectory[MAX_PATH];
@@ -65,4 +68,18 @@ const char* AppInfo_RawDir()
return rawDir;
#endif
+}
+
+const char* AppInfo_OutDir()
+{
+ if (strlen(fs_outdir.ValueString()) == 0)
+ return AppInfo_RawDir();
+
+ if (strstr(fs_outdir.ValueString(), ":") != NULL && strstr(fs_outdir.ValueString(), ":") != fs_outdir.ValueString() + 1)
+ {
+ Con_Warning("Invalid output directory - falling back to default\n");
+ fs_outdir.AssignRawString("");
+ }
+
+ return fs_outdir.ValueString();
}
\ No newline at end of file
diff --git a/components/asset_util/sys/AppInfo.h b/components/asset_util/sys/AppInfo.h
index e653e6f8..63759761 100644
--- a/components/asset_util/sys/AppInfo.h
+++ b/components/asset_util/sys/AppInfo.h
@@ -5,4 +5,5 @@ const char* AppInfo_AppDir();
const char* AppInfo_FFDir();
const char* AppInfo_ZoneDir();
const char* AppInfo_IWDDir();
-const char* AppInfo_RawDir();
\ No newline at end of file
+const char* AppInfo_RawDir();
+const char* AppInfo_OutDir();
\ No newline at end of file
diff --git a/components/asset_util/sys/process.cpp b/components/asset_util/sys/process.cpp
index 82278b51..310541df 100644
--- a/components/asset_util/sys/process.cpp
+++ b/components/asset_util/sys/process.cpp
@@ -1,7 +1,14 @@
#include
+#include
#include
#include "process.h"
#include "AppInfo.h"
+#include "../common/io.h"
+
+#include "../common/fs.h"
+
+#include
+#pragma comment(lib, "psapi.lib")
int Process_ExecuteConverter(void)
{
@@ -26,4 +33,172 @@ int Process_ExecuteConverter(void)
}
return 1;
-}
\ No newline at end of file
+}
+
+LPCWSTR processStringTable[] =
+{
+ L"BlackOps.exe",
+ //L"BlackOpsMP.exe" - Disabled for VAC potential
+};
+
+PROCESS_TYPE Process_GetProcessType(WCHAR* processString)
+{
+ DWORD supportedProcessCount = sizeof(processStringTable) / sizeof(LPCWSTR);
+
+ for (DWORD i = 0; i < supportedProcessCount; i++)
+ {
+ if (_wcsicmp(processString, processStringTable[i]) == 0)
+ {
+ return (PROCESS_TYPE)(i+1);
+ }
+ }
+
+ return PROCESS_UNKNOWN;
+};
+
+PROCESS_TYPE Process_GetProcessType(processId_t pid)
+{
+ PROCESS_TYPE type = PROCESS_UNKNOWN;
+
+ HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
+ if (!handle)
+ return type;
+
+ WCHAR buf[MAX_PATH];
+ if (GetModuleFileNameExW(handle, 0, buf, MAX_PATH))
+ {
+ type = Process_GetProcessType((WCHAR*)FS_GetFilenameSubStringW(buf));
+ }
+
+ CloseHandle(handle);
+ return type;
+}
+
+processId_t Process_FindSupportedProcess_Launched(void)
+{
+ PROCESSENTRY32 entry;
+ entry.dwSize = sizeof(PROCESSENTRY32);
+ HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
+
+ if (Process32First(snapshot, &entry) == TRUE)
+ {
+ while (Process32Next(snapshot, &entry) == TRUE)
+ {
+ if (PROCESS_TYPE processType = Process_GetProcessType(entry.szExeFile))
+ {
+ CloseHandle(snapshot);
+
+ switch (processType)
+ {
+ case PROCESS_BLACK_OPS:
+ case PROCESS_BLACK_OPS_MP:
+ wprintf(L"Supported process found! ('%s')\n", entry.szExeFile);
+ return entry.th32ProcessID;
+ default:
+ return NULL;
+ }
+ }
+ }
+ }
+
+ CloseHandle(snapshot);
+ return NULL;
+}
+
+processId_t Process_FindSupportedProcess(unsigned int timeoutDelay)
+{
+ _ASSERT(timeoutDelay < UINT_MAX);
+
+ LARGE_INTEGER freq;
+ QueryPerformanceFrequency(&freq);
+
+ LARGE_INTEGER start;
+ QueryPerformanceCounter(&start);
+
+ if (timeoutDelay > 0)
+ Con_Print("Waiting for supported process to launch...\n");
+
+ for (bool had_to_wait = false;; had_to_wait = true)
+ {
+ LARGE_INTEGER split;
+ QueryPerformanceCounter(&split);
+
+ if (processId_t pid = Process_FindSupportedProcess_Launched())
+ {
+ //
+ // If we had to wait for the process to launch - and *just* found the process
+ // that means the process probably *just* launched
+ // So we wait a bit for the process to initialize before returning it
+ //
+ if (timeoutDelay && had_to_wait)
+ Sleep(100);
+
+ return pid;
+ }
+
+ LARGE_INTEGER elapsed_ms;
+ elapsed_ms.QuadPart = split.QuadPart - start.QuadPart;
+ elapsed_ms.QuadPart *= 1000;
+ elapsed_ms.QuadPart /= freq.QuadPart;
+
+ if (elapsed_ms.QuadPart > timeoutDelay || elapsed_ms.QuadPart > UINT_MAX)
+ {
+ const char* reason = "";
+ if (timeoutDelay)
+ reason = " - timeout reached";
+ Con_Error("Could not find supported running process%s.\n", reason);
+ return NULL;
+ }
+ }
+}
+
+void Process_SuspendThreads(processId_t pid)
+{
+ HANDLE threadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
+
+ THREADENTRY32 threadEntry;
+ threadEntry.dwSize = sizeof(THREADENTRY32);
+
+ Thread32First(threadSnapshot, &threadEntry);
+ do
+ {
+ if (threadEntry.th32OwnerProcessID == pid)
+ {
+ HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
+ SuspendThread(hThread);
+ CloseHandle(hThread);
+ }
+ } while (Thread32Next(threadSnapshot, &threadEntry));
+
+ CloseHandle(threadSnapshot);
+}
+
+void Process_ResumeThreads(processId_t pid)
+{
+ HANDLE threadSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, NULL);
+
+ THREADENTRY32 threadEntry;
+ threadEntry.dwSize = sizeof(THREADENTRY32);
+
+ Thread32First(threadSnapshot, &threadEntry);
+ do
+ {
+ if (threadEntry.th32OwnerProcessID == pid)
+ {
+ HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
+ ResumeThread(hThread);
+ CloseHandle(hThread);
+ }
+ } while (Thread32Next(threadSnapshot, &threadEntry));
+
+ CloseHandle(threadSnapshot);
+}
+
+bool Process_KillProcess(processId_t pid)
+{
+ HANDLE hProc = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+ if (!hProc)
+ return false;
+
+ return TerminateProcess(hProc, 0) ? true : false;
+}
diff --git a/components/asset_util/sys/process.h b/components/asset_util/sys/process.h
index ff618f83..902f7b04 100644
--- a/components/asset_util/sys/process.h
+++ b/components/asset_util/sys/process.h
@@ -1,3 +1,38 @@
#pragma once
+enum PROCESS_TYPE
+{
+ PROCESS_UNKNOWN,
+ PROCESS_BLACK_OPS,
+ PROCESS_BLACK_OPS_MP,
+};
+
+typedef unsigned int processId_t;
+
int Process_ExecuteConverter(void);
+
+//
+// Resolve a process' PROCESS_TYPE from the pid
+//
+PROCESS_TYPE Process_GetProcessType(processId_t pid);
+
+//
+// Searches the active process list for a supported process, and returns the processId
+// Returns NULL if no supported process is found withing the given number of ms
+//
+processId_t Process_FindSupportedProcess(unsigned int timeoutDelay=0);
+
+//
+// Suspend all threads for a given processId
+//
+void Process_SuspendThreads(processId_t pid);
+
+//
+// Resume all threads for a given processId
+//
+void Process_ResumeThreads(processId_t pid);
+
+//
+// Kill a given process
+//
+bool Process_KillProcess(processId_t pid);
diff --git a/components/asset_viewer/asset_viewer.vcxproj b/components/asset_viewer/asset_viewer.vcxproj
index 03a90ba1..a8cdbaa8 100644
--- a/components/asset_viewer/asset_viewer.vcxproj
+++ b/components/asset_viewer/asset_viewer.vcxproj
@@ -21,15 +21,15 @@
DynamicLibrary
true
- v120_xp
Unicode
+ v120_xp
DynamicLibrary
false
- v120_xp
true
Unicode
+ v120_xp
@@ -56,6 +56,7 @@
Level3
Disabled
WIN32;_DEBUG;_WINDOWS;_USRDLL;ASSET_VIEWER_EXPORTS;%(PreprocessorDefinitions)
+ false
Windows
@@ -83,10 +84,12 @@
+
+
diff --git a/components/asset_viewer/common.cpp b/components/asset_viewer/common.cpp
new file mode 100644
index 00000000..d9c08ff6
--- /dev/null
+++ b/components/asset_viewer/common.cpp
@@ -0,0 +1,10 @@
+#include "stdafx.h"
+#include "common.h"
+
+void Com_SuppressNoModelSpam(int channel, const char* fmt, const char* name)
+{
+ if (*name)
+ {
+ Com_PrintError(channel, fmt, name);
+ }
+}
diff --git a/components/asset_viewer/common.h b/components/asset_viewer/common.h
new file mode 100644
index 00000000..9301038d
--- /dev/null
+++ b/components/asset_viewer/common.h
@@ -0,0 +1,6 @@
+#pragma once
+
+typedef void(__cdecl *Com_PrintError_t)(int channel, const char *fmt, ...);
+static Com_PrintError_t Com_PrintError = (Com_PrintError_t)0x0040A0B0;
+
+void Com_SuppressNoModelSpam(int channel, const char* fmt, const char* name);
diff --git a/components/asset_viewer/dllmain.cpp b/components/asset_viewer/dllmain.cpp
index 25777aec..93edc2aa 100644
--- a/components/asset_viewer/dllmain.cpp
+++ b/components/asset_viewer/dllmain.cpp
@@ -13,6 +13,19 @@ void strcpy_safe(char *Dest, const char *Src)
VirtualProtect(Dest, srcLen, d, &d);
}
+void DEJA_Printf(LPCSTR fmt, ...)
+{
+ char str[256];
+ va_list va;
+
+ va_start(va, fmt);
+ wvsprintfA(str, fmt, va);
+ printf("DEJA \\\\\\\nDEJA >>> ");
+ printf(str);
+ printf("\nDEJA ///\n");
+ va_end(va);
+}
+
bool g_Initted = false;
BOOL AssetViewerMod_Init()
@@ -72,6 +85,18 @@ BOOL AssetViewerMod_Init()
PatchMemory_WithNOP(0x0080481B, 1);
#endif
+ //
+ // Prevent "ERROR: xmodel '' not found" from being shown when theres no model loaded
+ //
+#if ASSET_VIEWER_DISABLE_NO_MODEL_SPAM
+ PatchCall(0x00433FA2, (PBYTE)&Com_SuppressNoModelSpam);
+#endif
+
+ //
+ // Print DEJA output to console
+ //
+ Detours::X86::DetourFunction((PBYTE)0x008907B0, (PBYTE)DEJA_Printf);
+
g_Initted = true;
return TRUE;
diff --git a/components/asset_viewer/stdafx.h b/components/asset_viewer/stdafx.h
index e8f29361..b4afc079 100644
--- a/components/asset_viewer/stdafx.h
+++ b/components/asset_viewer/stdafx.h
@@ -2,6 +2,9 @@
#define _CRT_SECURE_NO_WARNINGS 1
+#pragma comment(lib, "detours.lib")
+#include "../shared/detours/Detours.h"
+
#define WIN32_LEAN_AND_MEAN
#include
#include
@@ -12,7 +15,7 @@
//
// Shared files
//
-#include "../shared/utility.h"
+#include "../shared/shared_utility.h"
#include "../shared/minidx9/Include/d3dx9.h"
#pragma comment(lib, "../shared/minidx9/Lib/x86/d3dx9.lib")
@@ -25,9 +28,11 @@
#define SRCLINE(x)
#define CHECK_SIZE(Type, Size) static_assert(sizeof(Type) == Size, "Invalid type size!");
+#include "common.h"
+
//
// Asset Viewer Mod Options
// Can be used to easily toggle certain tweaks
//
#define ASSET_VIEWER_DISABLE_MATERIAL_ASSERT 1
-
+#define ASSET_VIEWER_DISABLE_NO_MODEL_SPAM 1
diff --git a/components/cod2map/arg.cpp b/components/cod2map/arg.cpp
new file mode 100644
index 00000000..222a0c34
--- /dev/null
+++ b/components/cod2map/arg.cpp
@@ -0,0 +1,73 @@
+#include "stdafx.h"
+
+#define VANILLA_ARG_COUNT 29
+
+struct Argument
+{
+ const char *arg;
+ const char *description;
+
+ // Returns the number of args it consumes
+ int(__cdecl *func)(int argc, const char **argv);
+};
+
+Argument* g_args = (Argument*)0x004F4390;
+
+Argument g_customArgs[] =
+{
+ { "-debugProbes", "Add a reflective debug sphere on top of each reflection probe", &Probe_EnableDebugSpheres_f }
+};
+
+void __declspec(naked) Arg_Handle_hk(void)
+{
+ _asm
+ {
+ push ebx // argv
+ push [esp+8] // argc
+ call Arg_Handle
+ add esp, 8
+ retn
+ }
+}
+
+int Arg_CheckForMatch(const char* name, int index)
+{
+ if (index < VANILLA_ARG_COUNT)
+ return _stricmp(name, g_args[index].arg);
+
+ return _stricmp(name, g_customArgs[index - VANILLA_ARG_COUNT].arg);
+}
+
+int Arg_Handle(int argc, const char** argv)
+{
+ int argIndex = 0;
+ while (Arg_CheckForMatch(*argv, argIndex))
+ {
+ if (++argIndex < VANILLA_ARG_COUNT + ARRAYSIZE(g_customArgs))
+ continue;
+
+ // There was an unknown arg - print usage
+ Com_Printf("\n\nUnknown argument '%s'\n\n", *argv);
+ Com_Printf("USAGE: cod2map [options] mapname, where options are 0 or more of the following.\n");
+ Com_Printf("Options ignore capitalization; it is only present in the list for clarity.\n");
+
+ // Print vanilla args
+ for (int i = 0; i < VANILLA_ARG_COUNT; i++)
+ {
+ Com_Printf("%-20s %s\n", g_args[i].arg, g_args[i].description);
+ }
+
+ // Print custom args
+ for (int i = 0; i < ARRAYSIZE(g_customArgs); i++)
+ {
+ Com_Printf("%-20s %s\n", g_customArgs[i].arg, g_customArgs[i].description);
+ }
+
+ exit(-1);
+ }
+
+ if (argIndex < VANILLA_ARG_COUNT)
+ return g_args[argIndex].func(argc, argv);
+ else
+ return g_customArgs[argIndex - VANILLA_ARG_COUNT].func(argc, argv);
+}
diff --git a/components/cod2map/arg.h b/components/cod2map/arg.h
new file mode 100644
index 00000000..c55d667f
--- /dev/null
+++ b/components/cod2map/arg.h
@@ -0,0 +1,4 @@
+#pragma once
+
+void Arg_Handle_hk(void);
+int Arg_Handle(int argc, const char** argv);
diff --git a/components/cod2map/cod2map.vcxproj b/components/cod2map/cod2map.vcxproj
index 6cef4704..850ee755 100644
--- a/components/cod2map/cod2map.vcxproj
+++ b/components/cod2map/cod2map.vcxproj
@@ -87,22 +87,32 @@
+
+
+
+
+
+
+
+ {5fb372dd-a7e1-4a4b-9a28-3ac02543e9e8}
+
+
diff --git a/components/cod2map/common.h b/components/cod2map/common.h
new file mode 100644
index 00000000..cb991483
--- /dev/null
+++ b/components/cod2map/common.h
@@ -0,0 +1,4 @@
+#pragma once
+
+typedef int (__cdecl* Com_Printf_t)(char *fmt, ...);
+static Com_Printf_t Com_Printf = (Com_Printf_t)0x00418F60;
diff --git a/components/cod2map/dllmain.cpp b/components/cod2map/dllmain.cpp
index 8314668d..1e8faac2 100644
--- a/components/cod2map/dllmain.cpp
+++ b/components/cod2map/dllmain.cpp
@@ -1,3 +1,4 @@
+#define G_VERSION 1, 0, 0
#include "stdafx.h"
LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo)
@@ -43,6 +44,9 @@ int __cdecl ConvertBSP_Post(FILE* h)
iBSP->Convert(BSPVERSION_COD_BO);
Light_FixPrimaryLightInfo(&iBSP->lumps[LUMP_PRIMARY_LIGHTS]);
+
+ if (Probe_UseDebugSpheres())
+ Probe_AddDebugSpheres(iBSP);
len = iBSP->PotentialFileSize();
buf = new BYTE[len];
@@ -136,11 +140,21 @@ void Init_MapMod()
//
Detours::X86::DetourFunction((PBYTE)0x0043D649, (PBYTE)&mfh_PrimaryLightHandler);
+ //
+ // Add -debugReflectionProbes launch arg
+ //
+ Detours::X86::DetourFunction((PBYTE)0x00406D20, (PBYTE)&Arg_Handle_hk);
+
+ //
+ // (Deprecated): Replacement for the pointfile extension
+ //
+ /*
void* stringPatch = ".pts";
PatchMemory(0x0042626F, (PBYTE)&stringPatch, 4);
PatchMemory(0x00426514, (PBYTE)&stringPatch, 4);
stringPatch = "%s.pts";
PatchMemory(0x00406F4E, (PBYTE)&stringPatch, 4);
+ */
//
// Increase (double) the max amount of curvenn/terrain collision verts
diff --git a/components/cod2map/probe.cpp b/components/cod2map/probe.cpp
new file mode 100644
index 00000000..f9db6384
--- /dev/null
+++ b/components/cod2map/probe.cpp
@@ -0,0 +1,53 @@
+#include "stdafx.h"
+#include "../D3DBSP_Lib/D3DBSP_Lib/ReflectionProbes.h"
+
+typedef DiskGfxReflectionProbe_BO Probe;
+
+static bool g_shouldAddDebugSpheres = false;
+
+int Probe_EnableDebugSpheres_f(int argc, const char **argv)
+{
+ g_shouldAddDebugSpheres = true;
+ return 1;
+}
+
+bool Probe_UseDebugSpheres(void)
+{
+ return g_shouldAddDebugSpheres;
+}
+
+int Probe_AddDebugSpheres(D3DBSP* bsp)
+{
+ if (bsp == NULL)
+ return -1;
+
+ Lump& lump_probes = bsp->lumps[LUMP_REFLECTION_PROBES];
+ Lump& lump_ents = bsp->lumps[LUMP_ENTITIES];
+
+ if (lump_probes.isEmpty || lump_ents.isEmpty)
+ return 0;
+
+ std::string str = (char*)lump_ents.content;
+ lump_ents.FreeMemory();
+
+ char buf[1024];
+ const char* fmt =
+ "{\n"
+ "\"classname\" \"misc_model\"\n"
+ "\"model\" \"%s\"\n"
+ "\"origin\" \"%f %f %f\"\n"
+ "}\n";
+
+ Probe* probe = (Probe*)lump_probes.content;
+ for (unsigned int i = 0; i < lump_probes.size / sizeof(Probe); i++)
+ {
+ buf[0] = '\0';
+ sprintf_s(buf, fmt, "test_sphere_silver", probe[i].origin[0], probe[i].origin[1], probe[i].origin[2]);
+ str += buf;
+ }
+
+ lump_ents.AllocateMemory(str.size());
+ memcpy(lump_ents.content, str.c_str(), str.size());
+
+ return 0;
+}
diff --git a/components/cod2map/probe.h b/components/cod2map/probe.h
new file mode 100644
index 00000000..2fadabe2
--- /dev/null
+++ b/components/cod2map/probe.h
@@ -0,0 +1,6 @@
+#pragma once
+
+int Probe_EnableDebugSpheres_f(int argc, const char **argv);
+
+bool Probe_UseDebugSpheres(void);
+int Probe_AddDebugSpheres(D3DBSP* bsp);
diff --git a/components/cod2map/stdafx.h b/components/cod2map/stdafx.h
index 88427e91..4ebd030f 100644
--- a/components/cod2map/stdafx.h
+++ b/components/cod2map/stdafx.h
@@ -2,7 +2,11 @@
#define _CRT_SECURE_NO_WARNINGS 1
+#pragma comment(lib, "detours.lib")
+#include "../shared/detours/Detours.h"
+
#define WIN32_LEAN_AND_MEAN
+
#include
#include
#include
@@ -11,7 +15,8 @@
//
// Shared files
//
-#include "../shared/utility.h"
+#include "../shared/shared_utility.h"
+#include "../shared/shared_version.h"
#include "PageGuard.h"
@@ -24,9 +29,13 @@
#pragma comment(lib, "../../build/Release/D3DBSP_Lib.lib")
#endif
+using namespace D3DBSP_Lib;
+#include "arg.h"
#include "vec.h"
#include "kvs.h"
+#include "probe.h"
#include "lights.h"
+#include "common.h"
#include "libqhull_geom.h"
#include "com_files.h"
\ No newline at end of file
diff --git a/components/cod2rad/cod2rad.vcxproj b/components/cod2rad/cod2rad.vcxproj
index 14bc8364..be181e25 100644
--- a/components/cod2rad/cod2rad.vcxproj
+++ b/components/cod2rad/cod2rad.vcxproj
@@ -21,15 +21,15 @@
DynamicLibrary
true
- v120_xp
Unicode
+ v120_xp
DynamicLibrary
false
- v120_xp
true
Unicode
+ v120_xp
@@ -57,6 +57,8 @@
WIN32;_DEBUG;_WINDOWS;_USRDLL;COD2RAD_EXPORTS;%(PreprocessorDefinitions)
MultiThreadedDebugDLL
false
+ true
+ Speed
Windows
@@ -75,6 +77,7 @@
WIN32;NDEBUG;_WINDOWS;_USRDLL;COD2RAD_EXPORTS;%(PreprocessorDefinitions)
MultiThreadedDLL
true
+ Speed
Windows
@@ -90,12 +93,17 @@
+
-
+
+
+
+
+
@@ -106,8 +114,8 @@
+
-
false
@@ -117,9 +125,14 @@
+
+
+
+
+
@@ -127,6 +140,12 @@
Create
+
+
+
+
+ {5fb372dd-a7e1-4a4b-9a28-3ac02543e9e8}
+
diff --git a/components/cod2rad/com_bsp_load_obj.cpp b/components/cod2rad/com_bsp_load_obj.cpp
index 90c76411..850d570c 100644
--- a/components/cod2rad/com_bsp_load_obj.cpp
+++ b/components/cod2rad/com_bsp_load_obj.cpp
@@ -54,22 +54,24 @@ void Com_SaveLightmaps_HDR(Lump* lump)
void Com_SaveLightgrid_HDR(Lump* lump)
{
- DWORD* lightgridColorCount = (DWORD*)0x112BAAB4;
-
DiskGfxLightGridColors_BO* lightgridColors = (DiskGfxLightGridColors_BO*)lump->content;
- for (DWORD i = 0; i < *lightgridColorCount; i++)
+ for (DWORD i = 0; i < lightGridColorCount; i++)
{
for (int y = 0; y < 56; y++)
{
for (int x = 0; x < 3; x++)
{
+#if USE_LEGACY_HDR
lightgridColors[i].rgb[y][x] = DiskLightGridSampleColors_HDR[i][y][x];
+#endif
+ lightgridColors[i].rgb[y][x] = disk_lightGridColorsHDR[i].rgb[y][x] * 4; // Multiplying the lighting by 4 (so its 0 - 1024 range) seems to look ok
}
}
}
-
+#if USE_LEGACY_HDR
delete[] DiskLightGridSampleColors_HDR;
+#endif
}
int __cdecl Com_SaveBsp_EnforceVersion(FILE* h)
@@ -93,7 +95,9 @@ int __cdecl Com_SaveBsp_EnforceVersion(FILE* h)
if (g_HDR)
{
+#if USE_LEGACY_HDR
delete[] LightGridSampleColors_HDR;
+#endif
Com_SaveLightmaps_HDR(&iBSP->lumps[LUMP_LIGHTBYTES]);
Com_SaveLightgrid_HDR(&iBSP->lumps[LUMP_LIGHTGRIDCOLORS]);
diff --git a/components/cod2rad/com_files.cpp b/components/cod2rad/com_files.cpp
index c8377ff8..e6ce5113 100644
--- a/components/cod2rad/com_files.cpp
+++ b/components/cod2rad/com_files.cpp
@@ -52,6 +52,35 @@ void __declspec(naked) hk_FS_FOpenFileRead()
}
}
+int __cdecl FS_ReadFile(const char *qpath, void **buffer)
+{
+ int result = 0;
+
+ _asm
+ {
+ pushad
+ push buffer
+ mov eax, qpath
+ mov ebx, 0x00423E80
+ call ebx
+ add esp, 4
+ mov result, eax
+ popad
+ }
+}
+
+void __cdecl FS_FreeFile(void *buffer)
+{
+ _asm
+ {
+ pushad
+ mov eax, buffer
+ mov ebx, 0x004232D0
+ call ebx
+ popad
+ }
+}
+
const char* strlastof(const char* str, const char* delims)
{
int delimCount = strlen(delims);
diff --git a/components/cod2rad/com_files.h b/components/cod2rad/com_files.h
index 912f1175..8c91f36d 100644
--- a/components/cod2rad/com_files.h
+++ b/components/cod2rad/com_files.h
@@ -24,6 +24,8 @@ static FS_FileRead_t FS_FileRead = (FS_FileRead_t)0x00403F10;
typedef size_t(__cdecl* FS_FileWrite_t)(const void *DstBuf, size_t Size, size_t Count, FILE *File);
static FS_FileWrite_t FS_FileWrite = (FS_FileWrite_t)0x0040408A;
+int __cdecl FS_ReadFile(const char *qpath, void **buffer);
+void __cdecl FS_FreeFile(void *buffer);
size_t FS_FileGetFileSize(FILE* h);
diff --git a/components/cod2rad/com_math.cpp b/components/cod2rad/com_math.cpp
new file mode 100644
index 00000000..caa15003
--- /dev/null
+++ b/components/cod2rad/com_math.cpp
@@ -0,0 +1,37 @@
+#include "stdafx.h"
+
+int ClampByte(int v)
+{
+ if (v < 0)
+ return 0;
+ if (v > 255)
+ return 255;
+ return v;
+}
+
+BYTE __cdecl EncodeFloatInByte(float flt)
+{
+ double f = flt;
+ if (f < 0.0)
+ {
+ return 0;
+ }
+
+ if (f > 1.0)
+ {
+ return 255;
+ }
+
+ return (BYTE)(f * 255.0);
+}
+
+WORD __cdecl EncodeFloatInWord(float flt)
+{
+ double f = flt;
+ if (f < 0.0)
+ {
+ return 0;
+ }
+
+ return (WORD)(f * 255.0);
+}
diff --git a/components/cod2rad/com_math.h b/components/cod2rad/com_math.h
new file mode 100644
index 00000000..5614a8a1
--- /dev/null
+++ b/components/cod2rad/com_math.h
@@ -0,0 +1,15 @@
+#pragma once
+#include
+#include "vector.h"
+
+//
+// Clamp an integer value to the 0 - 255 range
+//
+int ClampByte(int v);
+
+//
+// Encode a float ( 0.0 - 1.0 ) to a BYTE ( 0 - 255 )
+//
+BYTE __cdecl EncodeFloatInByte(float flt);
+
+WORD __cdecl EncodeFloatInWord(float flt);
diff --git a/components/cod2rad/com_memory.cpp b/components/cod2rad/com_memory.cpp
index 04de3a9b..62c41ea6 100644
--- a/components/cod2rad/com_memory.cpp
+++ b/components/cod2rad/com_memory.cpp
@@ -1,5 +1,29 @@
#include "stdafx.h"
+struct hunkHeader_t
+{
+ unsigned int magic;
+ int size;
+ const char *name;
+ int dummy;
+};
+STATIC_ASSERT_SIZE(hunkHeader_t, 16);
+
+struct hunkUsed_t
+{
+ int permanent;
+ int temp;
+};
+STATIC_ASSERT_SIZE(hunkUsed_t, 8);
+
+VANILLA_VALUE(s_hunkData, char*, 0x174EFFC0);
+VANILLA_VALUE(hunk_low, hunkUsed_t, 0x1748F158);
+
+namespace vanilla
+{
+ auto free = (void(__cdecl*)(void*lpMem))0x00402FD7;
+}
+
void* FS_AllocMem(size_t size)
{
if (size % 4096)
@@ -10,3 +34,35 @@ void* FS_AllocMem(size_t size)
return buf;
}
+
+void* _Hunk_AllocTempMemoryInternal(size_t _size)
+{
+ static DWORD dwFunc = 0x004219A0;
+
+ void* result = NULL;
+ _asm
+ {
+ mov eax, _size
+ call dwFunc
+ mov result, eax
+ }
+ return result;
+}
+
+void _Hunk_FreeTempMemory(void *buf)
+{
+ if (s_hunkData)
+ {
+ ASSERT(buf);
+ hunkHeader_t* hdr = reinterpret_cast(buf)-1;
+ if (hdr->magic != 0x89537892)
+ Com_Error("Hunk_FreeTempMemory: bad magic");
+ hdr->magic = 0x89537893;
+ ASSERT(hdr == (void *)(s_hunkData + ((hunk_low.temp - hdr->size + 15) & ~15)));
+ hunk_low.temp -= hdr->size;
+ }
+ else
+ {
+ vanilla::free(buf);
+ }
+}
diff --git a/components/cod2rad/com_memory.h b/components/cod2rad/com_memory.h
index 98dbd3a2..014ee192 100644
--- a/components/cod2rad/com_memory.h
+++ b/components/cod2rad/com_memory.h
@@ -7,3 +7,6 @@ typedef void (__cdecl* Z_Free_t)(void* ptr);
static Z_Free_t Z_Free = (Z_Free_t)0x00402FD7;
void* FS_AllocMem(size_t size);
+
+void* _Hunk_AllocTempMemoryInternal(size_t size);
+void _Hunk_FreeTempMemory(void *buf);
diff --git a/components/cod2rad/common.h b/components/cod2rad/common.h
index 3dba7e70..57416381 100644
--- a/components/cod2rad/common.h
+++ b/components/cod2rad/common.h
@@ -1,4 +1,10 @@
#pragma once
-typedef int(__cdecl* Com_FatalError_t)(char* format, ...);
-static Com_FatalError_t Com_FatalError = (Com_FatalError_t)0x004294B0;
+// ...
+
+typedef int(__cdecl* Com_Print_t)(const char* fmt, ...);
+static Com_Print_t Com_Printf = (Com_Print_t)0x00441010;
+static Com_Print_t Com_Error = (Com_Print_t)0x00440250;
+
+typedef void(__cdecl* Com_AssembleImageFilepath_t)(const char* image, char* buf);
+static Com_AssembleImageFilepath_t Com_AssembleImageFilepath = (Com_AssembleImageFilepath_t)0x0041E1A0;
diff --git a/components/cod2rad/console.cpp b/components/cod2rad/console.cpp
deleted file mode 100644
index 6c32c697..00000000
--- a/components/cod2rad/console.cpp
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "stdafx.h"
-
-void UpdateProgressPrint()
-{
- ((void(__cdecl *)())0x00429500)();
-}
-
-void SetProgress(int a1, int a2)
-{
- *(DWORD *)0x1730241C = a1;
- *(DWORD *)0x17302420 = a2;
- UpdateProgressPrint();
-}
\ No newline at end of file
diff --git a/components/cod2rad/console.h b/components/cod2rad/console.h
deleted file mode 100644
index 41258591..00000000
--- a/components/cod2rad/console.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#pragma once
-
-void UpdateProgressPrint();
-void SetProgress(int a1, int a2);
\ No newline at end of file
diff --git a/components/cod2rad/dllmain.cpp b/components/cod2rad/dllmain.cpp
index 44f9cbba..e1d0af9c 100644
--- a/components/cod2rad/dllmain.cpp
+++ b/components/cod2rad/dllmain.cpp
@@ -1,3 +1,4 @@
+#define G_VERSION 1, 3, 0
#include "stdafx.h"
static char* techsetPath = "pimp/techsets/%s%s.techset";
@@ -15,6 +16,27 @@ LONG WINAPI MyUnhandledExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo)
//return PageGuard_Check(ExceptionInfo);
}
+#if MINIMAL_MATERIALS
+int __cdecl MaterialRedirect(char* dst, const char* fmt, const char* name)
+{
+ printf("MTL: %s\n", name);
+
+ std::string n = name;
+ if (n == "$default" || n.find("sky") != std::string::npos)
+ return sprintf(dst, fmt, name);
+ else
+ return sprintf(dst, fmt, "blockout_test_asphalt");
+}
+#endif
+
+#if CUSTOM_RAND
+int getRandomNumber()
+{
+ return 4; // chosen by fair dice roll.
+ // guaranteed to be random.
+}
+#endif
+
const int MAX_MAP_COLLISIONVERTS = 65536 * 2;
const int MAX_MAP_COLLISIONVERTS_SIZE = MAX_MAP_COLLISIONVERTS * 12;
BYTE collVertData[MAX_MAP_COLLISIONVERTS_SIZE];
@@ -49,6 +71,18 @@ BOOL cod2rad_Init()
//
PatchArguments();
+ //
+ // Reenable Improved Quantization in Extra Mode
+ // Disabled - Currently Corrupts LightGrid Colors
+ //
+ // PatchMemory(0x00440870, (PBYTE)"\x01", 1);
+
+ //
+ // Patch Default Gamma 2.2 -> 2.0
+ //
+ float gamma_default = 2.0f;
+ PatchMemory(0x00462ABC, (PBYTE)&gamma_default, 4);
+
//
// Increase limits for LUMP_COLLISIONVERTS
//
@@ -61,8 +95,8 @@ BOOL cod2rad_Init()
//
// Enable Techset / Technique Path Redirection
//
- PatchMemory(0x0042CA85, (PBYTE)&techiquePath, 4);
- PatchMemory(0x0042CB4C, (PBYTE)&techsetPath, 4);
+ //PatchMemory(0x0042CA85, (PBYTE)&techiquePath, 4);
+ //PatchMemory(0x0042CB4C, (PBYTE)&techsetPath, 4);
//
// Patch the requested IWI version to match BO1
@@ -70,6 +104,7 @@ BOOL cod2rad_Init()
PatchMemory(0x00417AA7, (PBYTE)"\xEB", 1);
PatchMemory(0x00417B41, (PBYTE)"\x30", 1);
FS_FileOpen = (FS_FileOpen_t)Detours::X86::DetourFunction((PBYTE)0x004034E8, (PBYTE)&FS_ImageRedirect);
+ Detours::X86::DetourFunction((PBYTE)0x00417A50, (PBYTE)&hk_Image_GetRawPixels);
//
// Patch Xmodel loading functions to support Black Ops
@@ -94,6 +129,20 @@ BOOL cod2rad_Init()
Detours::X86::DetourFunction((PBYTE)0x004413D0, (PBYTE)&hk_FS_FOpenFileRead);
Detours::X86::DetourFunction((PBYTE)0x00442E97, (PBYTE)&mfh_Com_SaveBsp);
+#if !USE_LEGACY_HDR
+ Detours::X86::DetourFunction((PBYTE)0x00432830, (PBYTE)&hk_StoreLightBytes);
+#endif
+
+ Detours::X86::DetourFunction((PBYTE)0x0042B450, (PBYTE)&RegisterLightDef);
+
+#if MINIMAL_MATERIALS
+ PatchCall(0x0042CD4A, (PBYTE)&MaterialRedirect);
+#endif
+
+#if CUSTOM_RAND
+ Detours::X86::DetourFunction((PBYTE)0x004047C3, (PBYTE)&getRandomNumber);
+#endif
+
g_initted = true;
return TRUE;
}
@@ -107,6 +156,15 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv
cod2rad_Init();
break;
case DLL_PROCESS_DETACH:
+#if VARIANCE_TRACKER
+ VARIANCE_LOG(vt_GetInitialLightingHighlightDir);
+ VARIANCE_LOG(vt_GetColorsForHighlightDir_1);
+ VARIANCE_LOG(vt_GetColorsForHighlightDir_2);
+ VARIANCE_LOG(vt_GetLightingApproximationError);
+ VARIANCE_LOG(vt_GetGradientOfLightingErrorFunctionWithRespectToDir);
+ VARIANCE_LOG(vt_ImproveLightingApproximation_1);
+ VARIANCE_LOG(vt_ImproveLightingApproximation_2);
+#endif
Con_Restore();
break;
}
diff --git a/components/cod2rad/hdr.cpp b/components/cod2rad/hdr.cpp
index 47dba5a8..d838d894 100644
--- a/components/cod2rad/hdr.cpp
+++ b/components/cod2rad/hdr.cpp
@@ -3,8 +3,10 @@
vec4* LightmapBytes_HDR = nullptr;
vec4* Lightmap2Bytes_HDR = nullptr;
+#if USE_LEGACY_HDR
SampleColorHDR* LightGridSampleColors_HDR = nullptr;
DiskSampleColorHDR* DiskLightGridSampleColors_HDR = nullptr;
+#endif
void R_Init_LightmapsHDR()
{
@@ -12,6 +14,7 @@ void R_Init_LightmapsHDR()
Lightmap2Bytes_HDR = new vec4[512 * 512 * *g_LightmapCount];
}
+#if USE_LEGACY_HDR
void R_Init_LightgridHDR()
{
LightGridSampleColors_HDR = new SampleColorHDR[*g_lightgridSampleCount + 1];
@@ -22,11 +25,13 @@ void R_Init_DiskLightgridHDR()
{
DiskLightGridSampleColors_HDR = new DiskSampleColorHDR[*g_diskLightgridSampleCount];
}
+#endif
void PatchHDR_Lightmaps()
{
+#if USE_LEGACY_HDR
//
- // Remove HDR Clamping when getting lightmap pixels
+ // Remove HDR Clamping when getting lightmap pixels (GetColorsForHighlightDir)
//
PatchMemory(0x00431186, (PBYTE)"\xEB", 1);
PatchMemory(0x004311AB, (PBYTE)"\xEB", 1);
@@ -39,7 +44,7 @@ void PatchHDR_Lightmaps()
PatchMemory(0x00431388, (PBYTE)"\xEB", 1);
//
- // Remove HDR Pixel Clamping Before Converting to RGB8
+ // Remove HDR Pixel Clamping Before Converting to RGB8 (StoreLightBytes)
//
PatchMemory(0x004328AA, (PBYTE)"\xEB", 1);
PatchMemory(0x004328EC, (PBYTE)"\xEB", 1);
@@ -48,25 +53,56 @@ void PatchHDR_Lightmaps()
PatchMemory(0x004329C8, (PBYTE)"\xEB", 1);
PatchMemory(0x00432A0B, (PBYTE)"\xEB", 1);
PatchMemory(0x00432A86, (PBYTE)"\xEB", 1);
+#endif
//
// Enable HDR Lighmap Allocation and Storage
//
o_R_BuildFinalLightmaps = (R_BuildFinalLightmaps_t)Detours::X86::DetourFunction((PBYTE)0x00432D70, (PBYTE)&hk_R_BuildFinalLightmaps);
+
+#if USE_LEGACY_HDR
Detours::X86::DetourFunction((PBYTE)0x00432830, (PBYTE)&hk_R_StoreLightmapPixel);
Detours::X86::DetourFunction((PBYTE)0x00432885, (PBYTE)&mfh_R_StoreLightmapPixel);
+#endif
}
void PatchHDR_Lightgrid()
{
+#if USE_LEGACY_HDR
//
// Remove HDR Clamping
//
PatchMemory(0x00434E87, (PBYTE)"\xEB", 1);
+ // Allocates the HDR sample color buffer, runs just before SetupLightRegions()
+ //
Detours::X86::DetourFunction((PBYTE)0x00436847, (PBYTE)&mfh_R_Init_Lightgrid);
- Detours::X86::DetourFunction((PBYTE)0x00434EA7, (PBYTE)&mfh_R_Store_LightgridSample);
- Detours::X86::DetourFunction((PBYTE)0x00435C8E, (PBYTE)&mfh_R_Alloc_DiskLightGridColors);
- o_R_Store_QuantizedLightGridSample = (R_Store_QuantizedLightGridSample_t)Detours::X86::DetourFunction((PBYTE)0x00433890, (PBYTE)&hk_R_Store_QuantizedLightGridSample);
+ Detours::X86::DetourFunction((PBYTE)0x00434EA7, (PBYTE)&mfh_R_Store_LightgridSample); // StoreLightingForDir - Just rewrite the whole func
+
+ Detours::X86::DetourFunction((PBYTE)0x00435C8E, (PBYTE)&mfh_R_Alloc_DiskLightGridColors); // ClusterLightGridValues
+ o_R_Store_QuantizedLightGridSample = (R_Store_QuantizedLightGridSample_t)Detours::X86::DetourFunction((PBYTE)0x00433890, (PBYTE)&hk_R_Store_QuantizedLightGridSample); // SetLightGridColorsForCluster
+#endif
+
+ //
+ // Patch the struct size when allocating lightGridGlob->colors in CalculateLightGrid()
+ // Makes it twice as large to accommodate for the HDR colors
+ //
+ // Note - lightgridglob->colors pointers in essentially everywhere its used
+ //
+ int size = sizeof(GfxLightGridColorsHDR); //GfxLightGridColors
+ PatchMemory(0x004367C0, (PBYTE)&size, 4);
+
+ //
+ // Adjust the multiplication used to generate a pointer to a specific entry to pass to StoreLightingForDir
+ // Accomodates the new struct type
+ //
+ PatchMemory(0x004364F3, (PBYTE)&size, 4);
+ PatchMemory(0x0043561B, (PBYTE)&size, 4);
+
+ //
+ // Automatically adjust the GfxLightGridColors pointer passed to StoreLightingForDir for our HDR struct
+ //
+ Detours::X86::DetourFunction((PBYTE)0x00434E20, (PBYTE)&StoreLightingForDir);
+ Detours::X86::DetourFunction((PBYTE)0x00435B70, (PBYTE)&ClusterLightGridValues);
}
\ No newline at end of file
diff --git a/components/cod2rad/hdr.h b/components/cod2rad/hdr.h
index a04538fd..eefdf97d 100644
--- a/components/cod2rad/hdr.h
+++ b/components/cod2rad/hdr.h
@@ -5,14 +5,17 @@
extern vec4* LightmapBytes_HDR;
extern vec4* Lightmap2Bytes_HDR;
+#if USE_LEGACY_HDR
extern SampleColorHDR* LightGridSampleColors_HDR;
extern DiskSampleColorHDR* DiskLightGridSampleColors_HDR;
+#endif
void PatchHDR_Lightmaps();
void PatchHDR_Lightgrid();
void R_Init_LightmapsHDR();
-void R_Init_LightgridHDR();
+#if USE_LEGACY_HDR
void R_Init_DiskLightgridHDR();
+#endif
void __cdecl R_StoreLightmapPixelHDR(vec4* pel1, vec4* pel2, int lightmap, int row, int pel);
\ No newline at end of file
diff --git a/components/cod2rad/lighting.cpp b/components/cod2rad/lighting.cpp
new file mode 100644
index 00000000..3576c7e5
--- /dev/null
+++ b/components/cod2rad/lighting.cpp
@@ -0,0 +1,146 @@
+#include "stdafx.h"
+
+double __cdecl GammaCorrect(float color)
+{
+ long double _color = color;
+ if (_color > 0.0)
+ {
+ return pow(_color, 1.0 / g_Gamma);
+ }
+
+ return 0.0;
+}
+
+void __cdecl GammaCorrectColor(float *rgb)
+{
+ rgb[0] = (float)GammaCorrect(rgb[0]);
+ rgb[1] = (float)GammaCorrect(rgb[1]);
+ rgb[2] = (float)GammaCorrect(rgb[2]);
+}
+
+void __cdecl GammaCorrectColor(vec3* rgb)
+{
+ rgb->r = (float)GammaCorrect(rgb->r);
+ rgb->g = (float)GammaCorrect(rgb->g);
+ rgb->b = (float)GammaCorrect(rgb->b);
+}
+
+//
+// Clamp src to 0.0 - 'max' range, returns true if the value needed to be clamped
+//
+bool ClampColor(vec3 *dst, vec3 *src, float max)
+{
+ bool clamped = false;
+
+ if (src->r >= 0.0f)
+ {
+ if (src->r <= max)
+ {
+ dst->r = src->r;
+ }
+ else
+ {
+ dst->r = max;
+ clamped = true;
+ }
+ }
+ else
+ {
+ dst->r = 0.0f;
+ clamped = true;
+ }
+
+ if (src->g >= 0.0f)
+ {
+ if (src->g <= max)
+ {
+ dst->g = src->g;
+ }
+ else
+ {
+ dst->g = max;
+ clamped = true;
+ }
+ }
+ else
+ {
+ dst->g = 0.0f;
+ clamped = true;
+ }
+
+ if (src->b >= 0.0f)
+ {
+ if (src->b <= max)
+ {
+ dst->b = src->b;
+ }
+ else
+ {
+ dst->b = max;
+ clamped = true;
+ }
+ }
+ else
+ {
+ dst->b = 0.0f;
+ clamped = true;
+ }
+
+ return clamped;
+}
+
+vec3 ColorSRGBtoLinear(vec3* color)
+{
+ vec3 tmp = ((*color + 0.055f) / 1.055f);
+ tmp.r = pow(color->r, 2.4f);
+ tmp.g = pow(color->g, 2.4f);
+ tmp.b = pow(color->b, 2.4f);
+
+ vec3 linear;
+ linear.r = (color->r > 0.04045f) ? tmp.r : color->r / 12.92f;
+ linear.g = (color->g > 0.04045f) ? tmp.g : color->g / 12.92f;
+ linear.b = (color->b > 0.04045f) ? tmp.b : color->b / 12.92f;
+
+ return linear;
+}
+
+void EncodeNormalToFloats(vec3* normal, vec2* out)
+{
+ out->x = normal->x / normal->z * 0.25f + 0.5f;
+ out->y = normal->y / normal->z * 0.25f + 0.5f;
+}
+
+void EncodeNormalToBytes(vec3* normal, BYTE(&out)[2])
+{
+ out[0] = EncodeFloatInByte(normal->x / normal->z * 0.25f + 0.5f);
+ out[1] = EncodeFloatInByte(normal->y / normal->z * 0.25f + 0.5f);
+}
+
+void DecodeNormalFromBytes(int packed_x, int packed_y, vec3* out)
+{
+ /*
+ if a = b * 0.25 + 0.5
+ then b = 4*a-2
+ but since we're also converting from byte to float
+ we need b = (4*a)/255 + 0.5
+ which simplifies to the formula below
+ */
+ out->x = (float)packed_x / 63.75f - 2.0f;
+ out->y = (float)packed_y / 63.75f - 2.0f;
+ out->z = 1.0f;
+
+ // This only works if the original normal's z axis was > 0.0f (which is how all normals should be)
+ Vec3Normalize(out);
+}
+
+void DecodeNormalFromFloats(vec2* packed, vec3* out)
+{
+ /*
+ if a = b * 0.25 + 0.5
+ then b = 4*a-2
+ */
+ *out = vec3((*packed * 4.0f) - 2.0f, 1.0f);
+
+ // This only works if the original normal's z axis was > 0.0f (which is how all normals should be)
+ Vec3Normalize(out);
+}
\ No newline at end of file
diff --git a/components/cod2rad/lighting.h b/components/cod2rad/lighting.h
new file mode 100644
index 00000000..77e5eb49
--- /dev/null
+++ b/components/cod2rad/lighting.h
@@ -0,0 +1,25 @@
+#pragma once
+
+static int& g_basisDirectionsCount = *(int*)0x16E99F70;
+static vec3*& g_basisDirections = *(vec3**)0x16E99F74;
+
+static float& g_Gamma = *(float*)0x153C907C;
+
+double __cdecl GammaCorrect(float color);
+void __cdecl GammaCorrectColor(float *rgb);
+void __cdecl GammaCorrectColor(vec3* rgb);
+
+bool ClampColor(vec3 *dst, vec3 *src, float max = 1.0f);
+
+vec3 ColorSRGBtoLinear(vec3* color);
+
+void EncodeNormalToFloats(vec3* normal, vec2* out);
+void EncodeNormalToBytes(vec3* normal, BYTE(&out)[2]);
+
+/*
+ Generally the incoming packed values are really:
+ packed_xz = normal.x / normal.z * 0.25 + 0.5
+ packed_yz = normal.y / normal.z * 0.25 + 0.5
+*/
+void DecodeNormalFromBytes(int packed_xz, int packed_yz, vec3* out);
+void DecodeNormalFromFloats(vec2* packed, vec3* out);
diff --git a/components/cod2rad/print.cpp b/components/cod2rad/print.cpp
new file mode 100644
index 00000000..a60c4c83
--- /dev/null
+++ b/components/cod2rad/print.cpp
@@ -0,0 +1,21 @@
+#include "stdafx.h"
+
+static int& g_progressCounter_current = *(int*)0x1730241C;
+static int& g_progressCounter_max = *(int*)0x17302420;
+
+void __cdecl SetProgress(int current, int total)
+{
+ g_progressCounter_current = current;
+ g_progressCounter_max = total;
+
+ UpdateProgressPrint();
+}
+
+void __cdecl UpdateProgress(int amount)
+{
+ if (g_progressCounter_max)
+ {
+ g_progressCounter_current += amount;
+ UpdateProgressPrint();
+ }
+}
diff --git a/components/cod2rad/print.h b/components/cod2rad/print.h
new file mode 100644
index 00000000..d44ee5d3
--- /dev/null
+++ b/components/cod2rad/print.h
@@ -0,0 +1,16 @@
+#pragma once
+
+typedef int(__cdecl* Com_FatalError_t)(char* format, ...);
+static Com_FatalError_t Com_FatalError = (Com_FatalError_t)0x004294B0;
+
+typedef void(__cdecl* BeginProgress_t)(const char* msg);
+static BeginProgress_t BeginProgress = (BeginProgress_t)0x00429650;
+
+typedef void(__cdecl* EndProgress_t)(void);
+static EndProgress_t EndProgress = (EndProgress_t)0x004294C0;
+
+typedef void(__cdecl* UpdateProgressPrint_t)();
+static UpdateProgressPrint_t UpdateProgressPrint = (UpdateProgressPrint_t)0x00429500;
+
+void __cdecl SetProgress(int current, int total);
+void __cdecl UpdateProgress(int amount);
diff --git a/components/cod2rad/r_image_wavelet.cpp b/components/cod2rad/r_image_wavelet.cpp
new file mode 100644
index 00000000..6f66b117
--- /dev/null
+++ b/components/cod2rad/r_image_wavelet.cpp
@@ -0,0 +1,306 @@
+#include "stdafx.h"
+#include "r_imagedecode.h"
+
+struct WaveletDecode
+{
+ unsigned __int16 value;
+ unsigned __int16 bit;
+ const char *data;
+ int width;
+ int height;
+ int channels;
+ int bpp;
+ int mipLevel;
+ bool dataInitialized;
+};
+
+struct WaveletHuffmanDecode
+{
+ __int16 value;
+ __int16 bits;
+};
+
+static WaveletHuffmanDecode* waveletDecodeBlue = (WaveletHuffmanDecode*)0x0044E370;
+static WaveletHuffmanDecode* waveletDecodeRedGreen = (WaveletHuffmanDecode*)0x00452370;
+static WaveletHuffmanDecode* waveletDecodeAlpha = (WaveletHuffmanDecode*)0x00456370;
+
+static void Wavelet_ConsumeBits(unsigned __int16 bitCount, WaveletDecode *decode)
+{
+ ASSERT(bitCount > 0 && bitCount <= 16);
+ ASSERT(decode->bit < 8);
+
+ decode->value >>= bitCount;
+
+ decode->value |= (((BYTE)decode->data[0] << 0) |
+ ((BYTE)decode->data[1] << 8) |
+ ((BYTE)decode->data[2] << 16) |
+ ((BYTE)decode->data[3] << 24)) >> decode->bit << (16 - bitCount);
+
+ decode->bit += bitCount;
+ decode->data += decode->bit >> 3;
+ decode->bit &= 7u;
+}
+
+static int Wavelet_DecodeValue(WaveletHuffmanDecode *decodeTable, unsigned __int16 bitCount, int bias, WaveletDecode *decode)
+{
+ ASSERT((1 << bitCount) - 1 >= bias * 2 - 1);
+ ASSERT((1 << (bitCount - 1)) - 1 < bias * 2 - 1);
+
+ int index = decode->value & 0xFFF;
+ Wavelet_ConsumeBits(decodeTable[index].bits, decode);
+ int value = decodeTable[index].value;
+
+ if (value == 0xFFFF8000)
+ {
+ value = (((1 << bitCount) - 1) & decode->value) - bias;
+ Wavelet_ConsumeBits(bitCount, decode);
+ }
+
+ return value;
+}
+
+static void Wavelet_AddDeltaToMipmap(char *inout, int size, WaveletDecode *decode, const int *dstChanOffset)
+{
+ for (int i = 0; i < size; i++)
+ {
+ for (int chanIndex = 0; chanIndex < decode->channels; chanIndex++)
+ {
+ char* value = &inout[dstChanOffset[chanIndex]];
+ char old = *value;
+ *value = Wavelet_DecodeValue(waveletDecodeAlpha, 9u, 255, decode) + old;
+ }
+
+ inout += decode->bpp;
+ }
+}
+
+static void Wavelet_DecompressLevel(char *src, char *dst, WaveletDecode *decode)
+{
+ ASSERT_MSG_VA(decode->bpp >= 1 && decode->bpp <= 4, "decode->bpp not in [1, 4]\n\t%i not in [%i, %i]", decode->bpp, 1, 4);
+
+ ASSERT(decode->bpp == decode->channels || (decode->bpp == 4 && decode->channels == 3));
+ ASSERT(decode->mipLevel >= 0);
+
+ int dstChanOffset[4];
+ int dstBpp = decode->bpp;
+
+ switch (dstBpp)
+ {
+ case 1:
+ dstChanOffset[0] = 0;
+ break;
+ case 2:
+ dstChanOffset[0] = 0;
+ dstChanOffset[1] = 1;
+ break;
+ case 3:
+ case 4:
+ dstChanOffset[0] = 0;
+ dstChanOffset[1] = 1;
+ dstChanOffset[2] = 2;
+ dstChanOffset[3] = 3;
+ break;
+ default:
+ break;
+ }
+
+ int w = decode->width >> decode->mipLevel;
+ int h = decode->height >> decode->mipLevel;
+
+ if (w > 1 && h > 1)
+ {
+ if (!decode->dataInitialized)
+ {
+ decode->value = (((BYTE)decode->data[1]) << 8) | (BYTE)decode->data[0];
+ decode->bit = 0;
+ decode->data += 2;
+ decode->dataInitialized = 1;
+ }
+
+ bool needsMipDelta = (decode->value & 1) != 0;
+ Wavelet_ConsumeBits(1, decode);
+
+ if (needsMipDelta)
+ Wavelet_AddDeltaToMipmap(src, h * w / 4, decode, dstChanOffset);
+
+ int stride = dstBpp * w;
+
+ int coeff[4][3];
+ for (int y = 0; y < h; y += 2)
+ {
+ for (int x = 0; x < w; x += 2)
+ {
+ ASSERT(dst + stride + dstBpp <= src || dst > src);
+
+ if (decode->channels != 1)
+ {
+ int evenOddParity = decode->value & 1;
+ Wavelet_ConsumeBits(1, decode);
+
+ coeff[0][0] = Wavelet_DecodeValue(waveletDecodeBlue, 9, 255, decode);
+ coeff[0][1] = Wavelet_DecodeValue(waveletDecodeBlue, 9, 255, decode);
+ coeff[0][2] = Wavelet_DecodeValue(waveletDecodeBlue, 9, 255, decode);
+
+ int base = 2 * (BYTE)src[dstChanOffset[0]];
+ char* dstChan = &dst[dstChanOffset[0]];
+
+ dst[dstChanOffset[0]] = evenOddParity + ((coeff[0][2] + coeff[0][1] + coeff[0][0] + base) >> 1);
+
+ dstChan[dstBpp] = (coeff[0][0] + base - (coeff[0][2] + coeff[0][1])) >> 1;
+ dstChan[stride] = (coeff[0][1] - coeff[0][2] + base - coeff[0][0]) >> 1;
+ dstChan[dstBpp + stride] = (base - coeff[0][0] - (coeff[0][1] - coeff[0][2])) >> 1;
+
+ if (decode->channels >= 3)
+ {
+ evenOddParity = decode->value & 1;
+ Wavelet_ConsumeBits(1, decode);
+
+ coeff[1][0] = coeff[0][0] + Wavelet_DecodeValue(waveletDecodeRedGreen, 10, 510, decode);
+ coeff[1][1] = coeff[0][1] + Wavelet_DecodeValue(waveletDecodeRedGreen, 10, 510, decode);
+ coeff[1][2] = coeff[0][2] + Wavelet_DecodeValue(waveletDecodeRedGreen, 10, 510, decode);
+
+ base = 2 * (BYTE)src[dstChanOffset[1]];
+ dstChan = &dst[dstChanOffset[1]];
+
+ dst[dstChanOffset[1]] = evenOddParity + ((coeff[1][2] + coeff[1][1] + coeff[1][0] + base) >> 1);
+
+ dstChan[dstBpp] = (coeff[1][0] + base - (coeff[1][2] + coeff[1][1])) >> 1;
+ dstChan[stride] = (coeff[1][1] - coeff[1][2] + base - coeff[1][0]) >> 1;
+ dstChan[dstBpp + stride] = (base - coeff[1][0] - (coeff[1][1] - coeff[1][2])) >> 1;
+
+ evenOddParity = decode->value & 1;
+ Wavelet_ConsumeBits(1, decode);
+
+ coeff[2][0] = coeff[0][0] + Wavelet_DecodeValue(waveletDecodeRedGreen, 10, 510, decode);
+ coeff[2][1] = coeff[0][1] + Wavelet_DecodeValue(waveletDecodeRedGreen, 10, 510, decode);
+ coeff[2][2] = coeff[0][2] + Wavelet_DecodeValue(waveletDecodeRedGreen, 10, 510, decode);
+
+ base = 2 * (BYTE)src[dstChanOffset[2]];
+ dstChan = &dst[dstChanOffset[2]];
+
+ dst[dstChanOffset[2]] = evenOddParity + ((coeff[2][2] + coeff[2][1] + coeff[2][0] + base) >> 1);
+
+ dstChan[dstBpp] = (coeff[2][0] + base - (coeff[2][2] + coeff[2][1])) >> 1;
+ dstChan[stride] = (coeff[2][1] - coeff[2][2] + base - coeff[2][0]) >> 1;
+ dstChan[dstBpp + stride] = (base - coeff[2][0] - (coeff[2][1] - coeff[2][2])) >> 1;
+ }
+ }
+
+ if (decode->channels == 3)
+ {
+ if (decode->channels != decode->bpp)
+ {
+ char* dstChan = &dst[dstChanOffset[3]];
+
+ dst[dstChanOffset[3]] = -1;
+
+ dstChan[dstBpp] = -1;
+ dstChan[stride] = -1;
+ dstChan[dstBpp + stride] = -1;
+ }
+ }
+ else
+ {
+ int evenOddParity = decode->value & 1;
+ Wavelet_ConsumeBits(1, decode);
+
+ coeff[3][0] = Wavelet_DecodeValue(waveletDecodeAlpha, 9, 255, decode);
+ coeff[3][1] = Wavelet_DecodeValue(waveletDecodeAlpha, 9, 255, decode);
+ coeff[3][2] = Wavelet_DecodeValue(waveletDecodeAlpha, 9, 255, decode);
+
+ int base = 2 * src[dstChanOffset[decode->channels - 1]];
+ char* dstChan = &dst[dstChanOffset[decode->channels - 1]];
+
+ dstChan[0] = evenOddParity + ((coeff[3][2] + coeff[3][1] + coeff[3][0] + base) >> 1);
+
+ dstChan[dstBpp] = (coeff[3][0] + base - (coeff[3][2] + coeff[3][1])) >> 1;
+ dstChan[stride] = (coeff[3][1] - coeff[3][2] + base - coeff[3][0]) >> 1;
+ dstChan[dstBpp + stride] = (base - coeff[3][0] - (coeff[3][1] - coeff[3][2])) >> 1;
+ }
+
+ src += dstBpp;
+ dst += 2 * dstBpp;
+ }
+
+ dst += stride;
+ }
+ }
+ else
+ {
+ w = max(w, 1);
+ h = max(h, 1);
+
+ int size = h * w;
+ ASSERT(size >= 1);
+
+ for (; size > 0; size--)
+ {
+ for (int chanIndex = 0; chanIndex < decode->channels; ++chanIndex)
+ dst[dstChanOffset[chanIndex]] = *decode->data++;
+
+ if (decode->bpp != decode->channels)
+ dst[dstChanOffset[3]] = -1;
+
+ dst += dstBpp;
+ }
+ }
+}
+
+void __cdecl Image_DecodeWavelet(GfxRawImage *image, t5::GfxImageFileHeader *imageFile, unsigned __int8 *data, int bytesPerPixel)
+{
+ PBYTE to[6];
+ PBYTE from[6];
+
+ PBYTE pixels[6];
+
+ ASSERT(image);
+ ASSERT(imageFile);
+
+ WaveletDecode decode;
+ decode.width = imageFile->dimensions[0];
+ decode.height = imageFile->dimensions[1];
+ decode.value = 0;
+ decode.bit = 0;
+
+ int mipCount = Image_CountMipmapsForFile(imageFile);
+ int height = imageFile->dimensions[1];
+ int width = imageFile->dimensions[0];
+
+ decode.mipLevel = mipCount - 1;
+ decode.channels = bytesPerPixel;
+ decode.bpp = bytesPerPixel;
+ decode.dataInitialized = 0;
+
+ unsigned int totalSize = bytesPerPixel * width * height;
+ int faceCount = ((imageFile->flags & t5::IMG_FLAG_CUBEMAP) != 0) ? 6 : 1;
+
+ memset(to, 0, sizeof(PBYTE) * faceCount);
+ for (int faceIndex = 0; faceIndex < faceCount; faceIndex++)
+ {
+ pixels[faceIndex++] = (PBYTE)_Hunk_AllocTempMemoryInternal(totalSize);
+ }
+
+ unsigned __int8 mipLevel = decode.mipLevel;
+ for (decode.data = (const char *)data; decode.mipLevel >= 0; mipLevel = --decode.mipLevel)
+ {
+ int mipWidth = max(decode.width >> mipLevel, 1);
+ int mipHeight = max(decode.height >> mipLevel, 1);
+
+ unsigned int mipSize = bytesPerPixel * mipWidth * mipHeight;
+
+ for (int faceIndex = 0; faceIndex < faceCount; faceIndex++)
+ {
+ from[faceIndex] = to[faceIndex];
+ to[faceIndex] = &pixels[faceIndex][totalSize] - mipSize;
+
+ Wavelet_DecompressLevel((char*)from[faceIndex], (char*)to[faceIndex], &decode);
+ if (faceIndex == 0 && decode.mipLevel == 0)
+ Image_CopyBitmapData(to[faceIndex], image, imageFile);
+ }
+ }
+
+ for (int i = faceCount - 1; i >= 0; --i)
+ {
+ _Hunk_FreeTempMemory(pixels[i]);
+ }
+}
diff --git a/components/cod2rad/r_image_wavelet.h b/components/cod2rad/r_image_wavelet.h
new file mode 100644
index 00000000..ac76b841
--- /dev/null
+++ b/components/cod2rad/r_image_wavelet.h
@@ -0,0 +1,4 @@
+#pragma once
+#include "r_imagedecode.h"
+
+void __cdecl Image_DecodeWavelet(GfxRawImage *image, t5::GfxImageFileHeader *imageFile, unsigned __int8 *data, int bytesPerPixel);
diff --git a/components/cod2rad/r_imagedecode.cpp b/components/cod2rad/r_imagedecode.cpp
new file mode 100644
index 00000000..029c3c38
--- /dev/null
+++ b/components/cod2rad/r_imagedecode.cpp
@@ -0,0 +1,629 @@
+#include "stdafx.h"
+#include "r_image_wavelet.h"
+
+#define IMAGE_DUMP 0
+#define IMAGE_LOG 0
+
+union ddscolor_t
+{
+ struct
+ {
+ unsigned __int16 b : 5;
+ unsigned __int16 g : 6;
+ unsigned __int16 r : 5;
+ };
+
+ unsigned __int16 rgb;
+};
+STATIC_ASSERT_SIZE(ddscolor_t, 2);
+
+struct DdsBlock_Dxt1_t
+{
+ ddscolor_t color0;
+ ddscolor_t color1;
+ char bits[4];
+};
+STATIC_ASSERT_SIZE(DdsBlock_Dxt1_t, 8);
+
+struct DdsBlock_Dxt3_t
+{
+ char alpha[8];
+ DdsBlock_Dxt1_t color;
+};
+STATIC_ASSERT_SIZE(DdsBlock_Dxt3_t, 16);
+
+struct DdsBlock_Dxt5_t
+{
+ char alpha0;
+ char alpha1;
+ char alpha[6];
+ DdsBlock_Dxt1_t color;
+};
+STATIC_ASSERT_SIZE(DdsBlock_Dxt5_t, 16);
+
+GfxRawPixel::GfxRawPixel(void)
+{
+}
+
+GfxRawPixel::GfxRawPixel(vec3& vec)
+{
+ this->r = (BYTE)(vec.r * 255.0f);
+ this->g = (BYTE)(vec.g * 255.0f);
+ this->b = (BYTE)(vec.b * 255.0f);
+ this->a = -1;
+}
+
+GfxRawPixel::GfxRawPixel(vec4& vec)
+{
+ this->r = (BYTE)(vec.r * 255.0f);
+ this->g = (BYTE)(vec.g * 255.0f);
+ this->b = (BYTE)(vec.b * 255.0f);
+ this->a = (BYTE)(vec.a * 255.0f);
+}
+
+bool SaveBitmap(char* filepath, BYTE* pixels, int width, int height, int bytesPerPixel) {
+ FILE* f = NULL;
+ fopen_s(&f, filepath, "wb");
+ if (f == NULL)
+ {
+ Com_Printf("ERROR: Could not save image %s\n", filepath);
+ return false;
+ }
+
+ BITMAPV4HEADER info;
+ ZeroMemory(&info, sizeof(info));
+ info.bV4Size = sizeof(BITMAPV4HEADER);
+ info.bV4Width = width;
+ info.bV4Height = height;
+ info.bV4Planes = 1;
+ info.bV4BitCount = bytesPerPixel * 8;
+ info.bV4V4Compression = BI_BITFIELDS;
+ info.bV4SizeImage = width * height * bytesPerPixel;
+
+ info.bV4RedMask = 0x000000FF;
+ info.bV4GreenMask = 0x0000FF00;
+ info.bV4BlueMask = 0x00FF0000;
+ if (bytesPerPixel == 4)
+ info.bV4AlphaMask = 0xFF000000;
+
+ BITMAPFILEHEADER header;
+ ZeroMemory(&header, sizeof(header));
+ header.bfType = 'B' + ('M' << 8);
+ header.bfOffBits = sizeof(BITMAPFILEHEADER) + info.bV4Size;
+ header.bfSize = header.bfOffBits + info.bV4SizeImage;
+ header.bfReserved1 = 0;
+ header.bfReserved2 = 0;
+
+ fwrite(&header, 1, sizeof(BITMAPFILEHEADER), f);
+ fwrite(&info, 1, sizeof(BITMAPV4HEADER), f);
+ fwrite(pixels, 1, info.bV4SizeImage, f);
+ fclose(f);
+
+ return true;
+}
+
+void __declspec(naked) hk_Image_GetRawPixels(void)
+{
+ _asm
+ {
+ push[esp+4] // imageName
+ push esi // image
+ call Image_GetRawPixels
+ add esp, 8
+ retn
+ }
+}
+
+t4::GfxImageFileHeader::GfxImageFileHeader(t5::GfxImageFileHeader& header)
+{
+ tag[0] = header.tag[0];
+ tag[1] = header.tag[1];
+ tag[3] = header.tag[2];
+
+ version = header.version;
+ format = header.format;
+ flags = header.flags;
+
+ dimensions[0] = header.dimensions[0];
+ dimensions[1] = header.dimensions[1];
+ dimensions[2] = header.dimensions[2];
+
+ fileSizeForPicmip[0] = header.fileSizeForPicmip[0];
+ fileSizeForPicmip[1] = header.fileSizeForPicmip[1];
+ fileSizeForPicmip[2] = header.fileSizeForPicmip[2];
+ fileSizeForPicmip[3] = header.fileSizeForPicmip[3];
+}
+
+unsigned int Image_CountMipmaps(unsigned int imageFlags, unsigned int width, unsigned int height, unsigned int depth)
+{
+ if (imageFlags & 2)
+ return 1;
+
+ unsigned int mipCount = 1;
+ unsigned int mipRes = 1;
+
+ while (mipRes < width || mipRes < height || mipRes < depth)
+ {
+ mipRes *= 2;
+ ++mipCount;
+ }
+
+ return mipCount;
+}
+
+int Image_CountMipmapsForFile(t5::GfxImageFileHeader *fileHeader)
+{
+ return Image_CountMipmaps(
+ fileHeader->flags,
+ fileHeader->dimensions[0],
+ fileHeader->dimensions[1],
+ fileHeader->dimensions[2]);
+}
+
+bool __cdecl Image_ValidateHeader(t5::GfxImageFileHeader *imageFile, const char *filepath)
+{
+ if (imageFile->tag[0] == 'I' && imageFile->tag[1] == 'W' && imageFile->tag[2] == 'i')
+ {
+ if (imageFile->version == 13)
+ return true;
+
+ Com_Printf("ERROR: image '%s' is version %i but should be version %i\n", filepath, imageFile->version, 13);
+ return false;
+ }
+
+ Com_Printf("ERROR: image '%s' is not an IW image\n", filepath);
+ return false;
+}
+
+void __cdecl Image_GetRawPixels(GfxRawImage *image, const char *imageName)
+{
+ ASSERT(imageName != NULL);
+
+ char path[MAX_PATH];
+ Com_AssembleImageFilepath(imageName, path);
+
+ t5::GfxImageFileHeader* header = NULL;
+ if (FS_ReadFile(path, (void**)&header) < 0)
+ Com_Error("image '%s' is missing", path);
+
+ if (!Image_ValidateHeader(header, path))
+ Com_Error("image '%s' is not valid", path);
+
+ strcpy_s(image->name, imageName);
+
+ int width = header->dimensions[0];
+ image->width = width;
+ int height = header->dimensions[1];
+ image->height = height;
+ image->pixels = (GfxRawPixel *)Z_Malloc(4 * height * width);
+
+ BYTE* pixels = (PBYTE)&header[1];
+
+ t4::GfxImageFileHeader dummyHeader(*header);
+
+#if IMAGE_DUMP || IMAGE_LOG
+ sprintf_s(path, "dump/%s.bmp", imageName);
+ Com_Printf("Loading image %dx%d (%d fmt) %s\n", width, height, header->format, path);
+#endif
+
+ switch (header->format)
+ {
+ case IMG_FORMAT_BITMAP_RGBA:
+ image->hasAlpha = 1;
+ Image_DecodeBitmap(image, header, pixels, 4);
+ break;
+ case IMG_FORMAT_BITMAP_RGB:
+ image->hasAlpha = 0;
+ Image_DecodeBitmap(image, header, pixels, 3);
+ break;
+ case IMG_FORMAT_BITMAP_LUMINANCE:
+ image->hasAlpha = 0;
+ Image_DecodeBitmap(image, header, pixels, 1);
+ break;
+ case IMG_FORMAT_BITMAP_ALPHA:
+ image->hasAlpha = 1;
+ Image_DecodeBitmap(image, header, pixels, 1);
+ break;
+ case IMG_FORMAT_WAVELET_RGBA:
+ image->hasAlpha = 1;
+ Image_DecodeWavelet(image, header, pixels, 4);
+ break;
+ case IMG_FORMAT_WAVELET_RGB:
+ image->hasAlpha = 0;
+ Image_DecodeWavelet(image, header, pixels, 3);
+ break;
+ case IMG_FORMAT_WAVELET_LUMINANCE_ALPHA:
+ image->hasAlpha = 1;
+ Image_DecodeWavelet(image, header, pixels, 2);
+ break;
+ case IMG_FORMAT_WAVELET_LUMINANCE:
+ image->hasAlpha = 0;
+ Image_DecodeWavelet(image, header, pixels, 1);
+ break;
+ case IMG_FORMAT_WAVELET_ALPHA:
+ image->hasAlpha = 1;
+ Image_DecodeWavelet(image, header, pixels, 1);
+ break;
+ case IMG_FORMAT_DXT1:
+ image->hasAlpha = 0;
+ Image_DecodeDxtc(image, header, pixels, 8);
+ break;
+ case IMG_FORMAT_DXT3:
+ case IMG_FORMAT_DXT5:
+ image->hasAlpha = 1;
+ Image_DecodeDxtc(image, header, pixels, 16);
+ break;
+ case IMG_FORMAT_BITMAP_RGB565:
+ image->hasAlpha = 0;
+ Image_DecodeBitmap(image, header, pixels, 2);
+ break;
+ case IMG_FORMAT_BITMAP_LUMINANCE_ALPHA:
+ case IMG_FORMAT_BITMAP_RGB5A3:
+ image->hasAlpha = 1;
+ Image_DecodeBitmap(image, header, pixels, 2);
+ break;
+ default:
+ Con_Error("Unhandled image type: %d (%s)\n", header->format, imageName);
+ break;
+ }
+
+#if IMAGE_DUMP
+ SaveBitmap(path, (BYTE*)image->pixels, image->width, image->height, 4);
+#endif
+ FS_FreeFile(header);
+}
+
+void Image_CopyBitmapData(BYTE *pixels, GfxRawImage *image, t5::GfxImageFileHeader *imageFile)
+{
+ PBYTE src = pixels;
+ GfxRawPixel* dst = image->pixels;
+
+ int i = imageFile->dimensions[0] * imageFile->dimensions[1];
+
+ switch (imageFile->format)
+ {
+ case IMG_FORMAT_BITMAP_RGBA:
+ case IMG_FORMAT_WAVELET_RGBA:
+ for (; i; --i)
+ {
+ dst->r = src[2];
+ dst->g = src[1];
+ dst->b = src[0];
+ dst->a = src[3];
+ src += 4;
+ ++dst;
+ }
+ break;
+ case IMG_FORMAT_BITMAP_RGB:
+ case IMG_FORMAT_WAVELET_RGB:
+ for (; i; --i)
+ {
+ dst->r = src[2];
+ dst->g = src[1];
+ dst->b = src[0];
+ dst->a = -1;
+ src += 3;
+ ++dst;
+ }
+ break;
+ case IMG_FORMAT_BITMAP_LUMINANCE_ALPHA:
+ case IMG_FORMAT_WAVELET_LUMINANCE_ALPHA:
+ for (; i; --i)
+ {
+ dst->r = src[0];
+ dst->g = src[0];
+ dst->b = src[0];
+ dst->a = src[1];
+ src += 2;
+ ++dst;
+ }
+ break;
+ case IMG_FORMAT_BITMAP_LUMINANCE:
+ case IMG_FORMAT_WAVELET_LUMINANCE:
+ for (; i; --i)
+ {
+ dst->r = src[0];
+ dst->g = src[0];
+ dst->b = src[0];
+ dst->a = -1;
+ ++src;
+ ++dst;
+ }
+ break;
+ case IMG_FORMAT_BITMAP_ALPHA:
+ case IMG_FORMAT_WAVELET_ALPHA:
+ for (; i; --i)
+ {
+ dst->r = 0;
+ dst->g = 0;
+ dst->b = 0;
+ dst->a = *src++;
+ ++dst;
+ }
+ break;
+ case IMG_FORMAT_BITMAP_RGB565:
+ for (; i; --i)
+ {
+ dst->r = (src[0] >> 5) | src[0] & 0xF8;
+ dst->g = 32 * src[0] | ((BYTE)(src[0] & 6 | (src[1] >> 2) & 0x38) >> 1);
+ dst->b = 8 * src[1] | (src[1] >> 2) & 7;
+ dst->a = 0;
+ ++dst;
+ }
+ break;
+ case IMG_FORMAT_BITMAP_RGB5A3:
+ for (; i; --i)
+ {
+ BYTE r, g, b1, b2, a, v11, v12;
+ if ((*src & 0x80u) == 0)
+ {
+ v11 = 16 * src[0];
+ v12 = src[1];
+ a = ((BYTE)(2 * (src[0] & 0xF0) | ((BYTE)(2 * (src[0] & 0xF0)) >> 3)) >> 3) | 2 * (src[0] & 0xF0);
+ r = (v11 >> 4) | v11;
+ g = ((BYTE)(v12 & 0xF0) >> 4) | v12 & 0xF0;
+ b1 = 16 * v12;
+ b2 = b1 >> 4;
+ }
+ else
+ {
+ BYTE _b = src[1];
+ r = ((BYTE)(2 * (src[0] & 0xFC)) >> 5) | 2 * (src[0] & 0xFC);
+ g = ((BYTE)((src[0] << 6) | (_b >> 2) & 0x38) >> 5) | (src[0] << 6) | (_b >> 2) & 0x38;
+ b1 = 8 * _b;
+ a = -1;
+ b2 = (BYTE)(8 * _b) >> 5;
+ }
+
+ dst->g = g;
+ dst->r = r;
+ dst->b = b2 | b1;
+ dst->a = a;
+ ++dst;
+ }
+ break;
+ default:
+ ASSERT_MSG(false, "unhandled case");
+ break;
+ }
+}
+
+void __cdecl Image_DecodeBitmap(struct GfxRawImage *image, struct t5::GfxImageFileHeader *imageFile, BYTE *data, int bytesPerPixel)
+{
+ ASSERT(image);
+ ASSERT(imageFile);
+
+ int faceCount = (imageFile->flags & t5::IMG_FLAG_CUBEMAP) != 0 ? 6 : 1;
+ for (int mipLevel = Image_CountMipmapsForFile(imageFile) - 1; mipLevel >= 0; mipLevel--)
+ {
+ int width = max(imageFile->dimensions[0] >> mipLevel, 1);
+ int height = max(imageFile->dimensions[1] >> mipLevel, 1);
+
+ int mipSize = bytesPerPixel * width * height;
+
+ for (int faceIndex = 0; faceIndex < faceCount; faceIndex++)
+ {
+ if (faceIndex == 0 && mipLevel == 0)
+ Image_CopyBitmapData(data, image, imageFile);
+
+ data += mipSize;
+ }
+ }
+}
+
+inline vec3 Pixel_Unpack_RGB565(unsigned __int16 packed)
+{
+ vec3 dst;
+ dst.r = (packed >> 11) / 32.0f;
+ dst.g = ((packed >> 5) & 0x3F) / 64.0f;
+ dst.b = (packed & 0x1F) / 32.0f;
+ return dst;
+}
+
+void Image_DecompressDxt1_Internal(GfxRawImage *image, DdsBlock_Dxt1_t *data, int x, int y, bool hasAlpha)
+{
+ vec3 color0 = Pixel_Unpack_RGB565(data->color0.rgb);
+ vec3 color1 = Pixel_Unpack_RGB565(data->color1.rgb);
+
+ GfxRawPixel rgba[4];
+
+ if (hasAlpha || data->color0.rgb > data->color1.rgb)
+ {
+ rgba[0] = color0;
+ rgba[1] = color1;
+
+ rgba[2] = (color1 + (color0 * 2.0f)) / 3.0f;
+ rgba[3] = (color0 + (color1 * 2.0f)) / 3.0f;
+ }
+ else
+ {
+ rgba[0] = color0;
+ rgba[1] = color1;
+
+ rgba[2] = (color0 + color1) / 2.0f;
+
+ rgba[3].r = 0;
+ rgba[3].g = 0;
+ rgba[3].b = 0;
+ rgba[3].a = 0;
+ }
+
+ image->Pixel(x + 0, y + 0) = rgba[(data->bits[0] >> 0) & 3];
+ image->Pixel(x + 1, y + 0) = rgba[(data->bits[0] >> 2) & 3];
+ image->Pixel(x + 2, y + 0) = rgba[(data->bits[0] >> 4) & 3];
+ image->Pixel(x + 3, y + 0) = rgba[(data->bits[0] >> 6) & 3];
+
+ image->Pixel(x + 0, y + 1) = rgba[(data->bits[1] >> 0) & 3];
+ image->Pixel(x + 1, y + 1) = rgba[(data->bits[1] >> 2) & 3];
+ image->Pixel(x + 2, y + 1) = rgba[(data->bits[1] >> 4) & 3];
+ image->Pixel(x + 3, y + 1) = rgba[(data->bits[1] >> 6) & 3];
+
+ image->Pixel(x + 0, y + 2) = rgba[(data->bits[2] >> 0) & 3];
+ image->Pixel(x + 1, y + 2) = rgba[(data->bits[2] >> 2) & 3];
+ image->Pixel(x + 2, y + 2) = rgba[(data->bits[2] >> 4) & 3];
+ image->Pixel(x + 3, y + 2) = rgba[(data->bits[2] >> 6) & 3];
+
+ image->Pixel(x + 0, y + 3) = rgba[(data->bits[3] >> 0) & 3];
+ image->Pixel(x + 1, y + 3) = rgba[(data->bits[3] >> 2) & 3];
+ image->Pixel(x + 2, y + 3) = rgba[(data->bits[3] >> 4) & 3];
+ image->Pixel(x + 3, y + 3) = rgba[(data->bits[3] >> 6) & 3];
+}
+
+void Image_DecompressDxt1(DdsBlock_Dxt1_t* data, GfxRawImage *image, int x, int y)
+{
+ Image_DecompressDxt1_Internal(image, data, x, y, false);
+}
+
+void Image_DecompressDxt3(DdsBlock_Dxt3_t* data, GfxRawImage *image, int x, int y)
+{
+ Image_DecompressDxt1_Internal(image, &data->color, x, y, true);
+
+ image->Pixel(x + 0, y + 0).a = 17 * (data->alpha[0] & 0xF);
+ image->Pixel(x + 1, y + 0).a = 17 * (data->alpha[0] >> 4);
+ image->Pixel(x + 2, y + 0).a = 17 * (data->alpha[1] & 0xF);
+ image->Pixel(x + 3, y + 0).a = 17 * (data->alpha[1] >> 4);
+
+ image->Pixel(x + 0, y + 1).a = 17 * (data->alpha[2] & 0xF);
+ image->Pixel(x + 1, y + 1).a = 17 * (data->alpha[2] >> 4);
+ image->Pixel(x + 2, y + 1).a = 17 * (data->alpha[3] & 0xF);
+ image->Pixel(x + 3, y + 1).a = 17 * (data->alpha[3] >> 4);
+
+ image->Pixel(x + 0, y + 2).a = 17 * (data->alpha[4] & 0xF);
+ image->Pixel(x + 1, y + 2).a = 17 * (data->alpha[4] >> 4);
+ image->Pixel(x + 2, y + 2).a = 17 * (data->alpha[5] & 0xF);
+ image->Pixel(x + 3, y + 2).a = 17 * (data->alpha[5] >> 4);
+
+ image->Pixel(x + 0, y + 3).a = 17 * (data->alpha[6] & 0xF);
+ image->Pixel(x + 1, y + 3).a = 17 * (data->alpha[6] >> 4);
+ image->Pixel(x + 2, y + 3).a = 17 * (data->alpha[7] & 0xF);
+ image->Pixel(x + 3, y + 3).a = 17 * (data->alpha[7] >> 4);
+}
+
+void __cdecl Image_DecompressDxt5(DdsBlock_Dxt5_t *data, GfxRawImage *image, int x, int y)
+{
+ Image_DecompressDxt1_Internal(image, &data->color, x, y, true);
+
+ BYTE alpha[8];
+ alpha[0] = data->alpha0;
+ alpha[1] = data->alpha1;
+
+ int alpha0 = (BYTE)data->alpha0;
+ int alpha1 = (BYTE)data->alpha1;
+
+ // Calculate the remaining alpha values
+ if (alpha[0] > alpha[1])
+ {
+ alpha[2] = (BYTE)((6 * alpha0 + 1 * alpha1) / 7);
+ alpha[3] = (BYTE)((5 * alpha0 + 2 * alpha1) / 7);
+ alpha[4] = (BYTE)((4 * alpha0 + 3 * alpha1) / 7);
+ alpha[5] = (BYTE)((3 * alpha0 + 4 * alpha1) / 7);
+ alpha[6] = (BYTE)((2 * alpha0 + 5 * alpha1) / 7);
+ alpha[7] = (BYTE)((1 * alpha0 + 6 * alpha1) / 7);
+ }
+ else
+ {
+ alpha[2] = (BYTE)((4 * alpha0 + 1 * alpha1) / 5);
+ alpha[3] = (BYTE)((3 * alpha0 + 2 * alpha1) / 5);
+ alpha[4] = (BYTE)((2 * alpha0 + 3 * alpha1) / 5);
+ alpha[5] = (BYTE)((1 * alpha0 + 4 * alpha1) / 5);
+ alpha[6] = 0;
+ alpha[7] = 255;
+ }
+
+ // Calculate the alpha lookup indices
+ BYTE indices[16];
+ for (int i = 0; i < 2; i++)
+ {
+ int value = 0;
+ // Load 3 bytes into 'value'
+ for (int j = 0; j < 3; j++)
+ value |= ((BYTE)data->alpha[i * 3 + j] << 8 * j);
+
+ // Unpack 8 3-bit indices from the loaded bytes
+ for (int j = 0; j < 8; j++)
+ indices[i * 8 + j] = (BYTE)((value >> 3 * j) & 7);
+ }
+
+ // Apply the alpha values to their respective pixels
+ image->Pixel(x + 0, y + 0).a = alpha[indices[0]];
+ image->Pixel(x + 1, y + 0).a = alpha[indices[1]];
+ image->Pixel(x + 2, y + 0).a = alpha[indices[2]];
+ image->Pixel(x + 3, y + 0).a = alpha[indices[3]];
+
+ image->Pixel(x + 0, y + 1).a = alpha[indices[4]];
+ image->Pixel(x + 1, y + 1).a = alpha[indices[5]];
+ image->Pixel(x + 2, y + 1).a = alpha[indices[6]];
+ image->Pixel(x + 3, y + 1).a = alpha[indices[7]];
+
+ image->Pixel(x + 0, y + 2).a = alpha[indices[8]];
+ image->Pixel(x + 1, y + 2).a = alpha[indices[9]];
+ image->Pixel(x + 2, y + 2).a = alpha[indices[10]];
+ image->Pixel(x + 3, y + 2).a = alpha[indices[11]];
+
+ image->Pixel(x + 0, y + 3).a = alpha[indices[12]];
+ image->Pixel(x + 1, y + 3).a = alpha[indices[13]];
+ image->Pixel(x + 2, y + 3).a = alpha[indices[14]];
+ image->Pixel(x + 3, y + 3).a = alpha[indices[15]];
+}
+
+void Image_CopyDxtcData(BYTE *data, GfxRawImage *image, t5::GfxImageFileHeader *imageFile)
+{
+ typedef void(*Image_DecompressDxtcBlock_t)(void* data, GfxRawImage *image, int x, int y);
+ Image_DecompressDxtcBlock_t Image_DecompressDxtcBlock = NULL;
+ int bytesPerPixel = 1;
+
+ switch (imageFile->format)
+ {
+ case IMG_FORMAT_DXT1:
+ Image_DecompressDxtcBlock = (Image_DecompressDxtcBlock_t)Image_DecompressDxt1;
+ bytesPerPixel = 8;
+ break;
+ case IMG_FORMAT_DXT3:
+ Image_DecompressDxtcBlock = (Image_DecompressDxtcBlock_t)Image_DecompressDxt3;
+ bytesPerPixel = 16;
+ break;
+ case IMG_FORMAT_DXT5:
+ Image_DecompressDxtcBlock = (Image_DecompressDxtcBlock_t)Image_DecompressDxt5;
+ bytesPerPixel = 16;
+ break;
+ default:
+ Com_Error("unhandled case - unsupported image format for file '%s'", image->name);
+ return;
+ }
+
+ // Size of DXT compressed block in pixels
+ const int DXT_BLOCK_WIDTH = 4;
+ const int DXT_BLOCK_HEIGHT = 4;
+
+ for (int y = 0; y < image->height; y += DXT_BLOCK_WIDTH)
+ {
+ for (int x = 0; x < image->width; x += DXT_BLOCK_HEIGHT)
+ {
+ Image_DecompressDxtcBlock(data, image, x, y);
+ data += bytesPerPixel;
+ }
+ }
+}
+
+void __cdecl Image_DecodeDxtc(struct GfxRawImage *image, struct t5::GfxImageFileHeader *imageFile, BYTE *data, int bytesPerPixel)
+{
+ ASSERT(image);
+ ASSERT(imageFile);
+
+ int faceCount = (imageFile->flags & t5::IMG_FLAG_CUBEMAP) != 0 ? 6 : 1;
+ for (int mipLevel = Image_CountMipmapsForFile(imageFile) - 1; mipLevel >= 0; mipLevel--)
+ {
+ int width = max(imageFile->dimensions[0] >> mipLevel, 1);
+ int height = max(imageFile->dimensions[1] >> mipLevel, 1);
+
+ int mipSize = bytesPerPixel * ((width + 3) >> 2) * ((height + 3) >> 2);
+
+ for (int faceIndex = 0; faceIndex < faceCount; faceIndex++)
+ {
+ if (faceIndex == 0 && mipLevel == 0)
+ Image_CopyDxtcData(data, image, imageFile);
+
+ data += mipSize;
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/cod2rad/r_imagedecode.h b/components/cod2rad/r_imagedecode.h
new file mode 100644
index 00000000..d194b5bc
--- /dev/null
+++ b/components/cod2rad/r_imagedecode.h
@@ -0,0 +1,159 @@
+#pragma once
+
+union GfxTexture
+{
+ int *ptr;
+};
+
+struct Picmip
+{
+ char platform[2];
+};
+
+struct CardMemory
+{
+ int platform[2];
+};
+
+struct GfxImage
+{
+ int mapType;
+ GfxTexture texture;
+ Picmip picmip;
+ bool noPicmip;
+ char semantic;
+ char track;
+ char unk1[3];
+ CardMemory cardMemory;
+ unsigned __int16 width;
+ unsigned __int16 height;
+ unsigned __int16 depth;
+ char category;
+ char unk2[1];
+ const char *name;
+};
+
+enum GfxRefBlendMode : DWORD
+{
+ BLENDMODE_OPAQUE = 0x0,
+ BLENDMODE_BLEND = 0x1,
+ BLENDMODE_GT0 = 0x2,
+ BLENDMODE_GE128 = 0x3,
+ BLENDMODE_LT128 = 0x4,
+ BLENDMODE_ADD = 0x5,
+};
+
+struct GfxRawPixel
+{
+ char r;
+ char g;
+ char b;
+ char a;
+
+ GfxRawPixel(void);
+ GfxRawPixel(vec3& vec);
+ GfxRawPixel(vec4& vec);
+};
+
+struct GfxRawImage
+{
+ char name[64];
+ GfxRefBlendMode blendMode;
+ bool hasAlpha;
+ int width;
+ int height;
+ GfxRawPixel *pixels;
+
+ inline GfxRawPixel& Pixel(int x, int y)
+ {
+ return this->pixels[y * this->width + x];
+ }
+};
+STATIC_ASSERT_SIZE(GfxRawImage, 0x54);
+
+namespace t5
+{
+ // Black Ops image file header
+ struct GfxImageFileHeader
+ {
+ char tag[3];
+ char version;
+ char format;
+ char flags;
+ __int16 dimensions[3];
+ float gamma;
+ int fileSizeForPicmip[8];
+ };
+
+ enum file_image_flags_t
+ {
+ IMG_FLAG_NOPICMIP = 0x1,
+ IMG_FLAG_NOMIPMAPS = 0x2,
+ IMG_FLAG_CUBEMAP = 0x4,
+ IMG_FLAG_VOLMAP = 0x8,
+ IMG_FLAG_STREAMING = 0x10,
+ IMG_FLAG_LEGACY_NORMALS = 0x20,
+ IMG_FLAG_CLAMP_U = 0x40,
+ IMG_FLAG_CLAMP_V = 0x80,
+ IMG_FLAG_FORCE_SYSTEM = 0x100,
+ IMG_FLAG_DYNAMIC = 0x10000,
+ IMG_FLAG_RENDER_TARGET = 0x20000,
+ IMG_FLAG_SYSTEMMEM = 0x40000,
+ };
+
+}
+
+namespace t4
+{
+ // World at War image file header
+ struct GfxImageFileHeader
+ {
+ char tag[3];
+ char version;
+ char format;
+ char flags;
+ __int16 dimensions[3];
+ int fileSizeForPicmip[4];
+
+ GfxImageFileHeader(t5::GfxImageFileHeader& header);
+ };
+}
+
+enum GfxImageFileFormat
+{
+ IMG_FORMAT_INVALID = 0x0,
+ IMG_FORMAT_BITMAP_RGBA = 0x1,
+ IMG_FORMAT_BITMAP_RGB = 0x2,
+ IMG_FORMAT_BITMAP_LUMINANCE_ALPHA = 0x3,
+ IMG_FORMAT_BITMAP_LUMINANCE = 0x4,
+ IMG_FORMAT_BITMAP_ALPHA = 0x5,
+ IMG_FORMAT_WAVELET_RGBA = 0x6,
+ IMG_FORMAT_WAVELET_RGB = 0x7,
+ IMG_FORMAT_WAVELET_LUMINANCE_ALPHA = 0x8,
+ IMG_FORMAT_WAVELET_LUMINANCE = 0x9,
+ IMG_FORMAT_WAVELET_ALPHA = 0xA,
+ IMG_FORMAT_DXT1 = 0xB,
+ IMG_FORMAT_DXT3 = 0xC,
+ IMG_FORMAT_DXT5 = 0xD,
+ IMG_FORMAT_DXN = 0xE,
+ IMG_FORMAT_BITMAP_RGB565 = 0xF,
+ IMG_FORMAT_BITMAP_RGB5A3 = 0x10,
+ IMG_FORMAT_BITMAP_C8 = 0x11,
+ IMG_FORMAT_BITMAP_RGBA8 = 0x12,
+ IMG_FORMAT_A16B16G16R16F = 0x13,
+ IMG_FORMAT_COUNT = 0x14,
+};
+
+typedef void (__cdecl* Image_LoadPixels_t)(struct GfxRawImage * image, struct t4::GfxImageFileHeader * header, unsigned __int8 * pixels, int bitsPerPixel);
+//static Image_LoadPixels_t Image_DecodeBitmap = (Image_LoadPixels_t)0x004176B0;
+//static Image_LoadPixels_t Image_DecodeWavelet = (Image_LoadPixels_t)0x004178A0;
+//static Image_LoadPixels_t Image_DecodeDxtc = (Image_LoadPixels_t)0x004177F0;
+
+int Image_CountMipmapsForFile(t5::GfxImageFileHeader *fileHeader);
+
+void hk_Image_GetRawPixels(void);
+void __cdecl Image_GetRawPixels(GfxRawImage *image, const char *imageName);
+
+void Image_CopyBitmapData(BYTE *data, GfxRawImage *image, t5::GfxImageFileHeader *imageFile);
+void __cdecl Image_DecodeBitmap(struct GfxRawImage *image, struct t5::GfxImageFileHeader *imageFile, BYTE *data, int bytesPerPixel);
+void __cdecl Image_DecodeDxtc(struct GfxRawImage *image, struct t5::GfxImageFileHeader *imageFile, BYTE *data, int bytesPerPixel);
\ No newline at end of file
diff --git a/components/cod2rad/r_light_load_obj.cpp b/components/cod2rad/r_light_load_obj.cpp
new file mode 100644
index 00000000..ad9719d9
--- /dev/null
+++ b/components/cod2rad/r_light_load_obj.cpp
@@ -0,0 +1,83 @@
+#include "stdafx.h"
+
+#define LIGHTDEF_DEFAULT "light_dynamic"
+
+void GetDegammadImage(GfxRawImage* image, GfxLightImage* lightImage)
+{
+ _asm
+ {
+ pushad
+ push lightImage
+ mov edi, image
+ mov ebx, 0x0042AAE0
+ call ebx
+ add esp, 4
+ popad
+ }
+}
+
+GfxLightDef *__cdecl RegisterLightDef(char *lightDefName)
+{
+ for (int i = 0; i < pointLightGlob.defCount; i++)
+ {
+ GfxLightDef* def = &pointLightGlob.lightDefs[i];
+ if (_stricmp(def->name, lightDefName) == 0)
+ {
+ return def;
+ }
+ }
+
+ if (pointLightGlob.defCount >= 64)
+ {
+ Com_FatalError("More than %i lightDefs used by all lights combined; can't load '%s'\n", 64, lightDefName);
+ return NULL;
+ }
+
+ GfxLightDef* def = &pointLightGlob.lightDefs[pointLightGlob.defCount];
+
+ char lightDefPath[MAX_PATH];
+ sprintf_s(lightDefPath, "lights/%s", lightDefName);
+
+ const char* file = NULL;
+ int fileSize = FS_ReadFile(lightDefPath, (void**)&file);
+
+ // A lightdef file needs both the samplerState byte plus a null terminated string for the def image name (meaning it must be >= 2 bytes)
+ if (fileSize < 3)
+ {
+ Com_FatalError("Couldn't get light def images for '%s'\n", lightDefName);
+ return NULL;
+ }
+
+ GfxRawImage imageHeader;
+ if (strlen(file + 1) == 0)
+ {
+ memset(&imageHeader, 0, sizeof(GfxRawImage));
+ FS_FreeFile((void*)file);
+ }
+ else
+ {
+ Image_GetRawPixels(&imageHeader, file + 1);
+ FS_FreeFile((void*)file);
+ }
+
+ if (imageHeader.height != 1)
+ {
+ if (_stricmp(lightDefName, LIGHTDEF_DEFAULT) == 0)
+ {
+ Com_FatalError("Falloff image %s in light def %s has dimensions %ix%i; height should be 1\n", imageHeader.name, lightDefName, imageHeader.width, imageHeader.height);
+ return NULL;
+ }
+
+ printf("Falloff image %s in light def %s has dimensions %ix%i; height should be 1; Overriding with default light def %s",
+ imageHeader.name, lightDefName, imageHeader.width, imageHeader.height, LIGHTDEF_DEFAULT);
+ return RegisterLightDef(LIGHTDEF_DEFAULT);
+ }
+
+ def->name = _strdup(lightDefName);
+ GetDegammadImage(&imageHeader, &def->attenuation);
+
+ ++pointLightGlob.defCount;
+ printf("Registered lightDef '%s'\n", lightDefName);
+
+ return def;
+}
diff --git a/components/cod2rad/r_light_load_obj.h b/components/cod2rad/r_light_load_obj.h
new file mode 100644
index 00000000..340ee215
--- /dev/null
+++ b/components/cod2rad/r_light_load_obj.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "r_imagedecode.h"
+
+struct __declspec(align(4)) GfxLightImage
+{
+ GfxImage *image;
+ char samplerState;
+};
+
+struct GfxLightDef
+{
+ const char *name;
+ GfxLightImage attenuation;
+ int lmapLookupStart;
+};
+
+struct LightGlobals
+{
+ int defCount;
+ GfxLightDef lightDefs[64];
+};
+
+static LightGlobals& pointLightGlob = *(LightGlobals*)0x172DE010;
+
+GfxLightDef *__cdecl RegisterLightDef(char *lightDefName);
diff --git a/components/cod2rad/r_lightgrid.cpp b/components/cod2rad/r_lightgrid.cpp
index 7c5610bf..63ec687e 100644
--- a/components/cod2rad/r_lightgrid.cpp
+++ b/components/cod2rad/r_lightgrid.cpp
@@ -1,5 +1,7 @@
#include "stdafx.h"
+#if USE_LEGACY_HDR
+
R_Init_Lightgrid_t o_R_Init_Lightgrid = (R_Init_Lightgrid_t)0x00433440;
R_Store_QuantizedLightGridSample_t o_R_Store_QuantizedLightGridSample = (R_Store_QuantizedLightGridSample_t)0x00433890;
@@ -11,7 +13,11 @@ struct GfxLightGridColor_Internal
float f0;
float f1;
};
+#endif
+
+GfxLightGridColorsHDR disk_lightGridColorsHDR[0xFFFF];
+#if USE_LEGACY_HDR
void* rtn_R_Init_Lightgrid = (void*)0x0043684C;
void __declspec(naked) mfh_R_Init_Lightgrid()
{
@@ -147,4 +153,460 @@ void __declspec(naked) hk_R_Store_QuantizedLightGridSample()
call o_R_Store_QuantizedLightGridSample
retn
}
-}
\ No newline at end of file
+}
+
+#endif
+
+void __cdecl AdjustLightingContrast(int sampleCount, int baseIndex, vec3* colors)
+{
+ _asm
+ {
+ push colors
+ push baseIndex
+ mov edi, sampleCount
+ mov ebx, 0x00430410
+ call ebx
+ add esp, 8
+ }
+}
+
+// 0x00434E20
+void __cdecl StoreLightingForDir(vec3* lighting, GfxLightGridColorsHDR* dst)
+{
+ for (int i = 0; i < 56; i++)
+ {
+ GammaCorrectColor(&lighting[i]);
+ }
+
+ AdjustLightingContrast(56, -1, lighting);
+
+ for (int sampleIndex = 0; sampleIndex < 56; sampleIndex++)
+ {
+ vec3 v = lighting[sampleIndex] * 0.5;
+ dst->rgb[sampleIndex][0] = EncodeFloatInByte(v.r);
+ dst->rgb[sampleIndex][1] = EncodeFloatInByte(v.g);
+ dst->rgb[sampleIndex][2] = EncodeFloatInByte(v.b);
+ }
+}
+
+void CalculateClusterMean_o(GridColorsCluster *cluster, float *means)
+{
+ static DWORD dwCall = 0x004331A0;
+
+ __asm
+ {
+ mov ecx, cluster
+ mov eax, means
+ call dwCall
+ }
+}
+
+void CalculateClusterMean(GridColorsCluster *cluster, float *means)
+{
+ float sums[168];
+ memset(sums, 0, sizeof(float) * 168);
+
+ for (unsigned int clusterIndex = 0; clusterIndex < cluster->count; clusterIndex++)
+ {
+ GfxLightGridColorsHDR* colors = &lightGridGlob->colors[lightGridGlob->mapping[cluster->first + clusterIndex]];
+ for (int i = 0; i < 168; i++)
+ {
+ sums[i] += colors->all[i];
+ }
+ }
+
+ for (int i = 0; i < 168; i++)
+ {
+ means[i] = sums[i] / cluster->count;
+ }
+}
+
+void CalculateClusterMeanAndVariance_o(GridColorsCluster *cluster)
+{
+ ASSERT(cluster);
+ ASSERT(cluster->count);
+
+ static DWORD dwCall = 0x00434320;
+
+ __asm
+ {
+ mov edi, cluster
+ call dwCall
+ }
+}
+
+void CalculateClusterMeanAndVariance(GridColorsCluster *cluster)
+{
+ ASSERT(cluster);
+ ASSERT(cluster->count);
+
+ float means[168];
+ CalculateClusterMean(cluster, means);
+
+ float totals[168];
+ memset(totals, 0, sizeof(float) * 168);
+
+ for (unsigned int clusterIndex = 0; clusterIndex < cluster->count; clusterIndex++)
+ {
+ GfxLightGridColorsHDR* colors = &lightGridGlob->colors[lightGridGlob->mapping[cluster->first + clusterIndex]];
+ for (int i = 0; i < 168; i++)
+ {
+ double v = colors->all[i] - means[i];
+ totals[i] += (float)(v * v);
+ }
+ }
+
+ cluster->unknown3 = -FLT_MAX;
+ for (int i = 0; i < 168; i++)
+ {
+ float unk = sqrt(totals[i] / cluster->count);
+ if (cluster->unknown3 < unk)
+ {
+ cluster->unknown3 = unk;
+ cluster->unknown1 = i;
+ cluster->unknown2 = means[i];
+ }
+ }
+}
+
+GridColorsCluster *ChooseClusterToSplit()
+{
+ static DWORD dwCall = 0x00433090;
+
+ __asm
+ {
+ call dwCall
+ }
+}
+
+void SplitCluster_o(GridColorsCluster *cluster)
+{
+ ASSERT(cluster);
+ ASSERT(cluster->count >= 2);
+
+ static DWORD dwCall = 0x00434B50;
+
+ __asm
+ {
+ mov esi, cluster
+ call dwCall
+ }
+}
+
+void SplitCluster(GridColorsCluster *cluster)
+{
+ unsigned int head = 0;
+ unsigned int tail = 0;
+
+ unsigned int* mapping = lightGridGlob->mapping;
+
+ ASSERT(cluster);
+ ASSERT(cluster->count >= 2);
+
+ if (cluster->unknown3 <= 0.0)
+ {
+ head = cluster->first + ((cluster->count + 1) >> 1);
+ tail = head - 1;
+ }
+ else
+ {
+ head = cluster->first;
+ tail = cluster->first + cluster->count - 1;
+
+ while (1)
+ {
+ if (lightGridGlob->colors[mapping[head]].all[cluster->unknown1] <= cluster->unknown2)
+ break;
+
+ LABEL_10:
+ if (lightGridGlob->colors[mapping[tail]].all[cluster->unknown1] >= cluster->unknown2)
+ {
+ while (head <= --tail)
+ {
+ if (lightGridGlob->colors[mapping[tail]].all[cluster->unknown1] < cluster->unknown2)
+ goto LABEL_13;
+ }
+
+ goto LABEL_18;
+ }
+
+ LABEL_13:
+ ASSERT(head < tail);
+
+ int oldHead = mapping[head];
+ mapping[head] = mapping[tail];
+ mapping[tail] = oldHead;
+
+ if (++head > --tail)
+ goto LABEL_18;
+ }
+
+ while (++head <= tail)
+ {
+ if (lightGridGlob->colors[mapping[head]].all[cluster->unknown1] > cluster->unknown2)
+ goto LABEL_10;
+ }
+ }
+
+LABEL_18:
+ ASSERT(head == tail + 1);
+ ASSERT(head != 0);
+ ASSERT(head != cluster->first + cluster->count);
+
+ GridColorsCluster* newCluster = &lightGridGlob->clusters[lightGridGlob->clusterCount++];
+ newCluster->first = head;
+ newCluster->count = cluster->first + cluster->count - head;
+
+ cluster->count = head - cluster->first;
+
+ CalculateClusterMeanAndVariance(newCluster);
+ CalculateClusterMeanAndVariance(cluster);
+}
+
+void __cdecl SwapClusters(int fromIndex, int toIndex)
+{
+ // Vanilla: std::swap(disk_lightGridColors[toIndex], disk_lightGridColors[fromIndex]);
+ std::swap(disk_lightGridColorsHDR[toIndex], disk_lightGridColorsHDR[fromIndex]);
+ std::swap(lightGridGlob->clusters[fromIndex], lightGridGlob->clusters[toIndex]);
+
+ for (unsigned int i = 0; i < lightGridGlob->pointCount; i++)
+ {
+ int index = lightGridGlob->points[i].entry.colorsIndex;
+ if (index == fromIndex)
+ lightGridGlob->points[i].entry.colorsIndex = toIndex;
+ else if (index == toIndex)
+ lightGridGlob->points[i].entry.colorsIndex = fromIndex;
+ }
+}
+
+int GetClusterDefaultScore(GridColorsCluster *cluster)
+{
+ static DWORD dwCall = 0x00432E90;
+
+ __asm
+ {
+ mov ecx, cluster
+ call dwCall
+ }
+}
+
+void SetLightGridColorsForCluster2(GridColorsCluster *cluster, GfxLightGridColors *colors, GfxLightGridColorsHDR* colorsHDR=NULL)
+{
+ float means[168];
+ CalculateClusterMean(cluster, means);
+
+ for (int i = 0; i < 168; i++)
+ {
+ colors->all[i] = (BYTE)means[i];
+ if (colorsHDR)
+ colorsHDR->all[i] = (short)means[i];
+ }
+}
+
+void SetLightGridColorsForCluster(GridColorsCluster *cluster, GfxLightGridColorsHDR* colorsHDR)
+{
+ float means[168];
+ CalculateClusterMean(cluster, means);
+
+ for (int i = 0; i < 168; i++)
+ {
+ colorsHDR->all[i] = (short)means[i];
+ }
+}
+
+/*
+void __cdecl ImproveLightGridValues(int threadCount)
+{
+ _asm
+ {
+ mov eax, threadCount
+ mov ebx, 0x00435830
+ call ebx
+ }
+}
+*/
+
+void __cdecl ClusterLightGridValues(int threadCount)
+{
+ lightGridGlob->mapping = new unsigned int[lightGridGlob->pointCount];
+ if (!lightGridGlob->mapping)
+ Com_FatalError("Couldn't allocate %i bytes for light grid color mapping", sizeof(int) * lightGridGlob->pointCount);
+
+ lightGridGlob->clusters = new GridColorsCluster[LIGHTGRID_MAX_COLORCOUNT];
+ if (!lightGridGlob->clusters)
+ Com_FatalError("Couldn't allocate %i bytes for light grid colors", sizeof(GridColorsCluster) * LIGHTGRID_MAX_COLORCOUNT);
+
+ for (unsigned int i = 0; i < lightGridGlob->pointCount; i++)
+ {
+ lightGridGlob->mapping[i] = i;
+ }
+
+ lightGridGlob->clusterCount = 1;
+ lightGridGlob->clusters[0].first = 0;
+ lightGridGlob->clusters[0].count = lightGridGlob->pointCount;
+
+ CalculateClusterMeanAndVariance(&lightGridGlob->clusters[0]);
+
+ unsigned int clusterCount = lightGridGlob->clusterCount;
+ for (; lightGridGlob->clusterCount < LIGHTGRID_MAX_COLORCOUNT; clusterCount = lightGridGlob->clusterCount)
+ {
+ GridColorsCluster *cluster = ChooseClusterToSplit();
+
+ if (cluster->unknown3 < options_clusterThreshold && clusterCount >= 2)
+ break;
+
+ SplitCluster(cluster);
+ }
+
+ unsigned int maxScore = 0;
+ unsigned int maxScoreIndex = 0;
+
+ if (clusterCount > 0)
+ {
+ for (unsigned int colorIndex = 0; colorIndex < lightGridGlob->clusterCount; colorIndex++)
+ {
+ GridColorsCluster *cluster = &lightGridGlob->clusters[colorIndex];
+ for (unsigned int firstIndex = cluster->first; firstIndex < (cluster->first + cluster->count); firstIndex++)
+ {
+ lightGridGlob->points[lightGridGlob->mapping[firstIndex]].entry.colorsIndex = colorIndex;
+ }
+
+ SetLightGridColorsForCluster(cluster, &disk_lightGridColorsHDR[colorIndex]);
+
+ unsigned int score = GetClusterDefaultScore(cluster);
+ if (score > maxScore)
+ {
+ maxScore = score;
+ maxScoreIndex = colorIndex;
+ }
+ }
+ }
+
+ SwapClusters(0, maxScoreIndex);
+ SwapClusters(1, lightGridGlob->points[lightGridGlob->pointCount - 1].entry.colorsIndex);
+
+ lightGridColorCount = lightGridGlob->clusterCount;
+
+ if (options_ImproveLights)
+ ImproveLightGridValues(threadCount);
+
+ delete[] lightGridGlob->clusters;
+ delete[] lightGridGlob->mapping;
+ lightGridGlob->clusters = nullptr;
+ lightGridGlob->mapping = nullptr;
+
+ --lightGridGlob->pointCount;
+}
+
+bool CompareLightGridColors(GfxLightGridColorsHDR* a, GfxLightGridColorsHDR* b, unsigned int* out)
+{
+ unsigned int totalDifference = 0;
+
+ for (int i = 0; i < 56; i++)
+ {
+ for (int c = 0; c < 3; c++)
+ {
+ totalDifference += b->rgb[i][c] - a->rgb[i][c];
+ if (totalDifference >= *out)
+ return false;
+ }
+ }
+
+ *out = totalDifference;
+ return true;
+}
+
+unsigned short AssignLightGridColors(unsigned short colorIndex, GfxLightGridColorsHDR* colors)
+{
+ unsigned int unk = INT_MAX;
+ CompareLightGridColors(&lightGridGlob->colors[colorIndex], colors, &unk);
+ if (!unk)
+ {
+ return colorIndex;
+ }
+
+ for (unsigned int i = 0; i < lightGridGlob->clusterCount; i++)
+ {
+ if (CompareLightGridColors(&lightGridGlob->colors[i], colors, &unk))
+ {
+ if (!unk)
+ {
+ colorIndex = i;
+ break;
+ }
+ }
+ }
+
+ return colorIndex;
+}
+
+void __cdecl GuessLightGridColors(int index, int unk)
+{
+ GfxLightGridEntry *entry = &lightGridGlob->points[index].entry;
+ entry->colorsIndex = AssignLightGridColors(entry->colorsIndex, &lightGridGlob->colors[index]);
+}
+
+union GfxLightGridColorSums
+{
+ int rgb[56][3];
+ int all[56 * 3];
+};
+
+void __cdecl ImproveLightGridValues(int threadCount)
+{
+ GfxLightGridColorSums* sums = new GfxLightGridColorSums[lightGridGlob->clusterCount];
+ if (!sums)
+ Com_FatalError("Couldn't allocate %i bytes for light grid color sums", sizeof(GfxLightGridColorSums) * lightGridGlob->clusterCount);
+ memset(sums, 0, sizeof(GfxLightGridColorSums) * lightGridGlob->clusterCount);
+
+ int* counts = new int[LIGHTGRID_MAX_COLORCOUNT];
+ if (!counts)
+ Com_FatalError("Couldn't allocate %i bytes for light grid color counts", sizeof(int) * LIGHTGRID_MAX_COLORCOUNT);
+ memset(counts, 0, sizeof(int) * lightGridGlob->clusterCount);
+
+ BeginProgress("Improving quantization...");
+ ForEachQuantum(lightGridGlob->pointCount, GuessLightGridColors, threadCount);
+ EndProgress();
+
+ for (unsigned int pointIndex = 0; pointIndex < lightGridGlob->pointCount; pointIndex++)
+ {
+ counts[lightGridGlob->points[pointIndex].entry.colorsIndex]++;
+ GfxLightGridColors* colors = (GfxLightGridColors*)lightGridGlob->colors;
+ for (int i = 0; i < 56; i++)
+ {
+ sums[lightGridGlob->points[pointIndex].entry.colorsIndex].rgb[i][0] += colors[pointIndex].rgb[i][0];
+ sums[lightGridGlob->points[pointIndex].entry.colorsIndex].rgb[i][1] += colors[pointIndex].rgb[i][1];
+ sums[lightGridGlob->points[pointIndex].entry.colorsIndex].rgb[i][2] += colors[pointIndex].rgb[i][2];
+ }
+ }
+
+ for (unsigned int colorIndex = 0; colorIndex < lightGridColorCount; )
+ {
+ if (counts[colorIndex])
+ {
+ for (int i = 0; i < 56; i++)
+ {
+ // Add HDR Later
+ disk_lightGridColors[colorIndex].rgb[i][0] = (sums[colorIndex].rgb[i][0] + (counts[colorIndex] >> 1)) / counts[colorIndex];
+ disk_lightGridColors[colorIndex].rgb[i][1] = (sums[colorIndex].rgb[i][1] + (counts[colorIndex] >> 1)) / counts[colorIndex];
+ disk_lightGridColors[colorIndex].rgb[i][2] = (sums[colorIndex].rgb[i][2] + (counts[colorIndex] >> 1)) / counts[colorIndex];
+ }
+
+ colorIndex++;
+ }
+ else
+ {
+ counts[colorIndex] = counts[--lightGridColorCount];
+ memcpy(&sums[colorIndex], &sums[lightGridColorCount], sizeof(GfxLightGridColorSums));
+
+ for (unsigned int i = 0; i < lightGridGlob->pointCount; i++)
+ {
+ if (lightGridGlob->points[i].entry.colorsIndex == lightGridColorCount)
+ lightGridGlob->points[i].entry.colorsIndex = colorIndex;
+ }
+ }
+ }
+
+ delete[] counts;
+ delete[] sums;
+}
diff --git a/components/cod2rad/r_lightgrid.h b/components/cod2rad/r_lightgrid.h
index 99256935..f8bbd70c 100644
--- a/components/cod2rad/r_lightgrid.h
+++ b/components/cod2rad/r_lightgrid.h
@@ -1,8 +1,86 @@
#pragma once
+#define LIGHTGRID_MAX_COLORCOUNT 0xFFFF
+
const static DWORD* g_lightgridSampleCount = (DWORD*)0x153C91D0;
const static DWORD* g_diskLightgridSampleCount = (DWORD*)0x153C91E0;
+union GfxLightGridColors
+{
+ BYTE rgb[56][3];
+ BYTE all[56 * 3];
+};
+STATIC_ASSERT_SIZE(GfxLightGridColors, 56 * 3);
+
+union GfxLightGridColorsHDR
+{
+ short rgb[56][3];
+ short all[56 * 3];
+};
+
+struct GridColorsCluster
+{
+ unsigned int first; // 0x00
+ unsigned int count; // 0x04
+ unsigned int unknown1; // 0x08
+ float unknown2; // 0x0C
+ float unknown3; // 0x10
+};
+STATIC_ASSERT_SIZE(GridColorsCluster, 0x14);
+
+struct GfxLightGridEntry
+{
+ unsigned __int16 colorsIndex;
+ char primaryLightIndex;
+ char needsTrace;
+};
+STATIC_ASSERT_SIZE(GfxLightGridEntry, 0x04);
+
+struct GridSamplePoint
+{
+ WORD pos[3]; // 0x00
+ WORD unknown4; // 0x06 ???
+ //WORD unknown5; // 0x08 ColorsIndex?
+ //BYTE unknown6; // 0x0A PrimaryLightIndex?
+ //BYTE unknown7; // 0x0C NeedsTrace?
+ GfxLightGridEntry entry;
+};
+STATIC_ASSERT_SIZE(GridSamplePoint, 0xC);
+
+struct LightGridGlob
+{
+ unsigned int pointCount; // 0x00 0x153C91D0
+ unsigned int maxPoints; // 0x04 0x153C91D4
+ GridSamplePoint* points; // 0x08 0x153C91D8
+ GfxLightGridColorsHDR* colors; // 0x0C 0x153C91DC (In vanilla this is a GfxLightGridColors* which is allocated and freed in CalculateLightGrid)
+ unsigned int clusterCount; // 0x10 0x153C91E0
+ GridColorsCluster *clusters; // 0x14 0x153C91E4
+ unsigned int *mapping; // 0x18 0x153C91E8
+ unsigned int dword_153C91EC; // 0x1C 0x153C91EC
+ unsigned int dword_153C91F0; // 0x20 0x153C91F0
+ float flt_153C91F4; // 0x24 0x153C91F4
+ float flt_153C91F8; // 0x28 0x153C91F8
+ float flt_153C91FC; // 0x2C 0x153C91FC
+ float flt_153C9200; // 0x30 0x153C9200
+ unsigned int dword_153C9204; // 0x34 0x153C9204
+ // ...
+};
+
+static LightGridGlob *lightGridGlob = (LightGridGlob *)0x153C91D0;
+
+static unsigned int& lightGridColorCount = *(unsigned int *)0x112BAAB4;
+static GfxLightGridColors *disk_lightGridColors = (GfxLightGridColors *)0x96CAE08;
+extern GfxLightGridColorsHDR disk_lightGridColorsHDR[0xFFFF];
+
+static bool& options_ImproveLights = *(bool *)0x153C9005;
+static float& options_clusterThreshold = *(float*)0x153C902C;
+
+typedef WORD DiskSampleColorHDR[56][3];
+typedef BYTE DiskSampleColor[56][3];
+
+//static QuantumFunc_t GuessLightGridColors = (QuantumFunc_t)0x004342F0;
+
+#if USE_LEGACY_HDR
typedef float SampleColorHDR[56][3];
typedef WORD DiskSampleColorHDR[56][3];
typedef BYTE DiskSampleColor[56][3];
@@ -16,4 +94,13 @@ extern R_Store_QuantizedLightGridSample_t o_R_Store_QuantizedLightGridSample;
void mfh_R_Init_Lightgrid();
void mfh_R_Store_LightgridSample();
void mfh_R_Alloc_DiskLightGridColors();
-void hk_R_Store_QuantizedLightGridSample();
\ No newline at end of file
+void hk_R_Store_QuantizedLightGridSample();
+#else
+
+typedef void(__cdecl* SwapClusters_t)(int fromIndex, int toIndex);
+static SwapClusters_t SwapClusters_o = (SwapClusters_t)0x00432EF0;
+
+void __cdecl StoreLightingForDir(vec3* lighting, GfxLightGridColorsHDR* dst);
+void __cdecl ClusterLightGridValues(int ThreadCount);
+void __cdecl ImproveLightGridValues(int threadCount);
+#endif
diff --git a/components/cod2rad/r_lightmaps.cpp b/components/cod2rad/r_lightmaps.cpp
index 32120149..21b53b4b 100644
--- a/components/cod2rad/r_lightmaps.cpp
+++ b/components/cod2rad/r_lightmaps.cpp
@@ -1,4 +1,40 @@
-#include "stdafx.h"
+#include "stdafx.h"
+
+#define LOG_VEC_EQ(A,B) if (A != B) { printf("(%f) %f %f %f == %f %f %f\n", Vec3Variance(&A, &B), A.x, A.y, A.z, B.x, B.y, B.z); }
+
+#if VARIANCE_TRACKER
+VarianceTracker vt_GetInitialLightingHighlightDir;
+VarianceTracker vt_GetColorsForHighlightDir_1;
+VarianceTracker vt_GetColorsForHighlightDir_2;
+VarianceTracker vt_GetLightingApproximationError;
+VarianceTracker vt_GetGradientOfLightingErrorFunctionWithRespectToDir;
+VarianceTracker vt_ImproveLightingApproximation_1;
+VarianceTracker vt_ImproveLightingApproximation_2;
+
+VarianceTracker::VarianceTracker() : _min(FLT_MAX), _max(DBL_MIN), _total(0.0), _count(0)
+{
+}
+
+void VarianceTracker::Track(double v)
+{
+ mtx.lock();
+ if (v < _min)
+ _min = v;
+
+ if (v > _max)
+ _max = v;
+
+ _total += v;
+ _count++;
+ mtx.unlock();
+}
+
+double VarianceTracker::Min(void) const { return _min; }
+double VarianceTracker::Max(void) const { return _max; }
+double VarianceTracker::Total(void) const { return _total; }
+double VarianceTracker::Average(void) const { return _total / _count; }
+
+#endif
R_BuildFinalLightmaps_t o_R_BuildFinalLightmaps = (R_BuildFinalLightmaps_t)0x00432D70;
@@ -14,6 +50,7 @@ void __declspec(naked) hk_R_BuildFinalLightmaps()
}
}
+#if USE_LEGACY_HDR
//
// Store pixel information for later use by mfh_R_StoreLightmapPixel
//
@@ -34,13 +71,16 @@ void __declspec(naked) hk_R_StoreLightmapPixel() //00432830
}
}
+//
+// Currently only pel1 appears to have correct alpha data
+//
void __cdecl R_StoreLightmapPixelHDR(vec4* pel1, vec4* pel2, int lightmap, int row, int pel)
{
LightmapBytes_HDR[0x40000 * lightmap + 512 * row + pel] = *pel1;
Lightmap2Bytes_HDR[0x40000 * lightmap + 512 * row + pel] = *pel2;
}
-void* sub_4323E0 = (void*)0x4323E0;
+void* ImproveLightingApproximation = (void*)0x4323E0;
void* rtn_R_StoreLightmapPixel2 = (void*)0x0043288D;
void __declspec(naked) mfh_R_StoreLightmapPixel()
{
@@ -60,8 +100,490 @@ void __declspec(naked) mfh_R_StoreLightmapPixel()
add esp, 20
popad
- call sub_4323E0
+ call ImproveLightingApproximation
xorps xmm1, xmm1
jmp rtn_R_StoreLightmapPixel2
}
-}
\ No newline at end of file
+}
+#else
+
+void __cdecl GetInitialLightingHighlightDir_o(vec3* lighting, vec3* highlightDir)
+{
+ _asm
+ {
+ pushad
+ push lighting
+ mov eax, highlightDir
+ mov ebx, 0x00431980
+ call ebx
+ add esp, 4
+ popad
+ }
+}
+
+//
+// Verified
+// Variance: 0.000000 0.000014
+//
+void GetInitialLightingHighlightDir(vec3 *lighting, vec3 *out)
+{
+ float totals[256];
+
+ //
+ // Get the total R+G+B values for each sample and store it in 'totals'
+ //
+ for (int i = 0; i < g_basisDirectionsCount; i++)
+ {
+ totals[i] = lighting[i].r + lighting[i].g + lighting[i].b;
+ }
+
+ vec3 v25;
+ v25.x = 0;
+ v25.y = 0;
+ v25.z = 0;
+
+ for (int i = 0; i < g_basisDirectionsCount; i++)
+ {
+ v25.z = g_basisDirections[i].z * 0.5f + 0.5f;
+ v25.y = totals[i] * v25.z + v25.y;
+ v25.x = v25.z + v25.x;
+ }
+
+ v25.y = v25.y / v25.x;
+
+ out->x = 0.0f;
+ out->y = 0.0f;
+ out->z = 0.0f;
+
+ for (int i = 0; i < g_basisDirectionsCount; i++)
+ {
+ vec3* dir = &g_basisDirections[i];
+ v25.z = dir->z * 0.5f + 0.5f;
+ v25.z = totals[i] - v25.z * v25.y;
+ totals[i] = v25.z;
+
+ out->x = dir->x * v25.z + out->x;
+ out->y = dir->y * v25.z + out->y;
+ out->z = dir->z * v25.z + out->z;
+ }
+
+ Vec3Normalize(out);
+}
+
+void __cdecl GetColorsForHighlightDir_o(vec3* lighting, vec3* highlightDir, vec3* pel1, vec3* pel2)
+{
+ _asm
+ {
+ pushad
+ push pel2
+ push pel1
+ mov ecx, lighting
+ mov eax, highlightDir
+ mov ebx, 0x00430EF0
+ call ebx
+ add esp, 8
+ popad
+ }
+}
+
+//
+// Verified
+// Variance: 0.000000 0.000393 (pel1)
+// Variance: 0.000000 0.000372 (pel2)
+//
+void GetColorsForHighlightDir(vec3 *lighting, vec3 *highlightDir, vec3 *dstA, vec3 *dstB)
+{
+ vec3 total(0.0f, 0.0f, 0.0f);
+ for (int i = 0; i < g_basisDirectionsCount; i++)
+ {
+ total += lighting[i];
+ }
+
+ vec3 baz(0.0f, 0.0f, 0.0f);
+ vec3 total_gamma__lighting(0.0f, 0.0f, 0.0f);
+ vec3 total_dotpr__lighting(0.0f, 0.0f, 0.0f);
+
+ for (int i = 0; i < g_basisDirectionsCount; i++)
+ {
+ vec3* dir = &g_basisDirections[i];
+ float gamma_ = dir->z * 0.5f + 0.5f;
+
+ float dotpr_ = Vec3Dot(dir, highlightDir);
+ if (dotpr_ < 0.0f)
+ dotpr_ = 0.0f;
+
+ baz.x = dotpr_ * dotpr_ + baz.x;
+ baz.y = gamma_ * gamma_ + baz.y;
+ baz.z = dotpr_ * gamma_ + baz.z;
+
+ total_gamma__lighting += lighting[i] * gamma_;
+ total_dotpr__lighting += lighting[i] * dotpr_;
+ }
+
+ float unk2 = baz.x * baz.y - baz.z * baz.z;
+ if (unk2 == 0.0f)
+ {
+ dstA->r = 0.0;
+ dstA->g = 0.0;
+ dstA->b = 0.0;
+ dstB->r = 0.0;
+ dstB->g = 0.0;
+ dstB->b = 0.0;
+ }
+ else
+ {
+ vec3 srcA;
+ vec3 srcB;
+
+ unk2 = 1.0f / unk2;
+ srcA.r = (total_gamma__lighting.x * baz.x - total_dotpr__lighting.x * baz.z) * unk2;
+ srcB.r = (total_dotpr__lighting.x * baz.y - total_gamma__lighting.x * baz.z) * unk2;
+ srcA.g = (total_gamma__lighting.y * baz.x - total_dotpr__lighting.y * baz.z) * unk2;
+ srcB.g = (total_dotpr__lighting.y * baz.y - total_gamma__lighting.y * baz.z) * unk2;
+ srcA.b = (total_gamma__lighting.z * baz.x - total_dotpr__lighting.z * baz.z) * unk2;
+ srcB.b = (total_dotpr__lighting.z * baz.y - total_gamma__lighting.z * baz.z) * unk2;
+
+ if (ClampColor(dstB, &srcB))
+ {
+ srcA = *dstB * -baz.z + total_gamma__lighting;
+ srcA /= baz.y;
+ }
+
+ if (ClampColor(dstA, &srcA))
+ {
+ srcB = *dstA * -baz.z + total_dotpr__lighting;
+ srcB /= baz.x;
+
+ ClampColor(dstB, &srcB);
+ }
+ }
+}
+
+//
+// Regenerate the lighting sample from dir, highlightDir, and the two pels
+//
+void GetLightingSampleForDirFromLightmap(vec3* dir, vec3* highlightDir, vec3* pel_amb, vec3* pel_dir, vec3* out)
+{
+ // It would appear that half lambert is used for the ambient lighting
+ float weight_amb = dir->z * 0.5f + 0.5f; // weight_amb = Dot(dir, vec3(0, 0, 1)) * 0.5 + 0.5;
+ float weight_dir = Vec3Dot(dir, highlightDir);
+
+ if (weight_dir < 0.0)
+ weight_dir = 0.0f;
+
+ *out = (*pel_amb) * weight_amb + (*pel_dir) * weight_dir;
+}
+
+float GetLightingApproximationError_o(vec3 *lighting, vec3 *highlightDir, vec3 *pel1, vec3 *pel2)
+{
+ static DWORD dwCall = 0x004313B0;
+
+ DWORD outFloat;
+
+ _asm
+ {
+ push pel1
+ push lighting
+ mov edx, pel2
+ mov eax, highlightDir
+ call [dwCall]
+ add esp, 8
+ movd [outFloat], xmm0
+ xorps xmm0, xmm0
+ xorps xmm1, xmm1
+ }
+
+ return *(float *)&outFloat;
+}
+
+/*
+ Get the total value of error^2 for each lighting sample and the current lightmap pixels
+ Variance: 0.000000 0.000001
+*/
+double GetLightingApproximationError(vec3 *lighting, vec3 *highlightDir, vec3 *pel_amb, vec3 *pel_dir)
+{
+ double error = 0.0;
+
+ for (int i = 0; i < g_basisDirectionsCount; i++)
+ {
+ vec3 sample;
+ GetLightingSampleForDirFromLightmap(&g_basisDirections[i], highlightDir, pel_amb, pel_dir, &sample);
+
+ vec3 dif = lighting[i] - sample;
+ error += Vec3Dot(&dif, &dif);
+ }
+
+ return error;
+}
+
+void __cdecl GetGradientOfLightingErrorFunctionWithRespectToDir_o(vec3 *lighting, vec3 *highlightDir, vec3 *pel1, vec3 *pel2, vec2 *gradient)
+{
+ void* fn = (void*)0x00431D50;
+ _asm
+ {
+ push gradient
+ push highlightDir
+ push pel1
+ mov ecx, lighting
+ mov eax, pel2
+ call fn
+ add esp, 12
+ }
+}
+
+//
+// Verified
+// Variance: 0.000000 0.000002
+//
+void GetGradientOfLightingErrorFunctionWithRespectToDir(vec3 *lighting, vec3 *highlightDir, vec3 *pel1, vec3 *pel2, vec2 *gradient)
+{
+ vec2 total(0.0f, 0.0f);
+
+ for (int i = 0; i < g_basisDirectionsCount; i++)
+ {
+ vec3* basis = &g_basisDirections[i];
+
+ vec3 sample;
+ GetLightingSampleForDirFromLightmap(basis, highlightDir, pel1, pel2, &sample);
+
+ float yaa = Vec3Dot(basis, highlightDir);
+
+ vec2 tmp;
+ tmp.x = highlightDir->x * -yaa + basis->x;
+ tmp.y = highlightDir->y * -yaa + basis->y;
+
+ vec3 dif = lighting[i] - sample;
+
+ float dotp = Vec3Dot(pel2, &dif);
+ total += tmp * dotp;
+ }
+
+ *gradient = total * (highlightDir->z + highlightDir->z);
+}
+
+void __cdecl ImproveLightingApproximation_o(vec3* lighting, vec3* highlightDir, vec3* pel1, vec3* pel2)
+{
+ _asm
+ {
+ pushad
+ push pel2
+ push pel1
+ push lighting
+ mov edi, highlightDir
+ mov ebx, 0x004323E0
+ call ebx
+ add esp, 12
+ popad
+ }
+}
+
+//
+// Verified: Bad
+// Variance: 0.000000 0.813436 (pel1)
+// Variance: 0.000000 0.590278 (pel2)
+//
+void ImproveLightingApproximation(vec3* lighting, vec3 *highlightDir, vec3* pel_amb, vec3* pel_dir)
+{
+ double curError = 0.0;
+ double error = GetLightingApproximationError(lighting, highlightDir, pel_amb, pel_dir);
+
+#if VARIANCE_TRACKER
+ double err2 = GetLightingApproximationError_o(lighting, highlightDir, pel_amb, pel_dir);
+ vt_GetLightingApproximationError.Track(abs(error - err2));
+#endif
+
+ // Don't fix what isn't broken
+ if (error == 0.0)
+ return;
+
+ BYTE initialByteDir[2];
+ BYTE updatedByteDir[2];
+
+ EncodeNormalToBytes(highlightDir, initialByteDir);
+
+ // the formula for weight is 2^(2-x) where x is the iteration number
+ float weight = 4.0;
+ for(int iterations = 16;;) // the original number of iterations was 4
+ {
+ vec2 gradient;
+ GetGradientOfLightingErrorFunctionWithRespectToDir(lighting, highlightDir, pel_amb, pel_dir, &gradient);
+
+#if VARIANCE_TRACKER
+ vec2 gradient2;
+ GetGradientOfLightingErrorFunctionWithRespectToDir_o(lighting, highlightDir, pel_amb, pel_dir, &gradient2);
+ vt_GetGradientOfLightingErrorFunctionWithRespectToDir.Track(Vec2Variance(&gradient, &gradient2));
+#endif
+
+ vec2 absoluteGradient;
+ absoluteGradient.x = fabs(gradient.x);
+ absoluteGradient.y = fabs(gradient.y);
+
+ float tmp = absoluteGradient.x;
+ if (absoluteGradient.x - absoluteGradient.y < 0.0f)
+ tmp = absoluteGradient.y;
+
+ vec3 dir;
+ vec3 new_pel_amb;
+ vec3 new_pel_dir;
+
+ //
+ // Adjust using the gradient, keep adjustment if the new error is lower than the old one, otherwise try again
+ //
+ while (true)
+ {
+ float scalar = weight / tmp;
+
+ updatedByteDir[0] = ClampByte(initialByteDir[0] + (int)(gradient.x * scalar));
+ updatedByteDir[1] = ClampByte(initialByteDir[1] + (int)(gradient.y * scalar));
+
+ DecodeNormalFromBytes(updatedByteDir[0], updatedByteDir[1], &dir);
+
+ GetColorsForHighlightDir(lighting, &dir, &new_pel_amb, &new_pel_dir);
+#if VARIANCE_TRACKER
+ vec3 new_pel3;
+ vec3 new_pel4;
+ GetColorsForHighlightDir_o(lighting, &dir, &new_pel3, &new_pel4);
+ vt_GetColorsForHighlightDir_1.Track(Vec3Variance(&new_pel_amb, &new_pel3));
+ vt_GetColorsForHighlightDir_2.Track(Vec3Variance(&new_pel_dir, &new_pel4));
+#endif
+ curError = GetLightingApproximationError(lighting, &dir, &new_pel_amb, &new_pel_dir);
+#if VARIANCE_TRACKER
+ err2 = GetLightingApproximationError_o(lighting, &dir, &new_pel_amb, &new_pel_dir);
+ vt_GetLightingApproximationError.Track(abs(curError - err2));
+#endif
+
+ //
+ // If the adjusted approximation has a smaller error value - use the approximation
+ //
+ if (curError < error)
+ break;
+
+ weight /= 2.0f;
+
+ // Abort if we were unable to find a better approximation within a reasonable amount of iterations
+ if (!--iterations)
+ {
+ return;
+ }
+ }
+
+ *pel_amb = new_pel_amb;
+ *pel_dir = new_pel_dir;
+
+ initialByteDir[0] = updatedByteDir[0];
+ initialByteDir[1] = updatedByteDir[1];
+
+ *highlightDir = dir;
+
+ error = curError;
+ }
+}
+
+void __declspec(naked) hk_StoreLightBytes()
+{
+ _asm
+ {
+ push[esp+12] //pFloats esp + 12 + 0
+ push[esp+12] //highlightDir esp + 8 + 4
+ push[esp+12] //pixelIndex esp + 4 + 8
+ push ecx // lmapRow
+ push eax // lmapSet
+ call StoreLightBytes
+ add esp, 20
+ retn
+ }
+}
+
+/*
+ lighting - a pointer to an array of [g_basisDirectionsCount] RGB vec3 values for the given pixel - these correspond to each basis direction in g_basisDirections for that point
+ pFloats - a pointer to the primary light values (1 float per pixel) that are used for baked shadowmaps
+
+ pel1 - amb
+ pel2 - dir
+ pFloats - sm
+*/
+void __cdecl StoreLightBytes(int lmapSet, int lmapRow, int pixelIndex, vec3* lighting, float* pFloats)
+{
+ int subOffset = 0x600 * lmapSet + lmapRow;
+
+ vec3 highlightDir;
+ GetInitialLightingHighlightDir(lighting, &highlightDir);
+
+#if VARIANCE_TRACKER
+ vec3 highlightDir2;
+ GetInitialLightingHighlightDir_o(lighting, &highlightDir2);
+
+ vt_GetInitialLightingHighlightDir.Track(Vec3Variance(&highlightDir, &highlightDir2));
+#endif
+
+ vec3 pel1;
+ vec3 pel2;
+ GetColorsForHighlightDir(lighting, &highlightDir, &pel1, &pel2);
+
+#if VARIANCE_TRACKER
+ vec3 pel3;
+ vec3 pel4;
+ GetColorsForHighlightDir_o(lighting, &highlightDir, &pel3, &pel4);
+
+ vt_GetColorsForHighlightDir_1.Track(Vec3Variance(&pel1, &pel3));
+ vt_GetColorsForHighlightDir_2.Track(Vec3Variance(&pel2, &pel4));
+#endif
+
+ ImproveLightingApproximation(lighting, &highlightDir, &pel1, &pel2);
+#if VARIANCE_TRACKER
+ // Ensure that the starting values are the same
+ pel3 = pel1;
+ pel4 = pel2;
+
+ ImproveLightingApproximation_o(lighting, &highlightDir, &pel3, &pel4);
+ vt_ImproveLightingApproximation_1.Track(Vec3Variance(&pel1, &pel3));
+ vt_ImproveLightingApproximation_2.Track(Vec3Variance(&pel2, &pel4));
+#endif
+
+ BYTE packed_normal[2];
+ EncodeNormalToBytes(&highlightDir, packed_normal);
+
+ BYTE* lightBytes = (BYTE*)0x00471590;
+ WAW_LMAP_PEL* pel = (WAW_LMAP_PEL*)&lightBytes[4 * (pixelIndex + (subOffset << 9))];
+
+ pel->B = EncodeFloatInByte(pel1.b);
+ pel->G = EncodeFloatInByte(pel1.g);
+ pel->R = EncodeFloatInByte(pel1.r);
+ pel->A = packed_normal[0];
+
+ pel += 0x40000;
+
+ pel->B = EncodeFloatInByte(pel2.b);
+ pel->G = EncodeFloatInByte(pel2.g);
+ pel->R = EncodeFloatInByte(pel2.r);
+ pel->A = packed_normal[0];
+
+ if (g_HDR)
+ {
+ vec2 n;
+ EncodeNormalToFloats(&highlightDir, &n);
+
+ vec4* out1 = &LightmapBytes_HDR[0x40000 * lmapSet + 512 * lmapRow + pixelIndex];
+ *out1 = vec4(pel1, n.x);
+
+ vec4* out2 = &Lightmap2Bytes_HDR[0x40000 * lmapSet + 512 * lmapRow + pixelIndex];
+ *out2 = vec4(pel2, n.y);
+ }
+
+ //
+ // For each lightmap pixel copy a block of 4 pixels for the shadowmap
+ // since the colored images are 512x512 and the shadowmap is 1024x1024 this is necessary
+ //
+ BYTE* lightBytes_PrimaryImage = (BYTE*)0x00671590;
+ BYTE* sm_pel = &lightBytes_PrimaryImage[2 * (pixelIndex + (subOffset << 10))];
+
+ for (int i = 0; i < 2; i++)
+ {
+ for (int j = 0; j < 2; j++)
+ {
+ sm_pel[1024*i + j] = EncodeFloatInByte(pFloats[2*i + j]);
+ }
+ }
+}
+
+#endif
diff --git a/components/cod2rad/r_lightmaps.h b/components/cod2rad/r_lightmaps.h
index 99b9917a..cf31f66f 100644
--- a/components/cod2rad/r_lightmaps.h
+++ b/components/cod2rad/r_lightmaps.h
@@ -1,4 +1,7 @@
#pragma once
+#include
+
+#define VARIANCE_TRACKER _DEBUG
const static DWORD* g_LightmapCount = (DWORD*)0x16E99F58;
@@ -7,5 +10,53 @@ extern R_BuildFinalLightmaps_t o_R_BuildFinalLightmaps;
void hk_R_BuildFinalLightmaps();
+BYTE __cdecl EncodeFloatInByte(float flt);
+
+#if USE_LEGACY_HDR
void hk_R_StoreLightmapPixel();
-void mfh_R_StoreLightmapPixel();
\ No newline at end of file
+void mfh_R_StoreLightmapPixel();
+#else
+
+#if VARIANCE_TRACKER
+struct VarianceTracker
+{
+private:
+ std::mutex mtx;
+
+ double _min;
+ double _max;
+ double _total;
+
+ unsigned int _count;
+
+public:
+ VarianceTracker();
+
+ void Track(double v);
+
+ double Min(void) const;
+ double Max(void) const;
+ double Total(void) const;
+ double Average(void) const;
+};
+
+//
+// Global variance trackers for the rewritten functions
+//
+extern VarianceTracker vt_GetInitialLightingHighlightDir;
+extern VarianceTracker vt_GetColorsForHighlightDir_1;
+extern VarianceTracker vt_GetColorsForHighlightDir_2;
+extern VarianceTracker vt_GetLightingApproximationError;
+extern VarianceTracker vt_GetGradientOfLightingErrorFunctionWithRespectToDir;
+extern VarianceTracker vt_ImproveLightingApproximation_1;
+extern VarianceTracker vt_ImproveLightingApproximation_2;
+
+#ifndef VARIANCE_LOG
+#define VARIANCE_LOG(TRACKER) Con_Printf("%s:\n\tMin: %f\n\tMax: %f\n\tAverage: %f\n", #TRACKER, TRACKER.Min(), TRACKER.Max(), TRACKER.Average());
+#endif
+
+#endif
+
+void hk_StoreLightBytes();
+void __cdecl StoreLightBytes(int lmapSet, int lmapRow, int pixelIndex, vec3* lighting, float* pFloats);
+#endif
\ No newline at end of file
diff --git a/components/cod2rad/stdafx.h b/components/cod2rad/stdafx.h
index bc051dc3..1dde0a0e 100644
--- a/components/cod2rad/stdafx.h
+++ b/components/cod2rad/stdafx.h
@@ -1,22 +1,30 @@
#pragma once
+#pragma comment(lib, "detours.lib")
+#include "../shared/detours/Detours.h"
+
#define WIN32_LEAN_AND_MEAN
#include
#include
+#define USE_LEGACY_HDR 0
+
//
// Shared files
//
-#include "../shared/utility.h"
+#include "../shared/shared_utility.h"
+#include "../shared/shared_version.h"
#include "PageGuard.h"
-#include "console.h"
+#include "print.h"
#include "threading.h"
#include "arguments.h"
#include "vector.h"
#include "hdr.h"
+#include "lighting.h"
+
#include "r_lightmaps.h"
#include "r_lightgrid.h"
#include "r_xmodel_load_obj.h"
@@ -25,8 +33,10 @@
#include "common.h"
#include "com_memory.h"
#include "com_files.h"
+#include "com_math.h"
#include "com_bsp_load_obj.h"
+#include "r_light_load_obj.h"
#include "r_xmodel_load_obj.h"
#include "r_xsurface_load_obj.h"
@@ -38,3 +48,7 @@
#else
#pragma comment(lib, "../../build/Release/D3DBSP_Lib.lib")
#endif
+
+#define VANILLA_VALUE(NAME, TYPE, ADDRESS) static TYPE& NAME = *(TYPE*)ADDRESS;
+
+using namespace D3DBSP_Lib;
diff --git a/components/cod2rad/threading.cpp b/components/cod2rad/threading.cpp
index 4206820d..1180b260 100644
--- a/components/cod2rad/threading.cpp
+++ b/components/cod2rad/threading.cpp
@@ -21,6 +21,20 @@ void(__cdecl *g_QuantumWorkerCallback)(int, int);
volatile DWORD *g_ThreadLocks = (DWORD *)0x17302434;
volatile DWORD g_ThreadCounter;
+void __cdecl ForEachQuantum(int stepCount, QuantumFunc_t func, int threadCount)
+{
+ *(LONG *)0x17303430 = stepCount;
+ SetProgress(0, stepCount);
+ if (threadCount == 1)
+ for (int i = 0; i < *(LONG *)0x17303430; i++)
+ {
+ func(i, 0);
+ UpdateProgress(1);
+ }
+ else
+ ForEachQuantumMultiThreaded(threadCount, func);
+}
+
DWORD WINAPI ForEachQuantumWorkerThread(LPVOID ThreadParameter)
{
for (LONG i = InterlockedExchangeAdd(&g_ThreadCounter, 1); i < *(LONG *)0x17303430; i = InterlockedExchangeAdd(&g_ThreadCounter, 1))
diff --git a/components/cod2rad/threading.h b/components/cod2rad/threading.h
index bd9d37e6..8cd9e757 100644
--- a/components/cod2rad/threading.h
+++ b/components/cod2rad/threading.h
@@ -1,5 +1,8 @@
#pragma once
+typedef void(__cdecl* QuantumFunc_t)(int stepIndex, int a2);
+
+void __cdecl ForEachQuantum(int stepCount, void(__cdecl *func)(int, int), int threadCount);
DWORD WINAPI ForEachQuantumWorkerThread(LPVOID ThreadParameter);
void ForEachQuantumMultiThreaded(ULONG ThreadCount, void(__cdecl *Callback)(int, int));
diff --git a/components/cod2rad/vector.cpp b/components/cod2rad/vector.cpp
new file mode 100644
index 00000000..44edd7eb
--- /dev/null
+++ b/components/cod2rad/vector.cpp
@@ -0,0 +1,40 @@
+#include "stdafx.h"
+
+void Vec3Normalize(vec3* v)
+{
+ float m2 = v->x * v->x + v->y * v->y + v->z * v->z;
+
+ // Avoid sqrt of values <= 0
+ float m = m2 > 0.0f ? (float)sqrt(m2) : 1.0f;
+
+ // Avoid division by 0
+ if (-m >= 0.0f)
+ m = 1.0f;
+
+ *v /= m;
+}
+
+void Vec3Normalize(float* v)
+{
+ Vec3Normalize((vec3*)v);
+}
+
+float Vec3Dot(vec3* a, vec3* b)
+{
+ return a->x * b->x + a->y * b->y + a->z * b->z;
+}
+
+float Vec3Dot(float* a, float* b)
+{
+ return Vec3Dot((vec3*)a, (vec3*)b);
+}
+
+double Vec2Variance(vec2* a, vec2* b)
+{
+ return abs(a->x - b->x) + abs(a->y - b->y);
+}
+
+double Vec3Variance(vec3* a, vec3* b)
+{
+ return abs(a->x - b->x) + abs(a->y - b->y) + abs(a->z - b->z);
+}
\ No newline at end of file
diff --git a/components/cod2rad/vector.h b/components/cod2rad/vector.h
index 35e4db44..fe854c3d 100644
--- a/components/cod2rad/vector.h
+++ b/components/cod2rad/vector.h
@@ -1,23 +1,916 @@
#pragma once
-#include
+#ifndef __VECTOR_H_
+#define __VECTOR_H_
template
-struct tvec3
+class tvec2;
+typedef tvec2 vec2;
+typedef tvec2 dvec2;
+typedef tvec2 ivec2;
+typedef tvec2 uvec2;
+typedef tvec2 bvec2;
+
+template
+class tvec3;
+typedef tvec3 vec3;
+typedef tvec3 dvec3;
+typedef tvec3 ivec3;
+typedef tvec3 uvec3;
+typedef tvec3 bvec3;
+
+template
+class tvec4;
+typedef tvec4 vec4;
+typedef tvec4 dvec4;
+typedef tvec4 ivec4;
+typedef tvec4 uvec4;
+typedef tvec4 bvec4;
+
+template
+class tvec2
{
- T r;
- T g;
- T b;
+public:
+ union{ T x, r, s; };
+ union{ T y, g, t; };
+
+ template
+ T dot(tvec2 vec)
+ {
+ return(this->x * vec.x + this->y * vec.y);
+ }
+
+ T operator[](BYTE elem)
+ {
+ return ((T*)&x)[elem];
+ }
+
+ template
+ operator tvec2()
+ {
+ return tvec2((T2)this->x, (T2)this->y);
+ }
+
+ template
+ operator tvec3()
+ {
+ return tvec3((T3)this->x, (T3)this->y, (T3)0);
+ }
+
+ template
+ operator tvec4()
+ {
+ return tvec4((T4)this->x, (T4)this->y, (T4)0, (T4)0);
+ }
+
+ template
+ bool operator==(tvec2& arg)
+ {
+ return (this->x == arg.x || this->y == arg.y);
+ }
+
+ template
+ bool operator!=(tvec2& arg)
+ {
+ return !(*this == arg);
+ }
+
+ //Addition
+ tvec2 operator+(T arg)
+ {
+ return tvec2(this->x + arg, this->y + arg);
+ }
+
+ template
+ tvec2 operator+(tvec2& arg)
+ {
+ return tvec2(this->x + arg.x, this->y + arg.y);
+ }
+
+ template
+ tvec2 operator+(tvec3& arg)
+ {
+ return tvec2(this->x + arg.x, this->y + arg.y);
+ }
+
+ template
+ tvec2 operator+(tvec4& arg)
+ {
+ return tvec2(this->x + arg.x, this->y + arg.y);
+ }
+
+ tvec2& operator+=(T arg)
+ {
+ this->x += arg;
+ this->y += arg;
+ return *this;
+ }
+
+ template
+ tvec2& operator+=(tvec2& arg)
+ {
+ this->x += arg.x;
+ this->y += arg.y;
+ return *this;
+ }
+
+ template
+ tvec2& operator+=(tvec3& arg)
+ {
+ this->x += arg.x;
+ this->y += arg.y;
+ return *this;
+ }
+
+ template
+ tvec2& operator+=(tvec4& arg)
+ {
+ this->x += arg.x;
+ this->y += arg.y;
+ return *this;
+ }
+
+ //Subtraction
+ tvec2 operator-(T arg)
+ {
+ return tvec2(this->x - arg, this->y - arg);
+ }
+
+ template
+ tvec2