From bca4890b0abbd8ec9d5da05b27a0eb54b0ae4c61 Mon Sep 17 00:00:00 2001 From: Luis Michaelis Date: Wed, 13 Sep 2023 16:44:18 +0200 Subject: [PATCH] refactor(v2): step 3: update documentation --- assets/logo-square.png | Bin 0 -> 5142 bytes assets/logo-square.svg | 120 ++++++++++++ assets/logo.png | Bin 16792 -> 15383 bytes assets/logo.svg | 205 +++++++-------------- changelog.md | 6 +- contributing.md | 21 +-- docs/assets/logo-small.png | Bin 3611 -> 0 bytes docs/assets/logo-square.png | Bin 0 -> 5142 bytes docs/assets/logo.png | Bin 21 -> 15383 bytes docs/engine/formats/archive.md | 20 +- docs/index.md | 10 +- docs/library/api/archive.md | 215 ++++++++++++++++++++++ docs/library/api/cutscene-library.md | 62 +++++++ docs/library/api/daedalus-script.md | 60 ++++++ docs/library/api/daedalus-vm.md | 0 docs/library/api/font.md | 178 ++++++++++++++++++ docs/library/api/mesh.md | 58 ++++++ docs/library/api/model-animation.md | 72 ++++++++ docs/library/api/model-hierarchy.md | 57 ++++++ docs/library/api/model-mesh.md | 57 ++++++ docs/library/api/model-script.md | 57 ++++++ docs/library/api/model.md | 60 ++++++ docs/library/api/morph-mesh.md | 57 ++++++ docs/library/api/multi-resolution-mesh.md | 57 ++++++ docs/library/api/save-game.md | 34 ++++ docs/library/api/texture.md | 57 ++++++ docs/library/api/virtual-file-system.md | 86 +++++++++ docs/library/api/world.md | 57 ++++++ docs/library/formats/animation.md | 29 --- docs/library/formats/archive.md | 167 ----------------- docs/library/formats/font.md | 64 ------- docs/library/formats/vdf.md | 88 --------- docs/library/overview.md | 16 +- docs/library/quickstart.md | 152 +++++++-------- docs/library/reference.md | 77 ++++---- include/zenkit/Archive.hh | 72 +++++--- include/zenkit/CutsceneLibrary.hh | 4 +- include/zenkit/DaedalusVm.hh | 2 +- include/zenkit/Font.hh | 6 +- include/zenkit/Logger.hh | 4 +- include/zenkit/Material.hh | 2 +- include/zenkit/Mesh.hh | 4 +- include/zenkit/Model.hh | 10 +- include/zenkit/ModelAnimation.hh | 4 +- include/zenkit/ModelHierarchy.hh | 4 +- include/zenkit/ModelMesh.hh | 4 +- include/zenkit/MorphMesh.hh | 4 +- include/zenkit/MultiResolutionMesh.hh | 6 +- include/zenkit/SoftSkinMesh.hh | 4 +- include/zenkit/Stream.hh | 3 +- include/zenkit/Texture.hh | 4 +- include/zenkit/World.hh | 16 +- include/zenkit/vobs/VirtualObject.hh | 8 +- mkdocs.yml | 35 +++- readme.md | 192 ++++++++++--------- src/Archive.cc | 5 + src/Logger.cc | 3 - src/Mesh.cc | 2 +- src/ModelScriptDsl.hh | 2 +- src/archive/ArchiveBinsafe.hh | 2 +- 60 files changed, 1804 insertions(+), 797 deletions(-) create mode 100644 assets/logo-square.png create mode 100644 assets/logo-square.svg delete mode 100644 docs/assets/logo-small.png create mode 100644 docs/assets/logo-square.png mode change 120000 => 100644 docs/assets/logo.png create mode 100644 docs/library/api/archive.md create mode 100644 docs/library/api/cutscene-library.md create mode 100644 docs/library/api/daedalus-script.md create mode 100644 docs/library/api/daedalus-vm.md create mode 100644 docs/library/api/font.md create mode 100644 docs/library/api/mesh.md create mode 100644 docs/library/api/model-animation.md create mode 100644 docs/library/api/model-hierarchy.md create mode 100644 docs/library/api/model-mesh.md create mode 100644 docs/library/api/model-script.md create mode 100644 docs/library/api/model.md create mode 100644 docs/library/api/morph-mesh.md create mode 100644 docs/library/api/multi-resolution-mesh.md create mode 100644 docs/library/api/save-game.md create mode 100644 docs/library/api/texture.md create mode 100644 docs/library/api/virtual-file-system.md create mode 100644 docs/library/api/world.md delete mode 100644 docs/library/formats/animation.md delete mode 100644 docs/library/formats/archive.md delete mode 100644 docs/library/formats/font.md delete mode 100644 docs/library/formats/vdf.md diff --git a/assets/logo-square.png b/assets/logo-square.png new file mode 100644 index 0000000000000000000000000000000000000000..b03cdae9f5d7472837bf5579535f19a69e629313 GIT binary patch literal 5142 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 + + + + + + + + + + + + ZK + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/logo.png b/assets/logo.png index 99e9c13c322793355dadc5476bcf8bb6a67c81cc..b5dc49035c9d6d53b70baef4d43efefaf76d088d 100644 GIT binary patch literal 15383 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 gtW!DSi|-^Md|$HD)U zqf+~7n^Qd3*3PQvLK3NRw}53?AN9(Khw7jfz7MjrO_xyy#0DQx81c@1PY>D?>nlLT zfwv#Y{^10uD7`loT_T$`eYVyR36?DCgwbj+T`**Y1t&KTW~+Q8hgP3~y*=p`leT^B zz8E3oLgS93keqNxV+PtJhXWxH?*=fQ{4ib^b zdq)e@pV4q6sp~mw8Z?-(;vKuZAmFG^$BVMT>1{WjD=m?piX9%lkC65HEJJaJ=LdTK z>F!OVB*naA*yQEin(rKOBt}NVk0)3`MLBq7xiw-w>qS5f#!y!>KuACluvBnJFE#v1 z9J!K77b*~e16rV_)83$JQP^G`*U?}e(hGkbx|kY^nNSHsruAxr2q;j{rl+sx?-&8R zt}vvdBG$JXuw>jNPQrlPGnl0SBbru@OFb!TFwL29sOWGmTbR=t%k3gvoFCikUz&}b zdm#y+F}~X3?C>2MvjipuI2H!SSZLVZ zGL9rw=Lc^5JQ+}CXXWXvKhT zuG*^;27FmRosAVz-8Hu;(91T|eY}EqAYb;aGou`akqGrp!d*x1crnKjtQcrt*X_lT zlN)^QRp;)h$Oar5KwFl-(!_(&x%Yd-RI(2xj#=VMO?>f$4JP-8*jY5d1tWm0X#GnmT-(54<~QN?`w1ql>~$*Tj#5H|qG^pBld0{S9; zQ7yaA=`vy}n+cBb1J?d|H8svGMSxD>NBN;!&N^wR3AmZ)CRC_8l1v@MSGafm1JLD$ zK$liN854XKrhV{wcPJTP!0Ox81Wc$V$V1e7BF%ICCh_yy3A(vZ$F-b$89hCcpf)B0 zqR&Vn17&Ka=bZ})P|0+QtxKU;8v7kX>^+rniONBO@=-CMj_#>|isB~{zj!3svM3&O z@XeK#<^tQ$i&w4p=mVwzFx>Hggi?1bev=-U|Io*G^VJ@5u>3_ayX1cmtfg&k+hg9_ z?8pn~F{6>lWG$W%Uk^gfCz)%0(0`xR21*8YQ@)_r+aQV*sI>Rl^=o?;}D`K9^IM?KGUT++I5*1x8LP^<(E4n%coc=H;I zN;JyF{LlkPEq>pe%>+mjG5?cbPJx*7xFPanC-_wdolAR6a(wYU9zFbX>w*hxGWpU6 z(G176;2(hCWG5s`a#ZvlZCN!x?o{MT*}0!@V$#s#y>}l5sw_H_odT1D%rEl*e%qgt zmF33Qcf~K$5UQ-&TJ6VYrSApuyH=N@4yaHI!>TdY=x6Q=Q5G^8b9=bhh|dYLPBiac zC5G#Yt?15OPz(V0gaWWeZ1R5TM32rzAbsQt9+fCiaR=4Tm3;t%>{%3ld4yE?SA(r>SHO0$xdwmvA`qk61V(s3~^7K2LJjZcxc-LsL$HxRRZ{?yC0kZ9!H4&nBO8KYrHBHr%0%W#-o}|0p3y+z0 z9o}AGUwid?lSRO9X~)CNE?UdXj`$Xsi)+THs`~+H z!JQ88reF|#Ev+(D(|BsOsz6fct=d#%+=nV2K*T4Q1qG#fElb}{VAs=C9YX92{j7g) zc!)-73z#vwx9?ZR$?!A%p0aG0+T8;)Y&JN0SY+0q=eyVac}RcI%2j6RV91Apc54o| zFb9baE`?W(aTGJjhbeNSrKT*i1oo;tgKT@O9ri|-n`ILy$sKTi$2FxxAbiRTI-2~x z8dS`ZmpFfayYS|hLcqP~0Ikf{QD9v@qghH8kl>su;)lu2L+pfT4ROvuIK2cVi%ibl zu+Oa2W;X^>@0{U_t2LS~fnVfS)cAk#ivpp{xYxj%Zlw#!SMZn2@%G|e^KlSKqY2Jg zFIIS7zY1!nB7>xL7I(tKD^BZyTAD4@s}9YH7g!%_X;E5QyJzB&VEetatNHkNu=5nN zY<0s*s+2JRK7kwwz8K|MC++b#%&GoPUrby+kR3kJx!{+Cd!(}OwVq2G&9~*FrmiC* z@1>c>=D$d8mW{3{XaKra;yGM^*@}?3nefvh6EPx>OOTUN7%cb_1RWs>-{**bjm4xF z+=tKz)skV)Z&&t{aCLs=hEUJw3EyLk1ptuwX1u~okd|0R1MFk* zi1P?;S*-@RG}PRW4}qy2K!2Q~$P&)z9TgeYzs7@}HxKgv?;>S7M_13fAoKILoOw*Y zg|(;W-;;@=VI7F4x5vO4h+J9hX7x`TN&S=3&2#Z4z*dd1JLNAFN_Ez()IR`AR%;aU)+T<{AqNT^w4@rhQ_M<)(@k^8n(62N|CBz-C=Y6hteqYgD@> zPS%6TgH2c9gv|v#*;8KT;mUBY>tkUYV^yZk&GBV{EdbUc0yg&1je9gBVxanI2=FQ( z2i!yPQEIp5M-2`dNPAoYP-{aEbyj*_VE2N@$PNdSF zSgw!ht5Zw4;&93OhD`p7b*(|*?m)hEy-eqq)n7u}i-X04Wk5pkZwJPj|KjyX%}@jQ zR2)LXfflr+|Bg7lV%S)mHI@N(xmwGQr`7p8{e>g~woJey0Y$zPasF-dtWeZF6#(t) zRdyGlrbJ(bHz3%D^FFeDlF)P*TrI$a>rNa#dl5+KWL*T*^4)yr)Th!%zarZKzT_l% zK~*=5nOVF5o0y2XQi))Do|pk7gv}Ijdws1wov16Kiiig}HbeDy7J|B8i4MS z@LPE~=rWOI%buDIilye`39^hNoJY4JVJM&qjnX23&g^-~)8}Qzs1DG`f^OI8uXAg9 z&s_1Khn5DUfK`bBO*PQ@(yz8yL5dUo2D92+4OYY7l}GpvR+| zA5Z~cf&-uf3UYed9Y-t70q0Qd@f+^`@K5Pepc2Mrt$gx{lSwMm)$N;4@>LUCNMhIE z4V1<^5%tAQrT+^_TlKC-9}nC^@4uvQ_36| z+g-2bbE;(2(tS{zVt^7V&aTGNDS0Bf>T}~J;KbnUoo2?7^wh9MDhCi8-y6ZpJG8Tl z&P=}FdOwsyCRslBx_kBN)tt^LTNIhqFu^xywD=Vyxd#I2tslpAxo=k4aBbq89ZTzr zaxHE^!?rp*2nEuZ&oZ3^2o0ILc}tZuZ0Tfl6bs#9q9>=;OY7^%%5!(+&n| znn|>>2gBLAb5&avjOHm>R-8R;x-GNvBaik*gVqwwqni|yS~U}f1Lk#m4(Az+oXA?6 zYY2k@HC#{zH91nL3XqJpK?m{9HTH=d6Kj;88Gz7Arp$0d#j)oBUmJR%CmO1N$M$a2 zi93`S;K&Q!UBb2kP@?zjnFbOeaRW$V1WssV`H<#&>dmq~jh&D_7`{uyvgDm(+`!w- z7}P!Ts0X@ixPO|HzO ziwour8TNXu3z3XzvE$Bqpn0H>(t~;ygvZJO-=v;* zcE{3o+tm7E61m<;e?T_<{kX-z_lT?eGwEcWlD`{}{MAW3vKOe&wGG1I3)eYCDB~`w zyl?9O7vfuMCcXYk1F&Aj8=DA-5d+1i$=IyFLGc9}eJR@ctt7{0g@iN-F!_FG*-CLT zARUhm8#y5c2=9=3GoeO%No=`KuK``x$w#1qy}$eKux*?;HH--riPQG>ds!pusId_x zs(P-`&imf$23cuu9PsZ(G>b-I;VihDvNK6rVAd{dXkt9AG2#bf?*ZQPdJNf#@p9|3H6zx^Yap+kQB5M9IlwP{KJ4meVK;U*Ime*HG=FI=1S@%}-Dy=$6Ff?mg`U zECK982$&eRV5cGG2%J|l2&fHV{%u`-1zfoe874NX7}E2cM_s=>X@*nV9sx(q1Ert2z4(F@;d1MZyE)uIVP3P{`AmqfqL%>@YH{w{{PeCkQx3T wIvx+#&AkmqD}?^P0RImT`~TihTgO!2eFF_%JW&MVQE - + width="297mm" + height="69.660347mm" + viewBox="0 0 297 69.660344" + version="1.1" + id="svg5" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#"> + id="defs2" /> + id="layer2"> + style="fill:#282828;fill-opacity:1;stroke-width:0.254014" + id="rect996" + width="69.660347" + height="297" + x="-1.4210855e-14" + y="-297" + transform="rotate(90)" /> + id="layer1" + transform="translate(1.9510155e-6,0.00221184)"> + id="g331" + transform="translate(7.1391298,-0.3108601)"> + id="g1557" + transform="matrix(0.69600241,0,0,0.69600241,58.954704,-6.843108)"> ZenGin file parsing for modern C++ PHOENIX + y="45.645706">ZENKIT + style="fill:#ebdbb2;stroke-width:0.264583" + id="path933" + d="m 52.4455,288.5155 -11.411483,6.58843 v -13.17685 z" + transform="matrix(0,0.46985979,-0.43605585,0,315.03113,48.563945)" /> + id="g1576" + transform="matrix(0.69600241,0,0,0.69600241,72.40374,-6.0347703)"> + style="fill:#d79921;fill-opacity:1;stroke:none;stroke-width:0.279762" + id="path5979" + d="M 207.49882,74.501764 180.19015,56.87416 207.49882,39.246556 Z" /> + style="fill:#cc241d;fill-opacity:1;stroke:none;stroke-width:0.279762" + id="path5979-6" + d="M 13.903049,39.246556 41.211717,56.87416 13.90305,74.501764 Z" /> + id="metadata3526"> + rdf:about=""> + rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" /> + rdf:about="https://creativecommons.org/licenses/by-nc/4.0/"> + rdf:resource="http://creativecommons.org/ns#Reproduction" /> + rdf:resource="http://creativecommons.org/ns#Distribution" /> + rdf:resource="http://creativecommons.org/ns#Notice" /> + rdf:resource="http://creativecommons.org/ns#Attribution" /> + rdf:resource="http://creativecommons.org/ns#CommercialUse" /> + rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> diff --git a/changelog.md b/changelog.md index 26beb135..11e2d05e 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,7 @@ -# changelog +# Changelog -This file contains all changes made to _phoenix_ in reverse order, meaning the newest change is listed first. This -file is updated whenever a new version of _phoenix_ is released. More information about how versioning works can be +This file contains all changes made to _ZenKit_ in reverse order, meaning the newest change is listed first. This +file is updated whenever a new version of _ZenKit is released. More information about how versioning works can be found in [readme.md](readme.md#versioning). --- diff --git a/contributing.md b/contributing.md index b5fe056c..b5e00f0d 100644 --- a/contributing.md +++ b/contributing.md @@ -1,32 +1,25 @@ -# how to contribute to _phoenix_ +# How to contribute to _ZenKit_ If you'd like to add a feature or fix a bug, you're more than welcome to help! You can grab one of the open issues and start to work on it, too. -There are some conventions I'd ask you to keep in mind while writing code for _phoenix_. I've outlined them below. +There are some conventions I'd ask you to keep in mind while writing code for _ZenKit_. I've outlined them below. -## conventions +## Conventions There are a few things that should be followed to keep the codebase consistent. -### git +### Git -Your commits should be small. Avoid bundling multiple changes relating (for example) multiple different issues or bug +Your commits should be small. Avoid bundling multiple changes relating to (for example) multiple different issues or bugs into one commit. Rather, create multiple smaller commits, each self-contained only changing one part of the logic[^1]. -Git commits should follow roughly the following format: `: `. If you're working on a tool, -you can use `tools: : `. If your commit affects something about the build system or other -non-C++ related things, use `project: `. Take a look at existing commits to get a better idea as to -what I mean. - -[^2]: For example, if there is an issue with the VM and you change something, then add a new API to the `script`, +[^1]: For example, if there is an issue with the VM and you change something, then add a new API to the `DaedalusScript`, those should be two separate commits. ### C++ code -_phoenix_ roughly follows the naming convention of the standard library. This includes: - -* Class names are `snake_case` +* Class names are `PascalCase` * Function names are `snake_case` and private member functions are prefixed with an underscore * Member variables are `snake_case` and private member variables are prefixed with `_m_` diff --git a/docs/assets/logo-small.png b/docs/assets/logo-small.png deleted file mode 100644 index 8ea64fedb1cb30910e42f588f0953a279c5f4815..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3611 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 diff --git a/docs/assets/logo-square.png b/docs/assets/logo-square.png new file mode 100644 index 0000000000000000000000000000000000000000..b03cdae9f5d7472837bf5579535f19a69e629313 GIT binary patch literal 5142 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 VIdg7(@|dzpbrqe5 // for `ReadArchive` + #include // for `Read` + + int main(int, const char** argv) { + auto r = zenkit::Read::from("A.ZEN"); + auto ar = zenkit::ReadArchive::from(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, archives can also be loaded from a [virtual file system](virtual-file-system.md), passing the + input obtained from `zenkit::VfsNode::open_read` into `zenkit::ReadArchive::from`. + + **It must be noted in this case, however, that the unique pointer returned by `VfsNode::open_read` must be saved + for the lifetime of `ar` since it is the data source for all read operations on it.** + +=== "C#" + + !!! warning + Archives are currently not supported with the C# API since it depends on the C wrapper. + +=== "Java" + + !!! warning + Archives are currently not supported with the Java API since it depends on the C wrapper. + + +### Reading from an archive + +*ZenKit*'s implementation of *ZenGin Archives* is very bare-bones and does not provide a very rich interface. *ZenKit* +will automatically determine the kind of archive but that's about it. Reading more data from it requires knowing the +format you will be reading beforehand, no buffering or preloading of data is done. A more detailed explanation and +demonstration can be found in the example below. + +=== "C" + + !!! warning + Archives are currently not supported with the C API. + +=== "C++" + + ```cpp title="Example" + #include // for `ReadArchive` + #include // for `Read` + + #include + + int main(int, const char** argv) { + // First, open the archive + auto buf = zenkit::Read::from("WORLD.ZEN"); + auto zen = zenkit::ReadArchive::from(buf.get()); + + // Second, read the definition of the root object. + // Every archive has one object at its root which can be used to + // identify what kind of data it is storing. + zenkit::ArchiveObject object {}; + + if (!zen->read_object_begin(object)) { + // If no object begins at the current position `read_object_begin` + // will return `false`. + std::cerr << "The root object was not found.\n"; + return -1; + } + + // Here, for example, we're checking whether the root object is a + // serialized game world. + if (object.class_name != "oCWorld:zCWorld") { + std::cerr << "This archive does not contain a serialized game world.\n"; + return -1; + } + + // From now on, we need to know the format worlds come in. It is not documented here + // but if necessary, the format of most objects can be found in ZenKit's source code. + // + // In this case, we know that worlds contain three sub-objects, one describing the + // world mesh, one containing the way-net and one containing all dynamic objects in + // the world. + + while (!zen->read_object_end()) { + // `read_object_end` is used to read the end of an object. If it returns `false`, + // no object ends at the current position. It is required to `read_object_end` + // after reading all entries in that object. + + zen->read_object_begin(object); + + if (object.object_name == "VobTree") { + // Here, we're only interested in finding the sub-object containing the dynamic + // objects placed into the world. + + // The VOb tree consists of more nested objects, each followed by an integer + // denoting the number entries following it which should be considered children + // of the VOb. The first integer denotes the number of root objects in the tree. + + auto root_vobs = zen->read_int(); + + while (!zen->read_object_end()) { + zen->read_object_begin(object); + + if (object.class_name == "zCVob") { + // There is a whole system to the class names in the VOb tree but here, + // to give an example, we're just interested in the basic `zCVob`. Also + // there is a lot more data saved in the `zCVob` object but I will omit + // most of it for brevity sake. + // + // So here's some of the types supported by archives read from one: + + auto packed = zen->read_int() != 0; + auto preset_name = zen->read_string(); + auto bbox = zen->read_bbox(); + auto rotation = zen->read_mat3x3(); + auto position = zen->read_vec3(); + + auto vob_name = zen->read_string(); + auto visual_name = zen->read_string(); + auto show_visual = zen->read_bool(); + auto camera_alignment = zen->read_enum(); + + // As you can see, all fields must be read in order and with the correct type. + // If the archive contains a field of type `int` but you try to `read_string()`, + // ZenKit will throw an exception (as documented in the docstrings). + // + // For the full list of supported types, see the table below this example. + } + + if (!zen->read_object_end()) { + // (see below for an explanation) + zen->skip_object(true); + } + } + } + + // Now, we read the end of the current object. + if (!zen->read_object_end()) { + // But since this small example cannot parse the `WayNet` and `MeshAndBsp` + // objects of the world, we need to still make sure that the parser is in + // a sane state. `skip_object` can be used to fully ignore the next object + // in the archive or, if passed `true`, the object the parser is currently in. + // + // We pass `true` here, since we've already `read_object_begin` so the + // parser is now considered to be in the object we want to skip. Here, skipping + // the current object also has another great bonus: if, for some reason, one + // of the objects was not fully parsed, the remaining entries will be skipped + // so the parser is in a good state for the next iteration of the loop. + zen->skip_object(true); + } + } + + return 0; + } + ``` + + **Supported data types:** + + | Type Name | Read Function | Description | + |------------|--------------------------------------------------------------------|--------------------------------------------------------------------------------| + | `string` | `read_string` | A [Windows-1252][] encoded `std::string` | + | `int` | `read_int` | A 32-bit signed integer | + | `float` | `read_float` | An [IEEE 754][] floating point number | + | `byte` | `read_byte` | An 8-bit unsigned integer | + | `word` | `read_word` | A 16-bit unsigned integer | + | `enum` | `read_enum` | A 32-bit unsigned integer | + | `bool` | `read_bool` | A boolean value | + | `color` | `read_color` | An `RGBA` color quad | + | `vec3` | `read_vec3` | A 3-dimensional vector with floating point values | + | `rawFloat` | `read_bbox`, `read_mat3x3`, `read_vec2` | A mathematical structure consisting of multiple floating point values | + | `raw` | `read_raw` | A set of raw bytes, returned as a `zenkit::Read*` | + +=== "C#" + + !!! warning + Archives are currently not supported with the C# API since it depends on the C wrapper. + +=== "Java" + + !!! warning + Archives are currently not supported with the Java API since it depends on the C wrapper. + + +[JSON]: https://en.wikipedia.org/wiki/JSON +[XML]: https://en.wikipedia.org/wiki/XML +[Windows-1252]: https://en.wikipedia.org/wiki/Windows-1252 +[IEEE 754]: https://en.wikipedia.org/wiki/IEEE_754 diff --git a/docs/library/api/cutscene-library.md b/docs/library/api/cutscene-library.md new file mode 100644 index 00000000..835d05de --- /dev/null +++ b/docs/library/api/cutscene-library.md @@ -0,0 +1,62 @@ +# Cutscene Libraries + +*Cutscene libraries*, also called *"message databases"*, contain voice lines and a reference to an associated +audio recording. These files are used in conjunction with [scripts](daedalus-script.md) to facilitate PC to NPC +conversations in-game. *Cutscene libraries* are found within the `_work/data/scripts/content/cutscene/` directory +of Gothic and Gothic II installations. + +# Loading Cutscene Libraries + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("OU.csl"); + PxCutsceneLib* csl = pxCslLoad(buf); + pxBufferDestroy(csl); + + // ... + + pxCslDestroy(man); + return 0; + } + ``` + + !!! info + As with all resources, cutscene libraries can also be loaded from a [virtual file system](virtual-file-system.md) + by passing a `PxVfs` and the file name to `pxCslLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::CutsceneLibrary csl {}; + + auto r = zenkit::Read::from("OU.csl"); + csl.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, cutscene libraries can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::CutsceneLibrary::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/api/daedalus-script.md b/docs/library/api/daedalus-script.md new file mode 100644 index 00000000..6f60f5ed --- /dev/null +++ b/docs/library/api/daedalus-script.md @@ -0,0 +1,60 @@ +# Daedalus Scripts + +*Daedalus* is the domain-specific compiled scripting language used by the *ZenGin*. Currently, *ZenKit* supports +loading and executing compiled scripts using the `zenkit::DaedalusScript` API. + +# Loading Daedalus Scripts + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MENU.DAT"); + PxDaedalusScript* script = pxScriptLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxScriptDestroy(script); + return 0; + } + ``` + + !!! info + As with all resources, scripts can also be loaded from a [virtual file system](virtual-file-system.md) by + passing a `PxVfs` and the file name to `pxScriptLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::DaedalusScript script {}; + + auto r = zenkit::Read::from("MENU.DAT"); + script.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, scripts can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::DaedalusScript::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/api/daedalus-vm.md b/docs/library/api/daedalus-vm.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/library/api/font.md b/docs/library/api/font.md new file mode 100644 index 00000000..ccc61b52 --- /dev/null +++ b/docs/library/api/font.md @@ -0,0 +1,178 @@ +# Fonts + +*ZenGin* fonts are stored in a custom format which maps sections of an image to each character of the alphabet. To that +end, each font contains 256 glyphs which correspond to the characters of the [Windows-1252][] character encoding. + +## Overview + +Every font contains the name of the image containing the appearances of each glyph as well as a list of glyphs. Each +glyph contains two coordinates which together form a rectangle around the glyph in the image. See +[Dealing with glyphs](#dealing-with-glyphs) for more details + +### Loading Fonts + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("FONT_OLD_20.FNT"); + PxFont* font = pxFntLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxFntDestroy(font); + return 0; + } + ``` + + !!! info + As with all resources, fonts can also be loaded from a [virtual file system](virtual-file-system.md) by + passing a `PxVfs` and the file name to `pxFntLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::Font font {}; + + auto r = zenkit::Read::from("FONT_OLD_20.FNT"); + font.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, fonts can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::Font::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. + +### Dealing with glyphs + +To render a font, first the font [texture](texture.md) must be loaded. Its name is stored in the `Font::name` field. +Each of the 256 glyphs of the font then contains two UV-coordinates denoting the top and bottom corner of a +subsection of said texture. These UV-coordinates are between 0 and 1, so they have to be scaled to the actual width +and height of the image by multiplying them. The subsection contains the actual image data for the glyph. + +Since each glyph is saved at the same index as the number representation of its character in [Windows-1252][], to get +the glyph for the character `'a'`, one can just `font.glyphs[(int) 'a']` to get the correct glyph, assuming that `'a'` +follows *Windows-1252* encoding. + + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("FONT_OLD_20.FNT"); + PxFont* font = pxFntLoad(buf); + pxBufferDestroy(buf); + + // The texture (1) must be loaded from some other location, + // most likely the `Textures.vdf` disk. + buf = pxBufferMmap(font.name); + PxTexture* font_texture = pxTexLoad(buf); + pxBufferDestroy(buf); + + uint8_t glyph_width; + PxVec2 glyph_upper, glyph_lower; + + // The second parameter denotes the index of the glyph to + // get. There are 256 glyphs in every font but the number of + // glyphs can also be retrieved using `pxFntGetGlyphCount`. + pxFntGetGlyph(font, 0, &glyph_width, &glyph_upper, &glyph_lower); + + // To be able to determine the pixel offset of the glyph we + // need to retrieve the size of the font texture. + uint32_t texture_width, texture_height; + pxTexGetMeta(font_texture, NULL, &texture_width, &texture_height, NULL, NULL); + + // Each UV coordinate contains a value from 0 to 1 which is + // mapped to the actual with and height of the image + int actual_top_x = glyph_upper.x * texture_width; + int actual_top_y = glyph_upper.y * texture_height; + + int actual_bottom_x = glyph_lower.x * texture_width; + int actual_bottom_y = glyph_lower.y * texture_height; + + // ... + + pxTexDestroy(font_texture); + pxFntDestroy(font); + return 0; + } + ``` + + 1. See [Textures](texture.md) for information about loading texture files. + + !!! info + As with all resources, fonts can also be loaded from a [virtual file system](virtual-file-system.md) by + passing a `PxVfs` and the file name to `pxFntLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::Font font {}; + + auto r = zenkit::Read::from("FONT_OLD_20.FNT"); + font.load(r.get()); + + // The texture (1) must be loaded from some other location, + // most likely the `Textures.vdf` disk. + auto r_texture = zenkit::Read::from(font.name); + zenkit::Texture font_texture {}; + font_texture.load(r_texture.get()); + + zenkit::FontGlyph glyph0 = font.glyphs[0]; + + // Each UV coordinate contains a value from 0 to 1 which is + // mapped to the actual with and height of the image + int actual_top_x = glyph0.uv[0].x * font_texture.width(); + int actual_top_y = glyph0.uv[0].y * font_texture.height(); + + int actual_bottom_x = glyph0.uv[1].x * font_texture.width(); + int actual_bottom_y = glyph0.uv[1].y * font_texture.height(); + + // ... + + return 0; + } + ``` + + 1. See [Textures](texture.md) for information about loading texture files. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. + +[Windows-1252]: https://en.wikipedia.org/wiki/Windows-1252 diff --git a/docs/library/api/mesh.md b/docs/library/api/mesh.md new file mode 100644 index 00000000..a2d4bcea --- /dev/null +++ b/docs/library/api/mesh.md @@ -0,0 +1,58 @@ +# Meshes + +# Loading Meshes + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MyMesh.MSH"); + PxMesh* mesh = pxMshLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxMshDestroy(mesh); + return 0; + } + ``` + + !!! info + As with all resources, meshes can also be loaded from a [virtual file system](virtual-file-system.md) by + passing a `PxVfs` and the file name to `pxMshLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::Mesh mesh {}; + + auto r = zenkit::Read::from("MyMesh.MSH"); + mesh.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, meshes can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::Mesh::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. + diff --git a/docs/library/api/model-animation.md b/docs/library/api/model-animation.md new file mode 100644 index 00000000..5ea9ab73 --- /dev/null +++ b/docs/library/api/model-animation.md @@ -0,0 +1,72 @@ +# Model Animations + +*Model Animations* form part of the animations system of the *ZenGin* they contain only animation samples, i.e. the +position and orientation of each bone of the skeleton they're applied to. While there is space for additional data +within animation files, it is mostly empty. + +## Overview + +The most important part of an animation are its samples. They are stored in `ModelAnimation::samples` after it has been +parsed. Animation files themselves don't contain information about when to run animation or any other effects which +should be applied during it. Those parts of the animation system are defined in [Model Script](model-script.md) files +which should be loaded before animations. The `ModelAnimation::events` field will always be empty for that reason[^1]. + + +### Loading Animations + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MyAnimation.MAN"); + PxAnimation* model = pxAniLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxAniDestroy(model); + return 0; + } + ``` + + !!! info + As with all resources, animations can also be loaded from a [virtual file system](virtual-file-system.md) by + passing a `PxVfs` and the file name to `pxAniLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::ModelAnimation ani {}; + + auto r = zenkit::Read::from("MyAnimation.MAN"); + ani.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, animations can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::ModelAnimation::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. + +[^1]: This assertion is made after examining many animations from both *Gothic* and *Gothic II* + and finding no events being stored. diff --git a/docs/library/api/model-hierarchy.md b/docs/library/api/model-hierarchy.md new file mode 100644 index 00000000..5454976f --- /dev/null +++ b/docs/library/api/model-hierarchy.md @@ -0,0 +1,57 @@ +# Model Hierarchies + +### Loading Model Hierarchies + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MySkeleton.MDH"); + PxModelHierarchy* mdh = pxMdhLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxMdhDestroy(mdh); + return 0; + } + ``` + + !!! info + As with all resources, model hierarchies can also be loaded from a [virtual file system](virtual-file-system.md) + by passing a `PxVfs` and the file name to `pxMdhLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::ModelHierarchy mdh {}; + + auto r = zenkit::Read::from("MySkeleton.MDH"); + mdh.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, model hierarchies can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::ModelHierarchy::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/api/model-mesh.md b/docs/library/api/model-mesh.md new file mode 100644 index 00000000..3f8a9332 --- /dev/null +++ b/docs/library/api/model-mesh.md @@ -0,0 +1,57 @@ +# Model Meshes + +### Loading Model Meshes + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MyMesh.MDM"); + PxModelMesh* mdm = pxMdmLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxMdmDestroy(mdm); + return 0; + } + ``` + + !!! info + As with all resources, model meshes can also be loaded from a [virtual file system](virtual-file-system.md) + by passing a `PxVfs` and the file name to `pxMdmLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::ModelMesh mdm {}; + + auto r = zenkit::Read::from("MyMesh.MDM"); + mdm.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, model meshes can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::ModelMesh::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/api/model-script.md b/docs/library/api/model-script.md new file mode 100644 index 00000000..86d2adf9 --- /dev/null +++ b/docs/library/api/model-script.md @@ -0,0 +1,57 @@ +# Model Scripts + +### Loading Model Scripts + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MyScript.MDS"); + PxModelScript* mds = pxMdsLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxMdsDestroy(mds); + return 0; + } + ``` + + !!! info + As with all resources, model scripts can also be loaded from a [virtual file system](virtual-file-system.md) + by passing a `PxVfs` and the file name to `pxMdsLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::ModelScript mdm {}; + + auto r = zenkit::Read::from("MyScript.MDS"); + mds.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, model scripts can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::ModelScript::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/api/model.md b/docs/library/api/model.md new file mode 100644 index 00000000..7f380ad6 --- /dev/null +++ b/docs/library/api/model.md @@ -0,0 +1,60 @@ +# Models + +*Models* are basic containers for a [Model Hierarchy](model-hierarchy.md) and a [Model Mesh](model-mesh.md). + +# Loading Models + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MyMesh.MDL"); + PxModel* model = pxMdlLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxMdlDestroy(model); + return 0; + } + ``` + + !!! info + As with all resources, models can also be loaded from a [virtual file system](virtual-file-system.md) by + passing a `PxVfs` and the file name to `pxMdlLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::Model model {}; + + auto r = zenkit::Read::from("MyModel.MDL"); + model.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, models can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::Model::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. + diff --git a/docs/library/api/morph-mesh.md b/docs/library/api/morph-mesh.md new file mode 100644 index 00000000..73cbc241 --- /dev/null +++ b/docs/library/api/morph-mesh.md @@ -0,0 +1,57 @@ +# Morph Meshes + +### Loading Morph Meshes + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MyMesh.MMB"); + PxMorphMesh* mmb = pxMmbLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxMmbDestroy(mmb); + return 0; + } + ``` + + !!! info + As with all resources, morph meshes can also be loaded from a [virtual file system](virtual-file-system.md) + by passing a `PxVfs` and the file name to `pxMmbLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::MorphMesh mmb {}; + + auto r = zenkit::Read::from("MyMesh.MMB"); + mmb.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, morph meshes can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::MorphMesh::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/api/multi-resolution-mesh.md b/docs/library/api/multi-resolution-mesh.md new file mode 100644 index 00000000..9d13b81f --- /dev/null +++ b/docs/library/api/multi-resolution-mesh.md @@ -0,0 +1,57 @@ +# Multi Resolution Meshes + +### Loading Multi Resolution Meshes + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MyMesh.MRM"); + PxMultiResolutionMesh* mrm = pxMrmLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxMrmDestroy(mrm); + return 0; + } + ``` + + !!! info + As with all resources, multi resolution meshes can also be loaded from a [virtual file system](virtual-file-system.md) + by passing a `PxVfs` and the file name to `pxMrmLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::MultiResolutionMesh mrm {}; + + auto r = zenkit::Read::from("MyMesh.MRM"); + mrm.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, multi resolution meshes can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::MultiResolutionMesh::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/api/save-game.md b/docs/library/api/save-game.md new file mode 100644 index 00000000..3fadfc91 --- /dev/null +++ b/docs/library/api/save-game.md @@ -0,0 +1,34 @@ +# Save Games + +### Loading Save Games + +=== "C" + + !!! warning + Archives are currently not supported with the C API. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::SaveGame sav {}; + sav.load("/path/to/save/game/"); + + // ... + + return 0; + } + ``` + +=== "C#" + + !!! warning + Archives are currently not supported with the C# API since it depends on the C wrapper. + +=== "Java" + + !!! warning + Archives are currently not supported with the Java API since it depends on the C wrapper. diff --git a/docs/library/api/texture.md b/docs/library/api/texture.md new file mode 100644 index 00000000..04601d79 --- /dev/null +++ b/docs/library/api/texture.md @@ -0,0 +1,57 @@ +# Textures + +### Loading Textures + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("MyTexture.TEX"); + PxTexture* tex = pxTexLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxTexDestroy(tex); + return 0; + } + ``` + + !!! info + As with all resources, textures can also be loaded from a [virtual file system](virtual-file-system.md) + by passing a `PxVfs` and the file name to `pxTexLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::Texture tex {}; + + auto r = zenkit::Read::from("MyTexture.TEX"); + tex.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, texture can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::Texture::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/api/virtual-file-system.md b/docs/library/api/virtual-file-system.md new file mode 100644 index 00000000..1086b5f9 --- /dev/null +++ b/docs/library/api/virtual-file-system.md @@ -0,0 +1,86 @@ +# Virtual File Systems + +*Virtual Disk Files* (or `VDFs`) are container files like [ZIP][] or, more accurately [TAR][]. A VDF contains a +directory structure with multiple files within it. Originally, *Gothic* and *Gothic II* used a library called +[PhysicsFS][] to load these files and read from them. While *PhysicsFS* now supports VDFs out-of-the-box, _ZenKit_ +implements its own, modern parser for them. This page provides a high-level overview of the VDF implementation in +_ZenKit_. + +The original VDF implementation shipped with *Gothic* and *Gothic II* was not written by *Piranha Bytes* themselves but +by a now defunct company called [TRIACOM Software][]. + +## Overview + +### Using Virtual File Systems + +=== "C" + + ```c title="Example" + #include + + #include + + int main(int, const char** argv) { + PxVfs* vfs = pxVfsNew(); + pxVfsMountDisk(vfs, "Worlds.VDF", PxVfsOverwrite_Older) + + PxVfsNode const* node = pxVfsGetNodeByName(vfs, "OLDWORLD.ZEN"); + if (node == NULL) { + printf("Error: OLDWORLD.ZEN not found!"); + return -1; + } + + PxBuffer* buf = pxVfsNodeOpenBuffer(node); + + // ... + + pxBufferDestroy(buf); + pxVfsDestroy(tex); + return 0; + } + ``` + +=== "C++" + + ```cpp title="Example" + #include + #include + + #include + + int main(int, char const** argv) { + zenkit::Vfs vfs {}; + vfs.mount_disk("Worlds.VDF"); + + zenkit::VfsNode* node = vfs.find("OLDWORLD.ZEN"); + if (node == nullptr) { + printf("Error: OLDWORLD.ZEN not found!"); + return -1; + } + + std::unique_ptr r = node->open_read(); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, texture can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::Texture::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. + +[ZIP]: https://en.wikipedia.org/wiki/ZIP_(file_format) +[TAR]: https://en.wikipedia.org/wiki/Tar_(computing) +[PhysicsFS]: https://icculus.org/physfs/ +[TRIACOM Software]: https://www.northdata.com/Triacom+Software+GmbH,+Bochum/Amtsgericht+Gelsenkirchen+HRB+6340 diff --git a/docs/library/api/world.md b/docs/library/api/world.md new file mode 100644 index 00000000..a9c69e0e --- /dev/null +++ b/docs/library/api/world.md @@ -0,0 +1,57 @@ +# Worlds + +### Loading Worlds + +=== "C" + + ```c title="Example" + #include + #include + + int main(int, const char** argv) { + PxBuffer* buf = pxBufferMmap("OLDWORLD.ZEN"); + PxWorld* world = pxWorldLoad(buf); + pxBufferDestroy(buf); + + // ... + + pxWorldDestroy(world); + return 0; + } + ``` + + !!! info + As with all resources, worlds can also be loaded from a [virtual file system](virtual-file-system.md) + by passing a `PxVfs` and the file name to `pxWorldLoadFromVfs`. + +=== "C++" + + ```cpp title="Example" + #include + #include + + int main(int, char const** argv) { + zenkit::World world {}; + + auto r = zenkit::Read::from("OLDWORLD.ZEN"); + world.load(r.get()); + + // ... + + return 0; + } + ``` + + !!! info + As with all resources, texture can also be loaded from a [virtual file system](virtual-file-system.md) + by passing the input obtained from `zenkit::VfsNode::open_read` into `zenkit::Texture::load`. + +=== "C#" + + !!! note + No documentation available. + +=== "Java" + + !!! note + No documentation available. diff --git a/docs/library/formats/animation.md b/docs/library/formats/animation.md deleted file mode 100644 index 4f5b03dd..00000000 --- a/docs/library/formats/animation.md +++ /dev/null @@ -1,29 +0,0 @@ - - -Animations (also called _Model Animations_) form part of the animations system of the _ZenGin_ they contain only -animation samples, i.e. the position and orientation of each bone of the skeleton they're applied to. While there is -space for additional data within animation files, it is mostly empty. - -## Overview - -*phoenix'* implementation of animations lives in `include/phoenix/animation.hh` and `source/animation.cc`. The most -important part of an animation are its samples. They are stored in `animation::samples` after it has been parsed. -Animation files themselves don't contain information about when to run animation or any other effects which should be -applied during it. Those parts of the animation system are defined in [Model Script](formats/model-script.md) files -which should be loaded before animations. The `animation::events` field will always be empty for that reason. - - -### Loading a Font - -Like most data structures in *phoenix*, animations can be loaded using the `#!cpp phoenix::animation::parse()` function. -It takes a `phoenix::buffer` as a parameter and loads the animation from it. - -```cpp title="Example" -#include - -int main(int, const char** argv) { - auto anim_buffer = phoenix::buffer::mmap("A.man"); - [[maybe_unused]] auto anim = phoenix::animation::parse(anim_buffer); - return 0; -} -``` diff --git a/docs/library/formats/archive.md b/docs/library/formats/archive.md deleted file mode 100644 index 68767141..00000000 --- a/docs/library/formats/archive.md +++ /dev/null @@ -1,167 +0,0 @@ -# ZenGin Archives - -The *ZenGin Archive* format is similar in concept to [JSON](https://en.wikipedia.org/wiki/JSON) -or [XML](https://en.wikipedia.org/wiki/XML) -in that it is used to serialize nested data into a file which can be read later. There are three different kinds of -archives called `ASCII`, `BINARY` and `BIN_SAFE` but the way these work exactly does not matter when using *phoenix*. - -For detailed documentation on the inner workings of archives, see -the [ZenGin Reference](../../engine/formats/archive.md). - -## Overview - -Like JSON, archives store values using a key-value structure where every key-value pair belongs to an object. Objects -can also be nested. Unlike JSON, archives do not have first-class support for arrays, instead choosing a special type -and encoding scheme. The class which is responsible for loading *ZenGin Archives* is called `phoenix::archive_reader` -and can be found in `phoenix/include/archive.hh`. - -### Loading an archive - -Unlike most data structures in phoenix, archives can be loaded using the `phoenix::archive_reader::open()` function. -It takes a `phoenix::buffer` and uses it internally for loading values from the archive. - -!!! danger - The buffer passed to `open` is currently contained in the `archive_reader` as a reference. Trying to read from - an `archive_reader` after the buffer passed in `open` has been destroyed will result in undefined behavior! - -```cpp title="Example" -#include - -int main(int, const char** argv) { - auto buf = phoenix::buffer::mmap("A.ZEN"); - [[maybe_unused]] auto archive = phoenix::archive_reader::open(buf); - return 0; -} -``` - -### Reading from an archive - -*phoenix* implementation of *ZenGin Archives* is very bare-bones and does not provide a very rich interface. *phoenix* -will automatically determine the kind of archive but that's about it. Reading more data from it requires knowing the -format you will be reading beforehand, no buffering or preloading of data is done. A more detailed explanation and -demonstration can be found in the example below. - -```cpp title="Example" -#include - -#include - -int main(int, const char** argv) { - // First, open the archive - auto buf = phoenix::buffer::mmap("WORLD.ZEN"); - auto zen = phoenix::archive_reader::open(buf); - - // Second, read the definition of the root object. - // Every archive has one object at its root which can be used to - // identify what kind of data it is storing. - phoenix::archive_object object {}; - - if (!zen.read_object_begin(object)) { - // If no object begins at the current position `read_object_begin` - // will return `false`. - std::cerr << "The root object was not found.\n"; - return -1; - } - - // Here, for example, we're checking whether the root object is a - // serialized game world. - if (object.class_name != "oCWorld:zCWorld") { - std::cerr << "This archive does not contain a serialized game world.\n"; - return -1; - } - - // From now on, we need to know the format worlds come in. It is not documented here - // but if necessary, the format of most objects can be found in phoenix' source code. - // - // In this case, we know that worlds contain three sub-objects, one describing the - // world mesh, one containing the way-net and one containing all dynamic objects in - // the world. - - while (!zen.read_object_end()) { - // `read_object_end` is used to read the end of an object. If it returns `false`, - // no object ends at the current position. It is required to `read_object_end` - // after reading all entries in that object. - - zen.read_object_begin(object); - - if (object.object_name == "VobTree") { - // Here, we're only interested in finding the sub-object containing the dynamic - // objects placed into the world. - - // The VOb tree consists of more nested objects, each followed by an integer - // denoting the number entries following it which should be considered children - // of the VOb. The first integer denotes the number of root objects in the tree. - - [[maybe_unused]] int root_vobs = zen->read_int(); - - while (!zen->read_object_end()) { - zen->read_object_begin(object); - - if (object.class_name == "zCVob") { - // There is a whole system to the class names in the VOb tree but here, - // to give an example, we're just interested in the basic `zCVob`. Also - // there is a lot more data saved in the `zCVob` object but I will omit - // most of it for brevity sake. - // - // So here's some of the types supported by archives read from one: - - [[maybe_unused]] auto packed = in.read_int() != 0; - [[maybe_unused]] auto preset_name = in.read_string(); - [[maybe_unused]] auto bbox = in.read_bbox(); - [[maybe_unused]] auto rotation = in.read_mat3x3(); - [[maybe_unused]] auto position = in.read_vec3(); - - [[maybe_unused]] auto vob_name = in.read_string(); - [[maybe_unused]] auto visual_name = in.read_string(); - [[maybe_unused]] auto show_visual = in.read_bool(); - [[maybe_unused]] auto camera_alignment = in.read_enum(); - - // As you can see, all fields must be read in order and with the correct type. - // If the archive contains a field of type `int` but you try to `read_string()`, - // phoenix will throw an exception (as documented in the docstrings). - // - // For the full list of supported types, see the table below this example. - } - - if (!zen->read_object_end()) { - // (see below for an explanation) - zen->skip_object(true); - } - } - } - - // Now, we read the end of the current object. - if (!zen->read_object_end()) { - // But since this small example cannot parse the `WayNet` and `MeshAndBsp` - // objects of the world, we need to still make sure that the parser is in - // a sane state. `skip_object` can be used to fully ignore the next object - // in the archive or, if passed `true`, the object the parser is currently in. - // - // We pass `true` here, since we've already `read_object_begin` so the - // parser is now considered to be in the object we want to skip. Here, skipping - // the current object also has another great bonus: if, for some reason, one - // of the objects was not fully parsed, the remaining entries will be skipped - // so the parser is in a good state for the next iteration of the loop. - zen->skip_object(true); - } - } - - return 0; -} -``` - -**Supported datatypes:** - -| Type Name | Read Function | Description | -|------------|-----------------------------------------|----------------------------------------------------------------------------------| -| `string` | `read_string` | A [Windows-1252](https://en.wikipedia.org/wiki/Windows-1252) encoded std::string | -| `int` | `read_int` | A 32-bit signed integer | -| `float` | `read_float` | An [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754) floating point number | -| `byte` | `read_byte` | An 8-bit unsigned integer | -| `word` | `read_word` | A 16-bit unsigned integer | -| `enum` | `read_enum` | A 32-bit unsigned integer | -| `bool` | `read_bool` | A boolean value | -| `color` | `read_color` | An `RGBA` color quad | -| `vec3` | `read_vec3` | A 3-dimensional vector with floating point values | -| `rawFloat` | `read_bbox`, `read_mat3x3`, `read_vec2` | A mathematical structure consisting of multiple floating point values | -| `raw` | `read_raw_bytes` | A set of raw bytes, returned as a `phoenix::buffer` | diff --git a/docs/library/formats/font.md b/docs/library/formats/font.md deleted file mode 100644 index e7f76366..00000000 --- a/docs/library/formats/font.md +++ /dev/null @@ -1,64 +0,0 @@ -# ZenGin Fonts - -Fonts for use by the *ZenGin* are saved in a custom format which maps sections of an image to each character of the -alphabet. To that end, each font contains 256 glyphs which correspond to the characters of the -[Windows-1252](https://en.wikipedia.org/wiki/Windows-1252) character encoding. For example, the glyph at index `0xE4` -corresponds to the character `ä`. - -## Overview - -*phoenix'* implementation of fonts lives in `include/phoenix/font.hh` and `source/font.cc`. Every font contains the -name of the image containing the appearances of each glyph as well as a list of glyphs. Each glyph contains two -coordinates which together form a rectangle around the glyph in the image. -See [Dealing with glyphs](#dealing-with-glyphs) -for more details - -### Loading a Font - -Like most data structures in *phoenix*, fonts can be loaded using the `#!cpp phoenix::font::parse()` function. -It takes a `phoenix::buffer` as a parameter and loads the font from it. - -```cpp title="Example" -#include - -int main(int, const char** argv) { - auto font_buffer = phoenix::buffer::mmap("A.fnt"); - [[maybe_unused]] auto font = phoenix::font::parse(font_buffer); - return 0; -} -``` - -### Dealing with glyphs - -To render a font, first the font [Texture](texture.md) must be loaded. Its name is saved in the `phoenix::font::name` -variable. Each of the 256 glyphs of the font then contains two UV-coordinates denoting the top and bottom corner of a -subsection of said texture. These UV-coordinates are between 0 and 1, so they have to be scaled to the actual width -and height of the image by multiplying them. The subsection contains the actual image data for the glyph. - -Since each glyph is saved at the same index as the number representation of its character in -[Windows-1252](https://en.wikipedia.org/wiki/Windows-1252), to get the glyph for the character `'a'`, one can just -`font.glyphs[(int) 'a']` to get the correct glyph name, assuming that `'a'` follows *Windows-1252* encoding. - -```cpp title="Example" -#include -#include - -int main(int, const char** argv) { - auto font_buffer = phoenix::buffer::mmap("A.fnt"); - auto font = phoenix::font::parse(font_buffer) - - // The texture is loaded from some other location, probably the `Textures.vdf` file. - auto font_texture = phoenix::texture::parse(phoenix::buffer::mmap(font.name)); - - phoenix::glyph glyph0 = font.glyphs[0]; - - // Each UV coordinate contains a value from 0 to 1 which is mapped to the actual with and height of the image - int actual_top_x = glyph0.uv[0].x * font_texture.width(); - int actual_top_y = glyph0.uv[0].y * font_texture.height(); - - int actual_bottom_x = glyph0.uv[1].x * font_texture.width(); - int actual_bottom_y = glyph0.uv[1].y * font_texture.height(); - - return 0; -} -``` diff --git a/docs/library/formats/vdf.md b/docs/library/formats/vdf.md deleted file mode 100644 index 1350dbf1..00000000 --- a/docs/library/formats/vdf.md +++ /dev/null @@ -1,88 +0,0 @@ -# ZenGin Virtual File System - -*Virtual Disk Files* (or `VDFs`) are container files like [ZIP](https://en.wikipedia.org/wiki/ZIP_(file_format)) or, -more accurately [TAR](https://en.wikipedia.org/wiki/Tar_(computing)). A VDF contains a directory structure with -multiple files within it. Originally, *Gothic* and *Gothic II* used a library -called [PhysicsFS](https://icculus.org/physfs/) -to load these files and read from them. While *PhysicsFS* now supports VDFs out-of-the-box, _phoenix_ implements its -own, modern parser for them. This page provides a high-level overview of the VDF implementation in _phoenix_. - -The original VDF implementation shipped with *Gothic* and *Gothic II* was not written by *Piranha Bytes* themselves but -by a now defunct company -called [TRIACOM Software](https://www.northdata.com/Triacom+Software+GmbH,+Bochum/Amtsgericht+Gelsenkirchen+HRB+6340). - -## Overview - -*phoenix'* implementation of VDFs lives in `include/phoenix/vdfs.hh` and `source/vdfs.cc`. The most important data -structure here is `phoenix::vdf_file`. It contains the root entries of the VDF and its header. Each entry in a VDF is -represented by the `phoenix::vdf_entry` class and each entry can have multiple child entries. - -### Loading a VDF file - -Unlike most data structures in *phoenix*, VDF files can be loaded using the `phoenix::vdf_file::open()` function. It -takes either a `std::filesystem::path` or a `phoenix::buffer` and loads the VDF from it. - -```cpp title="Example" -#include - -int main(int, const char** argv) { - [[maybe_unused]] auto vdf = phoenix::vdf_file::open("A.vdf"); - return 0; -} -``` - -### Accessing VDF entries - -There are two ways of accessing the entries of a VDF. The `phoenix::vdf_file::find_entry()` method can be used to -find an entry with the given name anywhere in the VDF while the `phoenix::vdf_file::resolve_path()` method can be used -to find the entry at the given path. Both of these methods will return a `phoenix::vdf_entry*` which will be `nullptr` -if the entry was not found. - -Note that `find_entry` will only ever return the first result it finds. Which result is the first depends on the way -the VDF was created and in which order entries were written. If you know that there are two files with the same name -in a VDF, you can use `resolve_path` to explicitly select the one you want. - -```cpp title="Example" -#include - -int main(int, const char** argv) { - auto vdf = phoenix::vdf_file::open("A.vdf"); - - [[maybe_unused]] auto* found_entry = vdf.find_entry("SomeFile.txt"); - [[maybe_unused]] auto* resolved_entry = vdf.resolve_entry("some/other/path/SomeFile.txt"); - - return 0; -} -``` - -### Merging VDFs - -Another feature of *phoenix'* implementation of VDFs is merging. Sometimes it is necessary or convenient to merge -multiple VDFs into one. In Gothic, for example, there are multiple different VDFs containing texture data: - -- `textures_apostroph_patch_neu.VDF`, -- `textures_choicebox_32pixel_modialpha.VDF`, -- `textures_patch.VDF`, -- `textures_Startscreen_ohne_Logo.VDF` and -- `textures.VDF`. - -Some of them contain textures meant to be patches and some just add new textures. You can merge them together to -easily access all of the files using a single `phoenix::vdf_file` instance using `phoenix::vdf_file::merge()`. - -!!! danger - The `merge()` implementation currently does not work properly when replacing files. To make sure only the newest - file in a given VDF is kept, you have to order the VDFs by date before merging them. - -```cpp title="Example" -#include - -int main(int, const char** argv) { - auto vdf_a = phoenix::vdf_file::open("A.vdf"); - auto vdf_b = phoenix::vdf_file::open("Another.vdf"); - vdf_a.merge(vdf_b, false); - - // both VDFs remain valid on their own but vdf_a now also contains non-duplicate entries of vdf_b. - - return 0; -} -``` diff --git a/docs/library/overview.md b/docs/library/overview.md index 85f310ec..2e5e2a2d 100644 --- a/docs/library/overview.md +++ b/docs/library/overview.md @@ -5,12 +5,16 @@ hide: # Library Reference -Welcome to the *phoenix* reference documentation. This page contains information about how to use *phoenix*, a +![](/assets/logo.png) + +Welcome to the *ZenKit* reference documentation. This page contains information about how to use *ZenKit*, a C++-library for parsing file formats used by the *ZenGin*, an early 2000's game engine developed by -[Piranha Bytes](https://www.piranha-bytes.com/) for the -games [Gothic](https://en.wikipedia.org/wiki/Gothic_(video_game)) -and [Gothic II](https://en.wikipedia.org/wiki/Gothic_(video_game)). +[Piranha Bytes][] for the games [Gothic][] and [Gothic II][]. -If you are new to *phoenix*, a good place to start is [Getting Started](quickstart.md). If you are looking -for information about a specific file type, the [File Type Reference](reference.md) might be the place you're +If you are new to *ZenKit*, a good place to start is [the quickstart guide](quickstart.md). If you are looking +for information about a specific file type, the [file type reference](reference.md) might be the place you're looking for. + +[Piranha Bytes]: https://www.piranha-bytes.com/ +[Gothic]: https://en.wikipedia.org/wiki/Gothic_(video_game) +[Gothic II]: https://en.wikipedia.org/wiki/Gothic_(video_game) diff --git a/docs/library/quickstart.md b/docs/library/quickstart.md index e470d8b3..1cdf81c5 100644 --- a/docs/library/quickstart.md +++ b/docs/library/quickstart.md @@ -1,112 +1,98 @@ # Getting Started -To get started using *phoenix*, you will first need to add it to your project as a library. This page will show you two -ways of doing that but there are a lot more ways you can use. This page will also give you a brief overview of -*phoenix* and provide a simple example. You can find a list of file types and information about loading them with -*phoenix* in the [File Type Reference](reference.md). +## Adding *ZenKit* to your project -**Feel free to ask any questions you might have over in -the [:octicons-mark-github-16: Discussions](https://github.com/lmichaelis/phoenix/discussions) -section of the GitHub repository!** +To get started using *ZenKit*, you will first need to add it to your project as a library. The process of doing so is +different between depending on which programming language you choose. Select the language of your choice below and +continue. -## Adding *phoenix* to your project +=== "C/C++" + + *ZenKit* uses [CMake][] as its build system and thus it may be integrated into existing CMake projects using CMake's + [FetchContent][] API or by adding it as a submodule. A system-wide installation via `cmake --install` is currently + **not** supported. -*phoenix* uses the [CMake](https://cmake.org/) build system which also is the easiest way of adding it to your project. -There are two main ways of doing that + Adding *ZenKit* through [FetchContent][] is the easiest way of obtaining a working copy of the library. To do this, + simply add the following lines to your `CMakeLists.txt` file: -1. Add *phoenix* as a [Git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules) or -2. Let CMake load it at configuration time. + ```cmake + include(ExternalProject) + find_package(Git REQUIRED) -To add *phoenix* as a Git submodule, simply create a directory in your project to house submodules -(common names are `vendor`, `3rdparty` or `lib`) and run the following commands: + ExternalProject_Add( + ZenKit + GIT_REPOSITORY https://github.com/GothicKit/ZenKit.git + UPDATE_COMMAND ${GIT_EXECUTABLE} pull + LOG_DOWNLOAD ON + ) + ``` -```sh -# either this, to use the development version of phoenix -git submodule add https://github.com/lmichaelis/phoenix.git path-to-submodule-directory/phoenix + After doing this, you'll need to link against `zenkit` using the `#!cmake target_link_libraries(your-target PRIVATE zenkit)` + directive. With that, everything should be set up correctly for you to use *ZenKit* in your application. -# ... or this to use a phoenix release (recommended) -git submodule add -b v1.0 https://github.com/lmichaelis/phoenix.git path-to-submodule-directory/phoenix -``` +!!! info "Getting Help" + **Feel free to ask any questions you might have over in the [:material-github:/discussions][1] section of the + GitHub repository!** -After doing this, open your `CMakeLists.txt` file and add a -line `#!cmake add_subdirectory(path-to-submodule-directory/phoenix)` -to it. You can now add `phoenix` to your `#!cmake target_link_libraries(...)`. Make sure to also commit the new -directory to your repository, otherwise it will only exist in your local file system. +## A basic example -To add *phoenix* to your project without using Git submodules, add the following lines to your `CMakeLists.txt`. +These are some basic examples of how to use *ZenKit* for each supported language. In this example, we load a `.VDF`-file +into a [`Vfs`](api/virtual-file-system.md), find a [Font](api/font.md) definition within it and load it into memory. +The general mode of operation here is applicable to most assets that can be loaded using *ZenKit* -```cmake -include(ExternalProject) -find_package(Git REQUIRED) -ExternalProject_Add( - phoenix - GIT_REPOSITORY https://github.com/lmichaelis/phoenix - UPDATE_COMMAND ${GIT_EXECUTABLE} pull - LOG_DOWNLOAD ON -) -``` +=== "C++" -You can also do this without using Git by using -CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/FetchContent.html) -interface. Just point it to the URL of the source download of -the [:octicons-mark-github-16: Release](https://github.com/lmichaelis/phoenix/releases) -of *phoenix* you'd like to use. After doing either of these, you can add *phoenix* as a library by adding it to -your `#!cmake target_link_libraries(...)`. + ```cpp + #include // required to load VDF files + #include // required to load fonts -## A basic example + #include -This is a basic example of how to use *phoenix*. It opens a [VDF File](formats/vdf.md) (`.VDF`) and reads a single -entry from it. It then parses that entry as a [Font](formats/font.md) and prints the name of the associated texture -file to the terminal. + int main(int argc, const char** argv) { + if (argc < 2) { + std::cerr << "please provide a textures.vdf file to read from\n"; + return 1; + } -```cpp -#include // required to load VDF files -#include // required to parse fonts + // Create a new Vfs + zenkit::Vfs vfs {}; -#include + // Load the VDF-file into the Vfs + vfs.mount_disk(argv[1]); -int main(int argc, const char** argv) { - if (argc < 2) { - std::cerr << "please provide a textures.vdf file to read from\n"; - return 1; - } + // Find the font file in the Vfs + zenkit::VfsNode const* font_file = vfs.find("font_default.fnt"); - // Open the VDF file - phoenix::vdf_file vdf = phoenix::vdf_file::open(argv[1]); + // Make sure the entry actually exists + if (font_file == nullptr) { + std::cerr << "FONT_DEFAULT.FNT was not found in the VFS\n"; + return 2; + } - // Find the font entry in the VDF - const phoenix::vdf_entry* font_file = vdf.find_entry("font_default.fnt"); + // Open the file and parse a font from it + zenkit::Font font {}; + font.load(font_file->open_read().get()); - // Make sure the entry actually exists - if (font_file == nullptr) { - std::cerr << "FONT_DEFAULT.FNT was not found in the VDF\n"; - return 2; - } + // Print out the associated texture name + std::cout << "The associated texture for FONT_DEFAULT.FNT is \"" << font.name << "\"\n"; - // Open the file and parse a font from it - phoenix::buffer font_data = font_file->open(); - phoenix::font font = phoenix::font::parse(font_data); + return 0; + } + ``` - // Print out the associated texture name - std::cout << "The associated texture for FONT_DEFAULT.FNT is \"" << font.name << "\"\n"; + To run this, compile it while linking against `zenkit` and execute the resulting binary in a terminal giving it the + path to the `Data/textures.vdf` file in any *Gothic* or *Gothic II* installation: - return 0; -} -``` + ```sh + ./a.out /path/to/gothic/Data/textures.VDF + ``` -To run this, compile it while linking against `phoenix` and execute the resulting binary in a terminal giving it the -path to the `Data/textures.vdf` file in any *Gothic* or *Gothic II* installation like this: + More examples can be found in the [:material-github:/examples/][2] directory of the project. -```sh -./a.out /path/to/gothic/Data/textures.VDF -``` -The most important data structure in *phoenix* is the `buffer`. It encapsulates raw bytes and is used to read other -primitive types from files. Basically every public parsing API in *phoenix* accepts a `buffer` as input. As you can -see, in this case we get a buffer by `open()`-ing a VDF entry, but we can also obtain one by calling `buffer::mmap()`, -`buffer::read()` or `buffer::of()`. The functionality of each is described in the respective C++ documentation strings. +[CMake]: https://cmake.org/ +[FetchContent]: https://cmake.org/cmake/help/latest/module/FetchContent.html -More examples can be found in -the [:octicons-mark-github-16: examples/](https://github.com/lmichaelis/phoenix/tree/main/examples) -directory of the project. +[1]: https://github.com/GothicKit/ZenKit/discussions +[2]: https://github.com/ZenKit/phoenix/tree/main/examples diff --git a/docs/library/reference.md b/docs/library/reference.md index 9eb7a159..22f264c1 100644 --- a/docs/library/reference.md +++ b/docs/library/reference.md @@ -1,48 +1,63 @@ # File Type Reference The *ZenGin* uses a lot of custom file formats to store game data like meshes, animations and textures. Originally these -custom formats for specifically made with [Direct3D](https://en.wikipedia.org/wiki/Direct3D#Direct3D_8.0) and later -[DirectX](https://en.wikipedia.org/wiki/DirectX#DirectX_9) in mind and are thus sometimes confusing to understand or -use some proprietary API developed by Microsoft at the time. One example of this is -[DirectMusic](https://en.wikipedia.org/wiki/DirectMusic) which is used for the in-game music. +custom formats for specifically made with [Direct3D][] and later [DirectX][] in mind and are thus sometimes confusing to +understand or use some old, proprietary APIs common at the time. One example of this is [DirectMusic][] which is used +or the in-game music. All files used by the *ZenGin* are binary files with the little-endian byte order or text files encoded with the -[Windows-1252](https://en.wikipedia.org/wiki/Windows-1252) character set. +[Windows-1252][] character set. ## 1st-party formats The following is a list of file types and formats used by ZenGin. -| Format | Extension | Description | _phoenix_ Class Name | -|-----------------------------------------------------------|:------------------------------:|----------------------------------------------------------------------------------------------------------------------------|----------------------| -| [Model Animation](formats/animation.md) | `.MAN` | Contains animations for a model | `animation` | -| [Model Hierarchy](formats/model-hierarchy.md) | `.MDH` | Contains skeletal information for a model | `model_hierarchy` | -| [Model Mesh](formats/model-mesh.md) | `.MDM` | Contains the mesh of a model | `model_mesh` | -| [Model](formats/model.md) | `.MDL` | Contains a mesh and a hierarchy which make up a model | `model` | -| [Morph Mesh Binary](formats/morph-mesh-binary.md) | `.MMB` | Contains a morph mesh with its mesh, skeleton and animation data | `morph_mesh` | -| [Multi Resolution Mesh](formats/multi-resolution-mesh.md) | `.MRM` | Contains a mesh with [CLOD](https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics)) information | `proto_mesh` | -| [Mesh](formats/mesh.md) | `.MSH` | Contains mesh vertices and vertex features like materials | `mesh` | -| [Daedalus Script Binaries](scripting/script.md) | `.DAT` | Contains a compiled _Daedalus_ script | `script` | -| [Texture](formats/texture.md) | `.TEX` | Contains texture data in a variety of formats | `texture` | -| [Font](formats/font.md) | `.FNT` | Contains font data | `font` | -| [ZenGin Archive](formats/archive.md) | `.ZEN` | Contains various structured data. Used mostly for world hierarchy data and object persistence. | `archive` | -| [Text/Cutscenes](formats/cutscene.md) | `.BIN`, `.CSL`, `.DAT`, `.LSC` | Contains text and cutscene data | `messages` | -| [Model Script](formats/model-script.md) | `.MDS`, `.MSB` | Contains model animation script data and associated hierarchy and mesh information | `model_script` | -| [Virtual Disk](formats/vdf.md) | `.VDF` | Contains a directory structure containing multiple files; similar to [tar](https://en.wikipedia.org/wiki/Tar_(computing)). | `vdf_file` | +| Format | Extension | Description | _ZenKit_ Class Name | +|-------------------------------------------------------|:------------------------------:|------------------------------------------------------------------------------------|-----------------------| +| [Model Animation](api/model-animation.md) | `.MAN` | Contains animations for a model | `ModelAnimation` | +| [Model Hierarchy](api/model-hierarchy.md) | `.MDH` | Contains skeletal information for a model | `ModelHierarchy` | +| [Model Mesh](api/model-mesh.md) | `.MDM` | Contains the mesh of a model | `ModelMesh` | +| [Model](api/model.md) | `.MDL` | Contains a mesh and a hierarchy which make up a model | `Model` | +| [Morph Mesh](api/morph-mesh.md) | `.MMB` | Contains a morph mesh with its mesh, skeleton and animation data | `MorphMesh` | +| [Multi Resolution Mesh](api/multi-resolution-mesh.md) | `.MRM` | Contains a mesh with [LOD][] information | `MultiResolutionMesh` | +| [Mesh](api/mesh.md) | `.MSH` | Contains mesh vertices and vertex features like materials | `Mesh` | +| [Daedalus Script](api/daedalus-script.md) | `.DAT` | Contains a compiled _Daedalus_ script | `DaedalusScript` | +| [Texture](api/texture.md) | `.TEX` | Contains texture data in a variety of formats | `Texture` | +| [Font](api/font.md) | `.FNT` | Contains font data | `Font` | +| [ZenGin Archive](api/archive.md) | `.ZEN` | Contains various structured data (general object persistence). | `ReadArchive` | +| [Text/Cutscenes](api/cutscene-library.md) | `.BIN`, `.CSL`, `.DAT`, `.LSC` | Contains text and cutscene data | `CutsceneLibrary` | +| [Model Script](api/model-script.md) | `.MDS`, `.MSB` | Contains model animation script data and associated hierarchy and mesh information | `ModelScript` | +| [Virtual File System](api/virtual-file-system.md) | `.VDF` | Contains a directory structure containing multiple files; similar to [TAR][]. | `Vfs` | ## 3rd-party formats The *ZenGin* uses the following 3rd-party file formats: -* [DirectMusic](https://en.wikipedia.org/wiki/DirectMusic) for the soundtrack -* [DXT1](https://en.wikipedia.org/wiki/S3_Texture_Compression#DXT1), - [DXT3](https://en.wikipedia.org/wiki/S3_Texture_Compression#DXT2_and_DXT3) - and [DXT5](https://en.wikipedia.org/wiki/S3_Texture_Compression#DXT4_and_DXT5) for texture compression -* [WAV](https://en.wikipedia.org/wiki/WAV) for speech and sound effects -* [TGA](https://en.wikipedia.org/wiki/Truevision_TGA) for uncompressed images -* [Bink](http://www.radgametools.com/bnkmain.htm) for cutscene videos +- [DirectMusic][] for the soundtrack +- [DXT1][], [DXT3][] and [DXT5][] for texture compression +- [WAV][] for speech and sound effects +- [TGA][] for uncompressed images +- [Bink][] for cutscene videos For all of these, Open Source parsers are available, however some of them are no longer maintained. For *DirectMusic* -there is [libdmusic](https://github.com/libdmusic/libdmusic), for *DXT* decompression there is -[squish](https://sourceforge.net/projects/libsquish/), [stb](https://github.com/nothings/stb) can also decompress -*DXT*-compressed files and can read in *TGA* files and [ffmpeg](https://github.com/FFmpeg/FFmpeg) can load Bink video. +there is [libdmusic][] (_unmaintained_), for *DXT* decompression there is [libsquish][] (_unmaintained_), [stb][] can +also decompress *DXT*-compressed files and can read in *TGA* files and the amazing [ffmpeg][] can decode Bink video. + +--- + +[Direct3D]: https://en.wikipedia.org/wiki/Direct3D#Direct3D_8.0 +[DirectX]: https://en.wikipedia.org/wiki/DirectX#DirectX_9 +[DirectMusic]: https://en.wikipedia.org/wiki/DirectMusic +[Windows-1252]: https://en.wikipedia.org/wiki/Windows-1252 +[LOD]: https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics) +[DXT1]: https://en.wikipedia.org/wiki/S3_Texture_Compression#DXT1 +[DXT3]: https://en.wikipedia.org/wiki/S3_Texture_Compression#DXT2_and_DXT3 +[DXT5]: https://en.wikipedia.org/wiki/S3_Texture_Compression#DXT4_and_DXT5 +[WAV]: https://en.wikipedia.org/wiki/WAV +[TGA]: https://en.wikipedia.org/wiki/Truevision_TGA +[Bink]: http://www.radgametools.com/bnkmain.htm +[TAR]: https://en.wikipedia.org/wiki/Tar_(computing) +[libdmusic]: https://github.com/libdmusic/libdmusic +[libsquish]: https://sourceforge.net/projects/libsquish/ +[stb]: https://github.com/nothings/stb +[ffmpeg]: https://github.com/FFmpeg/FFmpeg diff --git a/include/zenkit/Archive.hh b/include/zenkit/Archive.hh index a35a0846..dadc6ff4 100644 --- a/include/zenkit/Archive.hh +++ b/include/zenkit/Archive.hh @@ -35,25 +35,53 @@ namespace zenkit { }; /// \brief Represents the header of a ZenGin archive. + /// + ///

The header contains general information about the archive, some of which is used during selection of the + /// backend parser used to load the archive. Potentially interesting are the #save, #user and #date fields which + /// can be used to attribute game saves.

+ /// + ///

If you only want to parse an archive's header without constructing an entire zenkit::ReadArchive, you can + /// use #load to manually do so. It is recommended however, to prefer ReadArchive#load instead since the behaviour + /// of #load might change without warning due to it being an internal API.

struct ArchiveHeader { + /// \brief The format version of the archive. + /// + ///

The version is always "1". Any other version identifiers are rejected.

int32_t version; - /// \brief The type of archiver used to create the archive. Either `zCArchiverGeneric` or `zCArchiverBinSafe`. + /// \brief The type of archiver used to create the archive. + /// + ///

Original Gothic archives contain either `"zCArchiverGeneric"` or `"zCArchiverBinSafe"`. Was originally + /// used to determine which class should be used to load the archive, however *ZenKit* uses the + /// #format to determine the backend parser implementation, which is more reliable.

std::string archiver; /// \brief The format of the archive. ArchiveFormat format; /// \brief Whether the archive contains a save-game or not. - bool save {false}; + bool save; /// \brief The user who created the archive. + /// + ///

Originally, this contained the name of the Windows user logged in while creating the archive. Save-games, + /// for example, contain the name of the Windows user account which performed the save.

std::string user; - /// \brief The date this archive was created at. + /// \brief The date the archive was created at. + /// + ///

This date is formatted according to German date formatting rules. The `strftime` format string would be + /// `"%Y.%m.%d %H:%M:%S"`, however leading zeroes are stripped out.

std::string date; - void load(Read* r); + /// \brief Load an archive header from a stream. + /// + ///

After loading finishes, the stream position of \p r is left at the end of the header. Parsing the + /// header is a fallible operation which will throw an exception on failure (see below).

+ /// + /// \param[in] r The stream to read from. + /// \throws zenkit::ParserError The header does not have a valid format. + ZKAPI void load(Read* r); }; /// \brief Represents the header of an object stored in a ZenGin archive. @@ -64,8 +92,7 @@ namespace zenkit { /// \brief The original class name of the object in the ZenGin. Used to identify the type of object. std::string class_name; - /// \brief A version identifier for the object. A way of determining the Gothic version from - /// this has yet to be discovered. + /// \brief A version identifier for the object. std::uint16_t version; /// \brief The index of the object in the archive. Unique for each object in an archive. @@ -105,11 +132,12 @@ namespace zenkit { public: virtual ~ReadArchive() = default; - /// \brief Creates a new archive_reader from the given buffer. + /// \brief Create a new archive reader from the given buffer. /// \param[in] in The buffer to use. - /// \return The new archive_reader. - /// \throws zenkit::ParserError - ZKREM("use ::from(Read*)") static std::unique_ptr open(phoenix::buffer& in); + /// \return A unique pointer containing an archive reader. + /// \throws zenkit::ParserError If the archive's header is invalid. + /// \sa ArchiveHeader#load + ZKREM("use ::from") static std::unique_ptr open(phoenix::buffer& in); /// \brief Creates a new archive_reader from the given buffer. /// \param[in] r The buffer to use. @@ -136,57 +164,57 @@ namespace zenkit { /// \brief Reads a string value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a string + /// \throws zenkit::ParserError if the value actually present is not a string virtual std::string read_string() = 0; /// \brief Reads an integer value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not an integer + /// \throws zenkit::ParserError if the value actually present is not an integer virtual std::int32_t read_int() = 0; /// \brief Reads a float value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a float + /// \throws zenkit::ParserError if the value actually present is not a float virtual float read_float() = 0; /// \brief Reads a byte value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a byte + /// \throws zenkit::ParserError if the value actually present is not a byte virtual std::uint8_t read_byte() = 0; /// \brief Reads a word (`uint16_t`) value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a word + /// \throws zenkit::ParserError if the value actually present is not a word virtual std::uint16_t read_word() = 0; /// \brief Reads a enum (`uint32_t`) value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a enum + /// \throws zenkit::ParserError if the value actually present is not a enum virtual std::uint32_t read_enum() = 0; /// \brief Reads a bool value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a bool + /// \throws zenkit::ParserError if the value actually present is not a bool virtual bool read_bool() = 0; /// \brief Reads a RGBA color value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a color + /// \throws zenkit::ParserError if the value actually present is not a color virtual glm::u8vec4 read_color() = 0; /// \brief Reads a vec3 value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a vec3 + /// \throws zenkit::ParserError if the value actually present is not a vec3 virtual glm::vec3 read_vec3() = 0; /// \brief Reads a vec2 value from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a vec2 + /// \throws zenkit::ParserError if the value actually present is not a vec2 virtual glm::vec2 read_vec2() = 0; /// \brief Reads a bounding box consisting of two consecutive vec3's from the reader. /// \return The value read. - /// \throws parser_error if the value actually present is not a bounding box + /// \throws zenkit::ParserError if the value actually present is not a bounding box virtual AxisAlignedBoundingBox read_bbox() = 0; /// \brief Reads a 3-by-3 matrix from the reader. @@ -196,7 +224,7 @@ namespace zenkit { /// \brief Reads a raw entry and returns the raw bytes stored within. /// \param size The number of bytes to read (checked at runtime for ASCII and BIN_SAFE archives) /// \return A vector containing the raw bytes of the entry. - /// \throws parser_error if the value actually present is not raw + /// \throws zenkit::ParserError if the value actually present is not raw ZKREM("use ::read_raw()") virtual phoenix::buffer read_raw_bytes(uint32_t size) = 0; virtual std::unique_ptr read_raw(uint32_t size) = 0; diff --git a/include/zenkit/CutsceneLibrary.hh b/include/zenkit/CutsceneLibrary.hh index 8c593c77..84aaedb4 100644 --- a/include/zenkit/CutsceneLibrary.hh +++ b/include/zenkit/CutsceneLibrary.hh @@ -58,14 +58,14 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static CutsceneLibrary parse(phoenix::buffer& path); /// \brief Parses a message database from the data in the given buffer. /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed message database object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static CutsceneLibrary parse(phoenix::buffer&& path); diff --git a/include/zenkit/DaedalusVm.hh b/include/zenkit/DaedalusVm.hh index 6adb9cc9..56b356a1 100644 --- a/include/zenkit/DaedalusVm.hh +++ b/include/zenkit/DaedalusVm.hh @@ -410,7 +410,7 @@ namespace zenkit { /// /// ///

- /// [1] instances in the C++-World have to inherit from phoenix::DaedalusInstance. + /// [1] instances in the C++-World have to inherit from zenkit::DaedalusInstance. ///

/// /// \tparam R The return type of the external. diff --git a/include/zenkit/Font.hh b/include/zenkit/Font.hh index 4b540e61..9de5be94 100644 --- a/include/zenkit/Font.hh +++ b/include/zenkit/Font.hh @@ -45,7 +45,7 @@ namespace zenkit { /// \param name The name of the font. /// \param height The height of each glyph in pixels. /// \param glyphs A list of glyphs of this font. There should always be 256 glyphs in a font. - /// \warning While *phoenix* supports an arbitrary number of glyphs for fonts, Gothic and Gothic II always + /// \warning While *ZenKit* supports an arbitrary number of glyphs for fonts, Gothic and Gothic II always /// expect 256 glyphs for all fonts. Should you create a font a number of glyphs not equal to 256 and /// try to load it into *ZenGin*, it will fail. ZKAPI Font(std::string name, std::uint32_t height, std::vector glyphs); @@ -62,14 +62,14 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static Font parse(phoenix::buffer& buf); /// \brief Parses a font from the data in the given buffer. /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed font object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static Font parse(phoenix::buffer&& in); diff --git a/include/zenkit/Logger.hh b/include/zenkit/Logger.hh index 54d1de20..bf47d0b8 100644 --- a/include/zenkit/Logger.hh +++ b/include/zenkit/Logger.hh @@ -31,12 +31,12 @@ namespace zenkit { public: using level ZKREM("renamed to zenkit::LogLevel") = LogLevel; - /// \brief Supply a custom logger callback to be used for log output from phoenix. + /// \brief Supply a custom logger callback to be used for log output from ZenKit. /// \param callback The callback to use. ZKREM("renamed to ::set") ZKAPI static void use_logger(std::function&& callback); - /// \brief Use the default logger callback for phoenix. + /// \brief Use the default logger callback for ZenKit. ZKREM("renamed to ::set_default") ZKAPI static void use_default_logger(); ZK_PRINTF_LIKE(3, 4) ZKAPI static void log(LogLevel lvl, char const* name, char const* fmt, ...); diff --git a/include/zenkit/Material.hh b/include/zenkit/Material.hh index be77c855..9b6229f8 100644 --- a/include/zenkit/Material.hh +++ b/include/zenkit/Material.hh @@ -119,7 +119,7 @@ namespace zenkit { /// \param[in,out] ctx The archive reader to read from. /// \note After this function returns the position of \p ctx will be at the end of the parsed object. /// \return The parsed material object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(archive_reader&&) for an owning version this function. [[nodiscard]] ZKREM("use ::load()") ZKAPI static Material parse(ReadArchive& ctx); diff --git a/include/zenkit/Mesh.hh b/include/zenkit/Mesh.hh index d06837e7..e36f4a2a 100644 --- a/include/zenkit/Mesh.hh +++ b/include/zenkit/Mesh.hh @@ -99,7 +99,7 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&, const std::vector&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static Mesh parse(phoenix::buffer& buf, std::vector const& include_polygons = {}, @@ -115,7 +115,7 @@ namespace zenkit { /// discarded. This is mainly used for world meshes which include level-of-detail /// polygons. /// \return The parsed mesh object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&, const std::vector&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static Mesh parse(phoenix::buffer&& buf, std::vector const& include_polygons = {}); diff --git a/include/zenkit/Model.hh b/include/zenkit/Model.hh index ac38671b..72ccd6ec 100644 --- a/include/zenkit/Model.hh +++ b/include/zenkit/Model.hh @@ -14,7 +14,7 @@ namespace zenkit { /// \brief Represents a *ZenGin* model. /// - ///

*ZenGin* models contain a phoenix::model_mesh and a phoenix::model_hierarchy bundled into one file. Try are + ///

*ZenGin* models contain a zenkit::ModelMesh and a zenkit::ModelHierarchy bundled into one file. Try are /// typically found in files with the `MDL` extension.

class Model { public: @@ -24,24 +24,24 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static Model parse(phoenix::buffer& buf); /// \brief Parses a model from the data in the given buffer. /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed model object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static Model parse(phoenix::buffer&& buf); ZKAPI void load(Read* r); public: - /// \brief The phoenix::model_hierarchy associated with this model. + /// \brief The zenkit::ModelHierarchy associated with this model. ModelHierarchy hierarchy {}; - /// \brief The phoenix::model_mesh associated with this model. + /// \brief The zenkit::ModelMesh associated with this model. ModelMesh mesh {}; }; } // namespace zenkit diff --git a/include/zenkit/ModelAnimation.hh b/include/zenkit/ModelAnimation.hh index 962899d4..e422b91a 100644 --- a/include/zenkit/ModelAnimation.hh +++ b/include/zenkit/ModelAnimation.hh @@ -120,14 +120,14 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static ModelAnimation parse(phoenix::buffer& in); /// \brief Parses an animation from the data in the given buffer. /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed animation. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static ModelAnimation parse(phoenix::buffer&& in); diff --git a/include/zenkit/ModelHierarchy.hh b/include/zenkit/ModelHierarchy.hh index 9fbda923..130de9ab 100644 --- a/include/zenkit/ModelHierarchy.hh +++ b/include/zenkit/ModelHierarchy.hh @@ -45,7 +45,7 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static ModelHierarchy parse(phoenix::buffer& in); @@ -56,7 +56,7 @@ namespace zenkit { /// /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed model hierarchy object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static ModelHierarchy parse(phoenix::buffer&& in); diff --git a/include/zenkit/ModelMesh.hh b/include/zenkit/ModelMesh.hh index 37785b29..a47e7f16 100644 --- a/include/zenkit/ModelMesh.hh +++ b/include/zenkit/ModelMesh.hh @@ -33,14 +33,14 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static ModelMesh parse(phoenix::buffer& buf); /// \brief Parses a model mesh from the data in the given buffer. /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed model mesh object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static ModelMesh parse(phoenix::buffer&& buf); diff --git a/include/zenkit/MorphMesh.hh b/include/zenkit/MorphMesh.hh index 43f46e54..8de6f6be 100644 --- a/include/zenkit/MorphMesh.hh +++ b/include/zenkit/MorphMesh.hh @@ -64,14 +64,14 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static MorphMesh parse(phoenix::buffer& buf); /// \brief Parses a morph mesh from the data in the given buffer. /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed morph mesh. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static MorphMesh parse(phoenix::buffer&& buf); diff --git a/include/zenkit/MultiResolutionMesh.hh b/include/zenkit/MultiResolutionMesh.hh index 52af2fc0..a4142736 100644 --- a/include/zenkit/MultiResolutionMesh.hh +++ b/include/zenkit/MultiResolutionMesh.hh @@ -47,7 +47,7 @@ namespace zenkit { }; /// \brief Offsets and sizes of binary data sections containing sub-mesh data. - /// \note This is only of use phoenix-internally. + /// \note This is only of use ZenKit-internally. struct SubMeshSection { MeshSection triangles; MeshSection wedges; @@ -91,14 +91,14 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static MultiResolutionMesh parse(phoenix::buffer& in); /// \brief Parses a proto mesh from the data in the given buffer. /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed proto mesh. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static MultiResolutionMesh parse(phoenix::buffer&& in); diff --git a/include/zenkit/SoftSkinMesh.hh b/include/zenkit/SoftSkinMesh.hh index a60fd2e4..188a6493 100644 --- a/include/zenkit/SoftSkinMesh.hh +++ b/include/zenkit/SoftSkinMesh.hh @@ -37,14 +37,14 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM(":: load()") ZKAPI static SoftSkinMesh parse(phoenix::buffer& in); /// \brief Parses a soft-skin mesh from the data in the given buffer. /// \param[in] buf The buffer to read from (by rvalue-reference). /// \return The parsed soft-skin mesh. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM(":: load()") ZKAPI static SoftSkinMesh parse(phoenix::buffer&& in); diff --git a/include/zenkit/Stream.hh b/include/zenkit/Stream.hh index 577a0a20..0f70b046 100644 --- a/include/zenkit/Stream.hh +++ b/include/zenkit/Stream.hh @@ -30,7 +30,8 @@ namespace zenkit { enum class Whence { BEG = 0x00, CUR = 0x01, END = 0x02 }; - class Read ZKAPI { + /// \brief Input stream thingy heh + class ZKAPI Read { public: virtual ~Read() noexcept = default; diff --git a/include/zenkit/Texture.hh b/include/zenkit/Texture.hh index 0a73829d..2e18bcf5 100644 --- a/include/zenkit/Texture.hh +++ b/include/zenkit/Texture.hh @@ -79,14 +79,14 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load") ZKAPI static Texture parse(phoenix::buffer& in); /// \brief Parses a texture from the data in the given buffer. /// \param[in,out] buf The buffer to read from (by rvalue-reference). /// \return The parsed texture. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load") ZKAPI static Texture parse(phoenix::buffer&& in); diff --git a/include/zenkit/World.hh b/include/zenkit/World.hh index 65f08911..e86a82fd 100644 --- a/include/zenkit/World.hh +++ b/include/zenkit/World.hh @@ -31,7 +31,7 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static World parse(phoenix::buffer& buf, GameVersion version); @@ -39,12 +39,12 @@ namespace zenkit { /// ///

This implementation is heavily based on the implementation found in /// [ZenLib](https://github.com/Try/ZenLib). This function will try to determine the world version - /// automatically. If it can't be detected, _phoenix_ will assume `game_version::gothic_1` and log + /// automatically. If it can't be detected, _ZenKit_ will assume `GameVersion::GOTHIC_1` and log /// an error message. /// - /// Using this function over #parse(buffer&, game_version) has the potential to lead to longer load times - /// but it has not been proven that such a case actually exists. If you absolutely need the performance and - /// you already know the game version you are trying to load, consider using #parse(buffer&, game_version) + /// Using this function over #parse(phoenix::buffer&, GameVersion) has the potential to lead to longer load + /// times but it has not been proven that such a case actually exists. If you absolutely need the performance + /// and you already know the game version you are trying to load, consider using #parse(buffer&, game_version) /// instead. /// /// \param[in,out] buf The buffer to read from. @@ -52,7 +52,7 @@ namespace zenkit { /// \note After this function returns the position of \p buf will be at the end of the parsed object. /// If you would like to keep your buffer immutable, consider passing a copy of it to #parse(buffer&&) /// using buffer::duplicate. - /// \throws parser_error if parsing fails. + /// \throws ParserError if parsing fails. /// \see #parse(buffer&&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static World parse(phoenix::buffer& buf); @@ -60,14 +60,14 @@ namespace zenkit { /// \param[in,out] buf The buffer to read from (by rvalue-reference). /// \param version The Gothic version to assume when loading the world /// \return The parsed world object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static World parse(phoenix::buffer&& buf, GameVersion version); /// \brief Parses a world from the data in the given buffer. /// \param[in,out] buf The buffer to read from (by rvalue-reference). /// \return The parsed world object. - /// \throws parser_error if parsing fails. + /// \throws zenkit::ParserError if parsing fails. /// \see #parse(buffer&) [[nodiscard]] ZKREM("use ::load()") ZKAPI static World parse(phoenix::buffer&& buf); diff --git a/include/zenkit/vobs/VirtualObject.hh b/include/zenkit/vobs/VirtualObject.hh index e348aa8f..97d3661d 100644 --- a/include/zenkit/vobs/VirtualObject.hh +++ b/include/zenkit/vobs/VirtualObject.hh @@ -83,12 +83,12 @@ namespace zenkit { /// \brief Ways a VOb is seen in the game world. enum class VisualType : std::uint8_t { DECAL = 0, ///< The VOb presents as a decal. - MESH = 1, ///< The VOb presents a phoenix::Mesh. - MULTI_RESOLUTION_MESH = 2, ///< The VOb presents a phoenix::MultiResolutionMesh. + MESH = 1, ///< The VOb presents a zenkit::Mesh. + MULTI_RESOLUTION_MESH = 2, ///< The VOb presents a zenkit::MultiResolutionMesh. PARTICLE_EFFECT = 3, ///< The VOb presents as a particle system. AI_CAMERA = 4, ///< The VOb is a game-controlled camera. - MODEL = 5, ///< The VOb presents a phoenix::Model. - MORPH_MESH = 6, ///< The VOb presents a phoenix::MorphMesh. + MODEL = 5, ///< The VOb presents a zenkit::Model. + MORPH_MESH = 6, ///< The VOb presents a zenkit::MorphMesh. UNKNOWN = 7, ///< The VOb presents an unknown visual or no visual at all. // Deprecated entries. diff --git a/mkdocs.yml b/mkdocs.yml index 881aa254..a242c594 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,16 +1,30 @@ -site_name: 'phoenix' -copyright: 'Copyright © 2022 Luis Michaelis' -repo_url: 'https://github.com/lmichaelis/phoenix' +site_name: 'ZenKit' +copyright: 'Copyright © 2022-2023 GothicKit Contributors.' +repo_url: 'https://github.com/GothicKit/phoenix' edit_uri: 'edit/main/docs/' nav: - 'Library Reference': - 'Overview': 'library/overview.md' - 'Getting Started': 'library/quickstart.md' - 'File Type Reference': 'library/reference.md' - - 'File Parsers': - - 'ZenGin Archive': 'library/formats/archive.md' - - 'Font': 'library/formats/font.md' - - 'Virtual File System': 'library/formats/vdf.md' + - 'ZenKit Concepts': + - 'Archive': 'library/api/archive.md' + - 'Cutscene Library': 'library/api/cutscene-library.md' + - 'Daedalus Script': 'library/api/daedalus-script.md' + - 'Daedalus VM': 'library/api/daedalus-vm.md' + - 'Font': 'library/api/font.md' + - 'Mesh': 'library/api/mesh.md' + - 'Model': 'library/api/model.md' + - 'Model Animation': 'library/api/model-animation.md' + - 'Model Hierarchy': 'library/api/model-hierarchy.md' + - 'Model Mesh': 'library/api/model-mesh.md' + - 'Model Script': 'library/api/model-script.md' + - 'Morph Mesh': 'library/api/morph-mesh.md' + - 'Multi-Resolution Mesh': 'library/api/multi-resolution-mesh.md' + - 'Save Game': 'library/api/save-game.md' + - 'Texture': 'library/api/texture.md' + - 'Virtual File System': 'library/api/virtual-file-system.md' + - 'World': 'library/api/world.md' - 'ZenGin Reference': - 'Overview': 'engine/overview.md' - 'Datatypes': 'engine/datatypes.md' @@ -26,8 +40,8 @@ nav: theme: name: 'material' language: 'en' - logo: 'assets/logo-small.png' - favicon: 'assets/logo-small.png' + logo: 'assets/logo-square.png' + favicon: 'assets/logo-square.png' icon: repo: 'fontawesome/brands/github' palette: @@ -40,6 +54,9 @@ theme: - 'navigation.expand' - 'search.suggest' - 'search.highlight' + - 'content.tabs.link' + - 'content.code.copy' + - 'content.code.annotate' extra_css: - 'assets/stylesheets/extra.css' extra_javascript: diff --git a/readme.md b/readme.md index 2b02a68b..18b09975 100644 --- a/readme.md +++ b/readme.md @@ -1,94 +1,88 @@ -![phoenix logo](assets/logo.png) +![ZenKit Logo](assets/logo.png) -# the _phoenix_ project +# The _ZenKit_ Project -[![Build](https://img.shields.io/github/actions/workflow/status/lmichaelis/phoenix/build.yml?label=Build&branch=main)](https://github.com/lmichaelis/phoenix/actions/workflows/build.yml) -![License](https://img.shields.io/github/license/lmichaelis/phoenix?label=License&color=important) -![C++](https://img.shields.io/static/v1?label=C%2B%2B&message=17&color=informational) -![Platforms](https://img.shields.io/static/v1?label=Supports&message=GCC%20|%20Clang%20|%20MSVC%20|%20Apple%20Clang&color=blueviolet) -![Version](https://img.shields.io/github/v/tag/lmichaelis/phoenix?label=Version&sort=semver) +[![Build](https://img.shields.io/github/actions/workflow/status/GothicKit/phoenix/build.yml?label=Build&branch=main)](https://github.com/GothicKit/phoenix/actions/workflows/build.yml) +[![License](https://img.shields.io/github/license/GothicKit/phoenix?label=License&color=important)](https://github.com/GothicKit/phoenix/blob/main/license.md) +[![C++](https://img.shields.io/static/v1?label=C%2B%2B&message=17&color=informational)]() +[![Platforms](https://img.shields.io/static/v1?label=Supports&message=GCC%20|%20Clang%20|%20MSVC%20|%20Apple%20Clang&color=blueviolet)]() +[![Version](https://img.shields.io/github/v/tag/GothicKit/phoenix?label=Version&sort=semver)](https://github.com/GothicKit/phoenix/releases/latest) -**🐲 Here be dragons! _phoenix_ is still changing a lot and might break your code if you choose to update. -See [versioning](#versioning) for details.** +**🐲 Here be dragons! _ZenKit_ is still changing a lot and might break your code if you choose to update. +See [Versioning](#versioning) for details.** -The _phoenix_ project aims to re-implement file formats used by the _ZenGin_ made -by [Piranha Bytes](https://www.piranha-bytes.com/) -for their early-2000s games [Gothic](https://en.wikipedia.org/wiki/Gothic_(video_game)) -and [Gothic II](https://en.wikipedia.org/wiki/Gothic_II). -It is heavily based on [ZenLib](https://github.com/Try/ZenLib) which is used as a reference implementation for the -different file formats used. +The _ZenKit_ project aims to re-implement file formats used by the _ZenGin_ made by [Piranha Bytes][] for their +early-2000s games [Gothic][] and [Gothic II][]. It is heavily based on [ZenLib][] which is used as a reference +implementation for the different file formats used. -_phoenix_ includes **parsers and basic datastructures** for most file formats used by the _ZenGin_ as well as a -type-safe **VM for _Daedalus_ scripts** and supporting infrastructure like _Gothic II_ class definitions. Tools for -inspecting and converting _ZenGin_ files can be found in [phoenix studio](https://github.com/lmichaelis/phoenix-studio). +_ZenKit_ includes parsers and basic datastructures for most file formats used by the _ZenGin_ as well as a +type-safe VM for _Daedalus_ scripts and supporting infrastructure like [Gothic II][] class definitions. Tools for +inspecting and converting _ZenGin_ files can be found in [phoenix studio][]. -To get started, take a look in the [Reference Documentation](https://phoenix.lmichaelis.de/library/overview). Don't -hesitate to open a discussion thread over in [Discussions](https://github.com/lmichaelis/phoenix/discussions) if you -have a question or need help. Please open an issue for any bug you encounter! +To get started, take a look in the [Reference Documentation][]. Don't hesitate to open a discussion thread over in +[Discussions][] if you have a question or need help. Please open an issue for any bug you encounter! -You can also contact me on Discord, ideally by pinging me (lmichaelis#6242) in the [GMC Discord](https://discord.gg/mCpS5b5SUY) in the _tools_ channel. +You can also contact me directly on Discord, ideally by pinging me (@lmichaelis) in the [GMC Discord](https://discord.gg/mCpS5b5SUY) in the _tools_ channel. + +## Supported File Formats -## supported file formats Currently, the following file formats are supported. -| Format | Extension | Description | _phoenix_ Class Name | -|--------------------------|:------------------------------:|----------------------------------------------------------------------------------------------------------------------------|----------------------| -| Model Animation | `.MAN` | Contains animations for a model | `animation` | -| Model Hierarchy | `.MDH` | Contains skeletal information for a model | `model_hierarchy` | -| Model Mesh | `.MDM` | Contains the mesh of a model | `model_mesh` | -| Model | `.MDL` | Contains a mesh and a hierarchy which make up a model | `model` | -| Morph Mesh Binary | `.MMB` | Contains a morph mesh with its mesh, skeleton and animation data | `morph_mesh` | -| Multi Resolution Mesh | `.MRM` | Contains a mesh with [CLOD](https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics)) information | `proto_mesh` | -| Mesh | `.MSH` | Contains mesh vertices and vertex features like materials | `mesh` | -| Daedalus Script Binaries | `.DAT` | Contains a compiled _Daedalus_ script | `script` | -| Texture | `.TEX` | Contains texture data in a variety of formats | `texture` | -| Font | `.FNT` | Contains font data | `font` | -| ZenGin Archive | `.ZEN` | Contains various structured data. Used mostly for world hierarchy data and object persistence. | `archive` | -| Text/Cutscenes | `.BIN`, `.CSL`, `.DAT`, `.LSC` | Contains text and cutscene data | `messages` | -| Model Script | `.MDS` | Contains model animation script data and associated hierarchy and mesh information | `model_script` | -| Model Script Binary | `.MSB` | Contains model animation script data and associated hierarchy and mesh information (binary form) | `model_script` | -| Virtual Disk | `.VDF` | Contains a directory structure containing multiple files; similar to [tar](https://en.wikipedia.org/wiki/Tar_(computing)). | `vdf_file` | - -## contributing - -If you'd like to contribute, please read [contributing](contributing.md) first. - -## building - -_phoenix_ is currently only tested on Linux and while Windows _should_ be supported you might run into issues. If so, +| Format | Extension | Description | _ZenKit_ Class Name | +|---------------------------|:------------------------------:|------------------------------------------------------------------------------------|-----------------------| +| [Model Animation][] | `.MAN` | Contains animations for a model | `ModelAnimation` | +| [Model Hierarchy][] | `.MDH` | Contains skeletal information for a model | `ModelHierarchy` | +| [Model Mesh][] | `.MDM` | Contains the mesh of a model | `ModelMesh` | +| [Model][] | `.MDL` | Contains a mesh and a hierarchy which make up a model | `Model` | +| [Morph Mesh][] | `.MMB` | Contains a morph mesh with its mesh, skeleton and animation data | `MorphMesh` | +| [Multi Resolution Mesh][] | `.MRM` | Contains a mesh with [LOD][] information | `MultiResolutionMesh` | +| [Mesh][] | `.MSH` | Contains mesh vertices and vertex features like materials | `Mesh` | +| [Daedalus Script][] | `.DAT` | Contains a compiled _Daedalus_ script | `DaedalusScript` | +| [Texture][] | `.TEX` | Contains texture data in a variety of formats | `Texture` | +| [Font][] | `.FNT` | Contains font data | `Font` | +| [ZenGin Archive][] | `.ZEN` | Contains various structured data (general object persistence). | `ReadArchive` | +| [Text/Cutscenes][] | `.BIN`, `.CSL`, `.DAT`, `.LSC` | Contains text and cutscene data | `CutsceneLibrary` | +| [Model Script][] | `.MDS`, `.MSB` | Contains model animation script data and associated hierarchy and mesh information | `ModelScript` | +| [Virtual File System][] | `.VDF` | Contains a directory structure containing multiple files; similar to [TAR][]. | `Vfs` | + +## Contributing + +If you'd like to contribute, please read [Contributing](contributing.md) first. + +## Building + +_ZenKit_ is currently only tested on Linux and while Windows _should_ be supported you might run into issues. If so, feel free to create an issue or open a merge request. You will need * A working compiler which supports C++17, like GCC 9 * CMake 3.10 or above * Git -To build _phoenix_ from scratch, just open a terminal in a directory of your choice and run +To build _ZenKit_ from scratch, just open a terminal in a directory of your choice and run ```bash -git clone --recursive https://github.com/lmichaelis/phoenix +git clone --recursive https://github.com/GothicKit/phoenix cd phoenix cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build ``` -You will find the library in `build/lib`. +You will find the library in `build/`. -## using -Using _phoenix_ in your project is pretty straightforward. Just add `include` to your include directories and link -against the _phoenix_ library. To start loading files, you just include the header and call `cls::parse(...)` or -`cls::open(...)`, depending on the file type on one of the classes from the table above. For example, to load a -model from a VDF file, you do this: +## Using ```cpp -#include -#include +#include +#include +#include int main(int, char**) { // Open the VDF file for reading - auto vdf = phoenix::vdf_file::open("Models.VDF"); + zenkit::Vfs vfs {}; + vfs.mount_disk("Models.VDF"); // Find the MyModel.MDL within the VDF - auto entry = vdf.find_entry("MyModel.MDL"); + auto entry = vdf.find("MyModel.MDL"); if (entry == nullptr) { // MyModel.MDL was not found in the VDF @@ -96,17 +90,18 @@ int main(int, char**) { } // Open MyModel.MDL for reading - auto buf = entry->open(); + auto buf = entry->open_read(); // One could also memory-map a normal file from disk: - // auto buf = phoenix::buffer::mmap("/path/to/file"); + // auto buf = zenkit::Read::from("/path/to/file"); // Or if you have a vector of data: // std::vector data { /* ... */ }; - // auto buf = phoenix::buffer::of(std::move(data)); + // auto buf = zenkit::Read::from(std::move(data)); // Parse the model - auto mdl = phoenix::model::parse(buf); + zeknit::Model mdl {}; + mdl.load(buf.get()); // Do something with mdl ... @@ -114,12 +109,12 @@ int main(int, char**) { } ``` -_phoenix_ also provides a VM implementation for the _Daedalus_ scripting language used by _ZenGin_: +_ZenKit_ also provides a VM implementation for the _Daedalus_ scripting language used by _ZenGin_: ```c++ -#include -#include -#include +#include +#include +#include #include #include @@ -130,7 +125,7 @@ enum class MyScriptEnum : int { }; // Declare a class to be bound to members in a script. This is used in `main`. -struct MyScriptClass : public phoenix::instance { +struct MyScriptClass : public zenkit::DaedalusInstance { // Declare the members present in the script class. // Supported types are: // * int @@ -171,13 +166,13 @@ std::string MyInternalFunction(int param1) { int main(int, char**) { // Open a buffer containing the script. - auto buf = phoenix::buffer::mmap("MyScript.DAT"); + auto buf = zenkit::Read::from("MyScript.DAT"); // Create the VM instance - phoenix::vm vm {phoenix::script::parse(buf)}; + zenkit::DaedalusScript script {}; + script.load(buf.get()); - // Alternatively, if you just need to inspect the script itself, you can just: - // auto script = phoenix::script::parse(buf); + zenkit::DaedalusVm vm {std::move(script)}; // You can register Daedalus -> C++ shared classes. The `register_member` function will automatically // validate that the definitions match at runtime. @@ -240,23 +235,50 @@ int main(int, char**) { } ``` -For more examples on how to use _phoenix_, take a look into the -[`examples`](https://github.com/lmichaelis/phoenix/tree/main/examples) directory and -[`phoenix-studio`](https://github.com/lmichaelis/phoenix-studio) repository. A working example of using the VM can be -found in [`examples/run_interpreter.cc`](https://github.com/lmichaelis/phoenix/blob/main/examples/run_interpreter.cc). +For more examples on how to use _ZenKit_, take a look into the [examples][] directory and [phoenix studio][] repository. -## versioning +## Versioning -_phoenix_ uses [semantic versioning](https://semver.org/). Before updating _phoenix_ in your application, make sure +_ZenKit_ uses [semantic versioning](https://semver.org/). Before updating _ZenKit_ in your application, make sure that you are aware of potential breaking changes to the API. A detailed log of changes can be found in [changelog.md](changelog.md) as well as the releases section of the GitHub repository page. -The `main` branch is used for _phoenix_ development and contains potentially breaking changes without any kind of -warning. Each minor version of _phoenix_ will get its own branch (e.g. `v1.0`). Within these branches API stability is +The `main` branch is used for _ZenKit_ development and contains potentially breaking changes without any kind of +warning. Each minor version of _ZenKit_ will get its own branch (e.g. `v1.0`). Within these branches API stability is guaranteed and patches will be merged into them as required. Patches will be backported to the last minor as well (i.e. if `v1.3.4` is a bugfix-release, its contents will be backported to `v1.2.*` but not `v1.1.*` or any previous version). -## licensing - -While the source code of _phoenix_ is licensed under the [MIT license](license.md), the -[_phoenix_ logo](assets/logo.svg) is licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). +## Licensing + +While the source code of _ZenKit_ is licensed under the [MIT license](license.md), the +[_ZenKit_ logo](assets/logo.svg) is licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). + +--- + +[Piranha Bytes]: https://www.piranha-bytes.com/ +[Gothic]: https://en.wikipedia.org/wiki/Gothic_(video_game) +[Gothic II]: https://en.wikipedia.org/wiki/Gothic_II +[ZenLib]: https://github.com/ataulien/ZenLib +[phoenix studio]: https://github.com/GothicKit/phoenix-studio +[examples]: https://github.com/lmichaelis/phoenix/tree/main/examples + +[TAR]: https://en.wikipedia.org/wiki/Tar_(computing) +[LOD]: https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics) + +[Reference Documentation]: https://phoenix.gothickit.dev/library/overview +[Discussions]: https://github.com/GothicKit/phoenix/discussions + +[Model Animation]: https://phoenix.gothickit.dev/library/api/model-animation/ +[Model Hierarchy]: https://phoenix.gothickit.dev/library/api/model-hierarchy/ +[Model Mesh]: https://phoenix.gothickit.dev/library/api/model-mesh/ +[Model]: https://phoenix.gothickit.dev/library/api/model/ +[Morph Mesh]: https://phoenix.gothickit.dev/library/api/morph-mesh/ +[Multi Resolution Mesh]: https://phoenix.gothickit.dev/library/api/multi-resolution-mesh/ +[Mesh]: https://phoenix.gothickit.dev/library/api/mesh/ +[Daedalus Script]: https://phoenix.gothickit.dev/library/api/daedalus-script/ +[Texture]: https://phoenix.gothickit.dev/library/api/texture/ +[Font]: https://phoenix.gothickit.dev/library/api/font/ +[ZenGin Archive]: https://phoenix.gothickit.dev/library/api/archive/ +[Text/Cutscenes]: https://phoenix.gothickit.dev/library/api/cutscene-library/ +[Model Script]: https://phoenix.gothickit.dev/library/api/model-script/ +[Virtual File System]: https://phoenix.gothickit.dev/library/api/virtual-file-system/ diff --git a/src/Archive.cc b/src/Archive.cc index 9f25a952..1d3a7781 100644 --- a/src/Archive.cc +++ b/src/Archive.cc @@ -30,7 +30,12 @@ namespace zenkit { if (ver.find("ver ") != 0) { throw zenkit::ParserError {"ReadArchive", "ver field missing"}; } + this->version = std::stoi(ver.substr(ver.find(' ') + 1)); + if (this->version != 1) { + throw zenkit::ParserError {"ReadArchive", "Unsupported format version: " + ver}; + } + this->archiver = r->read_line(true); auto fmt = r->read_line(true); diff --git a/src/Logger.cc b/src/Logger.cc index 730f39fb..63fe6e12 100644 --- a/src/Logger.cc +++ b/src/Logger.cc @@ -55,15 +55,12 @@ namespace zenkit { } } - /// \brief Supply a custom logger callback to be used for log output from phoenix. - /// \param callback The callback to use. void Logger::use_logger(std::function&& callback) { Logger::set(LogLevel::INFO, [callback](LogLevel lvl, std::string_view name, std::string_view sv) { callback(lvl, std::string {name} + ":" + std::string {sv}); }); } - /// \brief Use the default logger callback for phoenix. void Logger::use_default_logger() { Logger::set_default(LogLevel::INFO); } diff --git a/src/Mesh.cc b/src/Mesh.cc index 3ba2305b..fb5cbaaf 100644 --- a/src/Mesh.cc +++ b/src/Mesh.cc @@ -139,7 +139,7 @@ namespace zenkit { // This presents a problem: Taking the leaf polygons as a parameter makes creating a // unified parsing function for world meshes impossible. Instead, there should be a // function to remove this extra data which would grant the user more freedom in how they - // use _phoenix_. + // use _ZenKit_. if (!leaf_polygons.empty() && !std::binary_search(leaf_polygons.begin(), leaf_polygons.end(), i)) { // If the current polygon is not a leaf polygon, skip it. diff --git a/src/ModelScriptDsl.hh b/src/ModelScriptDsl.hh index 1c440609..7aa36fe4 100644 --- a/src/ModelScriptDsl.hh +++ b/src/ModelScriptDsl.hh @@ -98,4 +98,4 @@ namespace zenkit { private: MdsTokenizer _m_stream; }; -} // namespace phoenix +} // namespace zenkit diff --git a/src/archive/ArchiveBinsafe.hh b/src/archive/ArchiveBinsafe.hh index 8ba2204b..2e8e76f2 100644 --- a/src/archive/ArchiveBinsafe.hh +++ b/src/archive/ArchiveBinsafe.hh @@ -75,4 +75,4 @@ namespace zenkit { std::vector _m_hash_table_entries; }; -} // namespace phoenix +} // namespace zenkit