Skip to content

Commit 083742b

Browse files
authored
Merge pull request #311 from zrax/fix_location_encoding
Fix plLocation encoding/decoding to match Plasma, and add tests.
2 parents 85200e4 + 24a6b8e commit 083742b

8 files changed

Lines changed: 220 additions & 21 deletions

File tree

.github/workflows/linux-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ jobs:
2424
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH="${GITHUB_WORKSPACE}/build_deps/prefix" \
2525
-DENABLE_PYTHON=ON -DENABLE_TOOLS=ON -DENABLE_NET=ON -DENABLE_PHYSX=OFF ..
2626
make -j2
27+
make test

.github/workflows/macos-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ jobs:
3838
-DCMAKE_PREFIX_PATH="${GITHUB_WORKSPACE}/build_deps/prefix;/usr/local/opt/jpeg-turbo;/usr/local/opt/openssl" \
3939
-DENABLE_PYTHON=ON -DENABLE_TOOLS=ON -DENABLE_NET=ON -DENABLE_PHYSX=OFF ..
4040
make -j2
41+
make test

.github/workflows/windows-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ jobs:
7878
- name: Build
7979
run: |
8080
cmake --build build --config Release -j 2
81+
cmake --build build --target RUN_TESTS --config Release -j 2
8182
8283
- name: Install
8384
run: |

CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ option(ENABLE_PYTHON "Build HSPlasma Python integration" ON)
6666
option(ENABLE_TOOLS "Build the HSPlasma tools" ON)
6767
option(ENABLE_NET "Build HSPlasmaNet" ON)
6868
option(ENABLE_PHYSX "Build with PhysX Support" ON)
69+
option(ENABLE_TESTS "Build test suite" ON)
6970
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
7071

7172
find_package(string_theory 2.0 REQUIRED)
@@ -84,5 +85,10 @@ if(ENABLE_NET)
8485
add_subdirectory(net)
8586
endif()
8687

88+
if(ENABLE_TESTS)
89+
enable_testing()
90+
add_subdirectory(Tests)
91+
endif()
92+
8793
include(FeatureSummary)
8894
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)

Tests/CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
include(FetchContent)
2+
FetchContent_Declare(Catch2
3+
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
4+
GIT_TAG v2.13.10
5+
)
6+
FetchContent_MakeAvailable(Catch2)
7+
8+
set(test_SOURCES
9+
catch2_main.cpp
10+
Test_Location.cpp
11+
)
12+
add_executable(test_HSPlasma ${test_SOURCES})
13+
target_link_libraries(test_HSPlasma PRIVATE Catch2::Catch2 HSPlasma)
14+
15+
# Integrate with CTest so we can use the built-in test targets
16+
list(APPEND CMAKE_MODULE_PATH "${catch2_SOURCE_DIR}/contrib")
17+
include(CTest)
18+
include(Catch)
19+
catch_discover_tests(test_HSPlasma)

Tests/Test_Location.cpp

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/* This file is part of HSPlasma.
2+
*
3+
* HSPlasma is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
*
8+
* HSPlasma is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with HSPlasma. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
#include <catch2/catch.hpp>
18+
19+
#include "PRP/KeyedObject/plLocation.h"
20+
21+
#define CHECK_LOCATION(version, seqNum, valid, virt, seqPrefix, pageNum) \
22+
do { \
23+
plLocation loc(version); \
24+
loc.parse(seqNum); \
25+
CHECK(loc.isValid() == valid); \
26+
CHECK(loc.isVirtual() == virt); \
27+
CHECK(loc.getSeqPrefix() == seqPrefix); \
28+
CHECK(loc.getPageNum() == pageNum); \
29+
CHECK(loc.unparse() == seqNum); \
30+
} while (false)
31+
32+
#define CHECK_LOCATION_MOUL(seqNum, seqPrefix, pageNum) \
33+
CHECK_LOCATION(PlasmaVer::pvMoul, seqNum, true, false, seqPrefix, pageNum)
34+
35+
#define CHECK_LOCATION_CLASSIC(seqNum, seqPrefix, pageNum) \
36+
CHECK_LOCATION(PlasmaVer::pvPrime, seqNum, true, false, seqPrefix, pageNum)
37+
38+
TEST_CASE("plLocation packing and unpacking", "[location]")
39+
{
40+
SECTION("Uru Live format plLocations") {
41+
CHECK_LOCATION(PlasmaVer::pvMoul, 0, true, true, 0, 0);
42+
CHECK_LOCATION(PlasmaVer::pvMoul, 0xFFFF'FFFF, false, false, 0, 0);
43+
44+
CHECK_LOCATION_MOUL(0x0000'0021, 0, 0);
45+
CHECK_LOCATION_MOUL(0x0000'0022, 0, 1);
46+
CHECK_LOCATION_MOUL(0x0000'7FFF, 0, 0x7FDE);
47+
CHECK_LOCATION_MOUL(0x0000'8020, 0, 0x7FFF);
48+
CHECK_LOCATION_MOUL(0x0001'0000, 0, 0xFFDF);
49+
CHECK_LOCATION_MOUL(0x0001'0001, 0, -32); // 0, 0xFFE0
50+
CHECK_LOCATION_MOUL(0x0001'0020, 0, -1); // 0, 0xFFFF
51+
52+
CHECK_LOCATION_MOUL(0x0001'0021, 1, 0);
53+
CHECK_LOCATION_MOUL(0x00FF'0021, 255, 0);
54+
CHECK_LOCATION_MOUL(0x0100'0020, 255, -1);
55+
CHECK_LOCATION_MOUL(0x7FFF'0021, 0x7FFF, 0);
56+
CHECK_LOCATION_MOUL(0xFEFF'0021, 0xFEFF, 0);
57+
CHECK_LOCATION_MOUL(0xFF00'0020, 0xFEFF, -1);
58+
59+
CHECK_LOCATION_MOUL(0xFF01'0001, -1, 0);
60+
CHECK_LOCATION_MOUL(0xFF01'0002, -1, 1);
61+
CHECK_LOCATION_MOUL(0xFF01'8000, -1, 0x7FFF);
62+
CHECK_LOCATION_MOUL(0xFF02'0000, -1, -1);
63+
CHECK_LOCATION_MOUL(0xFFFE'0001, -254, 0);
64+
CHECK_LOCATION_MOUL(0xFFFF'0000, -254, -1);
65+
66+
// Examples from real PRPs
67+
CHECK_LOCATION_MOUL(0x0006'0022, 6, 1); // city:canyon
68+
CHECK_LOCATION_MOUL(0x0006'0086, 6, 101); // city:museumDoor
69+
CHECK_LOCATION_MOUL(0x0007'001F, 6, -2); // city:BuiltIn
70+
CHECK_LOCATION_MOUL(0x0007'0020, 6, -1); // city:Textures
71+
CHECK_LOCATION_MOUL(0x04D2'0026, 1234, 5); // GoMePubNew:Entry
72+
CHECK_LOCATION_MOUL(0x9C44'0022, 40004, 1); // VeeTsah:Temple
73+
CHECK_LOCATION_MOUL(0x9C45'0020, 40004, -1); // VeeTsah:Textures
74+
CHECK_LOCATION_MOUL(0xFF01'0009, -1, 8); // GlobalAnimations:MaleIdle
75+
CHECK_LOCATION_MOUL(0xFF01'01B4, -1, 435); // GlobalAnimations:FemaleSwimDockExit
76+
CHECK_LOCATION_MOUL(0xFF06'0004, -6, 3); // GlobalAvatars:Audio
77+
}
78+
79+
SECTION("Classic format plLocations") {
80+
CHECK_LOCATION(PlasmaVer::pvPrime, 0, true, true, 0, 0);
81+
CHECK_LOCATION(PlasmaVer::pvPrime, 0xFFFF'FFFF, false, false, 0, 0);
82+
83+
CHECK_LOCATION_CLASSIC(0x0000'0021, 0, 0);
84+
CHECK_LOCATION_CLASSIC(0x0000'0022, 0, 1);
85+
CHECK_LOCATION_CLASSIC(0x0000'007F, 0, 0x5E);
86+
CHECK_LOCATION_CLASSIC(0x0000'00A0, 0, 0x7F);
87+
CHECK_LOCATION_CLASSIC(0x0000'0100, 0, 0xDF);
88+
CHECK_LOCATION_CLASSIC(0x0000'0101, 0, -32); // 0, 0xE0
89+
CHECK_LOCATION_CLASSIC(0x0000'0120, 0, -1); // 0, 0xFF
90+
91+
CHECK_LOCATION_CLASSIC(0x0000'0121, 1, 0);
92+
CHECK_LOCATION_CLASSIC(0x0000'FF21, 255, 0);
93+
CHECK_LOCATION_CLASSIC(0x0001'0020, 255, -1);
94+
CHECK_LOCATION_CLASSIC(0x007F'FF21, 0x7FFF, 0);
95+
CHECK_LOCATION_CLASSIC(0x00FE'FF21, 0xFEFF, 0);
96+
CHECK_LOCATION_CLASSIC(0x00FF'0020, 0xFEFF, -1);
97+
CHECK_LOCATION_CLASSIC(0xFFFE'FF21, 0xFF'FEFF, 0);
98+
CHECK_LOCATION_CLASSIC(0xFFFF'0020, 0xFF'FEFF, -1);
99+
100+
CHECK_LOCATION_CLASSIC(0xFFFF'0101, -1, 0);
101+
CHECK_LOCATION_CLASSIC(0xFFFF'0102, -1, 1);
102+
CHECK_LOCATION_CLASSIC(0xFFFF'0180, -1, 0x7F);
103+
CHECK_LOCATION_CLASSIC(0xFFFF'0200, -1, -1);
104+
CHECK_LOCATION_CLASSIC(0xFFFF'FE01, -254, 0);
105+
CHECK_LOCATION_CLASSIC(0xFFFF'FF00, -254, -1);
106+
107+
// Examples from real PRPs
108+
CHECK_LOCATION_CLASSIC(0x0000'0622, 6, 1); // city:canyon
109+
CHECK_LOCATION_CLASSIC(0x0000'065E, 6, 61); // city:islmLibBanners03Vis
110+
CHECK_LOCATION_CLASSIC(0x0000'071F, 6, -2); // city:BuiltIn
111+
CHECK_LOCATION_CLASSIC(0x0000'0720, 6, -1); // city:Textures
112+
CHECK_LOCATION_CLASSIC(0x0000'2727, 39, 6); // GreatZero:CalibrationMarkerGameGUI
113+
CHECK_LOCATION_CLASSIC(0x0000'2820, 39, -1); // GreatZero:Textures
114+
CHECK_LOCATION_CLASSIC(0xFFFF'0109, -1, 8); // GlobalAnimations:MaleIdle
115+
CHECK_LOCATION_CLASSIC(0xFFFF'0604, -6, 3); // GlobalAvatars:Audio
116+
117+
// The below test case reveals a fault in the Plasma data files -- the
118+
// suffix (page) number overflows the maximum range of the encoding
119+
// and ends up overlapping with <-2|11>, and therefore can't actually
120+
// be decoded correctly.
121+
//CHECK_LOCATION_CLASSIC(0xFFFF'020C, -1, 267); // GlobalAnimations:FemaleBallPushWalk
122+
CHECK_LOCATION_CLASSIC(0xFFFF'020C, -2, 11); // GlobalAnimations:FemaleBallPushWalk
123+
}
124+
}

Tests/catch2_main.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* This file is part of HSPlasma.
2+
*
3+
* HSPlasma is free software: you can redistribute it and/or modify
4+
* it under the terms of the GNU General Public License as published by
5+
* the Free Software Foundation, either version 3 of the License, or
6+
* (at your option) any later version.
7+
*
8+
* HSPlasma is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with HSPlasma. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
// Stub source file that just provides a main() for catch2
18+
#define CATCH_CONFIG_MAIN
19+
#include <catch2/catch.hpp>

core/PRP/KeyedObject/plLocation.cpp

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
#include "plLocation.h"
18+
#include "Debug/plDebug.h"
1819
#include <string_theory/format>
1920

2021
bool plLocation::operator==(const plLocation& other) const
@@ -53,20 +54,37 @@ void plLocation::parse(unsigned int id)
5354
throw hsBadParamException(__FILE__, __LINE__, "Universal PRPs don't use encoded locations");
5455

5556
fState = kStateNormal;
56-
if ((id & 0x80000000) != 0) {
57-
id -= (fVer.isLive() ? 0xFF000001 : 0xFFFF0001);
58-
fSeqPrefix = id >> (fVer.isLive() ? 16 : 8);
59-
fPageNum = id - (fSeqPrefix << (fVer.isLive() ? 16 : 8));
60-
fSeqPrefix = -fSeqPrefix;
57+
if (fVer.isLive()) {
58+
if (id >= 0xFF01'0000) {
59+
id -= 0xFF00'0001;
60+
fSeqPrefix = -(int)(id >> 16);
61+
} else {
62+
id -= 33;
63+
fSeqPrefix = id >> 16;
64+
}
65+
fPageNum = (int16_t)(id & 0xFFFF);
6166
} else {
62-
id -= 33;
63-
fSeqPrefix = id >> (fVer.isLive() ? 16 : 8);
64-
fPageNum = id - (fSeqPrefix << (fVer.isLive() ? 16 : 8));
67+
if (id >= 0xFFFF'0100) {
68+
id -= 0xFFFF'0001;
69+
fSeqPrefix = -(int)(id >> 8);
70+
} else {
71+
id -= 33;
72+
fSeqPrefix = id >> 8;
73+
}
74+
fPageNum = (int8_t)(id & 0xFF);
75+
}
76+
77+
// Plasma does not actually use negative page numbers. However, for
78+
// correctly mapping and converting the "reserved" pages at the end of the
79+
// max-uint8/max-uint16 range, we pretend they are signed and negative.
80+
// In order to leave more room for non-reserved pages, we only apply this
81+
// special logic up to -32
82+
if (fPageNum < -32) {
83+
if (fVer.isLive())
84+
fPageNum += 0x10000;
85+
else
86+
fPageNum += 0x100;
6587
}
66-
if (fVer.isLive())
67-
fPageNum = (signed short)(unsigned short)fPageNum;
68-
else
69-
fPageNum = (signed char)(unsigned char)fPageNum;
7088
}
7189

7290
unsigned int plLocation::unparse() const
@@ -78,16 +96,26 @@ unsigned int plLocation::unparse() const
7896
if (fVer.isUniversal())
7997
throw hsBadParamException(__FILE__, __LINE__, "Universal PRPs don't use encoded locations");
8098

81-
int pgNum;
82-
if (fVer.isLive())
83-
pgNum = (unsigned short)(signed short)fPageNum;
84-
else
85-
pgNum = (unsigned char)(signed char)fPageNum;
86-
if (fSeqPrefix < 0) {
87-
return pgNum - (fSeqPrefix << (fVer.isLive() ? 16 : 8))
88-
+ (fVer.isLive() ? 0xFF000001 : 0xFFFF0001);
99+
if (fVer.isLive()) {
100+
if (fSeqPrefix < -254 || fSeqPrefix > 0xFEFF)
101+
plDebug::Warning("Sequence prefix {} cannot be uniquely encoded.", fSeqPrefix);
102+
if (fPageNum < -32 || fPageNum > 0xFFDF)
103+
plDebug::Warning("Page number {} cannot be uniquely encoded.", fPageNum);
104+
105+
if (fSeqPrefix < 0)
106+
return (-fSeqPrefix << 16) + (fPageNum & 0xFFFF) + 0xFF00'0001;
107+
else
108+
return (fSeqPrefix << 16) + (fPageNum & 0xFFFF) + 33;
89109
} else {
90-
return pgNum + (fSeqPrefix << (fVer.isLive() ? 16 : 8)) + 33;
110+
if (fSeqPrefix < -254 || fSeqPrefix > 0xFF'FEFF)
111+
plDebug::Warning("Sequence prefix {} cannot be uniquely encoded.", fSeqPrefix);
112+
if (fPageNum < -32 || fPageNum > 0xDF)
113+
plDebug::Warning("Page number {} cannot be uniquely encoded.", fPageNum);
114+
115+
if (fSeqPrefix < 0)
116+
return (-fSeqPrefix << 8) + (fPageNum & 0xFF) + 0xFFFF'0001;
117+
else
118+
return (fSeqPrefix << 8) + (fPageNum & 0xFF) + 33;
91119
}
92120
}
93121

0 commit comments

Comments
 (0)