1#include "prim/seadStringBuilder.h"
2#include "heap/seadHeapMgr.h"
3#include "math/seadMathCalcCommon.h"
4#include "prim/seadPtrUtil.h"
5#include "prim/seadStringUtil.h"
6
7namespace sead
8{
9template <>
10StringBuilder* StringBuilder::create(s32 buffer_size, Heap* heap, s32 alignment)
11{
12 return createImpl_(buffer_size, heap, alignment);
13}
14
15template <>
16WStringBuilder* WStringBuilder::create(s32 buffer_size, Heap* heap, s32 alignment)
17{
18 return createImpl_(buffer_size, heap, alignment);
19}
20
21template <typename T>
22StringBuilderBase<T>* StringBuilderBase<T>::create(const T* str, Heap* heap, s32 alignment)
23{
24 const s32 len = calcStrLength_(str);
25 auto* builder = createImpl_(buffer_size: len + 1, heap, alignment);
26 builder->copy(str, len);
27 return builder;
28}
29
30template StringBuilder* StringBuilder::create(const char* str, Heap* heap, s32 alignment);
31template WStringBuilder* WStringBuilder::create(const char16* str, Heap* heap, s32 alignment);
32
33template <typename T>
34StringBuilderBase<T>* StringBuilderBase<T>::createImpl_(s32 buffer_size, Heap* heap, s32 alignment)
35{
36 if (buffer_size <= 0)
37 {
38 SEAD_ASSERT_MSG(false, "buffer_size[%d] must be larger than 0", buffer_size);
39 return nullptr;
40 }
41
42 if (!heap)
43 heap = HeapMgr::instance()->getCurrentHeap();
44
45 if (alignment > s32(alignof(StringBuilderBase<T>)))
46 {
47 const s32 buffer_offset = Mathi::roundUpPow2(val: sizeof(StringBuilderBase<T>), base: alignment);
48 void* buffer = heap->alloc(size: buffer_offset + buffer_size * sizeof(T), alignment);
49 return new (buffer) StringBuilderBase<T>(
50 static_cast<T*>(PtrUtil::addOffset(ptr: buffer, offset: buffer_offset)), buffer_size);
51 }
52 else
53 {
54 void* buffer = heap->alloc(size: buffer_size * sizeof(T) + sizeof(StringBuilderBase<T>),
55 alignment: alignof(StringBuilderBase<T>));
56 return new (buffer) StringBuilderBase<T>(
57 static_cast<T*>(PtrUtil::addOffset(ptr: buffer, offset: sizeof(StringBuilderBase<T>))), buffer_size);
58 }
59}
60
61template <typename T>
62bool StringBuilderBase<T>::endsWith(const T* suffix) const
63{
64 const s32 sub_str_len = calcStrLength_(suffix);
65 if (sub_str_len == 0)
66 return true;
67
68 const T* strc = mBuffer;
69
70 const s32 len = calcLength();
71 if (len < sub_str_len)
72 return false;
73
74 for (s32 i = 0; i < sub_str_len; ++i)
75 {
76 if (strc[len - sub_str_len + i] != suffix[i])
77 return false;
78 }
79 return true;
80}
81
82template bool StringBuilder::endsWith(const char* suffix) const;
83template bool WStringBuilder::endsWith(const char16* suffix) const;
84
85template <typename T>
86s32 StringBuilderBase<T>::copy(const T* src, s32 copy_length)
87{
88 T* dst = mBuffer;
89 const s32 buffer_size = mBufferSize;
90 SEAD_ASSERT_MSG(src, "str must not be null");
91 if (dst == src)
92 return 0;
93
94 if (copy_length == -1)
95 copy_length = calcStrLength_(src);
96
97 if (copy_length >= buffer_size)
98 {
99 SEAD_ASSERT_MSG(false, "Buffer overflow. (Buffer Size: %d, Copy Size: %d)", buffer_size,
100 copy_length);
101 copy_length = buffer_size - 1;
102 }
103
104 if (copy_length >= 1)
105 {
106 MemUtil::copy(dest: dst, src, size: copy_length * sizeof(T));
107 dst[copy_length] = SafeStringBase<T>::cNullChar;
108 }
109 else
110 {
111 copy_length = 0;
112 *dst = SafeStringBase<T>::cNullChar;
113 }
114
115 mLength = copy_length;
116 return copy_length;
117}
118
119template s32 StringBuilder::copy(const char* src, s32 copy_length);
120template s32 WStringBuilder::copy(const char16* src, s32 copy_length);
121
122template <typename T>
123s32 StringBuilderBase<T>::copyAt(s32 at_, const T* src, s32 copy_length)
124{
125 T* dst = getMutableStringTop_();
126 const s32 buffer_size = mBufferSize;
127
128 SEAD_ASSERT_MSG(src, "str must not be null");
129
130 if (copy_length == -1)
131 copy_length = calcStrLength_(src);
132
133 s32 len = this->calcLength();
134 s32 at = at_;
135 if (at_ < 0)
136 {
137 const s32 at_new = len + at_ + 1;
138 if (at_new < 0)
139 {
140 SEAD_ASSERT_MSG(false, "at(%d) out of range[%d, %d]", at_, -len - 1, len);
141 at = 0;
142 goto check_buffer_overflow;
143 }
144 at = at_new;
145 }
146
147 if (len < at)
148 {
149 SEAD_ASSERT_MSG(false, "at(%d) out of range[%d, %d]", at_, -len - 1, len);
150 copy_length = 0;
151 return copy_length;
152 }
153
154check_buffer_overflow:
155 if (at + copy_length >= buffer_size)
156 {
157 SEAD_ASSERT_MSG(false, "Buffer overflow. (Buffer Size: %d, At: %d, Copy Length: %d)",
158 buffer_size, at, copy_length);
159 copy_length = buffer_size - at - 1;
160 }
161
162 if (copy_length < 1)
163 return 0;
164
165 MemUtil::copy(dest: dst + at, src, size: copy_length * sizeof(T));
166 if (mLength < at + copy_length)
167 dst[at + copy_length] = SafeStringBase<T>::cNullChar;
168
169 if (mLength < at + copy_length)
170 mLength = at + copy_length;
171 return copy_length;
172}
173
174template s32 StringBuilder::copyAt(s32 at, const char* src, s32 copy_length);
175template s32 WStringBuilder::copyAt(s32 at, const char16* src, s32 copy_length);
176
177template <typename T>
178s32 StringBuilderBase<T>::cutOffCopy(const T* src, s32 copy_length)
179{
180 T* dst = mBuffer;
181 const s32 buffer_size = mBufferSize;
182 SEAD_ASSERT_MSG(src, "str must not be null");
183 if (dst == src)
184 return 0;
185
186 if (copy_length == -1)
187 copy_length = calcStrLength_(src);
188
189 if (copy_length >= buffer_size)
190 copy_length = buffer_size - 1;
191
192 if (copy_length >= 1)
193 {
194 MemUtil::copy(dest: dst, src, size: copy_length * sizeof(T));
195 dst[copy_length] = SafeStringBase<T>::cNullChar;
196 }
197 else
198 {
199 copy_length = 0;
200 *dst = SafeStringBase<T>::cNullChar;
201 }
202
203 mLength = copy_length;
204 return copy_length;
205}
206
207template s32 StringBuilder::cutOffCopy(const char* src, s32 copy_length);
208template s32 WStringBuilder::cutOffCopy(const char16* src, s32 copy_length);
209
210template <typename T>
211s32 StringBuilderBase<T>::cutOffCopyAt(s32 at_, const T* src, s32 copy_length)
212{
213 T* dst = getMutableStringTop_();
214 const s32 buffer_size = mBufferSize;
215
216 SEAD_ASSERT_MSG(src, "str must not be null");
217
218 if (copy_length == -1)
219 copy_length = calcStrLength_(src);
220
221 s32 len = this->calcLength();
222 s32 at = at_;
223 if (at_ < 0)
224 {
225 const s32 at_new = len + at_ + 1;
226 if (at_new < 0)
227 {
228 SEAD_ASSERT_MSG(false, "at(%d) out of range[%d, %d]", at_, -len - 1, len);
229 at = 0;
230 goto check_buffer_overflow;
231 }
232 at = at_new;
233 }
234
235 if (len < at)
236 {
237 SEAD_ASSERT_MSG(false, "at(%d) out of range[%d, %d]", at_, -len - 1, len);
238 copy_length = 0;
239 return copy_length;
240 }
241
242check_buffer_overflow:
243 if (at + copy_length >= buffer_size)
244 copy_length = buffer_size - at - 1;
245
246 if (copy_length < 1)
247 return 0;
248
249 MemUtil::copy(dest: dst + at, src, size: copy_length * sizeof(T));
250 if (mLength < at + copy_length)
251 dst[at + copy_length] = SafeStringBase<T>::cNullChar;
252
253 if (mLength < at + copy_length)
254 mLength = at + copy_length;
255 return copy_length;
256}
257
258template s32 StringBuilder::cutOffCopyAt(s32 at, const char* src, s32 copy_length);
259template s32 WStringBuilder::cutOffCopyAt(s32 at, const char16* src, s32 copy_length);
260
261template <typename T>
262s32 StringBuilderBase<T>::copyAtWithTerminate(s32 at_, const T* src, s32 copy_length)
263{
264 T* dst = getMutableStringTop_();
265 const s32 buffer_size = mBufferSize;
266
267 SEAD_ASSERT_MSG(src, "str must not be null");
268
269 if (copy_length == -1)
270 copy_length = calcStrLength_(src);
271
272 s32 len = this->calcLength();
273 s32 at = at_;
274 if (at_ < 0)
275 {
276 const s32 at_new = len + at_ + 1;
277 if (at_new < 0)
278 {
279 SEAD_ASSERT_MSG(false, "at(%d) out of range[%d, %d]", at_, -len - 1, len);
280 at = 0;
281 goto check_buffer_overflow;
282 }
283 at = at_new;
284 }
285
286 if (len < at)
287 {
288 SEAD_ASSERT_MSG(false, "at(%d) out of range[%d, %d]", at_, -len - 1, len);
289 copy_length = 0;
290 return copy_length;
291 }
292
293check_buffer_overflow:
294 if (at + copy_length >= buffer_size)
295 {
296 SEAD_ASSERT_MSG(false, "Buffer overflow. (Buffer Size: %d, At: %d, Copy Length: %d)",
297 buffer_size, at, copy_length);
298 copy_length = buffer_size - at - 1;
299 }
300
301 if (copy_length < 1)
302 return 0;
303
304 MemUtil::copy(dest: dst + at, src, size: copy_length * sizeof(T));
305 dst[at + copy_length] = SafeStringBase<T>::cNullChar;
306
307 if (at <= mLength)
308 mLength = at + copy_length;
309 return copy_length;
310}
311
312template s32 StringBuilder::copyAtWithTerminate(s32 at, const char* src, s32 copy_length);
313template s32 WStringBuilder::copyAtWithTerminate(s32 at, const char16* src, s32 copy_length);
314
315template <typename T>
316s32 StringBuilderBase<T>::format(const T* format, ...)
317{
318 std::va_list args;
319 va_start(args, format);
320 s32 ret = formatV(format, args);
321 va_end(args);
322 return ret;
323}
324
325template s32 StringBuilder::format(const char* format, ...);
326template s32 WStringBuilder::format(const char16* format, ...);
327
328template <>
329s32 StringBuilder::formatImpl_(char* s, s32 n, const char* format, va_list args)
330{
331 const s32 ret = StringUtil::vsnprintf(s, n, format, args);
332 return ret < 0 ? n - 1 : ret;
333}
334
335template <>
336s32 WStringBuilder::formatImpl_(char16* s, s32 n, const char16* format, va_list args)
337{
338 const s32 ret = StringUtil::vsnw16printf(s, n, format, args);
339 if (ret >= 0 && ret < n)
340 return ret;
341 s[n - 1] = WSafeString::cNullChar;
342 return n - 1;
343}
344
345template <typename T>
346s32 StringBuilderBase<T>::appendWithFormat(const T* format, ...)
347{
348 std::va_list args;
349 va_start(args, format);
350 const s32 ret = appendWithFormatV(format, args);
351 va_end(args);
352 return ret;
353}
354
355template s32 StringBuilder::appendWithFormat(const char* format, ...);
356template s32 WStringBuilder::appendWithFormat(const char16* format, ...);
357
358template <typename T>
359s32 StringBuilderBase<T>::append(const T* str, s32 append_length)
360{
361 T* dst = getMutableStringTop_();
362 const s32 buffer_size = mBufferSize;
363
364 SEAD_ASSERT_MSG(str, "str must not be null");
365
366 if (append_length == -1)
367 append_length = calcStrLength_(str);
368
369 const s32 at = this->calcLength();
370
371 if (at + append_length >= buffer_size)
372 {
373 SEAD_ASSERT_MSG(false, "Buffer overflow. (Buffer Size: %d, At: %d, Str Length: %d)",
374 buffer_size, at, append_length);
375 append_length = buffer_size - at - 1;
376 }
377
378 if (append_length < 1)
379 return 0;
380
381 MemUtil::copy(dest: dst + at, src: str, size: append_length * sizeof(T));
382 dst[at + append_length] = SafeStringBase<T>::cNullChar;
383
384 mLength = at + append_length;
385 return append_length;
386}
387
388template s32 StringBuilder::append(const char* str, s32 append_length);
389template s32 WStringBuilder::append(const char16* str, s32 append_length);
390
391// NON_MATCHING: regalloc differences
392template <typename T>
393s32 appendImpl_(T* buffer_, s32* length_, const s32 buffer_size_, T c, s32 num)
394{
395 const s32 length = *length_;
396
397 if (buffer_size_ <= num + length)
398 {
399 SEAD_ASSERT_MSG(false, "Buffer overflow. (Buffer Size: %d, Length: %d, Num: %d)",
400 buffer_size_, length, num);
401 num = buffer_size_ - length - 1;
402 }
403
404 for (s32 i = 0; i < num; ++i)
405 buffer_[length + i] = c;
406
407 buffer_[length + num] = SafeStringBase<T>::cNullChar;
408 *length_ = length + num;
409 return num;
410}
411
412template <typename T>
413s32 StringBuilderBase<T>::append(T c, s32 num)
414{
415 if (num < 0)
416 {
417 SEAD_ASSERT_MSG(false, "append error. num < 0, num = %d", num);
418 return 0;
419 }
420
421 if (num == 0)
422 return 0;
423
424 return appendImpl_(mBuffer, &mLength, mBufferSize, c, num);
425}
426
427template s32 StringBuilder::append(char c, s32 n);
428template s32 WStringBuilder::append(char16 c, s32 n);
429
430template <typename T>
431s32 StringBuilderBase<T>::chop(s32 chop_num)
432{
433 s32 length = this->calcLength();
434 T* buffer = getMutableStringTop_();
435 const auto fail = [=] {
436 SEAD_ASSERT_MSG(false, "chop_num(%d) out of range[0, %d]", chop_num, length);
437 };
438
439 if (chop_num < 0)
440 {
441 fail();
442 return 0;
443 }
444
445 if (chop_num > length)
446 {
447 fail();
448 length = mLength;
449 chop_num = mLength;
450 }
451
452 const s32 new_length = length - chop_num;
453 buffer[new_length] = SafeStringBase<T>::cNullChar;
454 mLength = new_length;
455 return chop_num;
456}
457
458template s32 StringBuilder::chop(s32 chop_num);
459template s32 WStringBuilder::chop(s32 chop_num);
460
461template <typename T>
462s32 StringBuilderBase<T>::chopMatchedChar(T c)
463{
464 const s32 length = this->calcLength();
465 if (length < 1)
466 return 0;
467
468 const s32 new_length = length - 1;
469 if (mBuffer[new_length] == c)
470 {
471 mBuffer[new_length] = SafeStringBase<T>::cNullChar;
472 mLength = new_length;
473 return 1;
474 }
475
476 return 0;
477}
478
479template s32 StringBuilder::chopMatchedChar(char c);
480template s32 WStringBuilder::chopMatchedChar(char16 c);
481
482template <typename T>
483s32 StringBuilderBase<T>::chopMatchedChar(const T* characters)
484{
485 const s32 length = this->calcLength();
486 if (length < 1)
487 return 0;
488
489 T* buffer = getMutableStringTop_();
490 for (const T* it = characters; *it; ++it)
491 {
492 if (buffer[length - 1] == *it)
493 {
494 buffer[length - 1] = SafeStringBase<T>::cNullChar;
495 mLength = length - 1;
496 return 1;
497 }
498 }
499
500 return 0;
501}
502
503template s32 StringBuilder::chopMatchedChar(const char* characters);
504template s32 WStringBuilder::chopMatchedChar(const char16* characters);
505
506template <typename T>
507s32 StringBuilderBase<T>::chopUnprintableAsciiChar()
508{
509 const s32 length = this->calcLength();
510 if (length < 1)
511 return 0;
512
513 const s32 new_length = length - 1;
514 if (mBuffer[new_length] <= ' ' || mBuffer[new_length] == 0x7F)
515 {
516 mBuffer[new_length] = SafeStringBase<T>::cNullChar;
517 mLength = new_length;
518 return 1;
519 }
520
521 return 0;
522}
523
524template s32 StringBuilder::chopUnprintableAsciiChar();
525template s32 WStringBuilder::chopUnprintableAsciiChar();
526
527template <typename T>
528s32 StringBuilderBase<T>::rstrip(const T* characters)
529{
530 const s32 length = this->calcLength();
531 if (length <= 0)
532 return 0;
533
534 T* buffer = mBuffer;
535 s32 new_length = length;
536 const auto should_strip = [characters, buffer](s32 idx) {
537 for (auto it = characters; *it; ++it)
538 {
539 if (buffer[idx] == *it)
540 return true;
541 }
542 return false;
543 };
544 while (new_length >= 1 && should_strip(new_length - 1))
545 --new_length;
546
547 if (length <= new_length)
548 return 0;
549
550 mBuffer[new_length] = SafeStringBase<T>::cNullChar;
551 mLength = new_length;
552 return length - new_length;
553}
554
555template s32 StringBuilder::rstrip(const char* characters);
556template s32 WStringBuilder::rstrip(const char16* characters);
557
558// NON_MATCHING: equivalent, two instruction reorders
559template <typename T>
560s32 StringBuilderBase<T>::rstripUnprintableAsciiChars()
561{
562 const s32 length = this->calcLength();
563 if (length <= 0)
564 return 0;
565
566 T* buffer = mBuffer;
567 s32 new_length = length;
568 while (new_length >= 1 && (buffer[new_length - 1] <= 0x20 || buffer[new_length - 1] == 0x7F))
569 --new_length;
570
571 if (length <= new_length)
572 return 0;
573
574 const s32 ret = length - new_length;
575 mBuffer[new_length] = SafeStringBase<T>::cNullChar;
576 mLength = new_length;
577 return ret;
578}
579
580template s32 StringBuilder::rstripUnprintableAsciiChars();
581template s32 WStringBuilder::rstripUnprintableAsciiChars();
582
583template <typename T>
584s32 StringBuilderBase<T>::trim(s32 trim_length)
585{
586 T* mutableString = getMutableStringTop_();
587
588 if (trim_length >= mBufferSize)
589 {
590 SEAD_ASSERT_MSG(false, "trim_length(%d) out of bounds. [0, %d)", trim_length, mBufferSize);
591 return this->calcLength();
592 }
593
594 if (trim_length < 0)
595 {
596 SEAD_ASSERT_MSG(false, "trim_length(%d) out of bounds. [0, %d)", trim_length, mBufferSize);
597 trim_length = 0;
598 }
599
600 mutableString[trim_length] = SafeStringBase<T>::cNullChar;
601 if (trim_length < mLength)
602 mLength = trim_length;
603 return trim_length;
604}
605
606template s32 StringBuilder::trim(s32 trim_length);
607template s32 WStringBuilder::trim(s32 trim_length);
608
609template <typename T>
610s32 StringBuilderBase<T>::trimMatchedString(const T* str)
611{
612 T* buffer = getMutableStringTop_();
613 const s32 length = this->calcLength();
614
615 const s32 trim_str_length = calcStrLength_(str);
616 const s32 new_length = length - trim_str_length;
617
618 if (length < trim_str_length)
619 return length;
620
621 T* substring = &buffer[new_length];
622 for (s32 i = 0; i < trim_str_length; ++i)
623 {
624 if (substring[i] != str[i])
625 return length;
626 }
627
628 buffer[new_length] = SafeStringBase<T>::cNullChar;
629 mLength = new_length;
630 return new_length;
631}
632
633template s32 StringBuilder::trimMatchedString(const char* str);
634template s32 WStringBuilder::trimMatchedString(const char16* str);
635
636template <typename T>
637s32 StringBuilderBase<T>::replaceChar(T old_char, T new_char)
638{
639 const s32 length = this->calcLength();
640 T* buffer = getMutableStringTop_();
641
642 s32 replaced_count = 0;
643 for (s32 i = 0; i < length; ++i)
644 {
645 if (buffer[i] == old_char)
646 {
647 ++replaced_count;
648 buffer[i] = new_char;
649 }
650 }
651 return replaced_count;
652}
653
654template s32 StringBuilder::replaceChar(char old_char, char new_char);
655template s32 WStringBuilder::replaceChar(char16 old_char, char16 new_char);
656
657template <typename T>
658s32 StringBuilderBase<T>::replaceCharList(const SafeStringBase<T>& old_chars,
659 const SafeStringBase<T>& new_chars)
660{
661 T* buffer = getMutableStringTop_();
662 const s32 length = this->calcLength();
663
664 s32 old_chars_len = old_chars.calcLength();
665 const s32 new_chars_len = new_chars.calcLength();
666
667 if (old_chars_len != new_chars_len)
668 {
669 // Nintendo's code just uses the same format string for both T = char and T = char16_t,
670 // which is undefined behavior and produces annoying format warnings, so let's fix it...
671 if constexpr (std::is_same<T, char>())
672 {
673 SEAD_ASSERT_MSG(false, "old_chars(%s).length is not equal to new_chars(%s).length.",
674 old_chars.cstr(), new_chars.cstr());
675 }
676 else if constexpr (std::is_same<T, char16>())
677 {
678 // There is no standard format specifier for char16_t strings :/
679 SEAD_ASSERT_MSG(false, "old_chars(%p).length is not equal to new_chars(%p).length.",
680 old_chars.cstr(), new_chars.cstr());
681 }
682 if (old_chars_len > new_chars_len)
683 old_chars_len = new_chars_len;
684 }
685
686 const T* old_chars_c = old_chars.cstr();
687 const T* new_chars_c = new_chars.cstr();
688
689 if (length < 1)
690 return 0;
691
692 s32 replaced_count = 0;
693 for (s32 i = 0; i < length; ++i)
694 {
695 for (s32 character_idx = 0; character_idx < old_chars_len; ++character_idx)
696 {
697 if (buffer[i] == old_chars_c[character_idx])
698 {
699 ++replaced_count;
700 buffer[i] = new_chars_c[character_idx];
701 break;
702 }
703 }
704 }
705 return replaced_count;
706}
707
708template s32 StringBuilder::replaceCharList(const SafeString& old_chars,
709 const SafeString& new_chars);
710template s32 WStringBuilder::replaceCharList(const WSafeString& old_chars,
711 const WSafeString& new_chars);
712
713template <typename T>
714template <typename OtherType>
715s32 StringBuilderBase<T>::convertFromOtherType_(const OtherType* src, s32 src_size)
716{
717 T* dst = mBuffer;
718 const s32 buffer_size = mBufferSize;
719 SEAD_ASSERT_MSG(src, "str must not be null");
720
721 s32 copy_size = src_size;
722 if (src_size == -1)
723 copy_size = calcStrLength_(src);
724
725 if (copy_size >= buffer_size)
726 {
727 SEAD_ASSERT_MSG(false, "str_length(%d) out of bounds. [0, %d) \n", src_size, buffer_size);
728 copy_size = buffer_size - 1;
729 }
730
731 if (copy_size <= 0)
732 {
733 copy_size = 0;
734 *dst = SafeStringBase<T>::cNullChar;
735 }
736 else
737 {
738 for (s32 i = 0; i < copy_size; ++i)
739 dst[i] = src[i];
740
741 dst[copy_size] = SafeStringBase<T>::cNullChar;
742 }
743 mLength = copy_size;
744 return copy_size;
745}
746
747template <typename T>
748s32 StringBuilderBase<T>::convertFromMultiByteString(const char* str, s32 str_length)
749{
750 if constexpr (std::is_same<char, T>())
751 return copy(src: str, copy_length: str_length);
752 else
753 return convertFromOtherType_(str, str_length);
754}
755
756template <typename T>
757s32 StringBuilderBase<T>::convertFromWideCharString(const char16* str, s32 str_length)
758{
759 if constexpr (std::is_same<char16, T>())
760 return copy(src: str, copy_length: str_length);
761 else
762 return convertFromOtherType_(str, str_length);
763}
764
765template s32 StringBuilder::convertFromMultiByteString(const char* str, s32 str_length);
766template s32 StringBuilder::convertFromWideCharString(const char16* str, s32 str_length);
767template s32 WStringBuilder::convertFromMultiByteString(const char* str, s32 str_length);
768template s32 WStringBuilder::convertFromWideCharString(const char16* str, s32 str_length);
769
770template <typename T>
771s32 StringBuilderBase<T>::cutOffAppend(const T* str, s32 append_length)
772{
773 T* dst = getMutableStringTop_();
774 const s32 buffer_size = mBufferSize;
775
776 SEAD_ASSERT_MSG(str, "str must not be null");
777
778 if (append_length == -1)
779 append_length = calcStrLength_(str);
780
781 const s32 at = this->calcLength();
782
783 if (at + append_length >= buffer_size)
784 append_length = buffer_size - at - 1;
785
786 if (append_length < 1)
787 return 0;
788
789 MemUtil::copy(dest: dst + at, src: str, size: append_length * sizeof(T));
790 dst[at + append_length] = SafeStringBase<T>::cNullChar;
791
792 mLength = at + append_length;
793 return append_length;
794}
795
796template s32 StringBuilder::cutOffAppend(const char* str, s32 append_length);
797template s32 WStringBuilder::cutOffAppend(const char16* str, s32 append_length);
798
799template <typename T>
800s32 StringBuilderBase<T>::cutOffAppend(T c, s32 num)
801{
802 if (num < 0)
803 {
804 SEAD_ASSERT_MSG(false, "append error. num < 0, num = %d", num);
805 return 0;
806 }
807
808 if (num == 0)
809 return 0;
810
811 const s32 buffer_size = mBufferSize;
812 const s32 length = mLength;
813
814 if (num + length >= buffer_size)
815 num = buffer_size - length - 1;
816
817 if (num <= 0)
818 return 0;
819
820 T* buffer = mBuffer;
821 for (s32 i = 0; i < num; ++i)
822 buffer[length + i] = c;
823
824 buffer[length + num] = SafeStringBase<T>::cNullChar;
825 mLength = length + num;
826 return num;
827}
828
829template s32 StringBuilder::cutOffAppend(char c, s32 num);
830template s32 WStringBuilder::cutOffAppend(char16 c, s32 num);
831
832// NON_MATCHING: operands to some `add` instructions are swapped
833template <typename T>
834s32 StringBuilderBase<T>::prepend(const T* str, s32 prepend_length)
835{
836 T* buffer = getMutableStringTop_();
837 const s32 buffer_size = mBufferSize;
838
839 SEAD_ASSERT_MSG(str, "str must not be null");
840
841 if (prepend_length == -1)
842 prepend_length = calcStrLength_(str);
843
844 const s32 length = this->calcLength();
845
846 s32 move_length;
847 if (prepend_length >= buffer_size - length)
848 {
849 SEAD_ASSERT_MSG(false, "Buffer overflow. (Buffer Size: %d, Length: %d, Prepend Length: %d)",
850 buffer_size, length, prepend_length);
851 if (prepend_length >= buffer_size)
852 prepend_length = buffer_size - 1;
853 move_length = buffer_size - 1 - prepend_length;
854 }
855 else
856 {
857 move_length = mLength;
858 }
859
860 void* dest = PtrUtil::addOffset(ptr: buffer, offset: prepend_length * sizeof(T));
861 MemUtil::copyOverlap(dest, src: buffer, size: move_length * sizeof(T));
862
863 MemUtil::copy(dest: buffer, src: str, size: prepend_length * sizeof(T));
864 buffer[move_length + prepend_length] = SafeStringBase<T>::cNullChar;
865
866 mLength = move_length + prepend_length;
867 return move_length + prepend_length - length;
868}
869
870template s32 StringBuilder::prepend(const char* str, s32 prepend_length);
871template s32 WStringBuilder::prepend(const char16* str, s32 prepend_length);
872
873// NON_MATCHING: same regalloc issue as append()
874template <typename T>
875s32 StringBuilderBase<T>::prepend(T c, s32 num)
876{
877 if (num < 0)
878 {
879 // copy and paste error by Nintendo
880 SEAD_ASSERT_MSG(false, "append error. num < 0, num = %d", num);
881 return 0;
882 }
883
884 if (num == 0)
885 return 0;
886
887 const s32 length = mLength;
888 const s32 buffer_size = mBufferSize;
889 T* buffer = mBuffer;
890
891 s32 move_length = length;
892 if (buffer_size - length <= num)
893 {
894 SEAD_ASSERT_MSG(false, "Buffer overflow. (Buffer Size: %d, Length: %d, Num: %d)",
895 buffer_size, length, num);
896 if (buffer_size <= num)
897 num = buffer_size - 1;
898 move_length = buffer_size - 1 - num;
899 }
900
901 MemUtil::copyOverlap(dest: buffer + num, src: buffer, size: move_length * sizeof(T));
902 for (s32 i = 0; i < num; ++i)
903 buffer[i] = c;
904
905 buffer[num + move_length] = SafeStringBase<T>::cNullChar;
906 mLength = num + move_length;
907 return num + move_length - length;
908}
909
910template s32 StringBuilder::prepend(char c, s32 length);
911template s32 WStringBuilder::prepend(char16_t c, s32 length);
912} // namespace sead
913