2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2025-11-22 21:02:07 +00:00

Merge branch 'main' into bsp-compilation

This commit is contained in:
LJW-Dev
2025-11-04 14:20:37 +08:00
317 changed files with 6511 additions and 1998 deletions

View File

@@ -10,4 +10,10 @@ updates:
directory: "/"
schedule:
interval: "weekly"
day: "friday"
day: "friday"
- package-ecosystem: "npm"
directory: "src/ModManUi"
schedule:
interval: "weekly"
day: "friday"

View File

@@ -35,7 +35,7 @@ jobs:
update-alternatives --set g++ /usr/bin/g++-13
- name: Install node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 24
@@ -88,7 +88,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Install node
uses: actions/setup-node@v5
uses: actions/setup-node@v6
with:
node-version: 24

View File

@@ -40,7 +40,7 @@ jobs:
chmod +x build/bin/Release_x86/{ImageConverter,Unlinker,Linker}
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: oat-linux
path: |
@@ -68,7 +68,7 @@ jobs:
run: msbuild /m /p:Configuration=Release /p:Platform=Win32 build
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v5
with:
name: oat-windows
path: |
@@ -83,7 +83,7 @@ jobs:
actions: read
contents: write
steps:
- uses: actions/download-artifact@v5
- uses: actions/download-artifact@v6
- name: Zip artifacts
run: |
7z a oat-linux.tar ./oat-linux/*

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@ local/
/build/
.vscode
.idea
user*.*
user*.*
*.aps

BIN
repo/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

41
repo/logo.svg Normal file
View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="180mm"
height="280mm"
viewBox="0 0 180 280"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
sodipodi:docname="oat.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.55748644"
inkscape:cx="452.02893"
inkscape:cy="637.68367"
inkscape:window-width="1920"
inkscape:window-height="1129"
inkscape:window-x="1672"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs1" /><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"><path
style="fill:#ca893d;stroke:none;stroke-width:0.881059"
d="m 98.086477,10.284091 c -8.72518,12.545685 -15.237436,27.541318 -20.227373,41.960408 -3.104147,8.969857 -5.812459,19.004812 -4.896093,28.595242 0.41179,4.309657 1.595038,9.342328 3.883297,13.054348 1.19243,1.93436 3.972179,3.54359 4.644286,5.59952 1.132057,3.462881 0.909997,7.594171 1.067247,11.184641 0.22594,5.15968 0.51324,10.35958 0.28634,15.5409 -0.3654,8.34369 -3.489751,17.57917 -6.829117,25.17624 -2.04753,4.65813 -5.389125,9.45537 -5.902181,14.60844 H 69.802066 C 69.428692,157.30909 68.39352,149.17787 64.82898,141.1384 75.011385,136.3776 73.657115,122.65058 70.794713,113.78643 65.563672,97.587209 53.473562,84.176688 41.857476,72.149305 37.619329,67.76109 33.443501,62.288958 28.463292,58.771675 c 1.201365,14.708585 3.193196,29.718655 6.39527,44.136135 1.511576,6.80593 3.110873,14.60973 6.361456,20.82479 3.151072,6.02479 6.473392,11.85107 12.41952,15.52075 1.752628,1.08164 3.561182,1.74167 5.59472,2.0594 1.176058,0.18376 2.636821,-0.0988 3.528117,0.85691 1.173307,1.25817 1.535394,3.59115 2.066605,5.1851 1.69453,5.08456 2.404493,10.21034 2.76954,15.54089 1.469279,21.45514 -6.881483,41.26494 -14.154923,60.9203 -2.206922,5.96387 -1.917171,12.42981 -4.426517,18.33825 -2.403204,5.65852 -5.04249,10.67632 -8.37313,15.84715 -2.374548,3.68649 -5.354731,7.31098 -5.964301,11.81564 2.47018,-0.1903 3.038119,-2.82753 4.388386,-4.57352 3.89855,-5.04115 7.715127,-10.16928 10.461561,-15.94046 3.33192,-7.0015 3.745199,-14.84262 6.313487,-22.06806 2.818465,-7.92927 7.795915,-15.19426 10.096059,-23.31135 2.396537,-8.45722 3.160481,-18.98132 8.199263,-26.40872 1.46339,-2.15711 6.39217,1.07699 8.718006,1.4102 6.147516,0.88066 13.038256,-1.07257 18.338249,-4.15983 14.87798,-8.66642 26.73707,-22.12908 36.95136,-35.77476 3.64006,-4.8629 7.90141,-9.65862 10.91459,-14.94707 -17.5133,5.69666 -36.27908,7.95457 -52.528213,16.98706 -7.78769,4.32897 -16.026201,10.01268 -20.284457,18.13536 -2.56198,4.88701 -4.187247,11.9503 -4.891786,17.40579 h -0.310817 c 0.614948,-7.72723 3.670776,-18.18531 8.250587,-24.50184 3.749657,-5.17156 8.837313,-9.2803 13.094873,-14.01175 2.36883,-2.63249 4.88214,-5.13015 7.20804,-7.80282 1.245893,-1.43163 2.551273,-3.52678 4.393193,-4.25887 1.20406,-0.47856 2.57834,0.48361 3.72981,0.79503 1.8591,0.50279 3.99226,0.48511 5.90554,0.3312 5.65903,-0.45521 11.62338,-4.4788 15.83947,-7.99228 8.21271,-6.84406 14.21432,-17.10575 19.05247,-26.479481 5.65071,-10.948038 11.25178,-22.296627 15.46055,-33.879144 -7.39769,3.100324 -14.60944,8.079765 -21.44643,12.290015 -13.6263,8.391132 -28.16089,17.768635 -36.53572,31.84612 -2.21587,3.72473 -4.21456,8.09477 -4.75461,12.43271 -0.32263,2.59168 0.73864,7.23757 -0.56096,9.3243 -5.272543,8.46593 -15.701159,13.80125 -20.31228,22.68994 h -0.310811 c 3.562782,-9.69925 5.533592,-19.77335 5.279342,-30.14934 -0.0931,-3.79938 -0.23558,-7.72278 -0.70557,-11.50025 -0.20864,-1.67681 -1.0711,-4.18252 -0.56768,-5.834791 0.44415,-1.4578 5.237446,-2.58065 6.561706,-3.63844 3.757,-3.00105 6.54717,-7.522098 8.25898,-11.973201 4.556173,-11.847047 4.432803,-25.46557 3.579913,-37.919777 -0.82233,-12.008128 -1.45907,-24.202473 -3.757633,-36.05487 h -0.62164 M 41.206824,136.16532 c -4.577383,14.0675 -9.920637,27.90215 -13.512903,42.27122 -1.868976,7.47591 -3.775657,15.23233 -3.672256,23.00052 0.08961,6.73198 0.506919,13.59238 3.971806,19.58033 1.116888,1.93019 2.668842,3.59228 4.510453,4.84237 1.047967,0.71137 2.550759,1.50965 2.260382,2.98203 -2.011788,10.20091 -7.414724,20.8876 -7.54309,31.33985 -0.03129,2.54764 -0.188076,4.91327 -0.0089,7.45962 0.06313,0.89695 0.05796,2.52568 1.28644,2.62757 2.264499,0.18782 1.724703,-7.8199 1.837087,-9.46556 0.716764,-10.49542 4.319911,-21.07358 6.250172,-31.38683 0.412302,-2.20291 5.085544,-3.54345 6.784267,-4.9213 3.678849,-2.98392 6.186696,-7.17302 7.860861,-11.55782 6.997983,-18.32831 1.510219,-40.7053 -3.89505,-58.74457 -1.125111,-3.75489 -2.43295,-7.44285 -3.578958,-11.18944 -0.692919,-2.26534 -1.097206,-4.91594 -2.550342,-6.83799 m 68.379927,45.06858 c -7.01423,5.71207 -13.581003,12.15259 -20.203163,18.31044 -13.160612,12.23781 -29.547148,26.49536 -30.980819,45.71803 -0.232877,3.1224 0.512818,7.05521 1.688402,9.94618 0.542897,1.33509 1.866557,2.60843 2.060126,4.04063 0.502499,3.71786 -3.512484,7.06263 -2.295402,11.18944 h 0.310818 c 1.63038,-2.80664 2.85027,-6.70538 3.108178,-9.94617 2.677512,0.39543 4.987636,1.24904 7.770447,0.83893 7.879489,-1.16126 13.744304,-8.70168 17.87778,-14.82573 8.74176,-12.95153 13.041363,-29.03305 16.444333,-44.13613 1.14054,-5.06196 2.39095,-10.11934 3.31274,-15.23008 0.31706,-1.75783 1.43015,-4.18134 0.90656,-5.90554 z"
id="path1" /></g></svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
repo/logo_circle.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

77
repo/logo_circle.svg Normal file
View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="300mm"
height="300mm"
viewBox="0 0 300 300"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
sodipodi:docname="oat_circle.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.55748644"
inkscape:cx="452.02893"
inkscape:cy="637.68367"
inkscape:window-width="1920"
inkscape:window-height="1129"
inkscape:window-x="1672"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter19"
x="-0.16704432"
y="-0.08986926"
width="1.3601893"
height="1.1937806"><feFlood
result="flood"
in="SourceGraphic"
flood-opacity="0.403922"
flood-color="rgb(0,0,0)"
id="feFlood18" /><feGaussianBlur
result="blur"
in="SourceGraphic"
stdDeviation="8.000000"
id="feGaussianBlur18" /><feOffset
result="offset"
in="blur"
dx="3.000000"
dy="3.000000"
id="feOffset18" /><feComposite
result="comp1"
operator="in"
in="flood"
in2="offset"
id="feComposite18" /><feComposite
result="comp2"
operator="over"
in="SourceGraphic"
in2="comp1"
id="feComposite19" /></filter></defs><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"><circle
style="fill:#ca893d;stroke-width:0.264583;fill-opacity:1"
id="path2"
cx="149.73645"
cy="151.63484"
r="128" /><path
style="fill:#ffffff;stroke:none;stroke-width:0.724;fill-opacity:1;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter19)"
d="m 157.15533,40.658523 c -7.16528,10.302747 -12.51327,22.617436 -16.6111,34.458658 -2.54917,7.366211 -4.77329,15.607101 -4.02076,23.482935 0.33817,3.539174 1.30988,7.672094 3.18904,10.720474 0.97925,1.58854 3.26203,2.91006 3.81397,4.59843 0.92967,2.84378 0.74731,6.23647 0.87645,9.18503 0.18554,4.23723 0.42148,8.50748 0.23514,12.76248 -0.30007,6.85199 -2.86584,14.43633 -5.6082,20.67519 -1.68147,3.82534 -4.42564,7.76492 -4.84697,11.99672 h -0.25525 c -0.30662,-7.14028 -1.15672,-13.81779 -4.08399,-20.41995 8.36198,-3.90966 7.24983,-15.18254 4.89917,-22.46194 -4.29583,-13.3031 -14.22445,-24.31606 -23.76379,-34.193176 -3.48045,-3.603683 -6.90971,-8.097499 -10.999551,-10.985956 0.986581,12.07896 2.622311,24.405512 5.251911,36.245412 1.24134,5.58915 2.55471,11.99777 5.22415,17.1017 2.58771,4.94766 5.31606,9.73231 10.19914,12.74592 1.43929,0.88826 2.9245,1.43029 4.59448,1.69122 0.9658,0.15091 2.16541,-0.0811 2.89736,0.70371 0.96354,1.03323 1.26089,2.94912 1.69713,4.2581 1.39158,4.17553 1.97461,8.38492 2.2744,12.76246 1.2066,17.61936 -5.6512,33.88753 -11.62428,50.02887 -1.81237,4.89764 -1.57442,10.20759 -3.63514,15.05971 -1.97356,4.64688 -4.14099,8.76759 -6.87617,13.01397 -1.95003,3.02741 -4.3974,6.00391 -4.89799,9.70322 2.02855,-0.15628 2.49496,-2.32202 3.60382,-3.75586 3.20156,-4.13988 6.3358,-8.3512 8.59123,-13.0906 2.73623,-5.74976 3.07562,-12.18903 5.18474,-18.12269 2.31458,-6.51166 6.40216,-12.47781 8.29107,-19.14371 1.96809,-6.94523 2.59545,-15.58781 6.73339,-21.68733 1.20177,-1.77146 5.24937,0.88445 7.15939,1.15809 5.04845,0.72321 10.70726,-0.88082 15.0597,-3.41613 12.21808,-7.11703 21.95698,-18.17281 30.34514,-29.37889 2.98928,-3.99351 6.48878,-7.93184 8.96326,-12.27481 -14.38224,4.6782 -29.79304,6.53244 -43.13713,13.95008 -6.39539,3.55503 -13.16101,8.2226 -16.65797,14.89309 -2.10395,4.01331 -3.43865,9.81381 -4.01723,14.29396 h -0.25524 c 0.50501,-6.34574 3.0145,-14.93411 6.77553,-20.12136 3.07929,-4.24698 7.25737,-7.62116 10.75375,-11.50671 1.94533,-2.16185 4.00931,-4.21297 5.91938,-6.40782 1.02315,-1.17568 2.09515,-2.89626 3.60777,-3.49746 0.98879,-0.393 2.11738,0.39715 3.06298,0.65289 1.52673,0.4129 3.27852,0.39838 4.84974,0.27199 4.6473,-0.37383 9.54534,-3.67807 13.00767,-6.56341 6.74442,-5.62047 11.67306,-14.04755 15.64624,-21.74543 4.64046,-8.990734 9.24016,-18.310406 12.69648,-27.822182 -6.07512,2.546043 -11.99754,6.635251 -17.6122,10.092786 -11.19017,6.890952 -23.12624,14.591936 -30.0038,26.152626 -1.81972,3.05881 -3.46108,6.64757 -3.90458,10.20997 -0.26495,2.12833 0.60659,5.94362 -0.46067,7.65728 -4.32991,6.95238 -12.89408,11.33384 -16.68082,18.6334 h -0.25524 c 2.92582,-7.9652 4.54429,-16.23824 4.33549,-24.75919 -0.0764,-3.12012 -0.19346,-6.34209 -0.57942,-9.44422 -0.17134,-1.37703 -0.87961,-3.43476 -0.46619,-4.79164 0.36474,-1.19717 4.30109,-2.11927 5.38859,-2.98795 3.08532,-2.46452 5.37666,-6.17729 6.78243,-9.83261 3.74161,-9.72902 3.6403,-20.912802 2.93989,-31.140425 -0.67532,-9.861296 -1.19822,-19.875516 -3.08584,-29.608922 h -0.5105 M 110.44471,144.0345 c -3.75903,11.55249 -8.14701,22.91376 -11.097041,34.71391 -1.534837,6.13935 -3.100637,12.50907 -3.015727,18.88845 0.0736,5.52842 0.41629,11.16231 3.261717,16.07972 0.917211,1.58511 2.191701,2.95005 3.704061,3.97665 0.86062,0.58419 2.09474,1.23975 1.85628,2.44889 -1.65212,8.37718 -6.089111,17.15329 -6.194528,25.73687 -0.0257,2.09216 -0.15445,4.03486 -0.007,6.12597 0.0519,0.73659 0.0476,2.07414 1.056448,2.15781 1.85965,0.15424 1.41636,-6.42184 1.50865,-7.77329 0.58862,-8.61903 3.54759,-17.30601 5.13276,-25.77544 0.33859,-1.80907 4.17634,-2.90995 5.57136,-4.04146 3.02114,-2.45045 5.08063,-5.89062 6.45548,-9.4915 5.74688,-15.05154 1.24022,-33.42794 -3.19868,-48.24212 -0.92396,-3.08358 -1.99799,-6.1122 -2.9391,-9.18897 -0.56904,-1.86034 -0.90105,-4.03706 -2.0944,-5.61549 m 56.15486,37.01115 c -5.76022,4.69086 -11.15297,9.97993 -16.59121,15.03687 -10.80773,10.04991 -24.26466,21.75848 -25.44202,37.54449 -0.19124,2.56417 0.42114,5.79387 1.38655,8.16798 0.44584,1.0964 1.53285,2.14209 1.69181,3.31824 0.41267,3.05318 -2.88452,5.79996 -1.88503,9.18898 h 0.25525 c 1.3389,-2.30487 2.3407,-5.50659 2.5525,-8.16798 2.19882,0.32473 4.09594,1.02573 6.38123,0.68894 6.47079,-0.95364 11.28708,-7.14598 14.68157,-12.17516 7.17889,-10.63603 10.7098,-23.84247 13.50438,-36.2454 0.93663,-4.15697 1.96349,-8.31019 2.72048,-12.50722 0.26038,-1.44356 1.17447,-3.43379 0.74449,-4.84974 z"
id="path1" /></g></svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,26 @@
#include "CommonAsset.h"
#include "IW3/CommonAssetIW3.h"
#include "IW4/CommonAssetIW4.h"
#include "IW5/CommonAssetIW5.h"
#include "T5/CommonAssetT5.h"
#include "T6/CommonAssetT6.h"
#include <cassert>
ICommonAssetTypeMapper* ICommonAssetTypeMapper::GetCommonAssetMapperByGame(GameId gameId)
{
static ICommonAssetTypeMapper* assetTypeMappers[static_cast<unsigned>(GameId::COUNT)]{
new IW3::CommonAssetTypeMapper(),
new IW4::CommonAssetTypeMapper(),
new IW5::CommonAssetTypeMapper(),
new T5::CommonAssetTypeMapper(),
new T6::CommonAssetTypeMapper(),
};
assert(static_cast<unsigned>(gameId) < static_cast<unsigned>(GameId::COUNT));
auto* result = assetTypeMappers[static_cast<unsigned>(gameId)];
assert(result);
return result;
}

View File

@@ -0,0 +1,181 @@
#pragma once
#include "IGame.h"
#include "Zone/ZoneTypes.h"
#include <cstdint>
#include <optional>
enum class CommonAssetType : std::uint8_t
{
// IW3, IW4, IW5, T5, T6
PHYS_PRESET,
// IW3, IW4, IW5, T5, T6
XANIM,
// IW3, IW4, IW5, T5, T6
XMODEL,
// IW3, IW4, IW5, T5, T6
MATERIAL,
// IW3, IW4, IW5, T5, T6
TECHNIQUE_SET,
// IW3, IW4, IW5, T5, T6
IMAGE,
// IW3, IW4, IW5, T5, T6
SOUND,
// IW3, IW4, IW5
SOUND_CURVE,
// IW3, IW4, IW5
LOADED_SOUND,
// IW3, IW4, IW5, T5, T6
CLIP_MAP,
// IW3, IW4, IW5, T5, T6
COM_WORLD,
// IW3, IW4, T5, T6
GAME_WORLD_SP,
// IW3, IW4, T5, T6
GAME_WORLD_MP,
// IW3, IW4, IW5, T5, T6
MAP_ENTS,
// IW3, IW4, IW5, T5, T6
GFX_WORLD,
// IW3, IW4, IW5, T5, T6
LIGHT_DEF,
// IW3, IW4, IW5, T5, T6
UI_MAP,
// IW3, IW4, IW5, T5, T6
FONT,
// IW3, IW4, IW5, T5, T6
MENU_LIST,
// IW3, IW4, IW5, T5, T6
MENU,
// IW3, IW4, IW5, T5, T6
LOCALIZE_ENTRY,
// IW3, IW4, IW5, T5, T6
WEAPON,
// IW3, IW4, IW5, T5, T6
SOUND_DRIVER_GLOBALS,
// IW3, IW4, IW5, T5, T6
FX,
// IW3, IW4, IW5, T5, T6
IMPACT_FX,
// IW3, IW4, IW5, T5, T6
AI_TYPE,
// IW3, IW4, IW5, T5, T6
MP_TYPE,
// IW3, IW4, IW5, T5, T6
CHARACTER,
// IW3, IW4, IW5, T5, T6
XMODEL_ALIAS,
// IW3, IW4, IW5, T5, T6
RAW_FILE,
// IW3, IW4, IW5, T5, T6
STRING_TABLE,
// IW3, T5, T6
XMODEL_PIECES,
// IW4, IW5
PHYS_COLL_MAP,
// IW4, IW5
XMODEL_SURFS,
// IW4, IW5
PIXEL_SHADER,
// IW4, IW5
VERTEX_SHADER,
// IW4, IW5
VERTEX_DECL,
// IW4, IW5
FX_WORLD,
// IW4, IW5, T6
LEADERBOARD,
// IW4, IW5
STRUCTURED_DATA_DEF,
// IW4, IW5, T6
TRACER,
// IW4, IW5, T6
VEHICLE,
// IW4, IW5, T6
ADDON_MAP_ENTS,
// IW5
GLASS_WORLD,
// IW5
PATH_DATA,
// IW5
VEHICLE_TRACK,
// IW5, T6
ATTACHMENT,
// IW5
SURFACE_FX,
// IW5, T6
SCRIPT,
// T5, T6
PHYS_CONSTRAINTS,
// T5, T6
DESTRUCTIBLE_DEF,
// T5, T6
SOUND_PATCH,
// T5, T6
WEAPON_DEF,
// T5, T6
WEAPON_VARIANT,
// T5, T6
MP_BODY,
// T5, T6
MP_HEAD,
// T5
PACK_INDEX,
// T5, T6
XGLOBALS,
// T5, T6
DDL,
// T5, T6
GLASSES,
// T5, T6
EMBLEM_SET,
// T6
FONT_ICON,
// T6
WEAPON_FULL,
// T6
ATTACHMENT_UNIQUE,
// T6
WEAPON_CAMO,
// T6
KEY_VALUE_PAIRS,
// T6
MEMORY_BLOCK,
// T6
SKINNED_VERTS,
// T6
QDB,
// T6
SLUG,
// T6
FOOTSTEP_TABLE,
// T6
FOOTSTEP_FX_TABLE,
// T6
ZBARRIER,
COUNT,
};
class ICommonAssetTypeMapper
{
public:
[[nodiscard]] virtual CommonAssetType GameToCommonAssetType(asset_type_t gameAssetType) const = 0;
[[nodiscard]] virtual std::optional<asset_type_t> CommonToGameAssetType(CommonAssetType commonAssetType) const = 0;
static ICommonAssetTypeMapper* GetCommonAssetMapperByGame(GameId gameId);
protected:
ICommonAssetTypeMapper() = default;
virtual ~ICommonAssetTypeMapper() = default;
ICommonAssetTypeMapper(const ICommonAssetTypeMapper& other) = default;
ICommonAssetTypeMapper(ICommonAssetTypeMapper&& other) noexcept = default;
ICommonAssetTypeMapper& operator=(const ICommonAssetTypeMapper& other) = default;
ICommonAssetTypeMapper& operator=(ICommonAssetTypeMapper&& other) noexcept = default;
};

View File

@@ -2,10 +2,11 @@
#include "GameLanguage.h"
#include <cstdint>
#include <type_traits>
#include <vector>
enum class GameId
enum class GameId : std::uint8_t
{
IW3,
IW4,
@@ -18,7 +19,7 @@ enum class GameId
// The full uppercase names are macros in the standard lib
// So unfortunately not usable as values in the enum
enum class GameEndianness
enum class GameEndianness : std::uint8_t
{
/* Little endian */
LE,
@@ -26,12 +27,19 @@ enum class GameEndianness
BE
};
enum class GameWordSize
enum class GameWordSize : std::uint8_t
{
ARCH_32,
ARCH_64
};
enum class GamePlatform : std::uint8_t
{
PC,
XBOX,
PS3
};
static constexpr const char* GameId_Names[]{
"IW3",
"IW4",

View File

@@ -0,0 +1,100 @@
#include "CommonAssetIW3.h"
#include "IW3.h"
#include <cassert>
namespace IW3
{
CommonAssetTypeMapper::CommonAssetTypeMapper() = default;
CommonAssetType CommonAssetTypeMapper::GameToCommonAssetType(const asset_type_t gameAssetType) const
{
static CommonAssetType lookupTable[static_cast<unsigned>(ASSET_TYPE_COUNT)]{
CommonAssetType::XMODEL_PIECES, // ASSET_TYPE_XMODELPIECES
CommonAssetType::PHYS_PRESET, // ASSET_TYPE_PHYSPRESET
CommonAssetType::XANIM, // ASSET_TYPE_XANIMPARTS
CommonAssetType::XMODEL, // ASSET_TYPE_XMODEL
CommonAssetType::MATERIAL, // ASSET_TYPE_MATERIAL
CommonAssetType::TECHNIQUE_SET, // ASSET_TYPE_TECHNIQUE_SET
CommonAssetType::IMAGE, // ASSET_TYPE_IMAGE
CommonAssetType::SOUND, // ASSET_TYPE_SOUND
CommonAssetType::SOUND_CURVE, // ASSET_TYPE_SOUND_CURVE
CommonAssetType::LOADED_SOUND, // ASSET_TYPE_LOADED_SOUND
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP_PVS
CommonAssetType::COM_WORLD, // ASSET_TYPE_COMWORLD
CommonAssetType::GAME_WORLD_SP, // ASSET_TYPE_GAMEWORLD_SP
CommonAssetType::GAME_WORLD_MP, // ASSET_TYPE_GAMEWORLD_MP
CommonAssetType::MAP_ENTS, // ASSET_TYPE_MAP_ENTS
CommonAssetType::GFX_WORLD, // ASSET_TYPE_GFXWORLD
CommonAssetType::LIGHT_DEF, // ASSET_TYPE_LIGHT_DEF
CommonAssetType::UI_MAP, // ASSET_TYPE_UI_MAP
CommonAssetType::FONT, // ASSET_TYPE_FONT
CommonAssetType::MENU_LIST, // ASSET_TYPE_MENULIST
CommonAssetType::MENU, // ASSET_TYPE_MENU
CommonAssetType::LOCALIZE_ENTRY, // ASSET_TYPE_LOCALIZE_ENTRY
CommonAssetType::WEAPON, // ASSET_TYPE_WEAPON
CommonAssetType::SOUND_DRIVER_GLOBALS, // ASSET_TYPE_SNDDRIVER_GLOBALS
CommonAssetType::FX, // ASSET_TYPE_FX
CommonAssetType::IMPACT_FX, // ASSET_TYPE_IMPACT_FX
CommonAssetType::AI_TYPE, // ASSET_TYPE_AITYPE
CommonAssetType::MP_TYPE, // ASSET_TYPE_MPTYPE
CommonAssetType::CHARACTER, // ASSET_TYPE_CHARACTER
CommonAssetType::XMODEL_ALIAS, // ASSET_TYPE_XMODELALIAS
CommonAssetType::RAW_FILE, // ASSET_TYPE_RAWFILE
CommonAssetType::STRING_TABLE, // ASSET_TYPE_STRINGTABLE
};
assert(gameAssetType < ASSET_TYPE_COUNT);
return lookupTable[gameAssetType];
}
std::optional<asset_type_t> CommonAssetTypeMapper::CommonToGameAssetType(const CommonAssetType commonAssetType) const
{
#define MAP_COMMON(common, game) \
case common: \
return game;
switch (commonAssetType)
{
MAP_COMMON(CommonAssetType::XMODEL_PIECES, ASSET_TYPE_XMODELPIECES)
MAP_COMMON(CommonAssetType::PHYS_PRESET, ASSET_TYPE_PHYSPRESET)
MAP_COMMON(CommonAssetType::XANIM, ASSET_TYPE_XANIMPARTS)
MAP_COMMON(CommonAssetType::XMODEL, ASSET_TYPE_XMODEL)
MAP_COMMON(CommonAssetType::MATERIAL, ASSET_TYPE_MATERIAL)
MAP_COMMON(CommonAssetType::TECHNIQUE_SET, ASSET_TYPE_TECHNIQUE_SET)
MAP_COMMON(CommonAssetType::IMAGE, ASSET_TYPE_IMAGE)
MAP_COMMON(CommonAssetType::SOUND, ASSET_TYPE_SOUND)
MAP_COMMON(CommonAssetType::SOUND_CURVE, ASSET_TYPE_SOUND_CURVE)
MAP_COMMON(CommonAssetType::LOADED_SOUND, ASSET_TYPE_LOADED_SOUND)
MAP_COMMON(CommonAssetType::CLIP_MAP, ASSET_TYPE_CLIPMAP_PVS)
MAP_COMMON(CommonAssetType::COM_WORLD, ASSET_TYPE_COMWORLD)
MAP_COMMON(CommonAssetType::GAME_WORLD_SP, ASSET_TYPE_GAMEWORLD_SP)
MAP_COMMON(CommonAssetType::GAME_WORLD_MP, ASSET_TYPE_GAMEWORLD_MP)
MAP_COMMON(CommonAssetType::MAP_ENTS, ASSET_TYPE_MAP_ENTS)
MAP_COMMON(CommonAssetType::GFX_WORLD, ASSET_TYPE_GFXWORLD)
MAP_COMMON(CommonAssetType::LIGHT_DEF, ASSET_TYPE_LIGHT_DEF)
MAP_COMMON(CommonAssetType::UI_MAP, ASSET_TYPE_UI_MAP)
MAP_COMMON(CommonAssetType::FONT, ASSET_TYPE_FONT)
MAP_COMMON(CommonAssetType::MENU_LIST, ASSET_TYPE_MENULIST)
MAP_COMMON(CommonAssetType::MENU, ASSET_TYPE_MENU)
MAP_COMMON(CommonAssetType::LOCALIZE_ENTRY, ASSET_TYPE_LOCALIZE_ENTRY)
MAP_COMMON(CommonAssetType::WEAPON, ASSET_TYPE_WEAPON)
MAP_COMMON(CommonAssetType::SOUND_DRIVER_GLOBALS, ASSET_TYPE_SNDDRIVER_GLOBALS)
MAP_COMMON(CommonAssetType::FX, ASSET_TYPE_FX)
MAP_COMMON(CommonAssetType::IMPACT_FX, ASSET_TYPE_IMPACT_FX)
MAP_COMMON(CommonAssetType::AI_TYPE, ASSET_TYPE_AITYPE)
MAP_COMMON(CommonAssetType::MP_TYPE, ASSET_TYPE_MPTYPE)
MAP_COMMON(CommonAssetType::CHARACTER, ASSET_TYPE_CHARACTER)
MAP_COMMON(CommonAssetType::XMODEL_ALIAS, ASSET_TYPE_XMODELALIAS)
MAP_COMMON(CommonAssetType::RAW_FILE, ASSET_TYPE_RAWFILE)
MAP_COMMON(CommonAssetType::STRING_TABLE, ASSET_TYPE_STRINGTABLE)
default:
return std::nullopt;
}
#undef MAP_COMMON
}
} // namespace IW3

View File

@@ -0,0 +1,15 @@
#pragma once
#include "Game/CommonAsset.h"
namespace IW3
{
class CommonAssetTypeMapper final : public ICommonAssetTypeMapper
{
public:
CommonAssetTypeMapper();
[[nodiscard]] CommonAssetType GameToCommonAssetType(asset_type_t gameAssetType) const override;
[[nodiscard]] std::optional<asset_type_t> CommonToGameAssetType(CommonAssetType commonAssetType) const override;
};
} // namespace IW3

View File

@@ -0,0 +1,120 @@
#include "CommonAssetIW4.h"
#include "IW4.h"
#include <cassert>
namespace IW4
{
CommonAssetTypeMapper::CommonAssetTypeMapper() = default;
CommonAssetType CommonAssetTypeMapper::GameToCommonAssetType(const asset_type_t gameAssetType) const
{
static CommonAssetType lookupTable[static_cast<unsigned>(ASSET_TYPE_COUNT)]{
CommonAssetType::PHYS_PRESET, // ASSET_TYPE_PHYSPRESET
CommonAssetType::PHYS_COLL_MAP, // ASSET_TYPE_PHYSCOLLMAP
CommonAssetType::XANIM, // ASSET_TYPE_XANIMPARTS
CommonAssetType::XMODEL_SURFS, // ASSET_TYPE_XMODEL_SURFS
CommonAssetType::XMODEL, // ASSET_TYPE_XMODEL
CommonAssetType::MATERIAL, // ASSET_TYPE_MATERIAL
CommonAssetType::PIXEL_SHADER, // ASSET_TYPE_PIXELSHADER
CommonAssetType::VERTEX_SHADER, // ASSET_TYPE_VERTEXSHADER
CommonAssetType::VERTEX_DECL, // ASSET_TYPE_VERTEXDECL
CommonAssetType::TECHNIQUE_SET, // ASSET_TYPE_TECHNIQUE_SET
CommonAssetType::IMAGE, // ASSET_TYPE_IMAGE
CommonAssetType::SOUND, // ASSET_TYPE_SOUND
CommonAssetType::SOUND_CURVE, // ASSET_TYPE_SOUND_CURVE
CommonAssetType::LOADED_SOUND, // ASSET_TYPE_LOADED_SOUND
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP_SP
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP_MP
CommonAssetType::COM_WORLD, // ASSET_TYPE_COMWORLD
CommonAssetType::GAME_WORLD_SP, // ASSET_TYPE_GAMEWORLD_SP
CommonAssetType::GAME_WORLD_MP, // ASSET_TYPE_GAMEWORLD_MP
CommonAssetType::MAP_ENTS, // ASSET_TYPE_MAP_ENTS
CommonAssetType::FX_WORLD, // ASSET_TYPE_FXWORLD
CommonAssetType::GFX_WORLD, // ASSET_TYPE_GFXWORLD
CommonAssetType::LIGHT_DEF, // ASSET_TYPE_LIGHT_DEF
CommonAssetType::UI_MAP, // ASSET_TYPE_UI_MAP
CommonAssetType::FONT, // ASSET_TYPE_FONT
CommonAssetType::MENU_LIST, // ASSET_TYPE_MENULIST
CommonAssetType::MENU, // ASSET_TYPE_MENU
CommonAssetType::LOCALIZE_ENTRY, // ASSET_TYPE_LOCALIZE_ENTRY
CommonAssetType::WEAPON, // ASSET_TYPE_WEAPON
CommonAssetType::SOUND_DRIVER_GLOBALS, // ASSET_TYPE_SNDDRIVER_GLOBALS
CommonAssetType::FX, // ASSET_TYPE_FX
CommonAssetType::IMPACT_FX, // ASSET_TYPE_IMPACT_FX
CommonAssetType::AI_TYPE, // ASSET_TYPE_AITYPE
CommonAssetType::MP_TYPE, // ASSET_TYPE_MPTYPE
CommonAssetType::CHARACTER, // ASSET_TYPE_CHARACTER
CommonAssetType::XMODEL_ALIAS, // ASSET_TYPE_XMODELALIAS
CommonAssetType::RAW_FILE, // ASSET_TYPE_RAWFILE
CommonAssetType::STRING_TABLE, // ASSET_TYPE_STRINGTABLE
CommonAssetType::LEADERBOARD, // ASSET_TYPE_LEADERBOARD
CommonAssetType::STRUCTURED_DATA_DEF, // ASSET_TYPE_STRUCTURED_DATA_DEF
CommonAssetType::TRACER, // ASSET_TYPE_TRACER
CommonAssetType::VEHICLE, // ASSET_TYPE_VEHICLE
CommonAssetType::ADDON_MAP_ENTS, // ASSET_TYPE_ADDON_MAP_ENTS
};
assert(gameAssetType < ASSET_TYPE_COUNT);
return lookupTable[gameAssetType];
}
std::optional<asset_type_t> CommonAssetTypeMapper::CommonToGameAssetType(const CommonAssetType commonAssetType) const
{
#define MAP_COMMON(common, game) \
case common: \
return game;
switch (commonAssetType)
{
MAP_COMMON(CommonAssetType::PHYS_PRESET, ASSET_TYPE_PHYSPRESET)
MAP_COMMON(CommonAssetType::PHYS_COLL_MAP, ASSET_TYPE_PHYSCOLLMAP)
MAP_COMMON(CommonAssetType::XANIM, ASSET_TYPE_XANIMPARTS)
MAP_COMMON(CommonAssetType::XMODEL_SURFS, ASSET_TYPE_XMODEL_SURFS)
MAP_COMMON(CommonAssetType::XMODEL, ASSET_TYPE_XMODEL)
MAP_COMMON(CommonAssetType::MATERIAL, ASSET_TYPE_MATERIAL)
MAP_COMMON(CommonAssetType::PIXEL_SHADER, ASSET_TYPE_PIXELSHADER)
MAP_COMMON(CommonAssetType::VERTEX_SHADER, ASSET_TYPE_VERTEXSHADER)
MAP_COMMON(CommonAssetType::VERTEX_DECL, ASSET_TYPE_VERTEXDECL)
MAP_COMMON(CommonAssetType::TECHNIQUE_SET, ASSET_TYPE_TECHNIQUE_SET)
MAP_COMMON(CommonAssetType::IMAGE, ASSET_TYPE_IMAGE)
MAP_COMMON(CommonAssetType::SOUND, ASSET_TYPE_SOUND)
MAP_COMMON(CommonAssetType::SOUND_CURVE, ASSET_TYPE_SOUND_CURVE)
MAP_COMMON(CommonAssetType::LOADED_SOUND, ASSET_TYPE_LOADED_SOUND)
MAP_COMMON(CommonAssetType::CLIP_MAP, ASSET_TYPE_CLIPMAP_MP)
MAP_COMMON(CommonAssetType::COM_WORLD, ASSET_TYPE_COMWORLD)
MAP_COMMON(CommonAssetType::GAME_WORLD_SP, ASSET_TYPE_GAMEWORLD_SP)
MAP_COMMON(CommonAssetType::GAME_WORLD_MP, ASSET_TYPE_GAMEWORLD_MP)
MAP_COMMON(CommonAssetType::MAP_ENTS, ASSET_TYPE_MAP_ENTS)
MAP_COMMON(CommonAssetType::FX_WORLD, ASSET_TYPE_FXWORLD)
MAP_COMMON(CommonAssetType::GFX_WORLD, ASSET_TYPE_GFXWORLD)
MAP_COMMON(CommonAssetType::LIGHT_DEF, ASSET_TYPE_LIGHT_DEF)
MAP_COMMON(CommonAssetType::UI_MAP, ASSET_TYPE_UI_MAP)
MAP_COMMON(CommonAssetType::FONT, ASSET_TYPE_FONT)
MAP_COMMON(CommonAssetType::MENU_LIST, ASSET_TYPE_MENULIST)
MAP_COMMON(CommonAssetType::MENU, ASSET_TYPE_MENU)
MAP_COMMON(CommonAssetType::LOCALIZE_ENTRY, ASSET_TYPE_LOCALIZE_ENTRY)
MAP_COMMON(CommonAssetType::WEAPON, ASSET_TYPE_WEAPON)
MAP_COMMON(CommonAssetType::SOUND_DRIVER_GLOBALS, ASSET_TYPE_SNDDRIVER_GLOBALS)
MAP_COMMON(CommonAssetType::FX, ASSET_TYPE_FX)
MAP_COMMON(CommonAssetType::IMPACT_FX, ASSET_TYPE_IMPACT_FX)
MAP_COMMON(CommonAssetType::AI_TYPE, ASSET_TYPE_AITYPE)
MAP_COMMON(CommonAssetType::MP_TYPE, ASSET_TYPE_MPTYPE)
MAP_COMMON(CommonAssetType::CHARACTER, ASSET_TYPE_CHARACTER)
MAP_COMMON(CommonAssetType::XMODEL_ALIAS, ASSET_TYPE_XMODELALIAS)
MAP_COMMON(CommonAssetType::RAW_FILE, ASSET_TYPE_RAWFILE)
MAP_COMMON(CommonAssetType::STRING_TABLE, ASSET_TYPE_STRINGTABLE)
MAP_COMMON(CommonAssetType::LEADERBOARD, ASSET_TYPE_LEADERBOARD)
MAP_COMMON(CommonAssetType::STRUCTURED_DATA_DEF, ASSET_TYPE_STRUCTURED_DATA_DEF)
MAP_COMMON(CommonAssetType::TRACER, ASSET_TYPE_TRACER)
MAP_COMMON(CommonAssetType::VEHICLE, ASSET_TYPE_VEHICLE)
MAP_COMMON(CommonAssetType::ADDON_MAP_ENTS, ASSET_TYPE_ADDON_MAP_ENTS)
default:
return std::nullopt;
}
#undef MAP_COMMON
}
} // namespace IW4

View File

@@ -0,0 +1,15 @@
#pragma once
#include "Game/CommonAsset.h"
namespace IW4
{
class CommonAssetTypeMapper final : public ICommonAssetTypeMapper
{
public:
CommonAssetTypeMapper();
[[nodiscard]] CommonAssetType GameToCommonAssetType(asset_type_t gameAssetType) const override;
[[nodiscard]] std::optional<asset_type_t> CommonToGameAssetType(CommonAssetType commonAssetType) const override;
};
} // namespace IW4

View File

@@ -0,0 +1,127 @@
#include "CommonAssetIW5.h"
#include "IW5.h"
#include <cassert>
namespace IW5
{
CommonAssetTypeMapper::CommonAssetTypeMapper() = default;
CommonAssetType CommonAssetTypeMapper::GameToCommonAssetType(const asset_type_t gameAssetType) const
{
static CommonAssetType lookupTable[static_cast<unsigned>(ASSET_TYPE_COUNT)]{
CommonAssetType::PHYS_PRESET, // ASSET_TYPE_PHYSPRESET
CommonAssetType::PHYS_COLL_MAP, // ASSET_TYPE_PHYSCOLLMAP
CommonAssetType::XANIM, // ASSET_TYPE_XANIMPARTS
CommonAssetType::XMODEL_SURFS, // ASSET_TYPE_XMODEL_SURFS
CommonAssetType::XMODEL, // ASSET_TYPE_XMODEL
CommonAssetType::MATERIAL, // ASSET_TYPE_MATERIAL
CommonAssetType::PIXEL_SHADER, // ASSET_TYPE_PIXELSHADER
CommonAssetType::VERTEX_SHADER, // ASSET_TYPE_VERTEXSHADER
CommonAssetType::VERTEX_DECL, // ASSET_TYPE_VERTEXDECL
CommonAssetType::TECHNIQUE_SET, // ASSET_TYPE_TECHNIQUE_SET
CommonAssetType::IMAGE, // ASSET_TYPE_IMAGE
CommonAssetType::SOUND, // ASSET_TYPE_SOUND
CommonAssetType::SOUND_CURVE, // ASSET_TYPE_SOUND_CURVE
CommonAssetType::LOADED_SOUND, // ASSET_TYPE_LOADED_SOUND
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP
CommonAssetType::COM_WORLD, // ASSET_TYPE_COMWORLD
CommonAssetType::GLASS_WORLD, // ASSET_TYPE_GLASSWORLD
CommonAssetType::PATH_DATA, // ASSET_TYPE_PATHDATA
CommonAssetType::VEHICLE_TRACK, // ASSET_TYPE_VEHICLE_TRACK
CommonAssetType::MAP_ENTS, // ASSET_TYPE_MAP_ENTS
CommonAssetType::FX_WORLD, // ASSET_TYPE_FXWORLD
CommonAssetType::GFX_WORLD, // ASSET_TYPE_GFXWORLD
CommonAssetType::LIGHT_DEF, // ASSET_TYPE_LIGHT_DEF
CommonAssetType::UI_MAP, // ASSET_TYPE_UI_MAP
CommonAssetType::FONT, // ASSET_TYPE_FONT
CommonAssetType::MENU_LIST, // ASSET_TYPE_MENULIST
CommonAssetType::MENU, // ASSET_TYPE_MENU
CommonAssetType::LOCALIZE_ENTRY, // ASSET_TYPE_LOCALIZE_ENTRY
CommonAssetType::ATTACHMENT, // ASSET_TYPE_ATTACHMENT
CommonAssetType::WEAPON, // ASSET_TYPE_WEAPON
CommonAssetType::SOUND_DRIVER_GLOBALS, // ASSET_TYPE_SNDDRIVER_GLOBALS
CommonAssetType::FX, // ASSET_TYPE_FX
CommonAssetType::IMPACT_FX, // ASSET_TYPE_IMPACT_FX
CommonAssetType::SURFACE_FX, // ASSET_TYPE_SURFACE_FX
CommonAssetType::AI_TYPE, // ASSET_TYPE_AITYPE
CommonAssetType::MP_TYPE, // ASSET_TYPE_MPTYPE
CommonAssetType::CHARACTER, // ASSET_TYPE_CHARACTER
CommonAssetType::XMODEL_ALIAS, // ASSET_TYPE_XMODELALIAS
CommonAssetType::RAW_FILE, // ASSET_TYPE_RAWFILE
CommonAssetType::SCRIPT, // ASSET_TYPE_SCRIPTFILE
CommonAssetType::STRING_TABLE, // ASSET_TYPE_STRINGTABLE
CommonAssetType::LEADERBOARD, // ASSET_TYPE_LEADERBOARD
CommonAssetType::STRUCTURED_DATA_DEF, // ASSET_TYPE_STRUCTURED_DATA_DEF
CommonAssetType::TRACER, // ASSET_TYPE_TRACER
CommonAssetType::VEHICLE, // ASSET_TYPE_VEHICLE
CommonAssetType::ADDON_MAP_ENTS, // ASSET_TYPE_ADDON_MAP_ENTS
};
assert(gameAssetType < ASSET_TYPE_COUNT);
return lookupTable[gameAssetType];
}
std::optional<asset_type_t> CommonAssetTypeMapper::CommonToGameAssetType(const CommonAssetType commonAssetType) const
{
#define MAP_COMMON(common, game) \
case common: \
return game;
switch (commonAssetType)
{
MAP_COMMON(CommonAssetType::PHYS_PRESET, ASSET_TYPE_PHYSPRESET)
MAP_COMMON(CommonAssetType::PHYS_COLL_MAP, ASSET_TYPE_PHYSCOLLMAP)
MAP_COMMON(CommonAssetType::XANIM, ASSET_TYPE_XANIMPARTS)
MAP_COMMON(CommonAssetType::XMODEL_SURFS, ASSET_TYPE_XMODEL_SURFS)
MAP_COMMON(CommonAssetType::XMODEL, ASSET_TYPE_XMODEL)
MAP_COMMON(CommonAssetType::MATERIAL, ASSET_TYPE_MATERIAL)
MAP_COMMON(CommonAssetType::PIXEL_SHADER, ASSET_TYPE_PIXELSHADER)
MAP_COMMON(CommonAssetType::VERTEX_SHADER, ASSET_TYPE_VERTEXSHADER)
MAP_COMMON(CommonAssetType::VERTEX_DECL, ASSET_TYPE_VERTEXDECL)
MAP_COMMON(CommonAssetType::TECHNIQUE_SET, ASSET_TYPE_TECHNIQUE_SET)
MAP_COMMON(CommonAssetType::IMAGE, ASSET_TYPE_IMAGE)
MAP_COMMON(CommonAssetType::SOUND, ASSET_TYPE_SOUND)
MAP_COMMON(CommonAssetType::SOUND_CURVE, ASSET_TYPE_SOUND_CURVE)
MAP_COMMON(CommonAssetType::LOADED_SOUND, ASSET_TYPE_LOADED_SOUND)
MAP_COMMON(CommonAssetType::CLIP_MAP, ASSET_TYPE_CLIPMAP)
MAP_COMMON(CommonAssetType::COM_WORLD, ASSET_TYPE_COMWORLD)
MAP_COMMON(CommonAssetType::GLASS_WORLD, ASSET_TYPE_GLASSWORLD)
MAP_COMMON(CommonAssetType::PATH_DATA, ASSET_TYPE_PATHDATA)
MAP_COMMON(CommonAssetType::VEHICLE_TRACK, ASSET_TYPE_VEHICLE_TRACK)
MAP_COMMON(CommonAssetType::MAP_ENTS, ASSET_TYPE_MAP_ENTS)
MAP_COMMON(CommonAssetType::FX_WORLD, ASSET_TYPE_FXWORLD)
MAP_COMMON(CommonAssetType::GFX_WORLD, ASSET_TYPE_GFXWORLD)
MAP_COMMON(CommonAssetType::LIGHT_DEF, ASSET_TYPE_LIGHT_DEF)
MAP_COMMON(CommonAssetType::UI_MAP, ASSET_TYPE_UI_MAP)
MAP_COMMON(CommonAssetType::FONT, ASSET_TYPE_FONT)
MAP_COMMON(CommonAssetType::MENU_LIST, ASSET_TYPE_MENULIST)
MAP_COMMON(CommonAssetType::MENU, ASSET_TYPE_MENU)
MAP_COMMON(CommonAssetType::LOCALIZE_ENTRY, ASSET_TYPE_LOCALIZE_ENTRY)
MAP_COMMON(CommonAssetType::ATTACHMENT, ASSET_TYPE_ATTACHMENT)
MAP_COMMON(CommonAssetType::WEAPON, ASSET_TYPE_WEAPON)
MAP_COMMON(CommonAssetType::SOUND_DRIVER_GLOBALS, ASSET_TYPE_SNDDRIVER_GLOBALS)
MAP_COMMON(CommonAssetType::FX, ASSET_TYPE_FX)
MAP_COMMON(CommonAssetType::IMPACT_FX, ASSET_TYPE_IMPACT_FX)
MAP_COMMON(CommonAssetType::SURFACE_FX, ASSET_TYPE_SURFACE_FX)
MAP_COMMON(CommonAssetType::AI_TYPE, ASSET_TYPE_AITYPE)
MAP_COMMON(CommonAssetType::MP_TYPE, ASSET_TYPE_MPTYPE)
MAP_COMMON(CommonAssetType::CHARACTER, ASSET_TYPE_CHARACTER)
MAP_COMMON(CommonAssetType::XMODEL_ALIAS, ASSET_TYPE_XMODELALIAS)
MAP_COMMON(CommonAssetType::RAW_FILE, ASSET_TYPE_RAWFILE)
MAP_COMMON(CommonAssetType::SCRIPT, ASSET_TYPE_SCRIPTFILE)
MAP_COMMON(CommonAssetType::STRING_TABLE, ASSET_TYPE_STRINGTABLE)
MAP_COMMON(CommonAssetType::LEADERBOARD, ASSET_TYPE_LEADERBOARD)
MAP_COMMON(CommonAssetType::STRUCTURED_DATA_DEF, ASSET_TYPE_STRUCTURED_DATA_DEF)
MAP_COMMON(CommonAssetType::TRACER, ASSET_TYPE_TRACER)
MAP_COMMON(CommonAssetType::VEHICLE, ASSET_TYPE_VEHICLE)
MAP_COMMON(CommonAssetType::ADDON_MAP_ENTS, ASSET_TYPE_ADDON_MAP_ENTS)
default:
return std::nullopt;
}
#undef MAP_COMMON
}
} // namespace IW5

View File

@@ -0,0 +1,15 @@
#pragma once
#include "Game/CommonAsset.h"
namespace IW5
{
class CommonAssetTypeMapper final : public ICommonAssetTypeMapper
{
public:
CommonAssetTypeMapper();
[[nodiscard]] CommonAssetType GameToCommonAssetType(asset_type_t gameAssetType) const override;
[[nodiscard]] std::optional<asset_type_t> CommonToGameAssetType(CommonAssetType commonAssetType) const override;
};
} // namespace IW5

View File

@@ -0,0 +1,120 @@
#include "CommonAssetT5.h"
#include "T5.h"
#include <cassert>
namespace T5
{
CommonAssetTypeMapper::CommonAssetTypeMapper() = default;
CommonAssetType CommonAssetTypeMapper::GameToCommonAssetType(const asset_type_t gameAssetType) const
{
static CommonAssetType lookupTable[static_cast<unsigned>(ASSET_TYPE_COUNT)]{
CommonAssetType::XMODEL_PIECES, // ASSET_TYPE_XMODELPIECES
CommonAssetType::PHYS_PRESET, // ASSET_TYPE_PHYSPRESET
CommonAssetType::PHYS_CONSTRAINTS, // ASSET_TYPE_PHYSCONSTRAINTS
CommonAssetType::DESTRUCTIBLE_DEF, // ASSET_TYPE_DESTRUCTIBLEDEF
CommonAssetType::XANIM, // ASSET_TYPE_XANIMPARTS
CommonAssetType::XMODEL, // ASSET_TYPE_XMODEL
CommonAssetType::MATERIAL, // ASSET_TYPE_MATERIAL
CommonAssetType::TECHNIQUE_SET, // ASSET_TYPE_TECHNIQUE_SET
CommonAssetType::IMAGE, // ASSET_TYPE_IMAGE
CommonAssetType::SOUND, // ASSET_TYPE_SOUND
CommonAssetType::SOUND_PATCH, // ASSET_TYPE_SOUND_PATCH
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP_PVS
CommonAssetType::COM_WORLD, // ASSET_TYPE_COMWORLD
CommonAssetType::GAME_WORLD_SP, // ASSET_TYPE_GAMEWORLD_SP
CommonAssetType::GAME_WORLD_MP, // ASSET_TYPE_GAMEWORLD_MP
CommonAssetType::MAP_ENTS, // ASSET_TYPE_MAP_ENTS
CommonAssetType::GFX_WORLD, // ASSET_TYPE_GFXWORLD
CommonAssetType::LIGHT_DEF, // ASSET_TYPE_LIGHT_DEF
CommonAssetType::UI_MAP, // ASSET_TYPE_UI_MAP
CommonAssetType::FONT, // ASSET_TYPE_FONT
CommonAssetType::MENU_LIST, // ASSET_TYPE_MENULIST
CommonAssetType::MENU, // ASSET_TYPE_MENU
CommonAssetType::LOCALIZE_ENTRY, // ASSET_TYPE_LOCALIZE_ENTRY
CommonAssetType::WEAPON, // ASSET_TYPE_WEAPON
CommonAssetType::WEAPON_DEF, // ASSET_TYPE_WEAPONDEF
CommonAssetType::WEAPON_VARIANT, // ASSET_TYPE_WEAPON_VARIANT
CommonAssetType::SOUND_DRIVER_GLOBALS, // ASSET_TYPE_SNDDRIVER_GLOBALS
CommonAssetType::FX, // ASSET_TYPE_FX
CommonAssetType::IMPACT_FX, // ASSET_TYPE_IMPACT_FX
CommonAssetType::AI_TYPE, // ASSET_TYPE_AITYPE
CommonAssetType::MP_TYPE, // ASSET_TYPE_MPTYPE
CommonAssetType::MP_BODY, // ASSET_TYPE_MPBODY
CommonAssetType::MP_HEAD, // ASSET_TYPE_MPHEAD
CommonAssetType::CHARACTER, // ASSET_TYPE_CHARACTER
CommonAssetType::XMODEL_ALIAS, // ASSET_TYPE_XMODELALIAS
CommonAssetType::RAW_FILE, // ASSET_TYPE_RAWFILE
CommonAssetType::STRING_TABLE, // ASSET_TYPE_STRINGTABLE
CommonAssetType::PACK_INDEX, // ASSET_TYPE_PACK_INDEX
CommonAssetType::XGLOBALS, // ASSET_TYPE_XGLOBALS
CommonAssetType::DDL, // ASSET_TYPE_DDL
CommonAssetType::GLASSES, // ASSET_TYPE_GLASSES
CommonAssetType::EMBLEM_SET, // ASSET_TYPE_EMBLEMSET
};
assert(gameAssetType < ASSET_TYPE_COUNT);
return lookupTable[gameAssetType];
}
std::optional<asset_type_t> CommonAssetTypeMapper::CommonToGameAssetType(const CommonAssetType commonAssetType) const
{
#define MAP_COMMON(common, game) \
case common: \
return game;
switch (commonAssetType)
{
MAP_COMMON(CommonAssetType::XMODEL_PIECES, ASSET_TYPE_XMODELPIECES)
MAP_COMMON(CommonAssetType::PHYS_PRESET, ASSET_TYPE_PHYSPRESET)
MAP_COMMON(CommonAssetType::PHYS_CONSTRAINTS, ASSET_TYPE_PHYSCONSTRAINTS)
MAP_COMMON(CommonAssetType::DESTRUCTIBLE_DEF, ASSET_TYPE_DESTRUCTIBLEDEF)
MAP_COMMON(CommonAssetType::XANIM, ASSET_TYPE_XANIMPARTS)
MAP_COMMON(CommonAssetType::XMODEL, ASSET_TYPE_XMODEL)
MAP_COMMON(CommonAssetType::MATERIAL, ASSET_TYPE_MATERIAL)
MAP_COMMON(CommonAssetType::TECHNIQUE_SET, ASSET_TYPE_TECHNIQUE_SET)
MAP_COMMON(CommonAssetType::IMAGE, ASSET_TYPE_IMAGE)
MAP_COMMON(CommonAssetType::SOUND, ASSET_TYPE_SOUND)
MAP_COMMON(CommonAssetType::SOUND_PATCH, ASSET_TYPE_SOUND_PATCH)
MAP_COMMON(CommonAssetType::CLIP_MAP, ASSET_TYPE_CLIPMAP_PVS)
MAP_COMMON(CommonAssetType::COM_WORLD, ASSET_TYPE_COMWORLD)
MAP_COMMON(CommonAssetType::GAME_WORLD_SP, ASSET_TYPE_GAMEWORLD_SP)
MAP_COMMON(CommonAssetType::GAME_WORLD_MP, ASSET_TYPE_GAMEWORLD_MP)
MAP_COMMON(CommonAssetType::MAP_ENTS, ASSET_TYPE_MAP_ENTS)
MAP_COMMON(CommonAssetType::GFX_WORLD, ASSET_TYPE_GFXWORLD)
MAP_COMMON(CommonAssetType::LIGHT_DEF, ASSET_TYPE_LIGHT_DEF)
MAP_COMMON(CommonAssetType::UI_MAP, ASSET_TYPE_UI_MAP)
MAP_COMMON(CommonAssetType::FONT, ASSET_TYPE_FONT)
MAP_COMMON(CommonAssetType::MENU_LIST, ASSET_TYPE_MENULIST)
MAP_COMMON(CommonAssetType::MENU, ASSET_TYPE_MENU)
MAP_COMMON(CommonAssetType::LOCALIZE_ENTRY, ASSET_TYPE_LOCALIZE_ENTRY)
MAP_COMMON(CommonAssetType::WEAPON, ASSET_TYPE_WEAPON)
MAP_COMMON(CommonAssetType::WEAPON_DEF, ASSET_TYPE_WEAPONDEF)
MAP_COMMON(CommonAssetType::WEAPON_VARIANT, ASSET_TYPE_WEAPON_VARIANT)
MAP_COMMON(CommonAssetType::SOUND_DRIVER_GLOBALS, ASSET_TYPE_SNDDRIVER_GLOBALS)
MAP_COMMON(CommonAssetType::FX, ASSET_TYPE_FX)
MAP_COMMON(CommonAssetType::IMPACT_FX, ASSET_TYPE_IMPACT_FX)
MAP_COMMON(CommonAssetType::AI_TYPE, ASSET_TYPE_AITYPE)
MAP_COMMON(CommonAssetType::MP_TYPE, ASSET_TYPE_MPTYPE)
MAP_COMMON(CommonAssetType::MP_BODY, ASSET_TYPE_MPBODY)
MAP_COMMON(CommonAssetType::MP_HEAD, ASSET_TYPE_MPHEAD)
MAP_COMMON(CommonAssetType::CHARACTER, ASSET_TYPE_CHARACTER)
MAP_COMMON(CommonAssetType::XMODEL_ALIAS, ASSET_TYPE_XMODELALIAS)
MAP_COMMON(CommonAssetType::RAW_FILE, ASSET_TYPE_RAWFILE)
MAP_COMMON(CommonAssetType::STRING_TABLE, ASSET_TYPE_STRINGTABLE)
MAP_COMMON(CommonAssetType::PACK_INDEX, ASSET_TYPE_PACK_INDEX)
MAP_COMMON(CommonAssetType::XGLOBALS, ASSET_TYPE_XGLOBALS)
MAP_COMMON(CommonAssetType::DDL, ASSET_TYPE_DDL)
MAP_COMMON(CommonAssetType::GLASSES, ASSET_TYPE_GLASSES)
MAP_COMMON(CommonAssetType::EMBLEM_SET, ASSET_TYPE_EMBLEMSET)
default:
return std::nullopt;
}
#undef MAP_COMMON
}
} // namespace T5

View File

@@ -0,0 +1,15 @@
#pragma once
#include "Game/CommonAsset.h"
namespace T5
{
class CommonAssetTypeMapper final : public ICommonAssetTypeMapper
{
public:
CommonAssetTypeMapper();
[[nodiscard]] CommonAssetType GameToCommonAssetType(asset_type_t gameAssetType) const override;
[[nodiscard]] std::optional<asset_type_t> CommonToGameAssetType(CommonAssetType commonAssetType) const override;
};
} // namespace T5

View File

@@ -0,0 +1,154 @@
#include "CommonAssetT6.h"
#include "T6.h"
#include <cassert>
namespace T6
{
CommonAssetTypeMapper::CommonAssetTypeMapper() = default;
CommonAssetType CommonAssetTypeMapper::GameToCommonAssetType(const asset_type_t gameAssetType) const
{
static CommonAssetType lookupTable[static_cast<unsigned>(ASSET_TYPE_COUNT)]{
CommonAssetType::XMODEL_PIECES, // ASSET_TYPE_XMODELPIECES
CommonAssetType::PHYS_PRESET, // ASSET_TYPE_PHYSPRESET
CommonAssetType::PHYS_CONSTRAINTS, // ASSET_TYPE_PHYSCONSTRAINTS
CommonAssetType::DESTRUCTIBLE_DEF, // ASSET_TYPE_DESTRUCTIBLEDEF
CommonAssetType::XANIM, // ASSET_TYPE_XANIMPARTS
CommonAssetType::XMODEL, // ASSET_TYPE_XMODEL
CommonAssetType::MATERIAL, // ASSET_TYPE_MATERIAL
CommonAssetType::TECHNIQUE_SET, // ASSET_TYPE_TECHNIQUE_SET
CommonAssetType::IMAGE, // ASSET_TYPE_IMAGE
CommonAssetType::SOUND, // ASSET_TYPE_SOUND
CommonAssetType::SOUND_PATCH, // ASSET_TYPE_SOUND_PATCH
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP
CommonAssetType::CLIP_MAP, // ASSET_TYPE_CLIPMAP_PVS
CommonAssetType::COM_WORLD, // ASSET_TYPE_COMWORLD
CommonAssetType::GAME_WORLD_SP, // ASSET_TYPE_GAMEWORLD_SP
CommonAssetType::GAME_WORLD_MP, // ASSET_TYPE_GAMEWORLD_MP
CommonAssetType::MAP_ENTS, // ASSET_TYPE_MAP_ENTS
CommonAssetType::GFX_WORLD, // ASSET_TYPE_GFXWORLD
CommonAssetType::LIGHT_DEF, // ASSET_TYPE_LIGHT_DEF
CommonAssetType::UI_MAP, // ASSET_TYPE_UI_MAP
CommonAssetType::FONT, // ASSET_TYPE_FONT
CommonAssetType::FONT_ICON, // ASSET_TYPE_FONTICON
CommonAssetType::MENU_LIST, // ASSET_TYPE_MENULIST
CommonAssetType::MENU, // ASSET_TYPE_MENU
CommonAssetType::LOCALIZE_ENTRY, // ASSET_TYPE_LOCALIZE_ENTRY
CommonAssetType::WEAPON, // ASSET_TYPE_WEAPON
CommonAssetType::WEAPON_DEF, // ASSET_TYPE_WEAPONDEF
CommonAssetType::WEAPON_VARIANT, // ASSET_TYPE_WEAPON_VARIANT
CommonAssetType::WEAPON_FULL, // ASSET_TYPE_WEAPON_FULL
CommonAssetType::ATTACHMENT, // ASSET_TYPE_ATTACHMENT
CommonAssetType::ATTACHMENT_UNIQUE, // ASSET_TYPE_ATTACHMENT_UNIQUE
CommonAssetType::WEAPON_CAMO, // ASSET_TYPE_WEAPON_CAMO
CommonAssetType::SOUND_DRIVER_GLOBALS, // ASSET_TYPE_SNDDRIVER_GLOBALS
CommonAssetType::FX, // ASSET_TYPE_FX
CommonAssetType::IMPACT_FX, // ASSET_TYPE_IMPACT_FX
CommonAssetType::AI_TYPE, // ASSET_TYPE_AITYPE
CommonAssetType::MP_TYPE, // ASSET_TYPE_MPTYPE
CommonAssetType::MP_BODY, // ASSET_TYPE_MPBODY
CommonAssetType::MP_HEAD, // ASSET_TYPE_MPHEAD
CommonAssetType::CHARACTER, // ASSET_TYPE_CHARACTER
CommonAssetType::XMODEL_ALIAS, // ASSET_TYPE_XMODELALIAS
CommonAssetType::RAW_FILE, // ASSET_TYPE_RAWFILE
CommonAssetType::STRING_TABLE, // ASSET_TYPE_STRINGTABLE
CommonAssetType::LEADERBOARD, // ASSET_TYPE_LEADERBOARD
CommonAssetType::XGLOBALS, // ASSET_TYPE_XGLOBALS
CommonAssetType::DDL, // ASSET_TYPE_DDL
CommonAssetType::GLASSES, // ASSET_TYPE_GLASSES
CommonAssetType::EMBLEM_SET, // ASSET_TYPE_EMBLEMSET
CommonAssetType::SCRIPT, // ASSET_TYPE_SCRIPTPARSETREE
CommonAssetType::KEY_VALUE_PAIRS, // ASSET_TYPE_KEYVALUEPAIRS
CommonAssetType::VEHICLE, // ASSET_TYPE_VEHICLEDEF
CommonAssetType::MEMORY_BLOCK, // ASSET_TYPE_MEMORYBLOCK
CommonAssetType::ADDON_MAP_ENTS, // ASSET_TYPE_ADDON_MAP_ENTS
CommonAssetType::TRACER, // ASSET_TYPE_TRACER
CommonAssetType::SKINNED_VERTS, // ASSET_TYPE_SKINNEDVERTS
CommonAssetType::QDB, // ASSET_TYPE_QDB
CommonAssetType::SLUG, // ASSET_TYPE_SLUG
CommonAssetType::FOOTSTEP_TABLE, // ASSET_TYPE_FOOTSTEP_TABLE
CommonAssetType::FOOTSTEP_FX_TABLE, // ASSET_TYPE_FOOTSTEPFX_TABLE
CommonAssetType::ZBARRIER, // ASSET_TYPE_ZBARRIER
};
assert(gameAssetType < ASSET_TYPE_COUNT);
return lookupTable[gameAssetType];
}
std::optional<asset_type_t> CommonAssetTypeMapper::CommonToGameAssetType(const CommonAssetType commonAssetType) const
{
#define MAP_COMMON(common, game) \
case common: \
return game;
switch (commonAssetType)
{
MAP_COMMON(CommonAssetType::XMODEL_PIECES, ASSET_TYPE_XMODELPIECES)
MAP_COMMON(CommonAssetType::PHYS_PRESET, ASSET_TYPE_PHYSPRESET)
MAP_COMMON(CommonAssetType::PHYS_CONSTRAINTS, ASSET_TYPE_PHYSCONSTRAINTS)
MAP_COMMON(CommonAssetType::DESTRUCTIBLE_DEF, ASSET_TYPE_DESTRUCTIBLEDEF)
MAP_COMMON(CommonAssetType::XANIM, ASSET_TYPE_XANIMPARTS)
MAP_COMMON(CommonAssetType::XMODEL, ASSET_TYPE_XMODEL)
MAP_COMMON(CommonAssetType::MATERIAL, ASSET_TYPE_MATERIAL)
MAP_COMMON(CommonAssetType::TECHNIQUE_SET, ASSET_TYPE_TECHNIQUE_SET)
MAP_COMMON(CommonAssetType::IMAGE, ASSET_TYPE_IMAGE)
MAP_COMMON(CommonAssetType::SOUND, ASSET_TYPE_SOUND)
MAP_COMMON(CommonAssetType::SOUND_PATCH, ASSET_TYPE_SOUND_PATCH)
MAP_COMMON(CommonAssetType::CLIP_MAP, ASSET_TYPE_CLIPMAP_PVS)
MAP_COMMON(CommonAssetType::COM_WORLD, ASSET_TYPE_COMWORLD)
MAP_COMMON(CommonAssetType::GAME_WORLD_SP, ASSET_TYPE_GAMEWORLD_SP)
MAP_COMMON(CommonAssetType::GAME_WORLD_MP, ASSET_TYPE_GAMEWORLD_MP)
MAP_COMMON(CommonAssetType::MAP_ENTS, ASSET_TYPE_MAP_ENTS)
MAP_COMMON(CommonAssetType::GFX_WORLD, ASSET_TYPE_GFXWORLD)
MAP_COMMON(CommonAssetType::LIGHT_DEF, ASSET_TYPE_LIGHT_DEF)
MAP_COMMON(CommonAssetType::UI_MAP, ASSET_TYPE_UI_MAP)
MAP_COMMON(CommonAssetType::FONT, ASSET_TYPE_FONT)
MAP_COMMON(CommonAssetType::FONT_ICON, ASSET_TYPE_FONTICON)
MAP_COMMON(CommonAssetType::MENU_LIST, ASSET_TYPE_MENULIST)
MAP_COMMON(CommonAssetType::MENU, ASSET_TYPE_MENU)
MAP_COMMON(CommonAssetType::LOCALIZE_ENTRY, ASSET_TYPE_LOCALIZE_ENTRY)
MAP_COMMON(CommonAssetType::WEAPON, ASSET_TYPE_WEAPON)
MAP_COMMON(CommonAssetType::WEAPON_DEF, ASSET_TYPE_WEAPONDEF)
MAP_COMMON(CommonAssetType::WEAPON_VARIANT, ASSET_TYPE_WEAPON_VARIANT)
MAP_COMMON(CommonAssetType::WEAPON_FULL, ASSET_TYPE_WEAPON_FULL)
MAP_COMMON(CommonAssetType::ATTACHMENT, ASSET_TYPE_ATTACHMENT)
MAP_COMMON(CommonAssetType::ATTACHMENT_UNIQUE, ASSET_TYPE_ATTACHMENT_UNIQUE)
MAP_COMMON(CommonAssetType::WEAPON_CAMO, ASSET_TYPE_WEAPON_CAMO)
MAP_COMMON(CommonAssetType::SOUND_DRIVER_GLOBALS, ASSET_TYPE_SNDDRIVER_GLOBALS)
MAP_COMMON(CommonAssetType::FX, ASSET_TYPE_FX)
MAP_COMMON(CommonAssetType::IMPACT_FX, ASSET_TYPE_IMPACT_FX)
MAP_COMMON(CommonAssetType::AI_TYPE, ASSET_TYPE_AITYPE)
MAP_COMMON(CommonAssetType::MP_TYPE, ASSET_TYPE_MPTYPE)
MAP_COMMON(CommonAssetType::MP_BODY, ASSET_TYPE_MPBODY)
MAP_COMMON(CommonAssetType::MP_HEAD, ASSET_TYPE_MPHEAD)
MAP_COMMON(CommonAssetType::CHARACTER, ASSET_TYPE_CHARACTER)
MAP_COMMON(CommonAssetType::XMODEL_ALIAS, ASSET_TYPE_XMODELALIAS)
MAP_COMMON(CommonAssetType::RAW_FILE, ASSET_TYPE_RAWFILE)
MAP_COMMON(CommonAssetType::STRING_TABLE, ASSET_TYPE_STRINGTABLE)
MAP_COMMON(CommonAssetType::LEADERBOARD, ASSET_TYPE_LEADERBOARD)
MAP_COMMON(CommonAssetType::XGLOBALS, ASSET_TYPE_XGLOBALS)
MAP_COMMON(CommonAssetType::DDL, ASSET_TYPE_DDL)
MAP_COMMON(CommonAssetType::GLASSES, ASSET_TYPE_GLASSES)
MAP_COMMON(CommonAssetType::EMBLEM_SET, ASSET_TYPE_EMBLEMSET)
MAP_COMMON(CommonAssetType::SCRIPT, ASSET_TYPE_SCRIPTPARSETREE)
MAP_COMMON(CommonAssetType::KEY_VALUE_PAIRS, ASSET_TYPE_KEYVALUEPAIRS)
MAP_COMMON(CommonAssetType::VEHICLE, ASSET_TYPE_VEHICLEDEF)
MAP_COMMON(CommonAssetType::MEMORY_BLOCK, ASSET_TYPE_MEMORYBLOCK)
MAP_COMMON(CommonAssetType::ADDON_MAP_ENTS, ASSET_TYPE_ADDON_MAP_ENTS)
MAP_COMMON(CommonAssetType::TRACER, ASSET_TYPE_TRACER)
MAP_COMMON(CommonAssetType::SKINNED_VERTS, ASSET_TYPE_SKINNEDVERTS)
MAP_COMMON(CommonAssetType::QDB, ASSET_TYPE_QDB)
MAP_COMMON(CommonAssetType::SLUG, ASSET_TYPE_SLUG)
MAP_COMMON(CommonAssetType::FOOTSTEP_TABLE, ASSET_TYPE_FOOTSTEP_TABLE)
MAP_COMMON(CommonAssetType::FOOTSTEP_FX_TABLE, ASSET_TYPE_FOOTSTEPFX_TABLE)
MAP_COMMON(CommonAssetType::ZBARRIER, ASSET_TYPE_ZBARRIER)
default:
return std::nullopt;
}
#undef MAP_COMMON
}
} // namespace T6

View File

@@ -0,0 +1,15 @@
#pragma once
#include "Game/CommonAsset.h"
namespace T6
{
class CommonAssetTypeMapper final : public ICommonAssetTypeMapper
{
public:
CommonAssetTypeMapper();
[[nodiscard]] CommonAssetType GameToCommonAssetType(asset_type_t gameAssetType) const override;
[[nodiscard]] std::optional<asset_type_t> CommonToGameAssetType(CommonAssetType commonAssetType) const override;
};
} // namespace T6

View File

@@ -0,0 +1,12 @@
#pragma once
#include <cstdlib>
class ProgressCallback
{
public:
ProgressCallback() = default;
virtual ~ProgressCallback() = default;
virtual void OnProgress(size_t current, size_t total) = 0;
};

View File

@@ -363,14 +363,16 @@ class LinkerImpl final : public Linker
zoneDirectory = fs::current_path();
auto absoluteZoneDirectory = absolute(zoneDirectory).string();
auto zone = ZoneLoading::LoadZone(zonePath);
if (!zone)
auto maybeZone = ZoneLoading::LoadZone(zonePath, std::nullopt);
if (!maybeZone)
{
con::error("Failed to load zone \"{}\".", zonePath);
con::error("Failed to load zone \"{}\": {}", zonePath, maybeZone.error());
return false;
}
con::debug("Load zone \"{}\"", zone->m_name);
auto zone = std::move(*maybeZone);
con::debug("Loaded zone \"{}\"", zone->m_name);
m_loaded_zones.emplace_back(std::move(zone));
}

View File

@@ -11,7 +11,7 @@ namespace
{
std::unique_ptr<Zone> CreateZone(const ZoneCreationContext& context, const GameId gameId)
{
return std::make_unique<Zone>(context.m_definition->m_name, 0, gameId);
return std::make_unique<Zone>(context.m_definition->m_name, 0, gameId, GamePlatform::PC);
}
std::vector<Gdt*> CreateGdtList(const ZoneCreationContext& context)

View File

@@ -36,6 +36,12 @@ function ModMan:project()
path.join(folder, "ModMan/**.cpp")
}
filter { "system:windows" }
files {
path.join(folder, "ModMan/**.rc")
}
filter {}
includedirs {
"%{wks.location}/src/ModMan"
}
@@ -47,10 +53,16 @@ function ModMan:project()
self:include(includes)
Utils:include(includes)
ZoneLoading:include(includes)
ObjLoading:include(includes)
ObjWriting:include(includes)
json:include(includes)
webview:include(includes)
links:linkto(Utils)
links:linkto(ZoneLoading)
links:linkto(ObjLoading)
links:linkto(ObjWriting)
links:linkto(webview)
links:linkall()
end

View File

@@ -0,0 +1,91 @@
#include "FastFileContext.h"
#include "Web/Binds/ZoneBinds.h"
#include "Web/UiCommunication.h"
#include "ZoneLoading.h"
#include <filesystem>
namespace fs = std::filesystem;
namespace
{
constexpr double MIN_PROGRESS_TO_REPORT = 0.5;
class LoadingEventProgressReporter : public ProgressCallback
{
public:
explicit LoadingEventProgressReporter(std::string zoneName)
: m_zone_name(std::move(zoneName)),
m_last_progress(0)
{
}
void OnProgress(const size_t current, const size_t total) override
{
const double percentage = static_cast<double>(current) / static_cast<double>(total) * 100.0;
if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT)
{
m_last_progress = percentage;
ui::NotifyZoneLoadProgress(m_zone_name, percentage);
}
}
private:
std::string m_zone_name;
double m_last_progress;
};
} // namespace
LoadedZone::LoadedZone(std::unique_ptr<Zone> zone, std::string filePath)
: m_zone(std::move(zone)),
m_file_path(std::move(filePath))
{
}
void FastFileContext::Destroy()
{
// Unload all zones
m_loaded_zones.clear();
}
result::Expected<LoadedZone*, std::string> FastFileContext::LoadFastFile(const std::string& path)
{
auto zone = ZoneLoading::LoadZone(path, std::make_unique<LoadingEventProgressReporter>(fs::path(path).filename().replace_extension().string()));
if (!zone)
return result::Unexpected(std::move(zone.error()));
auto loadedZone = std::make_unique<LoadedZone>(std::move(*zone), path);
LoadedZone* result;
{
std::lock_guard lock(m_zone_lock);
result = m_loaded_zones.emplace_back(std::move(loadedZone)).get();
}
ui::NotifyZoneLoaded(*result);
return result;
}
result::Expected<NoResult, std::string> FastFileContext::UnloadZone(const std::string& zoneName)
{
{
std::lock_guard lock(m_zone_lock);
const auto existingZone = std::ranges::find_if(m_loaded_zones,
[&zoneName](const std::unique_ptr<LoadedZone>& loadedZone)
{
return loadedZone->m_zone->m_name == zoneName;
});
if (existingZone != m_loaded_zones.end())
{
m_loaded_zones.erase(existingZone);
ui::NotifyZoneUnloaded(zoneName);
return NoResult();
}
}
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
}

View File

@@ -0,0 +1,29 @@
#pragma once
#include "Utils/Result.h"
#include "Zone/Zone.h"
#include <memory>
#include <shared_mutex>
#include <vector>
class LoadedZone
{
public:
std::unique_ptr<Zone> m_zone;
std::string m_file_path;
LoadedZone(std::unique_ptr<Zone> zone, std::string filePath);
};
class FastFileContext
{
public:
void Destroy();
result::Expected<LoadedZone*, std::string> LoadFastFile(const std::string& path);
result::Expected<NoResult, std::string> UnloadZone(const std::string& zoneName);
std::vector<std::unique_ptr<LoadedZone>> m_loaded_zones;
std::shared_mutex m_zone_lock;
};

View File

@@ -0,0 +1,18 @@
#include "ModManContext.h"
ModManContext& ModManContext::Get()
{
static ModManContext context;
return context;
}
void ModManContext::Startup()
{
m_db_thread.Start();
}
void ModManContext::Destroy()
{
m_fast_file.Destroy();
m_db_thread.Terminate();
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "FastFileContext.h"
#include "Utils/DispatchableThread.h"
#include "Web/WebViewLib.h"
#include <memory>
class ModManContext
{
public:
static ModManContext& Get();
void Startup();
void Destroy();
std::unique_ptr<webview::webview> m_main_webview;
std::unique_ptr<webview::webview> m_dev_tools_webview;
FastFileContext m_fast_file;
DispatchableThread m_db_thread;
};

BIN
src/ModMan/ModMan.rc Normal file

Binary file not shown.

105
src/ModMan/ModManArgs.cpp Normal file
View File

@@ -0,0 +1,105 @@
#include "ModManArgs.h"
#include "GitVersion.h"
#include "Utils/Arguments/UsageInformation.h"
#include "Utils/Logging/Log.h"
#include <format>
#include <type_traits>
namespace
{
// clang-format off
const CommandLineOption* const OPTION_HELP =
CommandLineOption::Builder::Create()
.WithShortName("?")
.WithLongName("help")
.WithDescription("Displays usage information.")
.Build();
const CommandLineOption* const OPTION_VERSION =
CommandLineOption::Builder::Create()
.WithLongName("version")
.WithDescription("Prints the application version.")
.Build();
const CommandLineOption* const OPTION_VERBOSE =
CommandLineOption::Builder::Create()
.WithShortName("v")
.WithLongName("verbose")
.WithDescription("Outputs a lot more and more detailed messages.")
.Build();
const CommandLineOption* const OPTION_NO_COLOR =
CommandLineOption::Builder::Create()
.WithLongName("no-color")
.WithDescription("Disables colored terminal output.")
.Build();
// clang-format on
const CommandLineOption* const COMMAND_LINE_OPTIONS[]{
OPTION_HELP,
OPTION_VERSION,
OPTION_VERBOSE,
OPTION_NO_COLOR,
};
} // namespace
ModManArgs::ModManArgs()
: m_argument_parser(COMMAND_LINE_OPTIONS, std::extent_v<decltype(COMMAND_LINE_OPTIONS)>)
{
}
void ModManArgs::PrintUsage() const
{
UsageInformation usage(m_argument_parser.GetExecutableName());
for (const auto* commandLineOption : COMMAND_LINE_OPTIONS)
{
usage.AddCommandLineOption(commandLineOption);
}
usage.Print();
}
void ModManArgs::PrintVersion()
{
con::info("OpenAssetTools ModMan {}", GIT_VERSION);
}
bool ModManArgs::ParseArgs(const int argc, const char** argv, bool& shouldContinue)
{
shouldContinue = true;
if (!m_argument_parser.ParseArguments(argc, argv))
{
PrintUsage();
return false;
}
// Check if the user requested help
if (m_argument_parser.IsOptionSpecified(OPTION_HELP))
{
PrintUsage();
shouldContinue = false;
return true;
}
// Check if the user wants to see the version
if (m_argument_parser.IsOptionSpecified(OPTION_VERSION))
{
PrintVersion();
shouldContinue = false;
return true;
}
// -v; --verbose
if (m_argument_parser.IsOptionSpecified(OPTION_VERBOSE))
con::globalLogLevel = con::LogLevel::DEBUG;
else
con::globalLogLevel = con::LogLevel::INFO;
// --no-color
con::globalUseColor = !m_argument_parser.IsOptionSpecified(OPTION_NO_COLOR);
return true;
}

19
src/ModMan/ModManArgs.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include "Utils/Arguments/ArgumentParser.h"
class ModManArgs
{
public:
ModManArgs();
bool ParseArgs(int argc, const char** argv, bool& shouldContinue);
private:
/**
* \brief Prints a command line usage help text for the ModMan tool to stdout.
*/
void PrintUsage() const;
static void PrintVersion();
ArgumentParser m_argument_parser;
};

View File

@@ -0,0 +1,77 @@
#include "DispatchableThread.h"
DispatchableThread::DispatchableThread()
: m_terminate(false)
{
}
DispatchableThread::~DispatchableThread()
{
Terminate();
}
void DispatchableThread::Start()
{
m_terminate = false;
m_thread = std::thread(
[&]
{
ThreadLoop();
});
}
void DispatchableThread::Terminate()
{
std::unique_lock lock(m_cb_mutex);
if (!m_terminate)
{
m_terminate = true;
m_cv.notify_all();
lock.unlock();
m_thread.join();
}
else
{
lock.unlock();
}
}
void DispatchableThread::Dispatch(cb_t cb)
{
std::lock_guard lock(m_cb_mutex);
m_cb_list.emplace_back(std::move(cb));
m_cv.notify_one();
}
std::optional<DispatchableThread::cb_t> DispatchableThread::NextCallback()
{
if (m_terminate || m_cb_list.empty())
return std::nullopt;
auto cb = std::move(m_cb_list.front());
m_cb_list.pop_front();
return cb;
}
void DispatchableThread::ThreadLoop()
{
while (!m_terminate)
{
std::unique_lock lock(m_cb_mutex);
m_cv.wait(lock,
[&]
{
return !m_cb_list.empty() || m_terminate;
});
auto maybeCb = NextCallback();
lock.unlock();
if (maybeCb)
(*maybeCb)();
}
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <condition_variable>
#include <deque>
#include <functional>
#include <mutex>
#include <optional>
#include <thread>
class DispatchableThread
{
public:
using cb_t = std::function<void()>;
DispatchableThread();
~DispatchableThread();
DispatchableThread(const DispatchableThread& other) = delete;
DispatchableThread(DispatchableThread&& other) noexcept = default;
DispatchableThread& operator=(const DispatchableThread& other) = delete;
DispatchableThread& operator=(DispatchableThread&& other) noexcept = default;
void Start();
void Terminate();
void Dispatch(cb_t cb);
private:
std::optional<cb_t> NextCallback();
void ThreadLoop();
std::mutex m_cb_mutex;
std::deque<cb_t> m_cb_list;
std::condition_variable m_cv;
std::thread m_thread;
bool m_terminate;
};

View File

@@ -0,0 +1,174 @@
#include "AssetBinds.h"
#include "Context/ModManContext.h"
#include "Game/CommonAsset.h"
#include "Web/UiCommunication.h"
#include "Json/JsonExtension.h"
#include <cstdint>
NLOHMANN_JSON_SERIALIZE_ENUM(CommonAssetType,
{
{CommonAssetType::PHYS_PRESET, "PHYS_PRESET" },
{CommonAssetType::XANIM, "XANIM" },
{CommonAssetType::XMODEL, "XMODEL" },
{CommonAssetType::MATERIAL, "MATERIAL" },
{CommonAssetType::TECHNIQUE_SET, "TECHNIQUE_SET" },
{CommonAssetType::IMAGE, "IMAGE" },
{CommonAssetType::SOUND, "SOUND" },
{CommonAssetType::SOUND_CURVE, "SOUND_CURVE" },
{CommonAssetType::LOADED_SOUND, "LOADED_SOUND" },
{CommonAssetType::CLIP_MAP, "CLIP_MAP" },
{CommonAssetType::COM_WORLD, "COM_WORLD" },
{CommonAssetType::GAME_WORLD_SP, "GAME_WORLD_SP" },
{CommonAssetType::GAME_WORLD_MP, "GAME_WORLD_MP" },
{CommonAssetType::MAP_ENTS, "MAP_ENTS" },
{CommonAssetType::GFX_WORLD, "GFX_WORLD" },
{CommonAssetType::LIGHT_DEF, "LIGHT_DEF" },
{CommonAssetType::UI_MAP, "UI_MAP" },
{CommonAssetType::FONT, "FONT" },
{CommonAssetType::MENU_LIST, "MENU_LIST" },
{CommonAssetType::MENU, "MENU" },
{CommonAssetType::LOCALIZE_ENTRY, "LOCALIZE_ENTRY" },
{CommonAssetType::WEAPON, "WEAPON" },
{CommonAssetType::SOUND_DRIVER_GLOBALS, "SOUND_DRIVER_GLOBALS"},
{CommonAssetType::FX, "FX" },
{CommonAssetType::IMPACT_FX, "IMPACT_FX" },
{CommonAssetType::AI_TYPE, "AI_TYPE" },
{CommonAssetType::MP_TYPE, "MP_TYPE" },
{CommonAssetType::CHARACTER, "CHARACTER" },
{CommonAssetType::XMODEL_ALIAS, "XMODEL_ALIAS" },
{CommonAssetType::RAW_FILE, "RAW_FILE" },
{CommonAssetType::STRING_TABLE, "STRING_TABLE" },
{CommonAssetType::XMODEL_PIECES, "XMODEL_PIECES" },
{CommonAssetType::PHYS_COLL_MAP, "PHYS_COLL_MAP" },
{CommonAssetType::XMODEL_SURFS, "XMODEL_SURFS" },
{CommonAssetType::PIXEL_SHADER, "PIXEL_SHADER" },
{CommonAssetType::VERTEX_SHADER, "VERTEX_SHADER" },
{CommonAssetType::VERTEX_DECL, "VERTEX_DECL" },
{CommonAssetType::FX_WORLD, "FX_WORLD" },
{CommonAssetType::LEADERBOARD, "LEADERBOARD" },
{CommonAssetType::STRUCTURED_DATA_DEF, "STRUCTURED_DATA_DEF" },
{CommonAssetType::TRACER, "TRACER" },
{CommonAssetType::VEHICLE, "VEHICLE" },
{CommonAssetType::ADDON_MAP_ENTS, "ADDON_MAP_ENTS" },
{CommonAssetType::GLASS_WORLD, "GLASS_WORLD" },
{CommonAssetType::PATH_DATA, "PATH_DATA" },
{CommonAssetType::VEHICLE_TRACK, "VEHICLE_TRACK" },
{CommonAssetType::ATTACHMENT, "ATTACHMENT" },
{CommonAssetType::SURFACE_FX, "SURFACE_FX" },
{CommonAssetType::SCRIPT, "SCRIPT" },
{CommonAssetType::PHYS_CONSTRAINTS, "PHYS_CONSTRAINTS" },
{CommonAssetType::DESTRUCTIBLE_DEF, "DESTRUCTIBLE_DEF" },
{CommonAssetType::SOUND_PATCH, "SOUND_PATCH" },
{CommonAssetType::WEAPON_DEF, "WEAPON_DEF" },
{CommonAssetType::WEAPON_VARIANT, "WEAPON_VARIANT" },
{CommonAssetType::MP_BODY, "MP_BODY" },
{CommonAssetType::MP_HEAD, "MP_HEAD" },
{CommonAssetType::PACK_INDEX, "PACK_INDEX" },
{CommonAssetType::XGLOBALS, "XGLOBALS" },
{CommonAssetType::DDL, "DDL" },
{CommonAssetType::GLASSES, "GLASSES" },
{CommonAssetType::EMBLEM_SET, "EMBLEM_SET" },
{CommonAssetType::FONT_ICON, "FONT_ICON" },
{CommonAssetType::WEAPON_FULL, "WEAPON_FULL" },
{CommonAssetType::ATTACHMENT_UNIQUE, "ATTACHMENT_UNIQUE" },
{CommonAssetType::WEAPON_CAMO, "WEAPON_CAMO" },
{CommonAssetType::KEY_VALUE_PAIRS, "KEY_VALUE_PAIRS" },
{CommonAssetType::MEMORY_BLOCK, "MEMORY_BLOCK" },
{CommonAssetType::SKINNED_VERTS, "SKINNED_VERTS" },
{CommonAssetType::QDB, "QDB" },
{CommonAssetType::SLUG, "SLUG" },
{CommonAssetType::FOOTSTEP_TABLE, "FOOTSTEP_TABLE" },
{CommonAssetType::FOOTSTEP_FX_TABLE, "FOOTSTEP_FX_TABLE" },
{CommonAssetType::ZBARRIER, "ZBARRIER" },
});
namespace
{
class AssetDto
{
public:
CommonAssetType type;
std::string name;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(AssetDto, type, name);
class ZoneAssetsDto
{
public:
std::vector<AssetDto> assets;
std::vector<AssetDto> references;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneAssetsDto, assets, references);
ZoneAssetsDto CreateZoneAssetsDto(const Zone& zone)
{
std::vector<AssetDto> assets;
std::vector<AssetDto> references;
// Reserve some entries already. Numbers are arbitrary.
const auto assetCount = zone.m_pools->GetTotalAssetCount();
assets.reserve(assetCount / 2);
references.reserve(assetCount / 8);
const auto& assetTypeMapper = *ICommonAssetTypeMapper::GetCommonAssetMapperByGame(zone.m_game_id);
for (const auto& asset : *zone.m_pools)
{
if (asset->IsReference())
{
references.emplace_back(AssetDto{
.type = assetTypeMapper.GameToCommonAssetType(asset->m_type),
.name = asset->ReferencedAssetName(),
});
}
else
{
assets.emplace_back(AssetDto{
.type = assetTypeMapper.GameToCommonAssetType(asset->m_type),
.name = asset->m_name,
});
}
}
return ZoneAssetsDto{
.assets = std::move(assets),
.references = std::move(references),
};
}
std::optional<ZoneAssetsDto> GetZonesForZone(const std::string& zoneName)
{
auto& context = ModManContext::Get().m_fast_file;
ZoneAssetsDto result;
{
std::shared_lock lock(context.m_zone_lock);
for (const auto& loadedZone : context.m_loaded_zones)
{
const auto& zone = *loadedZone->m_zone;
if (zone.m_name == zoneName)
return CreateZoneAssetsDto(zone);
}
}
return std::nullopt;
}
} // namespace
namespace ui
{
void RegisterAssetBinds(webview::webview& wv)
{
Bind<std::string, std::optional<ZoneAssetsDto>>(wv,
"getAssetsForZone",
[](const std::string& zoneName)
{
return GetZonesForZone(zoneName);
});
}
} // namespace ui

View File

@@ -0,0 +1,8 @@
#pragma once
#include "Web/WebViewLib.h"
namespace ui
{
void RegisterAssetBinds(webview::webview& wv);
} // namespace ui

View File

@@ -0,0 +1,17 @@
#include "Binds.h"
#include "AssetBinds.h"
#include "DialogBinds.h"
#include "UnlinkingBinds.h"
#include "ZoneBinds.h"
namespace ui
{
void RegisterAllBinds(webview::webview& wv)
{
RegisterAssetBinds(wv);
RegisterDialogHandlerBinds(wv);
RegisterUnlinkingBinds(wv);
RegisterZoneBinds(wv);
}
} // namespace ui

View File

@@ -0,0 +1,8 @@
#pragma once
#include "Web/WebViewLib.h"
namespace ui
{
void RegisterAllBinds(webview::webview& wv);
}

View File

@@ -0,0 +1,122 @@
#include "UnlinkingBinds.h"
#include "Context/ModManContext.h"
#include "IObjWriter.h"
#include "SearchPath/OutputPathFilesystem.h"
#include "SearchPath/SearchPaths.h"
#include "Utils/PathUtils.h"
#include "Web/UiCommunication.h"
#include "Json/JsonExtension.h"
#include <filesystem>
namespace fs = std::filesystem;
namespace
{
class ZoneUnlinkProgressDto
{
public:
std::string zoneName;
double percentage;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnlinkProgressDto, zoneName, percentage);
constexpr double MIN_PROGRESS_TO_REPORT = 0.5;
class UnlinkingEventProgressReporter : public ProgressCallback
{
public:
explicit UnlinkingEventProgressReporter(std::string zoneName)
: m_zone_name(std::move(zoneName)),
m_last_progress(0)
{
}
void OnProgress(const size_t current, const size_t total) override
{
const double percentage = static_cast<double>(current) / static_cast<double>(total) * 100.0;
if (percentage - m_last_progress >= MIN_PROGRESS_TO_REPORT)
{
m_last_progress = percentage;
ui::NotifyZoneUnlinkProgress(m_zone_name, percentage);
}
}
private:
std::string m_zone_name;
double m_last_progress;
};
result::Expected<NoResult, std::string> UnlinkZoneInDbThread(const std::string& zoneName)
{
const auto& context = ModManContext::Get().m_fast_file;
const auto existingZone = std::ranges::find_if(context.m_loaded_zones,
[&zoneName](const std::unique_ptr<LoadedZone>& loadedZone)
{
return loadedZone->m_zone->m_name == zoneName;
});
if (existingZone == context.m_loaded_zones.end())
return result::Unexpected(std::format("No zone with name {} loaded", zoneName));
const auto& loadedZone = *existingZone->get();
const auto* objWriter = IObjWriter::GetObjWriterForGame(loadedZone.m_zone->m_game_id);
const auto outputFolderPath = fs::path(utils::GetExecutablePath()).parent_path() / "zone_dump" / zoneName;
const auto outputFolderPathStr = outputFolderPath.string();
OutputPathFilesystem outputFolderOutputPath(outputFolderPath);
SearchPaths searchPaths;
AssetDumpingContext dumpingContext(
*loadedZone.m_zone, outputFolderPathStr, outputFolderOutputPath, searchPaths, std::make_unique<UnlinkingEventProgressReporter>(zoneName));
objWriter->DumpZone(dumpingContext);
return NoResult();
}
void UnlinkZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
{
ModManContext::Get().m_db_thread.Dispatch(
[&wv, id, zoneName]
{
auto result = UnlinkZoneInDbThread(zoneName);
if (result)
{
con::debug("Unlinked zone \"{}\"", zoneName);
ui::PromiseResolve(wv, id, true);
}
else
{
con::warn("Failed to unlink zone \"{}\": {}", zoneName, result.error());
ui::PromiseReject(wv, id, std::move(result).error());
}
});
}
} // namespace
namespace ui
{
void NotifyZoneUnlinkProgress(std::string zoneName, const double percentage)
{
const ZoneUnlinkProgressDto dto{
.zoneName = std::move(zoneName),
.percentage = percentage,
};
Notify(*ModManContext::Get().m_main_webview, "zoneUnlinkProgress", dto);
}
void RegisterUnlinkingBinds(webview::webview& wv)
{
BindAsync<std::string>(wv,
"unlinkZone",
[&wv](const std::string& id, std::string zoneName)
{
UnlinkZone(wv, id, std::move(zoneName));
});
}
} // namespace ui

View File

@@ -0,0 +1,10 @@
#pragma once
#include "Web/WebViewLib.h"
namespace ui
{
void NotifyZoneUnlinkProgress(std::string zoneName, double percentage);
void RegisterUnlinkingBinds(webview::webview& wv);
} // namespace ui

View File

@@ -0,0 +1,185 @@
#include "ZoneBinds.h"
#include "Context/ModManContext.h"
#include "Web/UiCommunication.h"
#include "Json/JsonExtension.h"
NLOHMANN_JSON_SERIALIZE_ENUM(GameId,
{
{GameId::IW3, "IW3"},
{GameId::IW4, "IW4"},
{GameId::IW5, "IW5"},
{GameId::T5, "T5" },
{GameId::T6, "T6" },
});
NLOHMANN_JSON_SERIALIZE_ENUM(GamePlatform,
{
{GamePlatform::PC, "PC" },
{GamePlatform::XBOX, "XBOX"},
{GamePlatform::PS3, "PS3" },
});
namespace
{
class ZoneDto
{
public:
std::string name;
std::string filePath;
GameId game;
GamePlatform platform;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneDto, name, filePath, game, platform);
class ZoneLoadProgressDto
{
public:
std::string zoneName;
double percentage;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadProgressDto, zoneName, percentage);
class ZoneLoadedDto
{
public:
ZoneDto zone;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneLoadedDto, zone);
class ZoneUnloadedDto
{
public:
std::string zoneName;
};
NLOHMANN_DEFINE_TYPE_EXTENSION(ZoneUnloadedDto, zoneName);
ZoneDto CreateZoneDto(const LoadedZone& loadedZone)
{
return ZoneDto{
.name = loadedZone.m_zone->m_name,
.filePath = loadedZone.m_file_path,
.game = loadedZone.m_zone->m_game_id,
.platform = loadedZone.m_zone->m_platform,
};
}
std::vector<ZoneDto> GetLoadedZones()
{
auto& context = ModManContext::Get().m_fast_file;
std::vector<ZoneDto> result;
{
std::shared_lock lock(context.m_zone_lock);
result.reserve(context.m_loaded_zones.size());
for (const auto& loadedZone : context.m_loaded_zones)
{
result.emplace_back(CreateZoneDto(*loadedZone));
}
}
return result;
}
void LoadFastFile(webview::webview& wv, std::string id, std::string path) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
{
ModManContext::Get().m_db_thread.Dispatch(
[&wv, id, path]
{
auto maybeZone = ModManContext::Get().m_fast_file.LoadFastFile(path);
if (maybeZone)
{
ui::PromiseResolve(wv,
id,
ZoneLoadedDto{
.zone = CreateZoneDto(*maybeZone.value()),
});
con::debug("Loaded zone \"{}\"", maybeZone.value()->m_zone->m_name);
}
else
{
con::warn("Failed to load zone \"{}\": {}", path, maybeZone.error());
ui::PromiseReject(wv, id, std::move(maybeZone).error());
}
});
}
void UnloadZone(webview::webview& wv, std::string id, std::string zoneName) // NOLINT(performance-unnecessary-value-param) Copy is made for thread safety
{
ModManContext::Get().m_db_thread.Dispatch(
[&wv, id, zoneName]
{
auto result = ModManContext::Get().m_fast_file.UnloadZone(zoneName);
if (result)
{
con::debug("Unloaded zone \"{}\"", zoneName);
ui::PromiseResolve(wv, id, true);
}
else
{
con::warn("Failed unloading zone {}: {}", zoneName, result.error());
ui::PromiseReject(wv, id, std::move(result).error());
}
});
}
} // namespace
namespace ui
{
void NotifyZoneLoadProgress(std::string zoneName, const double percentage)
{
const ZoneLoadProgressDto dto{
.zoneName = std::move(zoneName),
.percentage = percentage,
};
Notify(*ModManContext::Get().m_main_webview, "zoneLoadProgress", dto);
}
void NotifyZoneLoaded(const LoadedZone& loadedZone)
{
const ZoneLoadedDto dto{
.zone = CreateZoneDto(loadedZone),
};
Notify(*ModManContext::Get().m_main_webview, "zoneLoaded", dto);
}
void NotifyZoneUnloaded(std::string zoneName)
{
const ZoneUnloadedDto dto{
.zoneName = std::move(zoneName),
};
Notify(*ModManContext::Get().m_main_webview, "zoneUnloaded", dto);
}
void RegisterZoneBinds(webview::webview& wv)
{
BindRetOnly<std::vector<ZoneDto>>(wv,
"getZones",
[]
{
return GetLoadedZones();
});
BindAsync<std::string>(wv,
"loadFastFile",
[&wv](const std::string& id, std::string path)
{
LoadFastFile(wv, id, std::move(path));
});
BindAsync<std::string>(wv,
"unloadZone",
[&wv](const std::string& id, std::string zoneName)
{
UnloadZone(wv, id, std::move(zoneName));
});
}
} // namespace ui

View File

@@ -0,0 +1,13 @@
#pragma once
#include "Context/FastFileContext.h"
#include "Web/WebViewLib.h"
namespace ui
{
void NotifyZoneLoadProgress(std::string zoneName, double percentage);
void NotifyZoneLoaded(const LoadedZone& loadedZone);
void NotifyZoneUnloaded(std::string zoneName);
void RegisterZoneBinds(webview::webview& wv);
} // namespace ui

View File

@@ -1,7 +1,17 @@
#pragma once
#ifdef _WIN32
#include "Windows/AssetHandlerWindows.h"
#elif defined(__linux__)
#include "Linux/AssetHandlerLinux.h"
#include "Web/Platform/Platform.h"
#include "Web/WebViewLib.h"
#include <webview/macros.h>
namespace ui
{
#if defined(PLATFORM_WINDOWS)
constexpr auto URL_PREFIX = "http://modman.local/";
#elif defined(PLATFORM_LINUX)
constexpr auto URL_PREFIX = "modman://localhost/";
#endif
void InstallAssetHandler(webview::webview& wv);
} // namespace ui

View File

@@ -0,0 +1,10 @@
#pragma once
#include "Web/WebViewLib.h"
#include <webview/macros.h>
namespace ui
{
void InstallFaviconHandler(webview::webview& wv);
} // namespace ui

View File

@@ -1,6 +1,7 @@
#include "AssetHandlerLinux.h"
#include "Web/Platform/AssetHandler.h"
#include "Web/Platform/Platform.h"
#if defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK)
#ifdef PLATFORM_LINUX
#include "Web/UiAssets.h"
@@ -8,8 +9,6 @@
#include <iostream>
#include <unordered_map>
using namespace PLATFORM_NAMESPACE_LINUX;
namespace
{
std::unordered_map<std::string, UiFile> assetLookup;
@@ -37,7 +36,7 @@ namespace
}
} // namespace
namespace PLATFORM_NAMESPACE_LINUX
namespace ui
{
void InstallAssetHandler(webview::webview& wv)
{
@@ -49,6 +48,6 @@ namespace PLATFORM_NAMESPACE_LINUX
webkit_web_context_register_uri_scheme(context, "modman", ModManUriSchemeRequestCb, NULL, nullptr);
}
} // namespace PLATFORM_NAMESPACE_LINUX
} // namespace ui
#endif

View File

@@ -1,18 +0,0 @@
#pragma once
#include "Web/Platform/Platform.h"
#include <webview/macros.h>
#if defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK)
#include "Web/WebViewLib.h"
namespace PLATFORM_NAMESPACE_LINUX
{
constexpr auto URL_PREFIX = "modman://localhost/";
void InstallAssetHandler(webview::webview& wv);
} // namespace PLATFORM_NAMESPACE_LINUX
#endif

View File

@@ -1,6 +1,7 @@
#include "Web/Platform/DialogHandler.h"
#include "Web/Platform/Platform.h"
#ifdef __linux__
#ifdef PLATFORM_LINUX
#include <cassert>
#include <cstdint>
@@ -37,8 +38,6 @@ namespace
#endif
}
void OpenFileDialog() {}
void SetFilters(GtkFileDialog* pDialog, const std::vector<ui::FileDialogFilter>& filters)
{
if (filters.empty())

View File

@@ -0,0 +1,22 @@
#include "Web/Platform/FaviconHandler.h"
#include "Web/Platform/Platform.h"
#ifdef PLATFORM_LINUX
#include "Web/UiAssets.h"
#include <format>
#include <iostream>
#include <unordered_map>
namespace ui
{
void InstallFaviconHandler(webview::webview& wv)
{
// The icon system on Linux works a bit different than on Windows
// and doesn't really support this kind of dynamic icon setting
// we skip it for now
}
} // namespace ui
#endif

View File

@@ -0,0 +1,30 @@
#include "Web/Platform/Platform.h"
#include "Web/Platform/TitleHandler.h"
#ifdef PLATFORM_LINUX
#include "Web/UiAssets.h"
#include <format>
#include <iostream>
#include <unordered_map>
namespace ui
{
void InstallTitleHandler(webview::webview& wv)
{
const auto webViewWidget = static_cast<GtkWidget*>(wv.browser_controller().value());
const auto webView = WEBKIT_WEB_VIEW(webViewWidget);
const auto windowWidget = static_cast<GtkWidget*>(wv.window().value());
const auto window = GTK_WINDOW(windowWidget);
auto on_title_changed = +[](GtkWidget* widget, GParamSpec paramSpec, GtkWindow* window)
{
gtk_window_set_title(window, webkit_web_view_get_title(WEBKIT_WEB_VIEW(widget)));
};
g_signal_connect(G_OBJECT(webView), "notify::title", G_CALLBACK(on_title_changed), (gpointer)window);
}
} // namespace ui
#endif

View File

@@ -1,10 +1,7 @@
#pragma once
#define PLATFORM_NAMESPACE_WINDOWS windows
#define PLATFORM_NAMESPACE_LINUX linux
#ifdef _WIN32
#define PLATFORM_NAMESPACE PLATFORM_NAMESPACE_WINDOWS
#define PLATFORM_WINDOWS
#elif defined(__linux__)
#define PLATFORM_NAMESPACE PLATFORM_NAMESPACE_LINUX
#define PLATFORM_LINUX
#endif

View File

@@ -0,0 +1,10 @@
#pragma once
#include "Web/WebViewLib.h"
#include <webview/macros.h>
namespace ui
{
void InstallTitleHandler(webview::webview& wv);
} // namespace ui

View File

@@ -1,6 +1,7 @@
#include "AssetHandlerWindows.h"
#include "Web/Platform/AssetHandler.h"
#include "Web/Platform/Platform.h"
#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE)
#ifdef PLATFORM_WINDOWS
#include "PlatformUtilsWindows.h"
#include "Web/UiAssets.h"
@@ -12,8 +13,6 @@
#include <webview/detail/backends/win32_edge.hh>
#include <wrl/event.h>
using namespace PLATFORM_NAMESPACE_WINDOWS;
namespace
{
constexpr auto LOCALHOST_PREFIX = "http://localhost:";
@@ -25,7 +24,7 @@ namespace
std::wstringstream wss;
wss << std::format(L"Content-Length: {}\n", contentLength);
wss << L"Content-Type: " << StringToWideString(ui::GetMimeTypeForFileName(assetName));
wss << L"Content-Type: " << utils::StringToWideString(ui::GetMimeTypeForFileName(assetName));
return wss.str();
}
@@ -65,7 +64,7 @@ namespace
Microsoft::WRL::ComPtr<ICoreWebView2WebResourceResponse> response;
const auto uri = WideStringToString(wUri);
const auto uri = utils::WideStringToString(wUri);
bool fileFound = false;
#ifdef _DEBUG
@@ -74,9 +73,9 @@ namespace
return S_OK;
#endif
if (uri.starts_with(URL_PREFIX))
if (uri.starts_with(ui::URL_PREFIX))
{
const auto asset = uri.substr(std::char_traits<char>::length(URL_PREFIX) - 1);
const auto asset = uri.substr(std::char_traits<char>::length(ui::URL_PREFIX) - 1);
const auto foundUiFile = assetLookup.find(asset);
if (foundUiFile != assetLookup.end())
@@ -117,7 +116,7 @@ namespace
}
} // namespace
namespace PLATFORM_NAMESPACE_WINDOWS
namespace ui
{
void InstallAssetHandler(webview::webview& wv)
{
@@ -157,6 +156,6 @@ namespace PLATFORM_NAMESPACE_WINDOWS
std::cerr << "Failed to add resource requested filter\n";
}
}
} // namespace PLATFORM_NAMESPACE_WINDOWS
} // namespace ui
#endif

View File

@@ -1,18 +0,0 @@
#pragma once
#include "Web/Platform/Platform.h"
#include <webview/macros.h>
#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE)
#include "Web/WebViewLib.h"
namespace PLATFORM_NAMESPACE_WINDOWS
{
constexpr auto URL_PREFIX = "http://modman.local/";
void InstallAssetHandler(webview::webview& wv);
} // namespace PLATFORM_NAMESPACE_WINDOWS
#endif

View File

@@ -1,6 +1,7 @@
#include "Web/Platform/DialogHandler.h"
#include "Web/Platform/Platform.h"
#ifdef _WIN32
#ifdef PLATFORM_WINDOWS
#include "PlatformUtilsWindows.h"
@@ -8,8 +9,6 @@
#include <thread>
#include <windows.h>
using namespace PLATFORM_NAMESPACE_WINDOWS;
namespace
{
bool SetFilters(IFileDialog* pFileOpen, const std::vector<ui::FileDialogFilter>& filters)
@@ -29,8 +28,8 @@ namespace
const auto& filter = filters[i];
COMDLG_FILTERSPEC filterSpec;
const auto& wName = filterStrings.emplace_back(StringToWideString(filter.m_name));
const auto& wSpec = filterStrings.emplace_back(StringToWideString(filter.m_filter));
const auto& wName = filterStrings.emplace_back(utils::StringToWideString(filter.m_name));
const auto& wSpec = filterStrings.emplace_back(utils::StringToWideString(filter.m_filter));
filterSpec.pszName = wName.c_str();
filterSpec.pszSpec = wSpec.c_str();
@@ -65,7 +64,7 @@ namespace
// Display the file name to the user.
if (SUCCEEDED(hr))
{
result = WideStringToString(pszFilePath);
result = utils::WideStringToString(pszFilePath);
CoTaskMemFree(pszFilePath);
resultType = ui::DialogCallbackResultType::SUCCESSFUL;

View File

@@ -0,0 +1,150 @@
#include "Web/Platform/FaviconHandler.h"
#include "Web/Platform/Platform.h"
#ifdef PLATFORM_WINDOWS
#include "PlatformUtilsWindows.h"
#include "Web/UiAssets.h"
#include <Windows.h>
#include <format>
#include <gdiplus.h>
#include <iostream>
#include <sstream>
#include <webview/detail/backends/win32_edge.hh>
#include <wrl/event.h>
namespace
{
class UniqueHIcon
{
public:
UniqueHIcon()
: m_icon(nullptr)
{
}
~UniqueHIcon()
{
if (m_icon)
{
DestroyIcon(m_icon);
m_icon = nullptr;
}
}
UniqueHIcon(const UniqueHIcon& other) = default;
UniqueHIcon(UniqueHIcon&& other) noexcept
: m_icon(other.m_icon)
{
other.m_icon = nullptr;
}
UniqueHIcon& operator=(const UniqueHIcon& other) = default;
UniqueHIcon& operator=(UniqueHIcon&& other) noexcept
{
m_icon = other.m_icon;
other.m_icon = nullptr;
return *this;
}
HICON& get()
{
return m_icon;
}
private:
HICON m_icon;
};
std::unordered_map<HWND, UniqueHIcon> icons;
HRESULT HandleFaviconChanged(ICoreWebView2_15* core15, HWND window)
{
LPWSTR url;
if (!SUCCEEDED(core15->get_FaviconUri(&url)))
{
std::cerr << "Failed to get favicon uri\n";
return S_FALSE;
}
const std::wstring strUrl(url);
CoTaskMemFree(url);
if (strUrl.empty())
{
icons.erase(icons.find(window));
SendMessage(window, WM_SETICON, ICON_SMALL, (LPARAM)NULL);
}
else
{
if (!SUCCEEDED(core15->GetFavicon(COREWEBVIEW2_FAVICON_IMAGE_FORMAT_PNG,
Microsoft::WRL::Callback<ICoreWebView2GetFaviconCompletedHandler>(
[window](HRESULT errorCode, IStream* iconStream) -> HRESULT
{
Gdiplus::Bitmap iconBitmap(iconStream);
UniqueHIcon icon;
if (iconBitmap.GetHICON(&icon.get()) == Gdiplus::Status::Ok)
{
SendMessage(window, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(icon.get()));
icons.emplace(window, std::move(icon)).first->second.get();
}
else
{
icons.erase(icons.find(window));
SendMessage(window, WM_SETICON, ICON_SMALL, NULL);
}
return S_OK;
})
.Get())))
{
std::cerr << "Failed to get favicon\n";
return S_FALSE;
}
}
return S_OK;
}
} // namespace
namespace ui
{
void InstallFaviconHandler(webview::webview& wv)
{
const auto controller = static_cast<ICoreWebView2Controller*>(wv.browser_controller().value());
auto window = static_cast<HWND>(wv.window().value());
Microsoft::WRL::ComPtr<ICoreWebView2> core;
if (!SUCCEEDED(controller->get_CoreWebView2(&core)))
{
std::cerr << "Failed to get webview\n";
return;
}
Microsoft::WRL::ComPtr<ICoreWebView2_15> core15;
if (!SUCCEEDED(core->QueryInterface(IID_PPV_ARGS(&core15))))
{
std::cerr << "Failed to get core15\n";
return;
}
const Gdiplus::GdiplusStartupInput gdiPlusStartupInput;
ULONG_PTR gdiPlusToken;
Gdiplus::GdiplusStartup(&gdiPlusToken, &gdiPlusStartupInput, nullptr);
EventRegistrationToken token;
if (!SUCCEEDED(core15->add_FaviconChanged(Microsoft::WRL::Callback<ICoreWebView2FaviconChangedEventHandler>(
[core15, window](ICoreWebView2* sender, IUnknown* args) -> HRESULT
{
return HandleFaviconChanged(core15.Get(), window);
})
.Get(),
&token)))
{
std::cerr << "Failed to add favicon handler\n";
}
}
} // namespace ui
#endif

View File

@@ -1,12 +1,12 @@
#include "PlatformUtilsWindows.h"
#ifdef _WIN32
#ifdef PLATFORM_WINDOWS
#include <Windows.h>
#include <exception>
#include <format>
namespace PLATFORM_NAMESPACE_WINDOWS
namespace utils
{
std::string WideStringToString(const std::wstring& wideString)
{
@@ -35,6 +35,6 @@ namespace PLATFORM_NAMESPACE_WINDOWS
MultiByteToWideChar(CP_UTF8, 0, string.data(), static_cast<int>(string.size()), result.data(), sizeNeeded);
return result;
}
} // namespace PLATFORM_NAMESPACE_WINDOWS
} // namespace utils
#endif

View File

@@ -1,16 +1,15 @@
#pragma once
#ifdef _WIN32
#include "Web/Platform/Platform.h"
#include <optional>
#ifdef PLATFORM_WINDOWS
#include <string>
namespace PLATFORM_NAMESPACE_WINDOWS
namespace utils
{
std::string WideStringToString(const std::wstring& wideString);
std::wstring StringToWideString(const std::string& string);
} // namespace PLATFORM_NAMESPACE_WINDOWS
} // namespace utils
#endif

View File

@@ -0,0 +1,62 @@
#include "Web/Platform/Platform.h"
#include "Web/Platform/TitleHandler.h"
#ifdef PLATFORM_WINDOWS
#include "PlatformUtilsWindows.h"
#include "Web/UiAssets.h"
#include <Windows.h>
#include <format>
#include <iostream>
#include <sstream>
#include <webview/detail/backends/win32_edge.hh>
#include <wrl/event.h>
namespace
{
HRESULT HandleTitleChanged(ICoreWebView2* core, HWND window)
{
LPWSTR title;
if (!SUCCEEDED(core->get_DocumentTitle(&title)))
{
std::cerr << "Failed to get title\n";
return S_FALSE;
}
SetWindowTextW(window, title);
CoTaskMemFree(title);
return S_OK;
}
} // namespace
namespace ui
{
void InstallTitleHandler(webview::webview& wv)
{
const auto controller = static_cast<ICoreWebView2Controller*>(wv.browser_controller().value());
auto window = static_cast<HWND>(wv.window().value());
Microsoft::WRL::ComPtr<ICoreWebView2> core;
if (!SUCCEEDED(controller->get_CoreWebView2(&core)))
{
std::cerr << "Failed to get webview\n";
return;
}
EventRegistrationToken token;
if (!SUCCEEDED(core->add_DocumentTitleChanged(Microsoft::WRL::Callback<ICoreWebView2DocumentTitleChangedEventHandler>(
[window](ICoreWebView2* sender, IUnknown* args) -> HRESULT
{
return HandleTitleChanged(sender, window);
})
.Get(),
&token)))
{
std::cerr << "Failed to add title handler\n";
}
}
} // namespace ui
#endif

View File

@@ -1,5 +1,7 @@
#pragma once
#define NOMINMAX
#ifdef _MSC_VER
#pragma warning(push, 0)
#else

View File

@@ -1,32 +1,41 @@
#include "GitVersion.h"
#include "Web/Binds/DialogBinds.h"
#include "Context/ModManContext.h"
#include "GitVersion.h"
#include "ModManArgs.h"
#include "Web/Binds/Binds.h"
#include "Web/Platform/AssetHandler.h"
#include "Web/Platform/FaviconHandler.h"
#include "Web/Platform/TitleHandler.h"
#include "Web/UiCommunication.h"
#include "Web/ViteAssets.h"
#include "Web/WebViewLib.h"
#include <chrono>
#include <format>
#include <iostream>
#include <optional>
#include <string>
#include <thread>
#ifdef _WIN32
#include <Windows.h>
#endif
using namespace std::string_literals;
using namespace PLATFORM_NAMESPACE;
namespace
{
#ifdef _DEBUG
std::optional<webview::webview> devToolWindow;
void RunDevToolsWindow()
void SpawnDevToolsWindow()
{
con::debug("Creating dev tools window");
auto& context = ModManContext::Get();
try
{
auto& newWindow = devToolWindow.emplace(false, nullptr);
context.m_dev_tools_webview = std::make_unique<webview::webview>(false, nullptr);
auto& newWindow = *context.m_dev_tools_webview;
ui::InstallFaviconHandler(newWindow);
ui::InstallTitleHandler(newWindow);
newWindow.set_title("Devtools");
newWindow.set_size(640, 480, WEBVIEW_HINT_NONE);
newWindow.set_size(480, 320, WEBVIEW_HINT_MIN);
@@ -39,59 +48,46 @@ namespace
}
#endif
int RunMainWindow()
int SpawnMainWindow()
{
con::debug("Creating main window");
auto& context = ModManContext::Get();
try
{
webview::webview w(
context.m_main_webview = std::make_unique<webview::webview>(
#ifdef _DEBUG
true,
#else
false,
#endif
nullptr);
w.set_title("OpenAssetTools ModMan");
w.set_size(1280, 640, WEBVIEW_HINT_NONE);
w.set_size(480, 320, WEBVIEW_HINT_MIN);
auto& newWindow = *context.m_main_webview;
ui::RegisterDialogHandlerBinds(w);
newWindow.set_title("OpenAssetTools ModMan");
newWindow.set_size(1280, 640, WEBVIEW_HINT_NONE);
newWindow.set_size(640, 480, WEBVIEW_HINT_MIN);
// A binding that counts up or down and immediately returns the new value.
ui::Bind<std::string, std::string>(w,
"greet",
[&w](std::string name) -> std::string
{
ui::Notify(w, "greeting", name);
return std::format("Hello from C++ {}!", name);
});
#if defined(WEBVIEW_PLATFORM_WINDOWS) && defined(WEBVIEW_EDGE)
InstallAssetHandler(w);
constexpr auto urlPrefix = URL_PREFIX;
#elif defined(WEBVIEW_PLATFORM_LINUX) && defined(WEBVIEW_GTK)
InstallAssetHandler(w);
constexpr auto urlPrefix = URL_PREFIX;
#else
#error Unsupported platform
#endif
ui::InstallAssetHandler(newWindow);
ui::InstallFaviconHandler(newWindow);
ui::InstallTitleHandler(newWindow);
ui::RegisterAllBinds(newWindow);
#ifdef _DEBUG
w.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", urlPrefix));
newWindow.navigate(VITE_DEV_SERVER ? std::format("http://localhost:{}", VITE_DEV_SERVER_PORT) : std::format("{}index.html", ui::URL_PREFIX));
if (VITE_DEV_SERVER)
{
w.dispatch(
newWindow.dispatch(
[]
{
RunDevToolsWindow();
SpawnDevToolsWindow();
});
}
#else
w.navigate(std::format("{}index.html", urlPrefix));
newWindow.navigate(std::format("{}index.html", ui::URL_PREFIX));
#endif
w.run();
newWindow.run();
}
catch (const webview::exception& e)
{
@@ -104,16 +100,50 @@ namespace
} // namespace
#ifdef _WIN32
#define MODMAN_ARGC __argc
#define MODMAN_ARGV const_cast<const char**>(__argv)
int WINAPI WinMain(HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/, LPSTR /*lpCmdLine*/, int /*nCmdShow*/)
{
#else
int main()
{
#define MODMAN_ARGC argc
#define MODMAN_ARGV argv
int main(int argc, const char** argv)
#endif
{
#ifdef _WIN32
// Attach console if possible on Windows for stdout/stderr in console
if (AttachConsole(-1))
{
FILE* fDummy;
(void)freopen_s(&fDummy, "CONOUT$", "w", stdout);
(void)freopen_s(&fDummy, "CONOUT$", "w", stderr);
(void)freopen_s(&fDummy, "CONIN$", "r", stdin);
std::cout.clear();
std::clog.clear();
std::cerr.clear();
std::cin.clear();
}
#endif
#ifdef __linux__
g_set_prgname("OpenAssetTools-ModMan");
g_set_application_name("OpenAssetTools ModMan");
#endif
ModManArgs args;
auto shouldContinue = true;
if (!args.ParseArgs(MODMAN_ARGC, MODMAN_ARGV, shouldContinue))
return false;
if (!shouldContinue)
return true;
con::info("Starting ModMan " GIT_VERSION);
const auto result = RunMainWindow();
ModManContext::Get().Startup();
const auto result = SpawnMainWindow();
ModManContext::Get().Destroy();
return result;
}

View File

@@ -1,4 +1,4 @@
import type { Plugin } from "vite";
import type { Plugin, UserConfig } from "vite";
import type { OutputAsset, OutputChunk } from "rollup";
import path from "node:path";
import fs from "node:fs";
@@ -7,8 +7,35 @@ type MinimalOutputAsset = Pick<OutputAsset, "type" | "fileName" | "source">;
type MinimalOutputChunk = Pick<OutputChunk, "type" | "fileName" | "code">;
type MinimalOutputBundle = Record<string, MinimalOutputAsset | MinimalOutputChunk>;
interface PublicDirFile {
fullPath: string;
relativePath: string;
}
function getPublicDirFiles(publicDir?: string): PublicDirFile[] {
if (!publicDir) return [];
const result: PublicDirFile[] = [];
const files = fs.readdirSync(publicDir, { recursive: true, withFileTypes: true });
for (const file of files) {
if (!file.isFile()) continue;
const fullPath = path.join(file.parentPath, file.name);
let relativePath = path.relative(publicDir, fullPath).replaceAll(/\\/g, "/");
if (relativePath.startsWith("./")) {
relativePath = relativePath.substring(2);
}
result.push({
fullPath,
relativePath,
});
}
return result;
}
function createVarName(fileName: string) {
return fileName.replaceAll(".", "_").toUpperCase();
return fileName.replaceAll(/[\\/]/g, "__").replaceAll(/[\.-]/g, "_").toUpperCase();
}
function transformAsset(asset: MinimalOutputAsset) {
@@ -33,10 +60,19 @@ function transformChunk(chunk: MinimalOutputChunk) {
`;
}
function transformPublicFile(publicFile: PublicDirFile) {
const varName = createVarName(publicFile.relativePath);
const bytes = [...fs.readFileSync(publicFile.fullPath)].map((v) => String(v)).join(",");
return `constexpr const unsigned char ${varName}[] {${bytes}};
`;
}
function writeHeader(
bundle: MinimalOutputBundle,
outputDir?: string,
options?: HeaderTransformationPluginOptions,
publicDir?: string,
devServerPort?: number,
) {
const outputPath = options?.outputPath ?? path.join(outputDir ?? "dist", "ViteAssets.h");
@@ -71,12 +107,18 @@ constexpr auto VITE_DEV_SERVER_PORT = ${devServerPort ? String(devServerPort) :
`,
);
const fileNames: string[] = [];
for (const curBundle of Object.values(bundle)) {
if (curBundle.type === "asset") {
fs.writeSync(fd, transformAsset(curBundle));
} else {
fs.writeSync(fd, transformChunk(curBundle));
}
fileNames.push(curBundle.fileName);
}
for (const publicDirFile of getPublicDirFiles(publicDir)) {
fs.writeSync(fd, transformPublicFile(publicDirFile));
fileNames.push(publicDirFile.relativePath);
}
if (includeFileEnumeration) {
@@ -95,8 +137,7 @@ static inline const UiFile MOD_MAN_UI_FILES[] {
);
let index = 0;
for (const curBundle of Object.values(bundle)) {
const fileName = curBundle.fileName;
for (const fileName of fileNames) {
const varName = createVarName(fileName);
let prefix = " ";
@@ -133,16 +174,21 @@ export default function headerTransformationPlugin(
): Plugin {
let writeServerActive = false;
let writeBundleActive = false;
let publicDir: string | undefined = undefined;
return {
name: "vite-plugin-header-transformation",
enforce: "post",
config(_userOptions, env) {
config(userOptions: UserConfig, env) {
if (env.command === "serve") {
writeServerActive = true;
} else {
writeBundleActive = true;
}
if (typeof userOptions.publicDir === "string") {
publicDir = userOptions.publicDir;
}
},
configureServer(server) {
if (!writeServerActive) {
@@ -156,6 +202,7 @@ export default function headerTransformationPlugin(
},
server.config.build.outDir,
options,
publicDir,
server.config.server.port,
);
});
@@ -165,7 +212,7 @@ export default function headerTransformationPlugin(
return;
}
writeHeader(bundle, outputOptions.dir, options);
writeHeader(bundle, outputOptions.dir, options, publicDir);
},
};
}

View File

@@ -2,9 +2,10 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="/favicon.ico" sizes="512x512 256x256 128x128 64x64" />
<link rel="icon" href="/logo_circle.svg" sizes="any" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri + Vue + Typescript App</title>
<title>OpenAssetTools ModMan</title>
</head>
<body>
<div id="app"></div>

File diff suppressed because it is too large Load Diff

View File

@@ -13,30 +13,34 @@
"format": "prettier --write **/*.{js,ts,vue,html,json,yml,yaml,md}"
},
"dependencies": {
"@fontsource/inter": "^5.2.8",
"@primeuix/themes": "^1.2.5",
"pinia": "3.0.3",
"vue": "3.5.22"
"primevue": "^4.4.1",
"vue": "3.5.22",
"vue-router": "4.6.3"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/jsdom": "^21.1.7",
"@types/node": "^22.18.6",
"@types/jsdom": "^27.0.0",
"@types/node": "^24.9.2",
"@vitejs/plugin-vue": "6.0.1",
"@vitest/eslint-plugin": "^1.3.13",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.8.1",
"eslint": "^9.33.0",
"eslint-plugin-vue": "~10.4.0",
"eslint": "^9.39.0",
"eslint-plugin-vue": "~10.5.1",
"jiti": "^2.5.1",
"jsdom": "^27.0.0",
"jsdom": "^27.1.0",
"npm-run-all2": "^8.0.4",
"prettier": "3.6.2",
"sass": "1.93.2",
"sass": "1.93.3",
"typescript": "~5.9.3",
"vite": "7.1.7",
"vite-plugin-vue-devtools": "^8.0.2",
"vite": "7.1.12",
"vite-plugin-vue-devtools": "^8.0.3",
"vitest": "^3.2.4",
"vue-tsc": "3.1.0"
"vue-tsc": "3.1.2"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="300mm"
height="300mm"
viewBox="0 0 300 300"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
sodipodi:docname="oat_circle.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#999999"
borderopacity="1"
inkscape:showpageshadow="2"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.55748644"
inkscape:cx="452.02893"
inkscape:cy="637.68367"
inkscape:window-width="1920"
inkscape:window-height="1129"
inkscape:window-x="1672"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" /><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Drop Shadow"
id="filter19"
x="-0.16704432"
y="-0.08986926"
width="1.3601893"
height="1.1937806"><feFlood
result="flood"
in="SourceGraphic"
flood-opacity="0.403922"
flood-color="rgb(0,0,0)"
id="feFlood18" /><feGaussianBlur
result="blur"
in="SourceGraphic"
stdDeviation="8.000000"
id="feGaussianBlur18" /><feOffset
result="offset"
in="blur"
dx="3.000000"
dy="3.000000"
id="feOffset18" /><feComposite
result="comp1"
operator="in"
in="flood"
in2="offset"
id="feComposite18" /><feComposite
result="comp2"
operator="over"
in="SourceGraphic"
in2="comp1"
id="feComposite19" /></filter></defs><g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"><circle
style="fill:#ca893d;stroke-width:0.264583;fill-opacity:1"
id="path2"
cx="149.73645"
cy="151.63484"
r="128" /><path
style="fill:#ffffff;stroke:none;stroke-width:0.724;fill-opacity:1;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter19)"
d="m 157.15533,40.658523 c -7.16528,10.302747 -12.51327,22.617436 -16.6111,34.458658 -2.54917,7.366211 -4.77329,15.607101 -4.02076,23.482935 0.33817,3.539174 1.30988,7.672094 3.18904,10.720474 0.97925,1.58854 3.26203,2.91006 3.81397,4.59843 0.92967,2.84378 0.74731,6.23647 0.87645,9.18503 0.18554,4.23723 0.42148,8.50748 0.23514,12.76248 -0.30007,6.85199 -2.86584,14.43633 -5.6082,20.67519 -1.68147,3.82534 -4.42564,7.76492 -4.84697,11.99672 h -0.25525 c -0.30662,-7.14028 -1.15672,-13.81779 -4.08399,-20.41995 8.36198,-3.90966 7.24983,-15.18254 4.89917,-22.46194 -4.29583,-13.3031 -14.22445,-24.31606 -23.76379,-34.193176 -3.48045,-3.603683 -6.90971,-8.097499 -10.999551,-10.985956 0.986581,12.07896 2.622311,24.405512 5.251911,36.245412 1.24134,5.58915 2.55471,11.99777 5.22415,17.1017 2.58771,4.94766 5.31606,9.73231 10.19914,12.74592 1.43929,0.88826 2.9245,1.43029 4.59448,1.69122 0.9658,0.15091 2.16541,-0.0811 2.89736,0.70371 0.96354,1.03323 1.26089,2.94912 1.69713,4.2581 1.39158,4.17553 1.97461,8.38492 2.2744,12.76246 1.2066,17.61936 -5.6512,33.88753 -11.62428,50.02887 -1.81237,4.89764 -1.57442,10.20759 -3.63514,15.05971 -1.97356,4.64688 -4.14099,8.76759 -6.87617,13.01397 -1.95003,3.02741 -4.3974,6.00391 -4.89799,9.70322 2.02855,-0.15628 2.49496,-2.32202 3.60382,-3.75586 3.20156,-4.13988 6.3358,-8.3512 8.59123,-13.0906 2.73623,-5.74976 3.07562,-12.18903 5.18474,-18.12269 2.31458,-6.51166 6.40216,-12.47781 8.29107,-19.14371 1.96809,-6.94523 2.59545,-15.58781 6.73339,-21.68733 1.20177,-1.77146 5.24937,0.88445 7.15939,1.15809 5.04845,0.72321 10.70726,-0.88082 15.0597,-3.41613 12.21808,-7.11703 21.95698,-18.17281 30.34514,-29.37889 2.98928,-3.99351 6.48878,-7.93184 8.96326,-12.27481 -14.38224,4.6782 -29.79304,6.53244 -43.13713,13.95008 -6.39539,3.55503 -13.16101,8.2226 -16.65797,14.89309 -2.10395,4.01331 -3.43865,9.81381 -4.01723,14.29396 h -0.25524 c 0.50501,-6.34574 3.0145,-14.93411 6.77553,-20.12136 3.07929,-4.24698 7.25737,-7.62116 10.75375,-11.50671 1.94533,-2.16185 4.00931,-4.21297 5.91938,-6.40782 1.02315,-1.17568 2.09515,-2.89626 3.60777,-3.49746 0.98879,-0.393 2.11738,0.39715 3.06298,0.65289 1.52673,0.4129 3.27852,0.39838 4.84974,0.27199 4.6473,-0.37383 9.54534,-3.67807 13.00767,-6.56341 6.74442,-5.62047 11.67306,-14.04755 15.64624,-21.74543 4.64046,-8.990734 9.24016,-18.310406 12.69648,-27.822182 -6.07512,2.546043 -11.99754,6.635251 -17.6122,10.092786 -11.19017,6.890952 -23.12624,14.591936 -30.0038,26.152626 -1.81972,3.05881 -3.46108,6.64757 -3.90458,10.20997 -0.26495,2.12833 0.60659,5.94362 -0.46067,7.65728 -4.32991,6.95238 -12.89408,11.33384 -16.68082,18.6334 h -0.25524 c 2.92582,-7.9652 4.54429,-16.23824 4.33549,-24.75919 -0.0764,-3.12012 -0.19346,-6.34209 -0.57942,-9.44422 -0.17134,-1.37703 -0.87961,-3.43476 -0.46619,-4.79164 0.36474,-1.19717 4.30109,-2.11927 5.38859,-2.98795 3.08532,-2.46452 5.37666,-6.17729 6.78243,-9.83261 3.74161,-9.72902 3.6403,-20.912802 2.93989,-31.140425 -0.67532,-9.861296 -1.19822,-19.875516 -3.08584,-29.608922 h -0.5105 M 110.44471,144.0345 c -3.75903,11.55249 -8.14701,22.91376 -11.097041,34.71391 -1.534837,6.13935 -3.100637,12.50907 -3.015727,18.88845 0.0736,5.52842 0.41629,11.16231 3.261717,16.07972 0.917211,1.58511 2.191701,2.95005 3.704061,3.97665 0.86062,0.58419 2.09474,1.23975 1.85628,2.44889 -1.65212,8.37718 -6.089111,17.15329 -6.194528,25.73687 -0.0257,2.09216 -0.15445,4.03486 -0.007,6.12597 0.0519,0.73659 0.0476,2.07414 1.056448,2.15781 1.85965,0.15424 1.41636,-6.42184 1.50865,-7.77329 0.58862,-8.61903 3.54759,-17.30601 5.13276,-25.77544 0.33859,-1.80907 4.17634,-2.90995 5.57136,-4.04146 3.02114,-2.45045 5.08063,-5.89062 6.45548,-9.4915 5.74688,-15.05154 1.24022,-33.42794 -3.19868,-48.24212 -0.92396,-3.08358 -1.99799,-6.1122 -2.9391,-9.18897 -0.56904,-1.86034 -0.90105,-4.03706 -2.0944,-5.61549 m 56.15486,37.01115 c -5.76022,4.69086 -11.15297,9.97993 -16.59121,15.03687 -10.80773,10.04991 -24.26466,21.75848 -25.44202,37.54449 -0.19124,2.56417 0.42114,5.79387 1.38655,8.16798 0.44584,1.0964 1.53285,2.14209 1.69181,3.31824 0.41267,3.05318 -2.88452,5.79996 -1.88503,9.18898 h 0.25525 c 1.3389,-2.30487 2.3407,-5.50659 2.5525,-8.16798 2.19882,0.32473 4.09594,1.02573 6.38123,0.68894 6.47079,-0.95364 11.28708,-7.14598 14.68157,-12.17516 7.17889,-10.63603 10.7098,-23.84247 13.50438,-36.2454 0.93663,-4.15697 1.96349,-8.31019 2.72048,-12.50722 0.26038,-1.44356 1.17447,-3.43379 0.74449,-4.84974 z"
id="path1" /></g></svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1,54 +1,56 @@
<script setup lang="ts">
import { onUnmounted, ref } from "vue";
import { webviewBinds, webviewAddEventListener, webviewRemoveEventListener } from "./native";
const greetMsg = ref("");
const lastPersonGreeted = ref("");
const lastPath = ref("");
const name = ref("");
async function greet() {
greetMsg.value = await webviewBinds.greet(name.value);
}
function onPersonGreeted(person: string) {
lastPersonGreeted.value = person;
}
async function onOpenFastfileClick() {
lastPath.value =
(await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] })) ?? "";
}
webviewAddEventListener("greeting", onPersonGreeted);
onUnmounted(() => webviewRemoveEventListener("greeting", onPersonGreeted));
import ModManHeader from "./components/header/ModManHeader.vue";
</script>
<template>
<main class="container">
<h1>Welcome to ModMan</h1>
<small>Nothing to see here yet, this is mainly for testing</small>
<ModManHeader />
<form class="row" @submit.prevent="greet">
<input id="greet-input" v-model="name" placeholder="Enter a name..." autocomplete="off" />
<button type="submit">Greet</button>
</form>
<p>{{ greetMsg }}</p>
<p>The last person greeted is: {{ lastPersonGreeted }}</p>
<p>
<button @click="onOpenFastfileClick">Open fastfile</button>
<span>The last path: {{ lastPath }}</span>
</p>
<div class="router-wrapper">
<RouterView v-slot="{ Component, route }">
<Transition name="blend">
<div class="router-rewrapper" :key="route.name">
<component :is="Component" />
</div>
</Transition>
</RouterView>
</div>
</main>
</template>
<style scoped>
.logo.vite:hover {
filter: drop-shadow(0 0 2em #747bff);
<style scoped lang="scss">
@use "@style/variables";
.container {
margin: 0;
display: flex;
position: relative;
flex-direction: column;
height: 100vh;
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #249b73);
.router-wrapper {
position: relative;
height: 100%;
}
.router-rewrapper {
height: 100%;
}
.blend-enter-from,
.blend-leave-to {
opacity: 0;
}
.blend-enter-active,
.blend-leave-active {
transition: opacity 0.25s ease-in-out;
}
.blend-leave-active {
position: absolute;
inset: 0;
}
</style>

View File

@@ -0,0 +1,51 @@
import type { App } from "vue";
import PrimeVue from "primevue/config";
import Aura from "@primeuix/themes/aura";
import { definePreset } from "@primeuix/themes";
import type { ColorScale } from "@primeuix/styled";
const ModManTheme = definePreset(Aura, {
primitive: {
brand: {
50: "var(--color-brand-50)",
100: "var(--color-brand-100)",
200: "var(--color-brand-200)",
300: "var(--color-brand-300)",
400: "var(--color-brand-400)",
500: "var(--color-brand-500)",
600: "var(--color-brand-600)",
700: "var(--color-brand-700)",
800: "var(--color-brand-800)",
900: "var(--color-brand-900)",
950: "var(--color-brand-950)",
} satisfies ColorScale,
},
semantic: {
primary: {
50: "{orange.50}",
100: "{orange.100}",
200: "{orange.200}",
300: "{orange.300}",
400: "{orange.400}",
500: "{orange.500}",
600: "{orange.600}",
700: "{orange.700}",
800: "{orange.800}",
900: "{orange.900}",
950: "{orange.950}",
},
},
});
export function configurePrimeVue(app: App) {
app.use(PrimeVue, {
theme: {
preset: ModManTheme,
options: {
darkModeSelector: ".dark",
},
},
});
// Always make dark mode for now
document.documentElement.classList.add("dark");
}

View File

@@ -0,0 +1,30 @@
<template>
<svg viewBox="0 0 100 100" class="loading-spinner">
<circle class="loading-spinner-box" cx="50" cy="50" r="40" fill="transparent" />
</svg>
</template>
<style lang="scss" scoped>
.loading-spinner {
width: 0.8em;
height: 0.8em;
display: inline-block;
animation: rotation 1s infinite linear;
.loading-spinner-box {
stroke: currentColor;
stroke-width: 10;
stroke-dasharray: 100% 100%;
}
}
@keyframes rotation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@@ -0,0 +1,78 @@
<script setup lang="ts">
import Button from "primevue/button";
import { useRoute, useRouter } from "vue-router";
import IconArrowLeft from "../icons/IconArrowLeft.vue";
import IconGear from "../icons/IconGear.vue";
import { useRouteMeta } from "@/router/RouteMeta.ts";
const route = useRoute();
const { headerTitle, routeNavigateBackTo } = useRouteMeta();
const router = useRouter();
function onClickBack() {
if (!routeNavigateBackTo.value) return;
router.push({ name: routeNavigateBackTo.value });
}
</script>
<template>
<header class="header">
<div class="header-section left">
<Button
v-if="routeNavigateBackTo"
variant="text"
severity="secondary"
aria-label="Back"
@click="onClickBack"
>
<template #icon>
<IconArrowLeft class="icon" />
</template>
</Button>
</div>
<h1 class="title">
<span v-if="typeof headerTitle === 'string'">{{ headerTitle }}</span>
<component v-else :is="headerTitle" v-bind="route.params" />
</h1>
<div class="header-section right">
<Button variant="text" severity="secondary" aria-label="Settings">
<template #icon>
<IconGear class="icon" />
</template>
</Button>
</div>
</header>
</template>
<style scoped lang="scss">
@use "@style/colors";
.header {
background-color: colors.$color-content-background;
text-align: center;
padding: 0.25rem 0.25rem;
display: flex;
justify-content: space-between;
border-bottom: 1px solid colors.$color-content-border;
}
.header-section {
width: 12em;
&.left {
text-align: left;
}
&.right {
text-align: right;
}
}
.title {
font-size: 1rem;
line-height: 1;
}
</style>

View File

@@ -0,0 +1,9 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
<!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path
d="M73.4 297.4C60.9 309.9 60.9 330.2 73.4 342.7L233.4 502.7C245.9 515.2 266.2 515.2 278.7 502.7C291.2 490.2 291.2 469.9 278.7 457.4L173.3 352L544 352C561.7 352 576 337.7 576 320C576 302.3 561.7 288 544 288L173.3 288L278.7 182.6C291.2 170.1 291.2 149.8 278.7 137.3C266.2 124.8 245.9 124.8 233.4 137.3L73.4 297.3z"
fill="currentColor"
/>
</svg>
</template>

View File

@@ -0,0 +1,9 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640">
<!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
<path
d="M259.1 73.5C262.1 58.7 275.2 48 290.4 48L350.2 48C365.4 48 378.5 58.7 381.5 73.5L396 143.5C410.1 149.5 423.3 157.2 435.3 166.3L503.1 143.8C517.5 139 533.3 145 540.9 158.2L570.8 210C578.4 223.2 575.7 239.8 564.3 249.9L511 297.3C511.9 304.7 512.3 312.3 512.3 320C512.3 327.7 511.8 335.3 511 342.7L564.4 390.2C575.8 400.3 578.4 417 570.9 430.1L541 481.9C533.4 495 517.6 501.1 503.2 496.3L435.4 473.8C423.3 482.9 410.1 490.5 396.1 496.6L381.7 566.5C378.6 581.4 365.5 592 350.4 592L290.6 592C275.4 592 262.3 581.3 259.3 566.5L244.9 496.6C230.8 490.6 217.7 482.9 205.6 473.8L137.5 496.3C123.1 501.1 107.3 495.1 99.7 481.9L69.8 430.1C62.2 416.9 64.9 400.3 76.3 390.2L129.7 342.7C128.8 335.3 128.4 327.7 128.4 320C128.4 312.3 128.9 304.7 129.7 297.3L76.3 249.8C64.9 239.7 62.3 223 69.8 209.9L99.7 158.1C107.3 144.9 123.1 138.9 137.5 143.7L205.3 166.2C217.4 157.1 230.6 149.5 244.6 143.4L259.1 73.5zM320.3 400C364.5 399.8 400.2 363.9 400 319.7C399.8 275.5 363.9 239.8 319.7 240C275.5 240.2 239.8 276.1 240 320.3C240.2 364.5 276.1 400.2 320.3 400z"
fill="currentColor"
/>
</svg>
</template>

View File

@@ -1,109 +0,0 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
.container {
margin: 0;
padding-top: 10vh;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
display: flex;
justify-content: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
text-align: center;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
}
button:hover {
border-color: #396cd8;
}
button:active {
border-color: #396cd8;
background-color: #e8e8e8;
}
input,
button {
outline: none;
}
#greet-input {
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
}

View File

@@ -1,13 +1,17 @@
import "../public/favicon.ico";
import "./main.scss";
import "@style/main.scss";
import { createApp } from "vue";
import { createPinia } from "pinia";
import { configurePrimeVue } from "./PrimeVue.ts";
import App from "./App.vue";
import { createModManRouter } from "./router/Router.ts";
const app = createApp(App);
app.use(createPinia());
app.use(createModManRouter());
configurePrimeVue(app);
app.mount("#app");

View File

@@ -0,0 +1,89 @@
export enum CommonAssetType {
PHYS_PRESET = "PHYS_PRESET",
XANIM = "XANIM",
XMODEL = "XMODEL",
MATERIAL = "MATERIAL",
TECHNIQUE_SET = "TECHNIQUE_SET",
IMAGE = "IMAGE",
SOUND = "SOUND",
SOUND_CURVE = "SOUND_CURVE",
LOADED_SOUND = "LOADED_SOUND",
CLIP_MAP = "CLIP_MAP",
COM_WORLD = "COM_WORLD",
GAME_WORLD_SP = "GAME_WORLD_SP",
GAME_WORLD_MP = "GAME_WORLD_MP",
MAP_ENTS = "MAP_ENTS",
GFX_WORLD = "GFX_WORLD",
LIGHT_DEF = "LIGHT_DEF",
UI_MAP = "UI_MAP",
FONT = "FONT",
MENU_LIST = "MENU_LIST",
MENU = "MENU",
LOCALIZE_ENTRY = "LOCALIZE_ENTRY",
WEAPON = "WEAPON",
SOUND_DRIVER_GLOBALS = "SOUND_DRIVER_GLOBALS",
FX = "FX",
IMPACT_FX = "IMPACT_FX",
AI_TYPE = "AI_TYPE",
MP_TYPE = "MP_TYPE",
CHARACTER = "CHARACTER",
XMODEL_ALIAS = "XMODEL_ALIAS",
RAW_FILE = "RAW_FILE",
STRING_TABLE = "STRING_TABLE",
XMODEL_PIECES = "XMODEL_PIECES",
PHYS_COLL_MAP = "PHYS_COLL_MAP",
XMODEL_SURFS = "XMODEL_SURFS",
PIXEL_SHADER = "PIXEL_SHADER",
VERTEX_SHADER = "VERTEX_SHADER",
VERTEX_DECL = "VERTEX_DECL",
FX_WORLD = "FX_WORLD",
LEADERBOARD = "LEADERBOARD",
STRUCTURED_DATA_DEF = "STRUCTURED_DATA_DEF",
TRACER = "TRACER",
VEHICLE = "VEHICLE",
ADDON_MAP_ENTS = "ADDON_MAP_ENTS",
GLASS_WORLD = "GLASS_WORLD",
PATH_DATA = "PATH_DATA",
VEHICLE_TRACK = "VEHICLE_TRACK",
ATTACHMENT = "ATTACHMENT",
SURFACE_FX = "SURFACE_FX",
SCRIPT = "SCRIPT",
PHYS_CONSTRAINTS = "PHYS_CONSTRAINTS",
DESTRUCTIBLE_DEF = "DESTRUCTIBLE_DEF",
SOUND_PATCH = "SOUND_PATCH",
WEAPON_DEF = "WEAPON_DEF",
WEAPON_VARIANT = "WEAPON_VARIANT",
MP_BODY = "MP_BODY",
MP_HEAD = "MP_HEAD",
PACK_INDEX = "PACK_INDEX",
XGLOBALS = "XGLOBALS",
DDL = "DDL",
GLASSES = "GLASSES",
EMBLEM_SET = "EMBLEM_SET",
FONT_ICON = "FONT_ICON",
WEAPON_FULL = "WEAPON_FULL",
ATTACHMENT_UNIQUE = "ATTACHMENT_UNIQUE",
WEAPON_CAMO = "WEAPON_CAMO",
KEY_VALUE_PAIRS = "KEY_VALUE_PAIRS",
MEMORY_BLOCK = "MEMORY_BLOCK",
SKINNED_VERTS = "SKINNED_VERTS",
QDB = "QDB",
SLUG = "SLUG",
FOOTSTEP_TABLE = "FOOTSTEP_TABLE",
FOOTSTEP_FX_TABLE = "FOOTSTEP_FX_TABLE",
ZBARRIER = "ZBARRIER",
}
export interface AssetDto {
type: CommonAssetType;
name: string;
}
export interface ZoneAssetsDto {
assets: AssetDto[];
references: AssetDto[];
}
export interface AssetBinds {
getAssetsForZone(zoneName: string): Promise<ZoneAssetsDto | null>;
}

View File

@@ -12,7 +12,7 @@ export interface SaveFileDialogDto {
}
export interface DialogBinds {
openFileDialog(options?: OpenFileDialogDto): string | null;
saveFileDialog(options?: SaveFileDialogDto): string | null;
folderSelectDialog(): string | null;
openFileDialog(options?: OpenFileDialogDto): Promise<string | null>;
saveFileDialog(options?: SaveFileDialogDto): Promise<string | null>;
folderSelectDialog(): Promise<string | null>;
}

View File

@@ -0,0 +1,15 @@
export interface ZoneUnlinkProgressDto {
zoneName: string;
/**
* Between 0-100
*/
percentage: number;
}
export interface UnlinkingBinds {
unlinkZone(zoneName: string): Promise<void>;
}
export interface UnlinkingEventMap {
zoneUnlinkProgress: ZoneUnlinkProgressDto;
}

View File

@@ -0,0 +1,48 @@
export enum GameId {
IW3 = "IW3",
IW4 = "IW4",
IW5 = "IW5",
T5 = "T5",
T6 = "T6",
}
export enum GamePlatform {
PC = "PC",
XBOX = "XBOX",
PS3 = "PS3",
}
export interface ZoneDto {
name: string;
filePath: string;
game: GameId;
platform: GamePlatform;
}
export interface ZoneLoadProgressDto {
zoneName: string;
/**
* Between 0-100
*/
percentage: number;
}
export interface ZoneLoadedDto {
zone: ZoneDto;
}
export interface ZoneUnloadedDto {
zoneName: string;
}
export interface ZoneBinds {
getZones(): Promise<ZoneDto[]>;
loadFastFile(path: string): Promise<ZoneLoadedDto>;
unloadZone(zoneName: string): Promise<void>;
}
export interface ZoneEventMap {
zoneLoadProgress: ZoneLoadProgressDto;
zoneLoaded: ZoneLoadedDto;
zoneUnloaded: ZoneUnloadedDto;
}

View File

@@ -1,13 +1,11 @@
import type { AssetBinds } from "./AssetBinds";
import type { DialogBinds } from "./DialogBinds";
import type { UnlinkingBinds, UnlinkingEventMap } from "./UnlinkingBinds";
import type { ZoneBinds, ZoneEventMap } from "./ZoneBinds";
export type NativeMethods = AssetBinds & DialogBinds & UnlinkingBinds & ZoneBinds;
export type NativeMethods = {
greet(name: string): Promise<string>;
} & DialogBinds;
interface NativeEventMap {
greeting: string;
}
type NativeEventMap = UnlinkingEventMap & ZoneEventMap;
type WebViewExtensions = {
webviewBinds: NativeMethods;

View File

@@ -0,0 +1,8 @@
export const PAGE = {
HOME: "Home",
INSPECT: {
SELECT_ZONE: "Inspect zone",
ZONE_DETAILS: "Zone details",
},
OPTIONS: "Options",
};

View File

@@ -0,0 +1,30 @@
import { type Component, computed } from "vue";
import { useRoute, type RouteLocationNormalizedGeneric } from "vue-router";
type HeaderTitleFunc = (route: RouteLocationNormalizedGeneric) => string;
export interface TypedRouteMeta {
backTo?: string;
headerTitleFunc?: HeaderTitleFunc;
headerTitleComponent?: Component;
}
export type RouteMeta = TypedRouteMeta & Record<string, unknown>;
export function useRouteMeta() {
const route = useRoute();
const meta = computed<RouteMeta>(() => route.meta as RouteMeta);
const headerTitle = computed<string | Component>(() => {
if (meta.value.headerTitleFunc) {
return meta.value.headerTitleFunc(route);
} else if (meta.value.headerTitleComponent) {
return meta.value.headerTitleComponent;
}
return String(route.name);
});
const routeNavigateBackTo = computed<string | null>(() => meta.value.backTo ?? null);
return { headerTitle, routeNavigateBackTo };
}

View File

@@ -0,0 +1,36 @@
import { createRouter, createWebHashHistory, type RouteRecordRaw } from "vue-router";
import { PAGE } from "./Page";
import ZoneInspector from "@/view/inspect/ZoneInspector.vue";
import InspectDetails from "@/view/inspect_details/InspectDetails.vue";
import type { RouteMeta } from "./RouteMeta";
import InspectDetailsHeader from "@/view/inspect_details/InspectDetailsHeader.vue";
const ROUTES: RouteRecordRaw[] = [
{
path: "/",
children: [
{
path: "",
name: PAGE.INSPECT.SELECT_ZONE,
component: ZoneInspector,
},
{
path: ":zoneName",
name: PAGE.INSPECT.ZONE_DETAILS,
component: InspectDetails,
meta: {
backTo: PAGE.INSPECT.SELECT_ZONE,
headerTitleComponent: InspectDetailsHeader,
} satisfies RouteMeta,
props: true,
},
],
},
];
export function createModManRouter() {
return createRouter({
history: createWebHashHistory(),
routes: ROUTES,
});
}

View File

@@ -0,0 +1,32 @@
import { computed, ref } from "vue";
import { defineStore } from "pinia";
import type { ZoneAssetsDto } from "@/native/AssetBinds";
import { webviewBinds } from "@/native";
export const useAssetStore = defineStore("asset", () => {
const zoneName = ref<string | null>(null);
const assetsOfZone = ref<ZoneAssetsDto | null>(null);
const isLoading = computed(() => Boolean(assetsOfZone.value));
const assetCount = computed(() => assetsOfZone.value?.assets.length ?? 0);
const referenceCount = computed(() => assetsOfZone.value?.references.length ?? 0);
function loadAssetsForZone(newZoneName: string | null) {
// Skip if assets are already loaded
if (newZoneName === zoneName.value) return;
// Reset current state
zoneName.value = newZoneName;
assetsOfZone.value = null;
// Only load assets when there is a new zone name specified
if (!newZoneName) return;
webviewBinds.getAssetsForZone(newZoneName).then((res) => {
if (zoneName.value === newZoneName) {
assetsOfZone.value = res;
}
});
}
return { zoneName, isLoading, assetsOfZone, assetCount, referenceCount, loadAssetsForZone };
});

View File

@@ -0,0 +1,31 @@
import { ref } from "vue";
import { defineStore } from "pinia";
import { webviewAddEventListener, webviewBinds } from "@/native";
export const useUnlinkingStore = defineStore("unlinking", () => {
const isUnlinking = ref(false);
const lastPercentage = ref<number>(0);
const failureMessage = ref<string | null>(null);
function unlinkZone(zoneName: string) {
isUnlinking.value = true;
lastPercentage.value = 0;
failureMessage.value = null;
return webviewBinds
.unlinkZone(zoneName)
.catch((e: string) => {
console.error("Failed to unlink fastfile:", e);
failureMessage.value = e;
})
.finally(() => {
isUnlinking.value = false;
lastPercentage.value = 100;
});
}
webviewAddEventListener("zoneUnlinkProgress", (dto) => {
lastPercentage.value = dto.percentage;
});
return { isUnlinking, lastPercentage, unlinkZone };
});

View File

@@ -0,0 +1,77 @@
import { computed, ref } from "vue";
import { defineStore } from "pinia";
import { webviewAddEventListener, webviewBinds } from "@/native";
import type { ZoneDto, ZoneLoadedDto } from "@/native/ZoneBinds";
export const useZoneStore = defineStore("zone", () => {
const loadedZones = ref<ZoneDto[]>([]);
const zonesCurrentlyBeingLoaded = ref<string[]>([]);
const lastPercentageByZoneName = ref<Record<string, number>>({});
const isLoadingZone = computed(() => zonesCurrentlyBeingLoaded.value.length > 0);
function loadFastFile(fastFilePath: string): Promise<ZoneLoadedDto> {
const lastDirectorySeparator = fastFilePath.replace(/\\/g, "/").lastIndexOf("/");
const lastDot = fastFilePath.lastIndexOf(".");
const expectedZoneName = fastFilePath.substring(
lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0,
lastDot > lastDirectorySeparator ? lastDot : fastFilePath.length,
);
zonesCurrentlyBeingLoaded.value.push(expectedZoneName);
lastPercentageByZoneName.value[expectedZoneName] = 0;
return webviewBinds
.loadFastFile(fastFilePath)
.catch((e: string) => {
console.error("Failed to load fastfile:", e);
})
.finally(() => {
zonesCurrentlyBeingLoaded.value.splice(
zonesCurrentlyBeingLoaded.value.indexOf(expectedZoneName),
1,
);
delete lastPercentageByZoneName.value[expectedZoneName];
}) as Promise<ZoneLoadedDto>;
}
function getPercentageForZoneBeingLoaded(zoneName: string) {
return lastPercentageByZoneName.value[zoneName] ?? 100;
}
function getLoadedZoneByName(zoneName: string) {
return loadedZones.value.find((zone) => zone.name === zoneName) ?? null;
}
// Initially get all loaded zones
webviewBinds.getZones().then((allZones) => {
loadedZones.value = allZones;
});
webviewAddEventListener("zoneLoadProgress", (dto) => {
if (lastPercentageByZoneName.value[dto.zoneName] !== undefined) {
lastPercentageByZoneName.value[dto.zoneName] = dto.percentage;
}
});
webviewAddEventListener("zoneLoaded", (dto) => {
loadedZones.value.push(dto.zone);
});
webviewAddEventListener("zoneUnloaded", (dto) => {
const index = loadedZones.value.findIndex((zone) => zone.name === dto.zoneName);
if (index >= 0) {
loadedZones.value.splice(index, 1);
}
});
return {
loadedZones,
zonesCurrentlyBeingLoaded,
isLoadingZone,
lastPercentageByZoneName,
loadFastFile,
getPercentageForZoneBeingLoaded,
getLoadedZoneByName,
};
});

View File

@@ -1,12 +0,0 @@
import { ref, computed } from "vue";
import { defineStore } from "pinia";
export const useCounterStore = defineStore("counter", () => {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() {
count.value++;
}
return { count, doubleCount, increment };
});

View File

@@ -0,0 +1,5 @@
@use "colors_primitive";
@forward "colors_primitive";
@use "colors_semantic";
@forward "colors_semantic";

View File

@@ -0,0 +1,278 @@
// Primitive colors are colors that have literal values and they do not change depending on the context.
$color-brand-50: #f7ebdf;
$color-brand-100: #f1dfca;
$color-brand-200: #eaceae;
$color-brand-300: #e0b685;
$color-brand-400: #d59d5d;
$color-brand-500: #cc8838;
$color-brand-600: #b9772c;
$color-brand-700: #784818;
$color-brand-800: #573816;
$color-brand-900: #3d2810;
$color-brand-950: #291b0a;
$color-brand: $color-brand-500;
// These colors are taken from PrimeVue Aura theme
$color-emerald-50: var(--p-emerald-50);
$color-emerald-100: var(--p-emerald-100);
$color-emerald-200: var(--p-emerald-200);
$color-emerald-300: var(--p-emerald-300);
$color-emerald-400: var(--p-emerald-400);
$color-emerald-500: var(--p-emerald-500);
$color-emerald-600: var(--p-emerald-600);
$color-emerald-700: var(--p-emerald-700);
$color-emerald-800: var(--p-emerald-800);
$color-emerald-900: var(--p-emerald-900);
$color-emerald-950: var(--p-emerald-950);
$color-green-50: var(--p-green-50);
$color-green-100: var(--p-green-100);
$color-green-200: var(--p-green-200);
$color-green-300: var(--p-green-300);
$color-green-400: var(--p-green-400);
$color-green-500: var(--p-green-500);
$color-green-600: var(--p-green-600);
$color-green-700: var(--p-green-700);
$color-green-800: var(--p-green-800);
$color-green-900: var(--p-green-900);
$color-green-950: var(--p-green-950);
$color-lime-50: var(--p-lime-50);
$color-lime-100: var(--p-lime-100);
$color-lime-200: var(--p-lime-200);
$color-lime-300: var(--p-lime-300);
$color-lime-400: var(--p-lime-400);
$color-lime-500: var(--p-lime-500);
$color-lime-600: var(--p-lime-600);
$color-lime-700: var(--p-lime-700);
$color-lime-800: var(--p-lime-800);
$color-lime-900: var(--p-lime-900);
$color-lime-950: var(--p-lime-950);
$color-red-50: var(--p-red-50);
$color-red-100: var(--p-red-100);
$color-red-200: var(--p-red-200);
$color-red-300: var(--p-red-300);
$color-red-400: var(--p-red-400);
$color-red-500: var(--p-red-500);
$color-red-600: var(--p-red-600);
$color-red-700: var(--p-red-700);
$color-red-800: var(--p-red-800);
$color-red-900: var(--p-red-900);
$color-red-950: var(--p-red-950);
$color-orange-50: var(--p-orange-50);
$color-orange-100: var(--p-orange-100);
$color-orange-200: var(--p-orange-200);
$color-orange-300: var(--p-orange-300);
$color-orange-400: var(--p-orange-400);
$color-orange-500: var(--p-orange-500);
$color-orange-600: var(--p-orange-600);
$color-orange-700: var(--p-orange-700);
$color-orange-800: var(--p-orange-800);
$color-orange-900: var(--p-orange-900);
$color-orange-950: var(--p-orange-950);
$color-amber-50: var(--p-amber-50);
$color-amber-100: var(--p-amber-100);
$color-amber-200: var(--p-amber-200);
$color-amber-300: var(--p-amber-300);
$color-amber-400: var(--p-amber-400);
$color-amber-500: var(--p-amber-500);
$color-amber-600: var(--p-amber-600);
$color-amber-700: var(--p-amber-700);
$color-amber-800: var(--p-amber-800);
$color-amber-900: var(--p-amber-900);
$color-amber-950: var(--p-amber-950);
$color-yellow-50: var(--p-yellow-50);
$color-yellow-100: var(--p-yellow-100);
$color-yellow-200: var(--p-yellow-200);
$color-yellow-300: var(--p-yellow-300);
$color-yellow-400: var(--p-yellow-400);
$color-yellow-500: var(--p-yellow-500);
$color-yellow-600: var(--p-yellow-600);
$color-yellow-700: var(--p-yellow-700);
$color-yellow-800: var(--p-yellow-800);
$color-yellow-900: var(--p-yellow-900);
$color-yellow-950: var(--p-yellow-950);
$color-teal-50: var(--p-teal-50);
$color-teal-100: var(--p-teal-100);
$color-teal-200: var(--p-teal-200);
$color-teal-300: var(--p-teal-300);
$color-teal-400: var(--p-teal-400);
$color-teal-500: var(--p-teal-500);
$color-teal-600: var(--p-teal-600);
$color-teal-700: var(--p-teal-700);
$color-teal-800: var(--p-teal-800);
$color-teal-900: var(--p-teal-900);
$color-teal-950: var(--p-teal-950);
$color-cyan-50: var(--p-cyan-50);
$color-cyan-100: var(--p-cyan-100);
$color-cyan-200: var(--p-cyan-200);
$color-cyan-300: var(--p-cyan-300);
$color-cyan-400: var(--p-cyan-400);
$color-cyan-500: var(--p-cyan-500);
$color-cyan-600: var(--p-cyan-600);
$color-cyan-700: var(--p-cyan-700);
$color-cyan-800: var(--p-cyan-800);
$color-cyan-900: var(--p-cyan-900);
$color-cyan-950: var(--p-cyan-950);
$color-sky-50: var(--p-sky-50);
$color-sky-100: var(--p-sky-100);
$color-sky-200: var(--p-sky-200);
$color-sky-300: var(--p-sky-300);
$color-sky-400: var(--p-sky-400);
$color-sky-500: var(--p-sky-500);
$color-sky-600: var(--p-sky-600);
$color-sky-700: var(--p-sky-700);
$color-sky-800: var(--p-sky-800);
$color-sky-900: var(--p-sky-900);
$color-sky-950: var(--p-sky-950);
$color-blue-50: var(--p-blue-50);
$color-blue-100: var(--p-blue-100);
$color-blue-200: var(--p-blue-200);
$color-blue-300: var(--p-blue-300);
$color-blue-400: var(--p-blue-400);
$color-blue-500: var(--p-blue-500);
$color-blue-600: var(--p-blue-600);
$color-blue-700: var(--p-blue-700);
$color-blue-800: var(--p-blue-800);
$color-blue-900: var(--p-blue-900);
$color-blue-950: var(--p-blue-950);
$color-indigo-50: var(--p-indigo-50);
$color-indigo-100: var(--p-indigo-100);
$color-indigo-200: var(--p-indigo-200);
$color-indigo-300: var(--p-indigo-300);
$color-indigo-400: var(--p-indigo-400);
$color-indigo-500: var(--p-indigo-500);
$color-indigo-600: var(--p-indigo-600);
$color-indigo-700: var(--p-indigo-700);
$color-indigo-800: var(--p-indigo-800);
$color-indigo-900: var(--p-indigo-900);
$color-indigo-950: var(--p-indigo-950);
$color-violet-50: var(--p-violet-50);
$color-violet-100: var(--p-violet-100);
$color-violet-200: var(--p-violet-200);
$color-violet-300: var(--p-violet-300);
$color-violet-400: var(--p-violet-400);
$color-violet-500: var(--p-violet-500);
$color-violet-600: var(--p-violet-600);
$color-violet-700: var(--p-violet-700);
$color-violet-800: var(--p-violet-800);
$color-violet-900: var(--p-violet-900);
$color-violet-950: var(--p-violet-950);
$color-purple-50: var(--p-purple-50);
$color-purple-100: var(--p-purple-100);
$color-purple-200: var(--p-purple-200);
$color-purple-300: var(--p-purple-300);
$color-purple-400: var(--p-purple-400);
$color-purple-500: var(--p-purple-500);
$color-purple-600: var(--p-purple-600);
$color-purple-700: var(--p-purple-700);
$color-purple-800: var(--p-purple-800);
$color-purple-900: var(--p-purple-900);
$color-purple-950: var(--p-purple-950);
$color-fuchsia-50: var(--p-fuchsia-50);
$color-fuchsia-100: var(--p-fuchsia-100);
$color-fuchsia-200: var(--p-fuchsia-200);
$color-fuchsia-300: var(--p-fuchsia-300);
$color-fuchsia-400: var(--p-fuchsia-400);
$color-fuchsia-500: var(--p-fuchsia-500);
$color-fuchsia-600: var(--p-fuchsia-600);
$color-fuchsia-700: var(--p-fuchsia-700);
$color-fuchsia-800: var(--p-fuchsia-800);
$color-fuchsia-900: var(--p-fuchsia-900);
$color-fuchsia-950: var(--p-fuchsia-950);
$color-pink-50: var(--p-pink-50);
$color-pink-100: var(--p-pink-100);
$color-pink-200: var(--p-pink-200);
$color-pink-300: var(--p-pink-300);
$color-pink-400: var(--p-pink-400);
$color-pink-500: var(--p-pink-500);
$color-pink-600: var(--p-pink-600);
$color-pink-700: var(--p-pink-700);
$color-pink-800: var(--p-pink-800);
$color-pink-900: var(--p-pink-900);
$color-pink-950: var(--p-pink-950);
$color-rose-50: var(--p-rose-50);
$color-rose-100: var(--p-rose-100);
$color-rose-200: var(--p-rose-200);
$color-rose-300: var(--p-rose-300);
$color-rose-400: var(--p-rose-400);
$color-rose-500: var(--p-rose-500);
$color-rose-600: var(--p-rose-600);
$color-rose-700: var(--p-rose-700);
$color-rose-800: var(--p-rose-800);
$color-rose-900: var(--p-rose-900);
$color-rose-950: var(--p-rose-950);
$color-slate-50: var(--p-slate-50);
$color-slate-100: var(--p-slate-100);
$color-slate-200: var(--p-slate-200);
$color-slate-300: var(--p-slate-300);
$color-slate-400: var(--p-slate-400);
$color-slate-500: var(--p-slate-500);
$color-slate-600: var(--p-slate-600);
$color-slate-700: var(--p-slate-700);
$color-slate-800: var(--p-slate-800);
$color-slate-900: var(--p-slate-900);
$color-slate-950: var(--p-slate-950);
$color-gray-50: var(--p-gray-50);
$color-gray-100: var(--p-gray-100);
$color-gray-200: var(--p-gray-200);
$color-gray-300: var(--p-gray-300);
$color-gray-400: var(--p-gray-400);
$color-gray-500: var(--p-gray-500);
$color-gray-600: var(--p-gray-600);
$color-gray-700: var(--p-gray-700);
$color-gray-800: var(--p-gray-800);
$color-gray-900: var(--p-gray-900);
$color-gray-950: var(--p-gray-950);
$color-zinc-50: var(--p-zinc-50);
$color-zinc-100: var(--p-zinc-100);
$color-zinc-200: var(--p-zinc-200);
$color-zinc-300: var(--p-zinc-300);
$color-zinc-400: var(--p-zinc-400);
$color-zinc-500: var(--p-zinc-500);
$color-zinc-600: var(--p-zinc-600);
$color-zinc-700: var(--p-zinc-700);
$color-zinc-800: var(--p-zinc-800);
$color-zinc-900: var(--p-zinc-900);
$color-zinc-950: var(--p-zinc-950);
$color-neutral-50: var(--p-neutral-50);
$color-neutral-100: var(--p-neutral-100);
$color-neutral-200: var(--p-neutral-200);
$color-neutral-300: var(--p-neutral-300);
$color-neutral-400: var(--p-neutral-400);
$color-neutral-500: var(--p-neutral-500);
$color-neutral-600: var(--p-neutral-600);
$color-neutral-700: var(--p-neutral-700);
$color-neutral-800: var(--p-neutral-800);
$color-neutral-900: var(--p-neutral-900);
$color-neutral-950: var(--p-neutral-950);
$color-stone-50: var(--p-stone-50);
$color-stone-100: var(--p-stone-100);
$color-stone-200: var(--p-stone-200);
$color-stone-300: var(--p-stone-300);
$color-stone-400: var(--p-stone-400);
$color-stone-500: var(--p-stone-500);
$color-stone-600: var(--p-stone-600);
$color-stone-700: var(--p-stone-700);
$color-stone-800: var(--p-stone-800);
$color-stone-900: var(--p-stone-900);
$color-stone-950: var(--p-stone-950);

View File

@@ -0,0 +1,28 @@
// Semantic colors are colors that may be different depending on the context.
// They have to be used via CSS variables (which is what the SASS variables access).
$color-surface-0: var(--color-surface-0);
$color-surface-50: var(--color-surface-50);
$color-surface-100: var(--color-surface-100);
$color-surface-200: var(--color-surface-200);
$color-surface-300: var(--color-surface-300);
$color-surface-400: var(--color-surface-400);
$color-surface-500: var(--color-surface-500);
$color-surface-600: var(--color-surface-600);
$color-surface-700: var(--color-surface-700);
$color-surface-800: var(--color-surface-800);
$color-surface-900: var(--color-surface-900);
$color-surface-950: var(--color-surface-950);
$color-primary: var(--color-primary);
$color-primary-contrast: var(--color-primary-contrast);
$color-primary-hover: var(--color-primary-hover);
$color-primary-active: var(--color-primary-active);
$color-content-background: var(--color-content-background);
$color-content-hover-background: var(--color-content-hover-background);
$color-content-border: var(--color-content-border);
$color-text: var(--color-text);
$color-text-hover: var(--color-text-hover);
$color-text-muted: var(--color-text-muted);
$color-text-hover-muted: var(--color-text-hover-muted);

View File

@@ -0,0 +1,7 @@
$border-radius-xs: 0.125rem;
$border-radius-sm: 0.25rem;
$border-radius-md: 0.375rem;
$border-radius-lg: 0.5rem;
$border-radius-xl: 0.75rem;
$transition-duration: 0.2s;

View File

@@ -0,0 +1,68 @@
@use "colors";
@use "colors_primitive";
@use "sass:meta";
@use "sass:string";
:root {
@each $name, $value in meta.module-variables(colors_primitive) {
--#{string.to-lower-case($name)}: #{$value};
}
}
:root:not(.dark) {
--color-surface-0: #ffffff;
--color-surface-50: #{colors.$color-slate-50};
--color-surface-100: #{colors.$color-slate-100};
--color-surface-200: #{colors.$color-slate-200};
--color-surface-300: #{colors.$color-slate-300};
--color-surface-400: #{colors.$color-slate-400};
--color-surface-500: #{colors.$color-slate-500};
--color-surface-600: #{colors.$color-slate-600};
--color-surface-700: #{colors.$color-slate-700};
--color-surface-800: #{colors.$color-slate-800};
--color-surface-900: #{colors.$color-slate-900};
--color-surface-950: #{colors.$color-slate-950};
--color-primary: #{colors.$color-brand-500};
--color-primary-contrast: #ffffff;
--color-primary-hover: var(--color-brand-600);
--color-primary-active: var(--color-brand-700);
--color-content-background: var(--color-surface-0);
--color-content-hover-background: var(--color-surface-100);
--color-content-border: var(--color-surface-200);
--color-text: var(--color-surface-700);
--color-text-hover: var(--color-surface-800);
--color-text-muted: var(--color-surface-500);
--color-text-hover-muted: var(--color-surface-600);
}
:root.dark {
--color-surface-0: #ffffff;
--color-surface-50: #{colors.$color-zinc-50};
--color-surface-100: #{colors.$color-zinc-100};
--color-surface-200: #{colors.$color-zinc-200};
--color-surface-300: #{colors.$color-zinc-300};
--color-surface-400: #{colors.$color-zinc-400};
--color-surface-500: #{colors.$color-zinc-500};
--color-surface-600: #{colors.$color-zinc-600};
--color-surface-700: #{colors.$color-zinc-700};
--color-surface-800: #{colors.$color-zinc-800};
--color-surface-900: #{colors.$color-zinc-900};
--color-surface-950: #{colors.$color-zinc-950};
--color-primary: #{colors.$color-brand-400};
--color-primary-contrast: var(--color-surface-900);
--color-primary-hover: var(--color-brand-300);
--color-primary-active: var(--color-brand-200);
--color-content-background: var(--color-surface-900);
--color-content-hover-background: var(--color-surface-800);
--color-content-border: var(--color-surface-700);
--color-text: var(--color-surface-0);
--color-text-hover: var(--color-surface-0);
--color-text-muted: var(--color-surface-400);
--color-text-hover-muted: var(--color-surface-300);
}

View File

@@ -0,0 +1,36 @@
@use "colors";
@use "css_vars";
@use "utils.scss";
@import "@fontsource/inter/latin-300.css";
@import "@fontsource/inter/latin-300-italic.css";
@import "@fontsource/inter/latin-400.css";
@import "@fontsource/inter/latin-400-italic.css";
@import "@fontsource/inter/latin-600.css";
@import "@fontsource/inter/latin-600-italic.css";
@import "@fontsource/inter/latin-700.css";
@import "@fontsource/inter/latin-700-italic.css";
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: colors.$color-text;
background-color: colors.$color-content-background;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
}

View File

@@ -0,0 +1,3 @@
.icon {
height: 1lh;
}

View File

@@ -0,0 +1,85 @@
import { CommonAssetType } from "@/native/AssetBinds";
const LOOKUP_CAPITALIZED: Record<CommonAssetType, string> = {
[CommonAssetType.PHYS_PRESET]: "Phys preset",
[CommonAssetType.XANIM]: "XAnim",
[CommonAssetType.XMODEL]: "XModel",
[CommonAssetType.MATERIAL]: "Material",
[CommonAssetType.TECHNIQUE_SET]: "Technique set",
[CommonAssetType.IMAGE]: "Image",
[CommonAssetType.SOUND]: "Sound",
[CommonAssetType.SOUND_CURVE]: "Sound curve",
[CommonAssetType.LOADED_SOUND]: "Loaded sound",
[CommonAssetType.CLIP_MAP]: "Clip map",
[CommonAssetType.COM_WORLD]: "Com world",
[CommonAssetType.GAME_WORLD_SP]: "Game world SP",
[CommonAssetType.GAME_WORLD_MP]: "Game world MP",
[CommonAssetType.MAP_ENTS]: "Map ents",
[CommonAssetType.GFX_WORLD]: "Gfx world",
[CommonAssetType.LIGHT_DEF]: "Light def",
[CommonAssetType.UI_MAP]: "UI map",
[CommonAssetType.FONT]: "Font",
[CommonAssetType.MENU_LIST]: "Menu list",
[CommonAssetType.MENU]: "Menu",
[CommonAssetType.LOCALIZE_ENTRY]: "Localize entry",
[CommonAssetType.WEAPON]: "Weapon",
[CommonAssetType.SOUND_DRIVER_GLOBALS]: "Sound driver globals",
[CommonAssetType.FX]: "FX",
[CommonAssetType.IMPACT_FX]: "Impact FX",
[CommonAssetType.AI_TYPE]: "AI type",
[CommonAssetType.MP_TYPE]: "MP type",
[CommonAssetType.CHARACTER]: "Character",
[CommonAssetType.XMODEL_ALIAS]: "XModel alias",
[CommonAssetType.RAW_FILE]: "Raw file",
[CommonAssetType.STRING_TABLE]: "String table",
[CommonAssetType.XMODEL_PIECES]: "XModel pieces",
[CommonAssetType.PHYS_COLL_MAP]: "Phys coll map",
[CommonAssetType.XMODEL_SURFS]: "XModel surfs",
[CommonAssetType.PIXEL_SHADER]: "Pixel shader",
[CommonAssetType.VERTEX_SHADER]: "Vertex shader",
[CommonAssetType.VERTEX_DECL]: "Vertex decl",
[CommonAssetType.FX_WORLD]: "FX world",
[CommonAssetType.LEADERBOARD]: "Leaderboard",
[CommonAssetType.STRUCTURED_DATA_DEF]: "Structured data def",
[CommonAssetType.TRACER]: "Tracer",
[CommonAssetType.VEHICLE]: "Vehicle",
[CommonAssetType.ADDON_MAP_ENTS]: "Addon map ents",
[CommonAssetType.GLASS_WORLD]: "Glass world",
[CommonAssetType.PATH_DATA]: "Path data",
[CommonAssetType.VEHICLE_TRACK]: "Vehicle track",
[CommonAssetType.ATTACHMENT]: "Attachment",
[CommonAssetType.SURFACE_FX]: "Surface FX",
[CommonAssetType.SCRIPT]: "Script",
[CommonAssetType.PHYS_CONSTRAINTS]: "Phys constraints",
[CommonAssetType.DESTRUCTIBLE_DEF]: "Destructible def",
[CommonAssetType.SOUND_PATCH]: "Sound patch",
[CommonAssetType.WEAPON_DEF]: "Weapon def",
[CommonAssetType.WEAPON_VARIANT]: "Weapon variant",
[CommonAssetType.MP_BODY]: "MP body",
[CommonAssetType.MP_HEAD]: "MP head",
[CommonAssetType.PACK_INDEX]: "Pack index",
[CommonAssetType.XGLOBALS]: "XGlobals",
[CommonAssetType.DDL]: "DDL",
[CommonAssetType.GLASSES]: "Glasses",
[CommonAssetType.EMBLEM_SET]: "Emblem set",
[CommonAssetType.FONT_ICON]: "Font icon",
[CommonAssetType.WEAPON_FULL]: "Weapon full",
[CommonAssetType.ATTACHMENT_UNIQUE]: "Attachment unique",
[CommonAssetType.WEAPON_CAMO]: "Weapon camo",
[CommonAssetType.KEY_VALUE_PAIRS]: "Key value pairs",
[CommonAssetType.MEMORY_BLOCK]: "Memory block",
[CommonAssetType.SKINNED_VERTS]: "Skinned verts",
[CommonAssetType.QDB]: "Qdb",
[CommonAssetType.SLUG]: "Slug",
[CommonAssetType.FOOTSTEP_TABLE]: "Footstep table",
[CommonAssetType.FOOTSTEP_FX_TABLE]: "Footstep FX table",
[CommonAssetType.ZBARRIER]: "ZBarrier",
};
export function getAssetTypeNameCapitalized(assetType: CommonAssetType): string {
return LOOKUP_CAPITALIZED[assetType];
}
export function getAssetTypeNameLower(assetType: CommonAssetType): string {
return getAssetTypeNameCapitalized(assetType).toLocaleLowerCase();
}

View File

@@ -0,0 +1,52 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { useZoneStore } from "@/stores/ZoneStore";
import ZoneInspectorDetails from "./ZoneInspectorDetails.vue";
import ZoneInspectorZoneList from "./ZoneInspectorZoneList.vue";
import { useAssetStore } from "@/stores/AssetStore";
const assetStore = useAssetStore();
const zoneStore = useZoneStore();
const selectedZone = ref<string | null>(null);
// Make sure we preselect the zone that was last loaded assets for
// if there is one
if (
assetStore.zoneName &&
zoneStore.loadedZones.findIndex((zone) => zone.name === assetStore.zoneName) >= 0
) {
selectedZone.value = assetStore.zoneName;
}
watch(
() => zoneStore.loadedZones,
(newValue) => {
// Reset selection if unloaded
if (!selectedZone.value) return;
if (newValue.findIndex((loadedZone) => loadedZone.name === selectedZone.value) >= 0) return;
selectedZone.value = null;
},
{ deep: true },
);
</script>
<template>
<div class="zone-inspector">
<ZoneInspectorZoneList v-model:selected-zone="selectedZone" />
<ZoneInspectorDetails :selected-zone="selectedZone" />
</div>
</template>
<style lang="scss" scoped>
.zone-inspector {
display: flex;
flex-direction: row;
width: 100%;
& > * {
width: 50%;
padding: 1rem 2rem;
}
}
</style>

View File

@@ -0,0 +1,151 @@
<script setup lang="ts">
import Button from "primevue/button";
import Tag from "primevue/tag";
import MeterGroup, { type MeterItem } from "primevue/metergroup";
import Skeleton from "primevue/skeleton";
import { dt } from "@primeuix/themes";
import type { ZoneDto } from "@/native/ZoneBinds";
import { useZoneStore } from "@/stores/ZoneStore";
import { computed, watch } from "vue";
import type { CommonAssetType } from "@/native/AssetBinds";
import { getAssetTypeNameCapitalized } from "@/utils/AssetTypeName";
import { useRouter } from "vue-router";
import { PAGE } from "@/router/Page";
import { useAssetStore } from "@/stores/AssetStore";
import { storeToRefs } from "pinia";
const assetStore = useAssetStore();
const zoneStore = useZoneStore();
const { assetsOfZone, assetCount, referenceCount } = storeToRefs(assetStore);
const props = defineProps<{
selectedZone: string | null;
}>();
const METER_COLORS = [
dt("blue.600"),
dt("red.600"),
dt("yellow.600"),
dt("green.600"),
dt("purple.600"),
dt("orange.600"),
dt("teal.600"),
dt("lime.600"),
dt("pink.600"),
];
const meterItems = computed<MeterItem[]>(() => {
const assetTypeCounts: Partial<Record<CommonAssetType, number>> = {};
for (const asset of assetsOfZone.value?.assets ?? []) {
if (!assetTypeCounts[asset.type]) {
assetTypeCounts[asset.type] = 1;
} else {
assetTypeCounts[asset.type]!++;
}
}
// Do not display asset types with under 3 percent representation
const minItemCountForDisplay = Math.floor(assetCount.value * 0.03);
const assetMeterItems: MeterItem[] = Object.entries(assetTypeCounts)
.filter((entry) => entry[1] > minItemCountForDisplay)
.sort((e0, e1) => e1[1] - e0[1])
.map((entry) => ({
label: getAssetTypeNameCapitalized(entry[0] as CommonAssetType),
value: Math.round((entry[1] / assetCount.value) * 100),
}));
// Since the PrimeVue component rounds to percent we want to fill up the bar completely
const otherCount = 100 - assetMeterItems.reduce((val, entry) => val + entry.value, 0);
if (otherCount > 0) {
assetMeterItems.push({
label: "Other",
value: otherCount,
});
}
return assetMeterItems.map(
(item, index) =>
({
...item,
color: METER_COLORS[index % METER_COLORS.length],
}) satisfies MeterItem,
);
});
const selectedZoneDetails = computed<ZoneDto | null>(() =>
props.selectedZone ? zoneStore.getLoadedZoneByName(props.selectedZone) : null,
);
const router = useRouter();
function onClickShowAssets() {
if (!props.selectedZone) return;
router.push({
name: PAGE.INSPECT.ZONE_DETAILS,
params: {
zoneName: props.selectedZone,
},
});
}
watch(
() => props.selectedZone,
(newValue) => {
assetStore.loadAssetsForZone(newValue);
},
{ immediate: true },
);
</script>
<template>
<div class="zone-details">
<h2>{{ selectedZone ?? "No zone selected" }}</h2>
<Button label="Show assets" :disabled="!selectedZone" @click="onClickShowAssets" />
<div v-if="selectedZoneDetails" class="zone-tags">
<Tag :value="selectedZoneDetails.game" />
<Tag :value="selectedZoneDetails.platform" />
</div>
<div class="zone-assets">
<template v-if="assetsOfZone">
<div>{{ assetCount }} assets</div>
<div>{{ referenceCount }} references</div>
<MeterGroup class="asset-meter" :value="meterItems" />
</template>
<template v-else-if="selectedZone">
<Skeleton class="count-skeleton" width="10em" />
<Skeleton class="count-skeleton" width="10em" />
<Skeleton class="count-skeleton" height="0.5lh" />
</template>
</div>
</div>
</template>
<style lang="scss" scoped>
.zone-tags {
display: flex;
margin-top: 0.5em;
column-gap: 0.5em;
row-gap: 0.5em;
}
.zone-assets {
margin-top: 0.5em;
display: flex;
flex-direction: column;
}
.asset-meter {
padding-top: 0.5em;
font-size: 0.8em;
line-height: 1.25;
--p-metergroup-gap: 1em;
--p-metergroup-label-list-horizontal-gap: 0.8em;
}
.count-skeleton {
margin-bottom: 0.5em;
}
</style>

View File

@@ -0,0 +1,111 @@
<script setup lang="ts">
import Button from "primevue/button";
import ProgressBar from "primevue/progressbar";
import Listbox from "primevue/listbox";
import { computed } from "vue";
import { useZoneStore } from "@/stores/ZoneStore";
import { webviewBinds } from "@/native";
interface SelectableZone {
isLoading: boolean;
zoneName: string;
}
const zoneStore = useZoneStore();
const selectedZone = defineModel<string | null>("selectedZone");
async function openFastFileSelect() {
return await webviewBinds.openFileDialog({ filters: [{ name: "Fastfiles", filter: "*.ff" }] });
}
async function onOpenFastFileClick() {
const fastFilePath = await openFastFileSelect();
if (!fastFilePath) return;
zoneStore.loadFastFile(fastFilePath);
}
const availableZones = computed<SelectableZone[]>(() => {
const result = [
...zoneStore.zonesCurrentlyBeingLoaded.map(
(zoneBeingLoaded) =>
({
isLoading: true,
zoneName: zoneBeingLoaded,
}) satisfies SelectableZone,
),
...zoneStore.loadedZones.map(
(loadedZone) =>
({
isLoading: false,
zoneName: loadedZone.name,
}) satisfies SelectableZone,
),
];
result.sort((a, b) => a.zoneName.localeCompare(b.zoneName));
return result;
});
function onUnloadClicked() {
if (!selectedZone.value) return;
webviewBinds.unloadZone(selectedZone.value).catch((e: string) => {
console.error("Failed to unload zone:", e);
});
}
</script>
<template>
<div class="zone-list">
<div class="zone-list-actions">
<Button label="Load fastfile" @click="onOpenFastFileClick" />
<Button label="Unload" :disabled="!selectedZone" @click="onUnloadClicked" />
</div>
<Listbox
v-model="selectedZone"
:options="availableZones"
option-disabled="isLoading"
option-value="zoneName"
data-key="zoneName"
emptyMessage="No zones loaded"
class="zone"
>
<template #option="{ option }: { option: SelectableZone }">
<div class="selectable-zone">
<span>{{ option.zoneName }}</span>
<ProgressBar
v-if="option.isLoading"
class="zone-progressbar"
:value="zoneStore.getPercentageForZoneBeingLoaded(option.zoneName)"
:show-value="false"
/>
</div>
</template>
</Listbox>
</div>
</template>
<style lang="scss" scoped>
.zone-list-actions {
display: flex;
column-gap: 0.25em;
row-gap: 0.25em;
padding: 0.25em 0;
}
.selectable-zone {
position: relative;
text-align: left;
width: 100%;
}
.zone-progressbar {
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 0.2em;
}
</style>

View File

@@ -0,0 +1,87 @@
<script setup lang="ts">
import Skeleton from "primevue/skeleton";
import { useAssetStore } from "@/stores/AssetStore";
import { storeToRefs } from "pinia";
import { ref, watch } from "vue";
import InspectPreview from "./components/InspectPreview.vue";
import InspectAssetDetails from "./components/InspectAssetDetails.vue";
import InspectZoneAssets from "./components/InspectZoneAssets.vue";
import type { AssetDto } from "@/native/AssetBinds.ts";
const assetStore = useAssetStore();
const { assetsOfZone } = storeToRefs(assetStore);
const props = defineProps<{
zoneName: string;
}>();
const selectedAsset = ref<AssetDto | null>(null);
watch(
() => props.zoneName,
(newValue) => {
assetStore.loadAssetsForZone(newValue);
},
{ immediate: true },
);
</script>
<template>
<div class="inspect-details">
<template v-if="assetsOfZone">
<InspectPreview class="inspect-area-preview" />
<InspectAssetDetails :selected-asset="selectedAsset" class="inspect-area-details" />
<InspectZoneAssets
v-model:selected-asset="selectedAsset"
:assets="assetsOfZone.assets"
class="inspect-area-assets"
/>
</template>
<template v-else>
<div class="skeleton-wrapper">
<Skeleton class="count-skeleton" width="100%" height="100%" />
</div>
<div class="skeleton-wrapper">
<Skeleton class="count-skeleton" width="100%" height="100%" />
</div>
<div class="skeleton-wrapper list">
<Skeleton v-for="i in 3" :key="i" class="count-skeleton" width="80%" height="1em" />
</div>
</template>
</div>
</template>
<style lang="scss" scoped>
.inspect-details {
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 30% 1fr;
grid-template-rows: 30% 1fr;
grid-template-areas:
"preview details"
"assets details";
gap: 0.5rem;
padding: 0.5rem;
}
.inspect-area-preview {
grid-area: preview;
}
.inspect-area-details {
grid-area: details;
}
.inspect-area-assets {
grid-area: assets;
}
.skeleton-wrapper {
&.list > *:not(:first-child) {
margin-top: calc(1lh - 1em);
}
}
</style>

View File

@@ -0,0 +1,31 @@
<script setup lang="ts">
import { computed } from "vue";
import type { ZoneDto } from "@/native/ZoneBinds.ts";
import { useZoneStore } from "@/stores/ZoneStore.ts";
import Tag from "primevue/tag";
const zoneStore = useZoneStore();
const props = defineProps<{
zoneName: string;
}>();
const zoneDetails = computed<ZoneDto | null>(() =>
props.zoneName ? zoneStore.getLoadedZoneByName(props.zoneName) : null,
);
</script>
<template>
<span>
<span>Inspect zone: {{ zoneName }}</span>
<template v-if="zoneDetails">
<Tag class="zone-header-tag" :value="zoneDetails.game" severity="secondary" />
<Tag class="zone-header-tag" :value="zoneDetails.platform" severity="secondary" />
</template>
</span>
</template>
<style scoped lang="scss">
.zone-header-tag {
margin-left: 0.5em;
}
</style>

View File

@@ -0,0 +1,37 @@
<script setup lang="ts">
import type { AssetDto } from "@/native/AssetBinds.ts";
import { getAssetTypeNameCapitalized } from "@/utils/AssetTypeName.ts";
defineProps<{
asset: AssetDto;
}>();
</script>
<template>
<div class="asset-option">
<span class="asset-type">{{ getAssetTypeNameCapitalized(asset.type) }}</span>
<span class="asset-name">{{ asset.name }}</span>
</div>
</template>
<style scoped lang="scss">
@use "@style/colors";
.asset-option {
font-size: 0.95rem;
max-width: 100%;
overflow-x: clip;
white-space: nowrap;
padding: 0 0.2em;
}
.asset-type {
display: inline-block;
color: colors.$color-text-muted;
min-width: 8em;
}
.asset-name {
margin-left: 0.5em;
}
</style>

Some files were not shown because too many files have changed in this diff Show More