11#include " RUL0.h"
22
3+ #include < cctype>
4+ #include < charconv>
35#include < cstdint>
4- #include < cstdio>
56#include < cstring>
67#include < format>
78#include < ranges>
89
910#include " ParseTypes.h"
1011#include " ini.h"
1112
13+ namespace RUL0 ::ParseHelpers {
14+ std::string_view Trim (std::string_view s) {
15+ while (!s.empty () && std::isspace (static_cast <unsigned char >(s.front ()))) {
16+ s.remove_prefix (1 );
17+ }
18+ while (!s.empty () && std::isspace (static_cast <unsigned char >(s.back ()))) {
19+ s.remove_suffix (1 );
20+ }
21+ return s;
22+ }
23+
24+ bool ParseFloat (std::string_view s, float & out) {
25+ s = Trim (s);
26+ const auto [ptr, ec] = std::from_chars (s.data (), s.data () + s.size (), out, std::chars_format::general);
27+ return ec == std::errc () && ptr == s.data () + s.size ();
28+ }
29+
30+ bool ParseHex (std::string_view s, uint32_t & out) {
31+ s = Trim (s);
32+ if (s.size () >= 2 && s[0 ] == ' 0' && (s[1 ] == ' x' || s[1 ] == ' X' )) {
33+ s = s.substr (2 );
34+ }
35+ const auto [ptr, ec] = std::from_chars (s.data (), s.data () + s.size (), out, 16 );
36+ return ec == std::errc () && ptr == s.data () + s.size ();
37+ }
38+
39+ bool EqualsIgnoreCase (const std::string_view a, const std::string_view b) {
40+ if (a.size () != b.size ()) {
41+ return false ;
42+ }
43+ for (size_t i = 0 ; i < a.size (); ++i) {
44+ const auto ca = static_cast <unsigned char >(a[i]);
45+ const auto cb = static_cast <unsigned char >(b[i]);
46+ if (std::tolower (ca) != std::tolower (cb)) {
47+ return false ;
48+ }
49+ }
50+ return true ;
51+ }
52+
53+ bool StartsWithIgnoreCase (const std::string_view text, const std::string_view prefix) {
54+ if (prefix.size () > text.size ()) {
55+ return false ;
56+ }
57+ for (size_t i = 0 ; i < prefix.size (); ++i) {
58+ const auto ct = static_cast <unsigned char >(text[i]);
59+ const auto cp = static_cast <unsigned char >(prefix[i]);
60+ if (std::tolower (ct) != std::tolower (cp)) {
61+ return false ;
62+ }
63+ }
64+ return true ;
65+ }
66+ }
67+
68+ namespace {
69+ using namespace RUL0 ::ParseHelpers;
70+ }
71+
1272namespace RUL0 {
1373 // Convert PuzzlePiece to human-readable string representation
1474 std::string PuzzlePiece::ToString () const {
@@ -153,10 +213,29 @@ namespace RUL0 {
153213 int rotation, flip; // The game reads these as %i (which can also be octal or hexadecimal format), so we do too
154214 uint32_t instanceId;
155215 std::string name;
156- const auto res = sscanf (value.data (), " %f, %f, %i, %i, 0x%x" , &x, &y, &rotation, &flip, &instanceId);
157- if (res != 5 ) {
216+
217+ std::string_view parts[5 ];
218+ size_t count = 0 ;
219+ size_t start = 0 ;
220+ size_t semi = value.find (kCommentPrefix );
221+ if (semi != std::string_view::npos) {
222+ value = value.substr (0 , semi);
223+ }
224+
225+ while (start < value.size () && count < 5 ) {
226+ size_t comma = value.find (kListDelimiter , start);
227+ if (comma == std::string_view::npos) comma = value.size ();
228+ parts[count++] = Trim (value.substr (start, comma - start));
229+ start = comma + 1 ;
230+ }
231+ if (count != 5 ) return false ;
232+
233+ if (!ParseFloat (parts[0 ], x) || !ParseFloat (parts[1 ], y) ||
234+ !ParseIntAuto (parts[2 ], rotation) || !ParseIntAuto (parts[3 ], flip) ||
235+ !ParseHex (parts[4 ], instanceId)) {
158236 return false ;
159237 }
238+
160239 previewEffect.initialized = true ;
161240 previewEffect.x = x;
162241 previewEffect.y = y;
@@ -228,7 +307,7 @@ namespace RUL0 {
228307
229308 if (expectChar (' ,' )) {
230309 auto mask = nextToken ();
231- nc.hexMask = std::stoul (std::string (mask.substr (0 ,std::min (mask.length (), 10zu))), nullptr , 16 );
310+ nc.hexMask = std::stoul (std::string (mask.substr (0 , std::min (mask.length (), 10zu))), nullptr , 16 );
232311 }
233312
234313 ct.networks .push_back (nc);
@@ -245,13 +324,13 @@ namespace RUL0 {
245324 const auto valStr = std::string_view (value);
246325
247326 // We are either in the Ordering section or in a sectionless preamble
248- if (secStr == kOrderingSection || secStr.empty ()) {
249- if (keyStr == kRotationRingKey ) {
327+ if (EqualsIgnoreCase ( secStr, kOrderingSection ) || secStr.empty ()) {
328+ if (EqualsIgnoreCase ( keyStr, kRotationRingKey ) ) {
250329 // Start a new ordering when we discovered a new RotationRing entry
251330 data->orderings .emplace_back ();
252331 data->orderings .back ().rotationRing = ParseIdList (valStr);
253332 }
254- else if (keyStr == kAddTypesKey ) {
333+ else if (EqualsIgnoreCase ( keyStr, kAddTypesKey ) ) {
255334 if (data->orderings .empty ()) {
256335 // Malformed RUL0: AddTypes before RotationRing
257336 return 0 ;
@@ -267,7 +346,7 @@ namespace RUL0 {
267346 }
268347
269348 // We have found a HighwayIntersectionInfo section
270- if (secStr. starts_with ( kIntersectionInfoPrefix )) {
349+ if (StartsWithIgnoreCase (secStr, kIntersectionInfoPrefix )) {
271350 const uint32_t id = ParsePieceId (secStr);
272351
273352 // We are starting a new puzzle piece
@@ -279,32 +358,31 @@ namespace RUL0 {
279358
280359 auto * piece = data->currentPiece ;
281360
282- if (keyStr == kPieceKey ) {
361+ if (EqualsIgnoreCase ( keyStr, kPieceKey ) ) {
283362 ParsePieceValue (valStr, piece->effect );
284363 }
285- else if (keyStr == kPreviewEffectKey ) {
364+ else if (EqualsIgnoreCase ( keyStr, kPreviewEffectKey ) ) {
286365 piece->effect .name = std::string (valStr);
287366 }
288- else if (keyStr == kCellLayoutKey ) {
289- piece->cellLayout .push_back ( std::string ( valStr) );
367+ else if (EqualsIgnoreCase ( keyStr, kCellLayoutKey ) ) {
368+ piece->cellLayout .emplace_back ( valStr);
290369 }
291- else if (keyStr == kCheckTypeKey ) {
370+ else if (EqualsIgnoreCase ( keyStr, kCheckTypeKey ) ) {
292371 piece->checkTypes .push_back (ParseCheckType (valStr));
293372 }
294- else if (keyStr == kConsLayoutKey ) {
295- piece->consLayout .push_back ( std::string ( valStr) );
373+ else if (EqualsIgnoreCase ( keyStr, kConsLayoutKey ) ) {
374+ piece->consLayout .emplace_back ( valStr);
296375 }
297- else if (keyStr == kAutoPathBaseKey ) {
376+ else if (EqualsIgnoreCase ( keyStr, kAutoPathBaseKey ) ) {
298377 piece->autoPathBase = std::strtoul (value, nullptr , 16 );
299378 }
300- else if (keyStr == kAutoTileBaseKey ) {
379+ else if (EqualsIgnoreCase ( keyStr, kAutoTileBaseKey ) ) {
301380 piece->autoTileBase = std::strtoul (value, nullptr , 16 );
302381 }
303- else if (keyStr == kReplacementIntersectionKey ) {
382+ else if (StartsWithIgnoreCase ( keyStr, kReplacementIntersectionKey ) ) {
304383 int replRotation;
305384 uint32_t replFlip;
306- auto const ret = sscanf (value, " %d, %d" , &replRotation, &replFlip);
307- if (ret != 2 ) {
385+ if (!ParseIntPair (value, replRotation, replFlip)) {
308386 // Invalid ReplacementIntersection format
309387 return 0 ;
310388 }
@@ -318,69 +396,65 @@ namespace RUL0 {
318396 replFlip
319397 };
320398 }
321- else if (keyStr == kPlaceQueryIdKey ) {
399+ else if (EqualsIgnoreCase ( keyStr, kPlaceQueryIdKey ) ) {
322400 piece->placeQueryId = std::strtoul (value, nullptr , 16 );
323401 }
324- else if (keyStr == kCostsKey ) {
325- if (valStr.size () > 0 ) {
402+ else if (EqualsIgnoreCase ( keyStr, kCostsKey ) ) {
403+ if (! valStr.empty () ) {
326404 piece->costs = std::stoi (value);
327405 }
328406 else {
329407 piece->costs = 0 ;
330408 }
331409 }
332- else if (keyStr == kConvertQueryIdKey ) {
410+ else if (EqualsIgnoreCase ( keyStr, kConvertQueryIdKey ) ) {
333411 piece->convertQueryId = std::strtoul (value, nullptr , 16 );
334412 }
335- else if (keyStr == kAutoPlaceKey ) {
413+ else if (EqualsIgnoreCase ( keyStr, kAutoPlaceKey ) ) {
336414 piece->autoPlace = (std::stoi (value) != 0 );
337415 }
338- else if (keyStr == kHandleOffsetKey ) {
339- const auto ret = sscanf (value,
340- " %d, %d" ,
341- &piece->handleOffset .deltaStraight ,
342- &piece->handleOffset .deltaSide
343- );
344- if (ret == 2 ) {
345- piece->stepOffsets .initialized = true ;
416+ else if (EqualsIgnoreCase (keyStr, kHandleOffsetKey )) {
417+ if (ParseIntPair (value,
418+ piece->handleOffset .deltaStraight ,
419+ piece->handleOffset .deltaSide )) {
420+ piece->handleOffset .initialized = true ;
346421 }
347422 }
348- else if (keyStr == kStepOffsetsKey ) {
349- const auto ret = sscanf (value,
350- " %d, %d" ,
351- &piece->stepOffsets .dragStartThreshold ,
352- &piece->stepOffsets .dragCompletionOffset
353- );
354- if (ret == 2 ) {
423+ else if (EqualsIgnoreCase (keyStr, kStepOffsetsKey )) {
424+ if (ParseIntPair (value,
425+ piece->stepOffsets .dragStartThreshold ,
426+ piece->stepOffsets .dragCompletionOffset )) {
355427 piece->stepOffsets .initialized = true ;
356428 }
357429 }
358- else if (keyStr == kOneWayDirKey ) {
430+ else if (EqualsIgnoreCase ( keyStr, kOneWayDirKey ) ) {
359431 const auto val = std::stoi (value);
360432 if (val < +OneWayDir::WEST || val > +OneWayDir::SOUTH_WEST) {
361433 // Invalid OneWayDir value
362434 return 0 ;
363435 }
364436 piece->oneWayDir = static_cast <OneWayDir>(val);
365437 }
366- else if (keyStr == kCopyFromKey ) {
438+ else if (EqualsIgnoreCase ( keyStr, kCopyFromKey ) ) {
367439 piece->copyFrom = std::strtoul (value, nullptr , 16 );
368440 // TODO: Actually do something with this!
369441 }
370- else if (keyStr == kRotateKey ) {
442+ else if (EqualsIgnoreCase ( keyStr, kRotateKey ) ) {
371443 const auto val = std::stoi (value);
372444 if (val < +Rotation::ROT_0 || val > +Rotation::ROT_270) {
373445 // Invalid rotation value
374446 return 0 ;
375447 }
376448 piece->rotate = static_cast <Rotation>(val);
377449 }
378- else if (keyStr == kTransposeKey ) {
450+ else if (EqualsIgnoreCase ( keyStr, kTransposeKey ) ) {
379451 piece->transpose = (std::stoi (value) != 0 );
380452 }
381- else if (keyStr == kTranslateKey ) {
453+ else if (EqualsIgnoreCase ( keyStr, kTranslateKey ) ) {
382454 // This key is not documented, but present in SC4 game decompilation, so included.
383- sscanf (value, " %d, %d" , &piece->translate .x , &piece->translate .z );
455+ if (ParseIntPair (value, piece->translate .x , piece->translate .z )) {
456+ piece->translate .initialized = true ;
457+ }
384458 }
385459 else {
386460 // Malformed RUL0: Unknown key in HighwayIntersectionInfo section
@@ -718,5 +792,4 @@ namespace RUL0 {
718792 BuildNavigationIndices (data);
719793 return data;
720794 }
721-
722795}
0 commit comments