summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorelijah-thomas774 <elijahthomas774@gmail.com>2024-05-05 22:27:50 -0400
committerelijah-thomas774 <elijahthomas774@gmail.com>2024-05-05 22:27:50 -0400
commit344348bc0508cc132b77bfb81404bfdb99ae3fcf (patch)
tree1a1eafebc8ad18fba08570d3f15b0031b25d733a /src
parent126c5db9431bef2b6f032bce673d7c3d61e62aa3 (diff)
nw4r ut almost matching
Diffstat (limited to 'src')
-rw-r--r--src/egg/core/eggAsyncDisplay.cpp4
-rw-r--r--src/nw4r/ut/ut_CharWriter.cpp260
-rw-r--r--src/nw4r/ut/ut_DvdFileStream.cpp180
-rw-r--r--src/nw4r/ut/ut_DvdLockedFileStream.cpp93
-rw-r--r--src/nw4r/ut/ut_Font.cpp25
-rw-r--r--src/nw4r/ut/ut_IOStream.cpp2
-rw-r--r--src/nw4r/ut/ut_LockedCache.cpp104
-rw-r--r--src/nw4r/ut/ut_NandFileStream.cpp178
-rw-r--r--src/nw4r/ut/ut_ResFont.cpp121
-rw-r--r--src/nw4r/ut/ut_ResFontBase.cpp221
-rw-r--r--src/nw4r/ut/ut_RomFont.cpp226
-rw-r--r--src/nw4r/ut/ut_TextWriterBase.cpp494
12 files changed, 1905 insertions, 3 deletions
diff --git a/src/egg/core/eggAsyncDisplay.cpp b/src/egg/core/eggAsyncDisplay.cpp
index 0901271f..d2b4bf81 100644
--- a/src/egg/core/eggAsyncDisplay.cpp
+++ b/src/egg/core/eggAsyncDisplay.cpp
@@ -196,9 +196,9 @@ void AsyncDisplay::clearEFB(u16 fbWidth, u16 fbHeight, u16 x, u16 y, u16 width,
GXSetTevColor(GX_TEVREG0, color);
GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0);
- GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_TEV_SCALE_0, 1, GX_TEVPREV);
+ GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV);
GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_A0);
- GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_TEV_SCALE_0, 1, GX_TEVPREV);
+ GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV);
GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0);
GXSetZTexture(2, GX_TF_Z24X8, 0);
GXSetZCompLoc(false);
diff --git a/src/nw4r/ut/ut_CharWriter.cpp b/src/nw4r/ut/ut_CharWriter.cpp
index e69de29b..1281dc01 100644
--- a/src/nw4r/ut/ut_CharWriter.cpp
+++ b/src/nw4r/ut/ut_CharWriter.cpp
@@ -0,0 +1,260 @@
+
+#include <nw4r/ut.h>
+
+namespace {
+
+static void SetupGXCommon() {
+ static const nw4r::ut::Color fog = 0;
+
+ GXSetFog(GX_FOG_NONE, fog, 0.0f, 0.0f, 0.0f, 0.0f);
+ GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
+ GXSetZTexture(GX_ZT_DISABLE, GX_TF_Z8, 0);
+ GXSetNumChans(1);
+ GXSetChanCtrl(GX_COLOR0A0, FALSE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);
+ GXSetChanCtrl(GX_COLOR1A1, FALSE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);
+ GXSetNumTexGens(1);
+ GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX_IDENT, FALSE, GX_DUALMTX_IDENT);
+ GXSetNumIndStages(0);
+ GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_SET);
+}
+
+} // namespace
+
+namespace nw4r {
+namespace ut {
+
+CharWriter::CharWriter() : mAlpha(255), mIsWidthFixed(false), mFixedWidth(0.0f), mFont(NULL) {
+ mLoadingTexture.Reset();
+ ResetColorMapping();
+ SetGradationMode(GRADMODE_NONE);
+ SetTextColor(Color(0xFFFFFFFF));
+ SetScale(1.0f, 1.0f);
+ SetCursor(0.0f, 0.0f, 0.0f);
+ EnableLinearFilter(true, true);
+}
+
+CharWriter::~CharWriter() {}
+
+void CharWriter::SetupGX() {
+ ResetTextureCache();
+
+ if (mColorMapping.min != 0x00000000 || mColorMapping.max != 0xFFFFFFFF) {
+ SetupGXWithColorMapping(mColorMapping.min, mColorMapping.max);
+ } else if (mFont != NULL) {
+ switch (mFont->GetTextureFormat()) {
+ case GX_TF_I4:
+ case GX_TF_I8:
+ SetupGXForI();
+ break;
+ case GX_TF_IA4:
+ case GX_TF_IA8:
+ SetupGXDefault();
+ break;
+ case GX_TF_RGB565:
+ case GX_TF_RGB5A3:
+ case GX_TF_RGBA8:
+ SetupGXForRGBA();
+ break;
+ default:
+ SetupGXDefault();
+ break;
+ }
+ } else {
+ SetupGXDefault();
+ }
+}
+
+void CharWriter::SetFontSize(f32 width, f32 height) {
+ SetScale(width / mFont->GetWidth(), height / mFont->GetHeight());
+}
+
+void CharWriter::SetFontSize(f32 height) {
+ f32 scale = height / mFont->GetHeight();
+ SetScale(scale, scale);
+}
+
+f32 CharWriter::GetFontWidth() const {
+ return mScale.x * mFont->GetWidth();
+}
+
+f32 CharWriter::GetFontHeight() const {
+ return mScale.y * mFont->GetHeight();
+}
+
+f32 CharWriter::GetFontAscent() const {
+ return mScale.y * mFont->GetAscent();
+}
+
+f32 CharWriter::GetFontDescent() const {
+ return mScale.y * mFont->GetDescent();
+}
+
+void CharWriter::EnableLinearFilter(bool atSmall, bool atLarge) {
+ mFilter.atSmall = atSmall ? GX_LINEAR : GX_NEAR;
+ mFilter.atLarge = atLarge ? GX_LINEAR : GX_NEAR;
+}
+
+f32 CharWriter::Print(u16 ch) {
+ f32 width;
+ f32 left;
+
+ Glyph glyph;
+ mFont->GetGlyph(&glyph, ch);
+
+ if (mIsWidthFixed) {
+ width = mFixedWidth;
+ left = (width - glyph.widths.charWidth * mScale.x) / 2 + (glyph.widths.leftSpacing * mScale.x);
+ } else {
+ width = glyph.widths.charWidth * mScale.x;
+ left = glyph.widths.leftSpacing * mScale.x;
+ }
+
+ PrintGlyph(mCursorPos.x + left, mCursorPos.y, mCursorPos.z, glyph);
+ mCursorPos.x += width;
+
+ return width;
+}
+
+void CharWriter::PrintGlyph(f32 x, f32 y, f32 z, const Glyph &glyph) {
+ f32 x2 = x + (glyph.widths.glyphWidth * mScale.x);
+ f32 y2 = y + (glyph.height * mScale.y);
+
+ u32 posLeft = glyph.cellX;
+ u16 texLeft = 0x8000 * posLeft / glyph.texWidth;
+
+ u32 posTop = glyph.cellY;
+ u16 texTop = 0x8000 * posTop / glyph.texHeight;
+
+ u32 posRight = posLeft + glyph.widths.glyphWidth;
+ u16 texRight = 0x8000 * posRight / glyph.texWidth;
+
+ u32 posBottom = posTop + glyph.height;
+ u16 texBottom = 0x8000 * posBottom / glyph.texHeight;
+
+ LoadTexture(glyph, GX_TEXMAP0);
+
+ GXBegin(GX_QUADS, GX_VTXFMT0, 4);
+ {
+ GXPosition3f32(x, y, z);
+ GXColor1u32(mVertexColor.tl);
+ GXTexCoord2s16(texLeft, texTop);
+
+ GXPosition3f32(x2, y, z);
+ GXColor1u32(mVertexColor.tr);
+ GXTexCoord2s16(texRight, texTop);
+
+ GXPosition3f32(x2, y2, z);
+ GXColor1u32(mVertexColor.br);
+ GXTexCoord2s16(texRight, texBottom);
+
+ GXPosition3f32(x, y2, z);
+ GXColor1u32(mVertexColor.bl);
+ GXTexCoord2s16(texLeft, texBottom);
+ }
+ // GXEnd();
+}
+
+void CharWriter::LoadTexture(const Glyph &glyph, GXTexMapID slot) {
+ LoadingTexture loadingTexture;
+
+ loadingTexture.slot = slot;
+ loadingTexture.texture = glyph.texture;
+ loadingTexture.filter = mFilter;
+
+ if (loadingTexture != mLoadingTexture) {
+ GXTexObj texObj;
+ GXInitTexObj(&texObj, glyph.texture, glyph.texWidth, glyph.texHeight, glyph.format, GX_CLAMP, GX_CLAMP, FALSE);
+ GXInitTexObjLOD(&texObj, mFilter.atSmall, mFilter.atLarge, 0.0f, 0.0f, 0.0f, FALSE, FALSE, GX_ANISO_1);
+ GXLoadTexObj(&texObj, slot);
+
+ mLoadingTexture = loadingTexture;
+ }
+}
+
+void CharWriter::UpdateVertexColor() {
+ // clang-format off
+ mVertexColor.tl = mTextColor.start;
+ mVertexColor.tr = mTextColor.gradMode != GRADMODE_H ? mTextColor.start : mTextColor.end;
+ mVertexColor.bl = mTextColor.gradMode != GRADMODE_V ? mTextColor.start : mTextColor.end;
+ mVertexColor.br = mTextColor.gradMode == GRADMODE_NONE ? mTextColor.start : mTextColor.end;
+ // clang-format on
+
+ mVertexColor.tl.a = (mVertexColor.tl.a * mAlpha) / 255, mVertexColor.tr.a = (mVertexColor.tr.a * mAlpha) / 255;
+ mVertexColor.bl.a = (mVertexColor.bl.a * mAlpha) / 255;
+ mVertexColor.br.a = (mVertexColor.br.a * mAlpha) / 255;
+}
+
+void CharWriter::SetupVertexFormat() {
+ GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
+ GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
+ GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U16, 15);
+
+ GXClearVtxDesc();
+ GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
+ GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
+ GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
+}
+
+void CharWriter::SetupGXDefault() {
+ SetupGXCommon();
+
+ GXSetNumTevStages(1);
+ GXSetTevDirect(GX_TEVSTAGE0);
+ GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);
+ GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
+ GXSetTevOp(GX_TEVSTAGE0, GX_MODULATE);
+
+ SetupVertexFormat();
+}
+
+void CharWriter::SetupGXWithColorMapping(Color min, Color max) {
+ SetupGXCommon();
+
+ GXSetNumTevStages(2);
+ GXSetTevDirect(GX_TEVSTAGE0);
+ GXSetTevDirect(GX_TEVSTAGE1);
+
+ GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);
+ GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0);
+ GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
+
+ GXSetTevColor(GX_TEVREG0, min);
+ GXSetTevColor(GX_TEVREG1, max);
+
+ GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_C0, GX_CC_C1, GX_CC_TEXC, GX_CC_ZERO);
+ GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_A0, GX_CA_A1, GX_CA_TEXA, GX_CA_ZERO);
+ GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
+ GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
+
+ GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
+ GXSetTevColorIn(GX_TEVSTAGE1, GX_CC_ZERO, GX_CC_CPREV, GX_CC_RASC, GX_CC_ZERO);
+ GXSetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_APREV, GX_CA_RASA, GX_CA_ZERO);
+ GXSetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
+ GXSetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
+
+ SetupVertexFormat();
+}
+
+void CharWriter::SetupGXForI() {
+ SetupGXCommon();
+
+ GXSetNumTevStages(1);
+ GXSetTevDirect(GX_TEVSTAGE0);
+ GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);
+ GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
+ GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_RASC);
+ GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO);
+ GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
+ GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
+
+ SetupVertexFormat();
+}
+
+void CharWriter::SetupGXForRGBA() {
+ SetupGXDefault();
+}
+
+CharWriter::LoadingTexture CharWriter::mLoadingTexture;
+
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_DvdFileStream.cpp b/src/nw4r/ut/ut_DvdFileStream.cpp
index e69de29b..317bbc40 100644
--- a/src/nw4r/ut/ut_DvdFileStream.cpp
+++ b/src/nw4r/ut/ut_DvdFileStream.cpp
@@ -0,0 +1,180 @@
+#include <nw4r/ut.h>
+
+namespace nw4r {
+namespace ut {
+
+NW4R_UT_RTTI_DEF_DERIVED(DvdFileStream, FileStream);
+
+void DvdFileStream::DvdAsyncCallback_(long result, DVDFileInfo *info) {
+ DvdFileStream *self = reinterpret_cast<AsyncContext *>(info)->stream;
+
+ self->mIsBusy = false;
+ self->mResult = result;
+
+ if (self->mCallback != NULL) {
+ self->mCallback(result, self, self->mCallbackArg);
+ }
+}
+
+void DvdFileStream::DvdCBAsyncCallback_(long result, DVDCommandBlock *block) {
+ DvdFileStream *self = reinterpret_cast<AsyncContext *>(block)->stream;
+
+ self->mIsCancelling = false;
+
+ if (self->mCancelCallback != NULL) {
+ self->mCancelCallback(result, self, self->mCancelCallbackArg);
+ }
+}
+
+void DvdFileStream::Initialize_() {
+ mCloseOnDestroy = false;
+ mAllowClose = false;
+ mIsOpen = false;
+ mPriority = DVD_PRIO_MEDIUM;
+ mIsBusy = false;
+ mCallback = NULL;
+ mCallbackArg = NULL;
+ mResult = DVD_RESULT_OK;
+ mCancelCallback = NULL;
+ mIsCancelling = false;
+ mCancelCallbackArg = NULL;
+ mAsyncContext.stream = this;
+}
+
+DvdFileStream::DvdFileStream(long entrynum) {
+ Initialize_();
+ Open(entrynum);
+}
+
+DvdFileStream::DvdFileStream(const DVDFileInfo *info, bool close) {
+ Initialize_();
+ Open(info, close);
+}
+
+DvdFileStream::~DvdFileStream() {
+ if (mCloseOnDestroy) {
+ Close();
+ }
+}
+
+bool DvdFileStream::Open(s32 entrynum) {
+ if (mCloseOnDestroy) {
+ Close();
+ }
+
+ if (DVDFastOpen(entrynum, &mAsyncContext.info)) {
+ mFilePosition.SetFileSize(mAsyncContext.info.size);
+ mFilePosition.Seek(0, SEEK_BEG);
+
+ mCloseOnDestroy = true;
+ mAllowClose = true;
+ mIsOpen = true;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool DvdFileStream::Open(const DVDFileInfo *info, bool close) {
+ if (mCloseOnDestroy) {
+ Close();
+ }
+
+ mAsyncContext.info = *info;
+ mFilePosition.SetFileSize(mAsyncContext.info.size);
+ mFilePosition.Seek(0, SEEK_BEG);
+
+ mCloseOnDestroy = false;
+ mAllowClose = close;
+ mIsOpen = true;
+
+ return true;
+}
+
+void DvdFileStream::Close() {
+ if (mAllowClose && mIsOpen) {
+ DVDClose(&mAsyncContext.info);
+ mIsOpen = false;
+ }
+}
+
+long DvdFileStream::Read(void *dst, unsigned long size) {
+ size = AdjustReadLength_(size);
+
+ long result = DVDReadPrio(&mAsyncContext.info, dst, size, mFilePosition.Tell(), mPriority);
+
+ if (result > 0) {
+ mFilePosition.Skip(result);
+ }
+
+ return result;
+}
+
+bool DvdFileStream::ReadAsync(void *dst, unsigned long size, AsyncCallback callback, void *arg) {
+ size = AdjustReadLength_(size);
+
+ bool success = DvdFileStream::PeekAsync(dst, size, callback, arg);
+
+ if (success) {
+ mFilePosition.Skip(size);
+ } else {
+ mIsBusy = false;
+ }
+
+ return success;
+}
+
+long DvdFileStream::Peek(void *dst, unsigned long size) {
+ size = AdjustReadLength_(size);
+
+ return DVDReadPrio(&mAsyncContext.info, dst, size, mFilePosition.Tell(), mPriority);
+}
+
+bool DvdFileStream::PeekAsync(void *dst, unsigned long size, AsyncCallback callback, void *arg) {
+ mCallback = callback;
+ mCallbackArg = arg;
+ mIsBusy = true;
+
+ size = AdjustReadLength_(size);
+
+ return DVDReadAsyncPrio(&mAsyncContext.info, dst, size, mFilePosition.Tell(), DvdAsyncCallback_, mPriority);
+}
+
+void DvdFileStream::Seek(long offset, unsigned long origin) {
+ mFilePosition.Seek(offset, origin);
+}
+
+void DvdFileStream::Cancel() {
+ DVDCancel(&mAsyncContext.info.block);
+}
+
+bool DvdFileStream::CancelAsync(AsyncCallback callback, void *arg) {
+ mCancelCallback = callback;
+ mCancelCallbackArg = arg;
+
+ BOOL success = DVDCancelAsync(&mAsyncContext.info.block, DvdCBAsyncCallback_);
+
+ if (success) {
+ mIsCancelling = true;
+ }
+
+ return success;
+}
+
+unsigned long DvdFileStream::AdjustReadLength_(unsigned long len) {
+ u32 fileOffset = mFilePosition.Tell();
+ u32 fileSize = mFilePosition.GetFileSize();
+
+ u32 alignSize = RoundUp(fileSize, 32);
+ u32 alignPos = RoundUp(fileOffset + len, 32);
+
+ if (alignPos > alignSize) {
+ len = RoundUp(fileSize - fileOffset, 32);
+ }
+
+ return len;
+}
+
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_DvdLockedFileStream.cpp b/src/nw4r/ut/ut_DvdLockedFileStream.cpp
index e69de29b..eb18ac54 100644
--- a/src/nw4r/ut/ut_DvdLockedFileStream.cpp
+++ b/src/nw4r/ut/ut_DvdLockedFileStream.cpp
@@ -0,0 +1,93 @@
+#include <RVL/OS.h>
+#include <nw4r/ut.h>
+
+namespace nw4r {
+namespace ut {
+
+NW4R_UT_RTTI_DEF_DERIVED(DvdLockedFileStream, DvdFileStream);
+
+OSThreadQueue DvdLockedFileStream::sThreadQueue;
+OSMutex DvdLockedFileStream::sMutex;
+bool DvdLockedFileStream::sInitialized = false;
+
+void DvdLockedFileStream::InitMutex_() {
+ BOOL enabled = OSDisableInterrupts();
+
+ if (!sInitialized) {
+ OSInitMutex(&sMutex);
+ OSInitThreadQueue(&sThreadQueue);
+ sInitialized = true;
+ }
+
+ OSRestoreInterrupts(enabled);
+}
+
+DvdLockedFileStream::DvdLockedFileStream(long entrynum) : DvdFileStream(entrynum) {
+ mCancelFlag = false;
+ InitMutex_();
+}
+
+DvdLockedFileStream::DvdLockedFileStream(const DVDFileInfo *info, bool close) : DvdFileStream(info, close) {
+ mCancelFlag = false;
+ InitMutex_();
+}
+
+DvdLockedFileStream::~DvdLockedFileStream() {}
+
+void DvdLockedFileStream::Close() {
+ DvdFileStream::Close();
+ mCancelFlag = false;
+}
+
+long DvdLockedFileStream::Read(void *dst, unsigned long size) {
+ if (!LockMutex()) {
+ return DVD_RESULT_CANCELED;
+ }
+ long ret = DvdFileStream::Read(dst, size);
+ UnlockMutex();
+ return ret;
+}
+
+long DvdLockedFileStream::Peek(void *dst, unsigned long size) {
+ if (!LockMutex()) {
+ return DVD_RESULT_CANCELED;
+ }
+ long ret = DvdFileStream::Peek(dst, size);
+ UnlockMutex();
+ return ret;
+}
+
+void DvdLockedFileStream::Cancel() {
+ CancelMutex();
+ DvdFileStream::Cancel();
+}
+
+bool DvdLockedFileStream::LockMutex() {
+ BOOL enabled = OSDisableInterrupts();
+ while (!OSTryLockMutex(&sMutex)) {
+ OSSleepThread(&sThreadQueue);
+ if (mCancelFlag) {
+ OSRestoreInterrupts(enabled);
+ return false;
+ }
+ };
+ OSRestoreInterrupts(enabled);
+ return true;
+}
+
+void DvdLockedFileStream::UnlockMutex() {
+ BOOL enabled = OSDisableInterrupts();
+ OSUnlockMutex(&sMutex);
+ OSWakeupThread(&sThreadQueue);
+ OSRestoreInterrupts(enabled);
+}
+
+void DvdLockedFileStream::CancelMutex() {
+ BOOL enabled = OSDisableInterrupts();
+ mCancelFlag = true;
+ OSWakeupThread(&sThreadQueue);
+ OSRestoreInterrupts(enabled);
+}
+
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_Font.cpp b/src/nw4r/ut/ut_Font.cpp
index e69de29b..0032e911 100644
--- a/src/nw4r/ut/ut_Font.cpp
+++ b/src/nw4r/ut/ut_Font.cpp
@@ -0,0 +1,25 @@
+#include <nw4r/ut.h>
+
+namespace nw4r {
+namespace ut {
+
+void Font::InitReaderFunc(FontEncoding encode) {
+ switch (encode) {
+ case FONT_ENCODE_UTF8:
+ mReadFunc = &CharStrmReader::ReadNextCharUTF8;
+ break;
+ case FONT_ENCODE_UTF16:
+ mReadFunc = &CharStrmReader::ReadNextCharUTF16;
+ break;
+ case FONT_ENCODE_SJIS:
+ mReadFunc = &CharStrmReader::ReadNextCharSJIS;
+ break;
+ case FONT_ENCODE_CP1252:
+ default:
+ mReadFunc = &CharStrmReader::ReadNextCharCP1252;
+ break;
+ }
+}
+
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_IOStream.cpp b/src/nw4r/ut/ut_IOStream.cpp
index dec6c090..01c7fed0 100644
--- a/src/nw4r/ut/ut_IOStream.cpp
+++ b/src/nw4r/ut/ut_IOStream.cpp
@@ -9,7 +9,7 @@ bool IOStream::ReadAsync(void *dst, unsigned long size, AsyncCallback callback,
return false;
}
-bool IOStream::Write(const void *src, unsigned long size) {
+long IOStream::Write(const void *src, unsigned long size) {
return false;
}
diff --git a/src/nw4r/ut/ut_LockedCache.cpp b/src/nw4r/ut/ut_LockedCache.cpp
index e69de29b..3972e471 100644
--- a/src/nw4r/ut/ut_LockedCache.cpp
+++ b/src/nw4r/ut/ut_LockedCache.cpp
@@ -0,0 +1,104 @@
+#include <nw4r/ut.h>
+
+namespace nw4r {
+namespace ut {
+namespace {
+
+class LCImpl {
+public:
+ class Lock_ {
+ public:
+ Lock_(LCImpl &lc) : mMutex(lc.mMutex) {
+ OSLockMutex(&mMutex);
+ }
+ ~Lock_() {
+ OSUnlockMutex(&mMutex);
+ }
+
+ private:
+ OSMutex &mMutex; // at 0x0
+ };
+
+public:
+ LCImpl() : mIsEnabled(false) {
+ OSInitMutex(&mMutex);
+ }
+
+ void Enable() {
+ Lock_ lock(*this);
+
+ if (!mIsEnabled) {
+ LCEnable();
+ mIsEnabled = true;
+ }
+ }
+
+ void Disable() {
+ Lock_ lock(*this);
+
+ if (mIsEnabled) {
+ LC::QueueWaitEx(0);
+ LCDisable();
+ mIsEnabled = false;
+ }
+ }
+
+ bool Lock() {
+ OSLockMutex(&mMutex);
+
+ if (mIsEnabled) {
+ LC::QueueWaitEx(0);
+ return true;
+ }
+
+ OSUnlockMutex(&mMutex);
+ return false;
+ }
+
+ void Unlock() {
+ LC::QueueWaitEx(0);
+ OSUnlockMutex(&mMutex);
+ }
+
+private:
+ bool mIsEnabled; // at 0x0
+ OSMutex mMutex; // at 0x4
+};
+
+static LCImpl sLCImpl;
+
+} // namespace
+
+namespace LC {
+
+void Enable() {
+ sLCImpl.Enable();
+}
+
+void Disable() {
+ sLCImpl.Disable();
+}
+
+bool Lock() {
+ return sLCImpl.Lock();
+}
+
+void Unlock() {
+ sLCImpl.Unlock();
+}
+
+void LoadBlocks(void *dst, void *src, unsigned long size) {
+ LCLoadBlocks(dst, src, size);
+}
+
+void StoreBlocks(void *dst, void *src, unsigned long size) {
+ LCStoreBlocks(dst, src, size);
+}
+
+void StoreData(void *dst, void *src, unsigned long size) {
+ LCStoreData(dst, src, size);
+}
+
+} // namespace LC
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_NandFileStream.cpp b/src/nw4r/ut/ut_NandFileStream.cpp
index e69de29b..29c7a0b8 100644
--- a/src/nw4r/ut/ut_NandFileStream.cpp
+++ b/src/nw4r/ut/ut_NandFileStream.cpp
@@ -0,0 +1,178 @@
+
+#include <nw4r/ut.h>
+
+namespace nw4r {
+namespace ut {
+
+NW4R_UT_RTTI_DEF_DERIVED(NandFileStream, FileStream);
+
+void NandFileStream::NandAsyncCallback_(long result, NANDCommandBlock *block) {
+ NandFileStream *self = reinterpret_cast<AsyncContext *>(block)->stream;
+
+ self->mIsBusy = false;
+ self->mResult = result;
+
+ if (self->mCallback != NULL) {
+ self->mCallback(result, self, self->mCallbackArg);
+ }
+}
+
+void NandFileStream::Initialize_() {
+ mCanRead = false;
+ mCanWrite = false;
+ mCloseOnDestroy = false;
+ mAllowClose = false;
+ mIsOpen = false;
+ mIsBusy = false;
+ mCallback = NULL;
+ mCallbackArg = NULL;
+ mResult = NAND_RESULT_OK;
+ mAsyncContext.stream = this;
+}
+
+NandFileStream::NandFileStream(const char *path, unsigned long access) {
+ Initialize_();
+ Open(path, access);
+}
+
+NandFileStream::NandFileStream(const NANDFileInfo *info, unsigned long access, bool close) {
+ Initialize_();
+ Open(info, access, close);
+}
+
+NandFileStream::~NandFileStream() {
+ if (mCloseOnDestroy) {
+ Close();
+ }
+}
+
+bool NandFileStream::Open(const char *path, unsigned long access) {
+ if (mCloseOnDestroy) {
+ Close();
+ }
+
+ mCanRead = access & NAND_ACCESS_READ;
+ mCanWrite = access & NAND_ACCESS_WRITE;
+
+ if (NANDOpen(path, &mAsyncContext.info, access) != NAND_RESULT_OK) {
+ return false;
+ }
+
+ if (mCanRead) {
+ unsigned long fileSize;
+
+ if (NANDGetLength(&mAsyncContext.info, &fileSize) != NAND_RESULT_OK) {
+ NANDClose(&mAsyncContext.info);
+ return false;
+ }
+
+ mFilePosition.SetFileSize(fileSize);
+ }
+
+ mFilePosition.Seek(0, SEEK_BEG);
+
+ mCloseOnDestroy = true;
+ mAllowClose = true;
+ mIsOpen = true;
+
+ return true;
+}
+
+bool NandFileStream::Open(const NANDFileInfo *info, unsigned long access, bool close) {
+ if (mCloseOnDestroy) {
+ Close();
+ }
+
+ mCanRead = access & NAND_ACCESS_READ;
+ mCanWrite = access & NAND_ACCESS_WRITE;
+
+ mAsyncContext.info = *info;
+
+ unsigned long fileSize;
+ if (mCanRead) {
+ if (NANDGetLength(&mAsyncContext.info, &fileSize) != NAND_RESULT_OK) {
+ if (close) {
+ NANDClose(&mAsyncContext.info);
+ }
+ return false;
+ }
+ mFilePosition.SetFileSize(fileSize);
+ }
+ mFilePosition.Seek(0, SEEK_BEG);
+
+ mCloseOnDestroy = false;
+ mAllowClose = close;
+ mIsOpen = true;
+ return true;
+}
+
+void NandFileStream::Close() {
+ if (mAllowClose && mIsOpen) {
+ NANDClose(&mAsyncContext.info);
+ mIsOpen = false;
+ }
+}
+
+long NandFileStream::Read(void *dst, unsigned long size) {
+ NANDSeek(&mAsyncContext.info, mFilePosition.Tell(), NAND_SEEK_BEG);
+
+ long result = NANDRead(&mAsyncContext.info, dst, size);
+ if (result > 0) {
+ mFilePosition.Skip(result);
+ }
+
+ return result;
+}
+
+bool NandFileStream::ReadAsync(void *dst, unsigned long size, AsyncCallback callback, void *arg) {
+ mCallback = callback;
+ mCallbackArg = arg;
+ mIsBusy = true;
+
+ NANDSeek(&mAsyncContext.info, mFilePosition.Tell(), NAND_SEEK_BEG);
+
+ bool success =
+ NANDReadAsync(&mAsyncContext.info, dst, size, NandAsyncCallback_, &mAsyncContext.block) == NAND_RESULT_OK;
+
+ if (success) {
+ mFilePosition.Skip(size);
+ } else {
+ mIsBusy = false;
+ }
+
+ return success;
+}
+
+long NandFileStream::Write(const void *src, unsigned long size) {
+ NANDSeek(&mAsyncContext.info, mFilePosition.Tell(), NAND_SEEK_BEG);
+ long result = NANDWrite(&mAsyncContext.info, src, size);
+ if (result > 0) {
+ mFilePosition.Append(result);
+ }
+ return result;
+}
+
+bool NandFileStream::WriteAsync(const void *src, unsigned long size, AsyncCallback callback, void *arg) {
+ mCallback = callback;
+ mCallbackArg = arg;
+ mIsBusy = true;
+
+ NANDSeek(&mAsyncContext.info, mFilePosition.Tell(), NAND_SEEK_BEG);
+
+ long result = NANDWriteAsync(&mAsyncContext.info, src, size, NandAsyncCallback_, &mAsyncContext.block);
+
+ if (result == NAND_RESULT_OK) {
+ mFilePosition.Append(size);
+ } else {
+ mIsBusy = false;
+ }
+
+ return result == NAND_RESULT_OK;
+}
+
+void NandFileStream::Seek(long offset, unsigned long origin) {
+ mFilePosition.Seek(offset, origin);
+}
+
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_ResFont.cpp b/src/nw4r/ut/ut_ResFont.cpp
index e69de29b..1dfc9f3b 100644
--- a/src/nw4r/ut/ut_ResFont.cpp
+++ b/src/nw4r/ut/ut_ResFont.cpp
@@ -0,0 +1,121 @@
+#include <nw4r/ut.h>
+
+const u32 MAGIC_RESFONT = 'RFNT';
+const u32 MAGIC_UNPACKED = 'RFNU';
+
+const u32 MAGIC_FONTINFO = 'FINF';
+const u32 MAGIC_TEXGLYPH = 'TGLP';
+const u32 MAGIC_CHARWIDTH = 'CWDH';
+const u32 MAGIC_CHARMAP = 'CMAP';
+const u32 MAGIC_GLGR = 'GLGR';
+
+namespace nw4r {
+namespace ut {
+namespace {
+
+template <typename T>
+void ResolveOffset(T *&ptr, void *base) {
+ ptr = reinterpret_cast<T *>(static_cast<char *>(base) + reinterpret_cast<ptrdiff_t>(ptr));
+}
+
+} // namespace
+
+ResFont::ResFont() {}
+
+ResFont::~ResFont() {}
+
+bool ResFont::SetResource(void *buffer) {
+ BinaryFileHeader *header = static_cast<BinaryFileHeader *>(buffer);
+ FontInformation *info = NULL;
+
+ if (!IsManaging(NULL)) {
+ return false;
+ }
+
+ if (header->magic == MAGIC_UNPACKED) {
+ BinaryBlockHeader *block =
+ reinterpret_cast<BinaryBlockHeader *>(reinterpret_cast<char *>(header) + header->headerSize);
+
+ for (int i = 0; i < header->numBlocks; i++) {
+ if (block->magic == MAGIC_FONTINFO) {
+ info = reinterpret_cast<FontInformation *>(block + 1);
+ break;
+ }
+
+ block = reinterpret_cast<BinaryBlockHeader *>(reinterpret_cast<char *>(block) + block->length);
+ }
+ } else {
+ if (header->version == NW4R_VERSION(1, 04)) {
+ if (!IsValidBinaryFile(header, MAGIC_RESFONT, NW4R_VERSION(1, 04), 2)) {
+ return false;
+ }
+ } else if (!IsValidBinaryFile(header, MAGIC_RESFONT, NW4R_VERSION(1, 02), 2)) {
+ return false;
+ }
+
+ info = Rebuild(header);
+ }
+
+ if (info == NULL) {
+ return false;
+ }
+
+ SetResourceBuffer(header, info);
+ InitReaderFunc(GetEncoding());
+
+ return true;
+}
+
+void *ResFont::RemoveResource() {
+ return ResFontBase::RemoveResourceBuffer();
+}
+
+FontInformation *ResFont::Rebuild(BinaryFileHeader *header) {
+ BinaryBlockHeader *block =
+ reinterpret_cast<BinaryBlockHeader *>(reinterpret_cast<char *>(header) + header->headerSize);
+ FontInformation *info = NULL;
+
+ for (int i = 0; i < header->numBlocks; i++) {
+ switch (block->magic) {
+ case MAGIC_FONTINFO:
+ info = reinterpret_cast<FontInformation *>(block + 1);
+ ResolveOffset<FontTextureGlyph>(info->fontGlyph, header);
+
+ if (info->fontWidth != 0) {
+ ResolveOffset<FontWidth>(info->fontWidth, header);
+ }
+
+ if (info->fontMap != 0) {
+ ResolveOffset<FontCodeMap>(info->fontMap, header);
+ }
+ break;
+ case MAGIC_TEXGLYPH:
+ ResolveOffset<u8>(reinterpret_cast<FontTextureGlyph *>(block + 1)->sheetImage, header);
+ break;
+ case MAGIC_CHARWIDTH:
+ FontWidth *width = reinterpret_cast<FontWidth *>(block + 1);
+ if (width->next != 0) {
+ ResolveOffset<FontWidth>(width->next, header);
+ }
+ break;
+ case MAGIC_CHARMAP:
+ FontCodeMap *map = reinterpret_cast<FontCodeMap *>(block + 1);
+ if (map->next != 0) {
+ ResolveOffset<FontCodeMap>(map->next, header);
+ }
+ break;
+ case MAGIC_GLGR:
+ break;
+ default:
+ return NULL;
+ }
+
+ block = reinterpret_cast<BinaryBlockHeader *>(reinterpret_cast<char *>(block) + block->length);
+ }
+
+ header->magic = MAGIC_UNPACKED;
+ return info;
+}
+
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_ResFontBase.cpp b/src/nw4r/ut/ut_ResFontBase.cpp
index e69de29b..40068a17 100644
--- a/src/nw4r/ut/ut_ResFontBase.cpp
+++ b/src/nw4r/ut/ut_ResFontBase.cpp
@@ -0,0 +1,221 @@
+#include <nw4r/ut.h>
+
+namespace nw4r {
+namespace ut {
+namespace detail {
+
+ResFontBase::ResFontBase() : mResource(NULL), mFontInfo(NULL), mLastCharCode(0), mLastGlyphIndex(-1) {}
+
+ResFontBase::~ResFontBase() {}
+
+void ResFontBase::SetResourceBuffer(void *buffer, FontInformation *info) {
+ mResource = buffer;
+ mFontInfo = info;
+}
+
+void *ResFontBase::RemoveResourceBuffer() {
+ void *pUserData = mResource;
+ mFontInfo = NULL;
+ mResource = NULL;
+ return pUserData;
+}
+
+int ResFontBase::GetWidth() const {
+ return mFontInfo->width;
+}
+
+int ResFontBase::GetHeight() const {
+ return mFontInfo->height;
+}
+
+int ResFontBase::GetAscent() const {
+ return mFontInfo->ascent;
+}
+
+int ResFontBase::GetDescent() const {
+ return mFontInfo->height - mFontInfo->ascent;
+}
+
+int ResFontBase::GetBaselinePos() const {
+ return mFontInfo->fontGlyph->baselinePos;
+}
+
+int ResFontBase::GetCellHeight() const {
+ return mFontInfo->fontGlyph->cellHeight;
+}
+
+int ResFontBase::GetCellWidth() const {
+ return mFontInfo->fontGlyph->cellWidth;
+}
+
+int ResFontBase::GetMaxCharWidth() const {
+ return mFontInfo->fontGlyph->maxCharWidth;
+}
+
+Font::Type ResFontBase::GetType() const {
+ return TYPE_RESOURCE;
+}
+
+GXTexFmt ResFontBase::GetTextureFormat() const {
+ return static_cast<GXTexFmt>(mFontInfo->fontGlyph->sheetFormat);
+}
+
+int ResFontBase::GetLineFeed() const {
+ return mFontInfo->lineFeed;
+}
+
+CharWidths ResFontBase::GetDefaultCharWidths() const {
+ return mFontInfo->defaultWidth;
+}
+
+void ResFontBase::SetDefaultCharWidths(const CharWidths &widths) {
+ mFontInfo->defaultWidth = widths;
+}
+
+bool ResFontBase::SetAlternateChar(u16 ch) {
+ u32 index = FindGlyphIndex(ch);
+ if (index != 0xFFFF) {
+ mFontInfo->alterCharIndex = index;
+ return true;
+ }
+
+ return false;
+}
+
+void ResFontBase::SetLineFeed(int lf) {
+ mFontInfo->lineFeed = lf;
+}
+
+int ResFontBase::GetCharWidth(u16 ch) const {
+ return GetCharWidths(ch).charWidth;
+}
+
+CharWidths ResFontBase::GetCharWidths(u16 ch) const {
+ u16 index = GetGlyphIndex(ch);
+ return GetCharWidthsFromIndex(index);
+}
+
+void ResFontBase::GetGlyph(Glyph *out, u16 ch) const {
+ u16 index = GetGlyphIndex(ch);
+ GetGlyphFromIndex(out, index);
+}
+
+bool ResFontBase::HasGlyph(u16 c) const {
+ return FindGlyphIndex(c) != 0xFFFF;
+}
+
+FontEncoding ResFontBase::GetEncoding() const {
+ return static_cast<FontEncoding>(mFontInfo->encoding);
+}
+
+u16 ResFontBase::GetGlyphIndex(u16 c) const {
+ u16 index = FindGlyphIndex(c);
+ return (index != 0xFFFF) ? index : mFontInfo->alterCharIndex;
+}
+
+u16 ResFontBase::FindGlyphIndex(u16 c) const {
+ if (c == mLastCharCode) {
+ return mLastGlyphIndex;
+ }
+ mLastCharCode = c;
+
+ for (FontCodeMap *it = mFontInfo->fontMap; it != NULL; it = it->next) {
+ if (it->firstChar <= c && c <= it->lastChar) {
+ mLastGlyphIndex = FindGlyphIndex(it, c);
+ return mLastGlyphIndex;
+ }
+ }
+
+ mLastGlyphIndex = 0xFFFF;
+ return mLastGlyphIndex;
+}
+
+u16 ResFontBase::FindGlyphIndex(const FontCodeMap *map, u16 c) const {
+ struct CMapScanEntry {
+ u16 code; // at 0x0
+ u16 index; // at 0x2
+ };
+
+ struct CMapInfoScan {
+ u16 num; // at 0x0
+ CMapScanEntry entries[]; // at 0x2
+ };
+
+ u16 index = 0xFFFF;
+
+ switch (map->mappingMethod) {
+ case FONT_MAPMETHOD_LINEAR:
+ index = map->mapInfo[0] + (c - map->firstChar);
+ break;
+ case FONT_MAPMETHOD_ARRAY:
+ index = map->mapInfo[c - map->firstChar];
+ break;
+ case FONT_MAPMETHOD_SCAN:
+ const CMapInfoScan *info = reinterpret_cast<const CMapInfoScan *>(map->mapInfo);
+
+ const CMapScanEntry *s = info->entries;
+ const CMapScanEntry *e = &info->entries[info->num - 1];
+
+ while (s <= e) {
+ const CMapScanEntry *m = s + (e - s) / 2;
+
+ if (m->code < c) {
+ s = m + 1;
+ } else if (c < m->code) {
+ e = m - 1;
+ } else {
+ return m->index;
+ }
+ }
+
+ break;
+ }
+
+ return index;
+}
+
+const CharWidths &ResFontBase::GetCharWidthsFromIndex(u16 index) const {
+ for (const FontWidth *it = mFontInfo->fontWidth; it != NULL; it = it->next) {
+ if (it->firstChar <= index && index <= it->lastChar) {
+ return GetCharWidthsFromIndex(it, index);
+ }
+ }
+
+ return mFontInfo->defaultWidth;
+}
+
+const CharWidths &ResFontBase::GetCharWidthsFromIndex(const FontWidth *width, u16 index) const {
+ return width->widthTable[index - width->firstChar];
+}
+
+void ResFontBase::GetGlyphFromIndex(Glyph *out, u16 index) const {
+ const FontTextureGlyph *texGlyph = mFontInfo->fontGlyph;
+
+ u32 cellsInASheet = texGlyph->sheetRow * texGlyph->sheetLine;
+
+ u32 glyphCell = index % cellsInASheet;
+ u32 glyphSheet = index / cellsInASheet;
+
+ u32 unitX = glyphCell % texGlyph->sheetRow;
+ u32 unitY = glyphCell / texGlyph->sheetRow;
+
+ u32 pixelX = unitX * (texGlyph->cellWidth + 1);
+ u32 pixelY = unitY * (texGlyph->cellHeight + 1);
+
+ out->texture = texGlyph->sheetImage + (glyphSheet * texGlyph->sheetSize);
+
+ out->widths = GetCharWidthsFromIndex(index);
+ out->height = texGlyph->cellHeight;
+
+ out->format = static_cast<GXTexFmt>(texGlyph->sheetFormat);
+
+ out->texWidth = texGlyph->sheetWidth;
+ out->texHeight = texGlyph->sheetHeight;
+
+ out->cellX = pixelX + 1;
+ out->cellY = pixelY + 1;
+}
+
+} // namespace detail
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_RomFont.cpp b/src/nw4r/ut/ut_RomFont.cpp
new file mode 100644
index 00000000..f3cb29f6
--- /dev/null
+++ b/src/nw4r/ut/ut_RomFont.cpp
@@ -0,0 +1,226 @@
+#include <nw4r/ut.h>
+
+namespace nw4r {
+namespace ut {
+
+u16 RomFont::mFontEncode = 0xFFFF;
+
+namespace {
+
+bool IsCP1252Char(u16 c) {
+ return c >= 0x20 && c <= 0xFF;
+}
+
+bool IsSJISHalfWidthChar(u16 c) {
+ if (c > 0xFF) {
+ return false;
+ }
+
+ return (c >= 0x20 && c <= 0x7E) || (c >= 0xA1 && c <= 0xDF);
+}
+
+bool IsSJISFullWidthChar(u16 c) {
+ u8 hi = BitExtract<u16>(c, 8, 8);
+ u8 lo = BitExtract<u16>(c, 0, 8);
+
+ return hi >= 0x81 && hi <= 0x98 && lo >= 0x40 && lo <= 0xFC;
+}
+
+} // namespace
+
+RomFont::RomFont() : mFontHeader(NULL), mAlternateChar('?') {
+ mDefaultWidths.leftSpacing = 0;
+ mDefaultWidths.glyphWidth = 0;
+ mDefaultWidths.charWidth = 0;
+}
+
+RomFont::~RomFont() {}
+
+bool RomFont::Load(void *buffer) {
+ if (mFontHeader != NULL) {
+ return false;
+ }
+
+ BOOL success = OSInitFont(static_cast<OSFontHeader *>(buffer));
+
+ if (success) {
+ mFontEncode = OSGetFontEncode();
+ mFontHeader = static_cast<OSFontHeader *>(buffer);
+
+ mDefaultWidths.leftSpacing = 0;
+ mDefaultWidths.glyphWidth = GetCellWidth();
+ mDefaultWidths.charWidth = GetMaxCharWidth();
+
+ InitReaderFunc(GetEncoding());
+ }
+
+ return success;
+}
+
+u32 RomFont::GetRequireBufferSize() {
+ switch (OSGetFontEncode()) {
+ case OS_FONT_ENCODE_ANSI:
+ return 0x00020120;
+ case OS_FONT_ENCODE_SJIS:
+ return 0x00120F00;
+ }
+
+ return 0;
+}
+
+int RomFont::GetWidth() const {
+ return mFontHeader->width;
+}
+
+int RomFont::GetHeight() const {
+ return GetAscent() + GetDescent();
+}
+
+int RomFont::GetAscent() const {
+ return mFontHeader->ascent;
+}
+
+int RomFont::GetDescent() const {
+ return mFontHeader->descent;
+}
+
+int RomFont::GetBaselinePos() const {
+ return mFontHeader->ascent;
+}
+
+int RomFont::GetCellHeight() const {
+ return mFontHeader->cellHeight;
+}
+
+int RomFont::GetCellWidth() const {
+ return mFontHeader->cellWidth;
+}
+
+int RomFont::GetMaxCharWidth() const {
+ return mFontHeader->width;
+}
+
+Font::Type RomFont::GetType() const {
+ return TYPE_ROM;
+}
+
+GXTexFmt RomFont::GetTextureFormat() const {
+ return GX_TF_I4;
+}
+
+int RomFont::GetLineFeed() const {
+ return mFontHeader->leading;
+}
+
+CharWidths RomFont::GetDefaultCharWidths() const {
+ return mDefaultWidths;
+}
+
+void RomFont::SetDefaultCharWidths(const CharWidths &widths) {
+ mDefaultWidths = widths;
+}
+
+bool RomFont::SetAlternateChar(u16 c) {
+ const u16 prev = mAlternateChar;
+ mAlternateChar = 0xFFFF;
+
+ u16 undef = HandleUndefinedChar(c);
+ if (undef != 0xFFFF) {
+ mAlternateChar = c;
+ return true;
+ } else {
+ mAlternateChar = prev;
+ return false;
+ }
+}
+
+void RomFont::SetLineFeed(int lf) {
+ mFontHeader->leading = lf;
+}
+
+int RomFont::GetCharWidth(u16 c) const {
+ u32 width;
+ char buffer[4];
+
+ MakeCharPtr(buffer, c);
+ OSGetFontWidth(buffer, &width);
+
+ return width;
+}
+
+CharWidths RomFont::GetCharWidths(u16 c) const {
+ int width = GetCharWidth(c);
+
+ CharWidths widths;
+ widths.leftSpacing = 0;
+ widths.glyphWidth = width;
+ widths.charWidth = width;
+
+ return widths;
+}
+
+void RomFont::GetGlyph(Glyph *out, u16 c) const {
+ void *texture;
+ u32 x, y, width;
+ char buffer[4];
+
+ MakeCharPtr(buffer, c);
+ OSGetFontTexture(buffer, &texture, &x, &y, &width);
+
+ out->texture = texture;
+
+ out->widths.leftSpacing = 0;
+ out->widths.glyphWidth = width;
+ out->widths.charWidth = width;
+
+ out->height = mFontHeader->cellHeight;
+ out->format = GX_TF_I4;
+
+ out->texWidth = mFontHeader->sheetWidth;
+ out->texHeight = mFontHeader->sheetHeight;
+
+ out->cellX = x;
+ out->cellY = y;
+}
+
+FontEncoding RomFont::GetEncoding() const {
+ switch (mFontEncode) {
+ case OS_FONT_ENCODE_ANSI:
+ return FONT_ENCODE_CP1252;
+ case OS_FONT_ENCODE_SJIS:
+ return FONT_ENCODE_SJIS;
+ }
+
+ return FONT_ENCODE_CP1252;
+}
+
+void RomFont::MakeCharPtr(char *buffer, u16 c) const {
+ c = HandleUndefinedChar(c);
+
+ if (BitExtract<u16>(c, 8, 8) == 0) {
+ buffer[0] = c & 0x00FF;
+ buffer[1] = '\0';
+ } else {
+ buffer[0] = BitExtract<u16>(c, 8, 8);
+ buffer[1] = c & 0x00FF;
+ buffer[2] = '\0';
+ }
+}
+
+u16 RomFont::HandleUndefinedChar(u16 c) const {
+ bool valid;
+
+ switch (mFontEncode) {
+ case OS_FONT_ENCODE_ANSI:
+ valid = IsCP1252Char(c);
+ break;
+ case OS_FONT_ENCODE_SJIS:
+ valid = IsSJISHalfWidthChar(c) || IsSJISFullWidthChar(c);
+ break;
+ }
+
+ return valid ? c : mAlternateChar;
+}
+
+} // namespace ut
+} // namespace nw4r
diff --git a/src/nw4r/ut/ut_TextWriterBase.cpp b/src/nw4r/ut/ut_TextWriterBase.cpp
index e69de29b..c3fc3032 100644
--- a/src/nw4r/ut/ut_TextWriterBase.cpp
+++ b/src/nw4r/ut/ut_TextWriterBase.cpp
@@ -0,0 +1,494 @@
+
+#include <Runtime.PPCEABI.H/__va_arg.h>
+#include <nw4r/ut.h>
+
+namespace nw4r {
+namespace ut {
+
+template <typename T>
+T *TextWriterBase<T>::mFormatBuffer;
+template <typename T>
+int TextWriterBase<T>::mFormatBufferSize = 0x100;
+
+template <typename T>
+TagProcessorBase<T> TextWriterBase<T>::mDefaultTagProcessor;
+
+template <typename T>
+TextWriterBase<T>::TextWriterBase()
+ : mCharSpace(0.0f), mWidthLimit(NW4R_MATH_FLT_MAX), mLineSpace(0.0f), mTabWidth(4), mDrawFlag(0),
+ mTagProcessor(&mDefaultTagProcessor) {}
+
+template <typename T>
+TextWriterBase<T>::~TextWriterBase() {}
+
+template <typename T>
+void TextWriterBase<T>::SetLineHeight(f32 height) {
+ const Font *font = GetFont();
+ int lf = font != NULL ? font->GetLineFeed() : 0;
+ mLineSpace = height - lf * GetScaleV();
+}
+
+template <typename T>
+f32 TextWriterBase<T>::GetLineHeight() const {
+ const Font *font = GetFont();
+ int lf = font != NULL ? font->GetLineFeed() : 0;
+ return mLineSpace + GetScaleV() * lf;
+}
+
+template <typename T>
+f32 TextWriterBase<T>::CalcFormatStringWidth(const T *format, ...) const {
+ Rect rect;
+ _va_list_struct args;
+
+ va_start(args, format);
+ CalcVStringRect(&rect, format, &args);
+ va_end(args);
+
+ return rect.GetWidth();
+}
+
+template <typename T>
+f32 TextWriterBase<T>::CalcFormatStringHeight(const T *format, ...) const {
+ Rect rect;
+ _va_list_struct args;
+
+ va_start(args, format);
+ CalcVStringRect(&rect, format, &args);
+ va_end(args);
+
+ return rect.GetHeight();
+}
+
+template <typename T>
+void TextWriterBase<T>::CalcFormatStringRect(Rect *rect, const T *format, ...) const {
+ _va_list_struct args;
+
+ va_start(args, format);
+ CalcVStringRect(rect, format, &args);
+ va_end(args);
+}
+
+template <typename T>
+void TextWriterBase<T>::CalcVStringRect(Rect *rect, const T *format, va_list args) const {
+ T *pBuffer;
+
+ if (mFormatBuffer != NULL) {
+ pBuffer = mFormatBuffer;
+ } else {
+ pBuffer = static_cast<T *>(__alloca(mFormatBufferSize));
+ }
+
+ int len = VSNPrintf(pBuffer, mFormatBufferSize, format, args);
+ len = Min(len, mFormatBufferSize - 1);
+
+ CalcStringRect(rect, pBuffer, len);
+}
+
+template <typename T>
+f32 TextWriterBase<T>::CalcStringWidth(const T *str, int len) const {
+ Rect rect;
+ CalcStringRect(&rect, str, len);
+ return rect.GetWidth();
+}
+
+template <typename T>
+f32 TextWriterBase<T>::CalcStringHeight(const T *str, int len) const {
+ Rect rect;
+ CalcStringRect(&rect, str, len);
+ return rect.GetHeight();
+}
+
+template <typename T>
+void TextWriterBase<T>::CalcStringRect(Rect *rect, const T *str, int len) const {
+ TextWriterBase<T> clone(*this);
+ clone.CalcStringRectImpl(rect, str, len);
+}
+
+template <typename T>
+f32 TextWriterBase<T>::Printf(const T *format, ...) {
+ va_list args;
+ va_start(args[0], format);
+ f32 width = VPrintf(format, args);
+ va_end(args[0]);
+ return width;
+}
+
+template <typename T>
+f32 TextWriterBase<T>::VPrintf(const T *str, va_list args) {
+ T *pBuffer;
+
+ if (mFormatBuffer != NULL) {
+ pBuffer = mFormatBuffer;
+ } else {
+ pBuffer = static_cast<T *>(__alloca(mFormatBufferSize));
+ }
+
+ int len = VSNPrintf(pBuffer, mFormatBufferSize, str, args);
+ len = Min(len, mFormatBufferSize - 1);
+ return Print(pBuffer, len);
+}
+
+template <typename T>
+f32 TextWriterBase<T>::Print(const T *str, int len) {
+ TextWriterBase<T> clone(*this);
+
+ f32 width = clone.PrintImpl(str, len, false);
+ SetCursor(clone.GetCursorX(), clone.GetCursorY());
+
+ return width;
+}
+
+template <typename T>
+f32 TextWriterBase<T>::PrintfMutable(const T *format, ...) {
+ va_list args;
+ va_start(args[0], format);
+ f32 width = VPrintfMutable(format, args);
+ va_end(args[0]);
+ return width;
+}
+
+template <typename T>
+f32 TextWriterBase<T>::VPrintfMutable(const T *format, va_list args) {
+ T *pBuffer;
+
+ if (mFormatBuffer != NULL) {
+ pBuffer = mFormatBuffer;
+ } else {
+ pBuffer = static_cast<T *>(__alloca(mFormatBufferSize));
+ }
+
+ int len = VSNPrintf(pBuffer, mFormatBufferSize, format, args);
+ len = Min(len, mFormatBufferSize - 1);
+ return PrintMutable(pBuffer, len);
+}
+
+template <typename T>
+f32 TextWriterBase<T>::PrintMutable(const T *str, int len) {
+ return PrintImpl(str, len, true);
+}
+
+template <typename T>
+f32 TextWriterBase<T>::CalcLineWidth(const T *str, int len) {
+ Rect rect;
+ TextWriterBase<T> clone(*this);
+
+ clone.SetCursor(0.0f, 0.0f);
+ clone.CalcLineRectImpl(&rect, &str, len);
+
+ return rect.GetWidth();
+}
+
+template <typename T>
+bool TextWriterBase<T>::CalcLineRectImpl(Rect *rect, const T **str, int len) {
+ const T *strBegin = *str;
+ const T *strEnd = strBegin + len;
+ const bool useLimit = mWidthLimit < NW4R_MATH_FLT_MAX;
+
+ PrintContext<T> context = {this, strBegin};
+
+ f32 x = 0.0f;
+ bool charSpace = false;
+ bool overLimit = false;
+
+ const T *prevStream = NULL;
+ Rect prevRect;
+
+ CharStrmReader reader = GetFont()->GetCharStrmReader();
+
+ rect->left = 0.0f;
+ rect->right = 0.0f;
+ rect->top = Min(0.0f, GetLineHeight());
+ rect->bottom = Max(0.0f, GetLineHeight());
+ prevRect = *rect;
+
+ reader.Set(strBegin);
+ prevStream = NULL;
+
+ u16 ch = reader.Next();
+
+ while (static_cast<const T *>(reader.GetCurrentPos()) <= strEnd) {
+ if (ch < ' ') {
+ Rect r(x, 0.0f, 0.0f, 0.0f);
+ context.str = static_cast<const T *>(reader.GetCurrentPos());
+ context.flags = charSpace ? 0 : PRINTFLAGS_CHARSPACE;
+ SetCursorX(x);
+
+ if (useLimit && ch != '\n' && prevStream != NULL) {
+ PrintContext<T> context2 = context;
+ TextWriterBase<T> clone(*this);
+
+ Rect r;
+ context2.writer = &clone;
+ mTagProcessor->CalcRect(&r, ch, &context2);
+
+ if (r.GetWidth() > 0.0f && clone.GetCursorX() - context.x > mWidthLimit) {
+ overLimit = true;
+ ch = '\n';
+ reader.Set(prevStream);
+ continue;
+ }
+ }
+
+ Operation oper = mTagProcessor->CalcRect(&r, ch, &context);
+ reader.Set(context.str);
+
+ rect->left = Min(rect->left, r.left);
+ rect->top = Min(rect->top, r.top);
+ rect->right = Max(rect->right, r.right);
+ rect->bottom = Max(rect->bottom, r.bottom);
+
+ x = GetCursorX();
+
+ if (oper == OPERATION_END_DRAW) {
+ *str += len;
+ return false;
+ }
+
+ if (oper == OPERATION_NO_CHAR_SPACE) {
+ charSpace = false;
+ } else if (oper == OPERATION_CHAR_SPACE) {
+ charSpace = true;
+ } else if (oper == OPERATION_NEXT_LINE) {
+ break;
+ }
+ } else {
+ f32 dx = 0.0f;
+
+ if (charSpace) {
+ dx += GetCharSpace();
+ }
+
+ if (IsWidthFixed()) {
+ dx += GetFixedWidth();
+ } else {
+ dx += GetFont()->GetCharWidth(ch) * GetScaleH();
+ }
+
+ if (useLimit && prevStream != NULL && x + dx > mWidthLimit) {
+ overLimit = true;
+ ch = '\n';
+ reader.Set(prevStream);
+ continue;
+ }
+
+ x += dx;
+ rect->left = Min(rect->left, x);
+ rect->right = Max(rect->right, x);
+
+ charSpace = true;
+ }
+
+ if (useLimit) {
+ prevStream = static_cast<const T *>(reader.GetCurrentPos());
+ }
+
+ ch = reader.Next();
+ }
+
+ *str = static_cast<const T *>(reader.GetCurrentPos());
+ return overLimit;
+}
+
+template <typename T>
+void TextWriterBase<T>::CalcStringRectImpl(Rect *rect, const T *str, int len) {
+ const T *end = str + len;
+ int remain = len;
+
+ rect->left = 0.0f;
+ rect->right = 0.0f;
+ rect->top = 0.0f;
+ rect->bottom = 0.0f;
+
+ SetCursor(0.0f, 0.0f);
+
+ do {
+ Rect r;
+ CalcLineRectImpl(&r, &str, remain);
+ remain = end - str;
+
+ rect->left = Min(rect->left, r.left);
+ rect->top = Min(rect->top, r.top);
+ rect->right = Max(rect->right, r.right);
+ rect->bottom = Max(rect->bottom, r.bottom);
+ } while (remain > 0);
+}
+
+template <typename T>
+f32 TextWriterBase<T>::PrintImpl(const T *str, int len, bool m) {
+ f32 cursorX = GetCursorX();
+ f32 cursorY = GetCursorY();
+
+ bool useLimit = mWidthLimit < NW4R_MATH_FLT_MAX;
+
+ f32 orgCursorX = cursorX;
+ f32 orgCursorY = cursorY;
+
+ f32 cursorXAdj = 0.0f;
+ f32 cursorYAdj = 0.0f;
+
+ bool charSpace = false;
+
+ const T *prevStream = str;
+ const T *prevNewline = str;
+
+ f32 textWidth = AdjustCursor(&cursorX, &cursorY, str, len);
+
+ cursorXAdj = orgCursorX - GetCursorX();
+ cursorYAdj = orgCursorY - GetCursorY();
+
+ PrintContext<T> context = {this, str, cursorX, cursorY};
+
+ CharStrmReader reader = GetFont()->GetCharStrmReader();
+ reader.Set(str);
+
+ Operation oper;
+ u16 ch = reader.Next();
+
+ while (static_cast<const T *>(reader.GetCurrentPos()) - str <= len) {
+ if (ch < ' ') {
+ context.str = static_cast<const T *>(reader.GetCurrentPos());
+ context.flags = charSpace ? 0 : PRINTFLAGS_CHARSPACE;
+
+ if (useLimit && ch != '\n' && prevStream != prevNewline) {
+ PrintContext<T> context2 = context;
+ TextWriterBase<T> clone(*this);
+ Rect rect;
+
+ context2.writer = &clone;
+ oper = mTagProcessor->CalcRect(&rect, ch, &context2);
+
+ if (rect.GetWidth() > 0.0f && clone.GetCursorX() - context.x > mWidthLimit) {
+ ch = '\n';
+ reader.Set(prevStream);
+ continue;
+ }
+ }
+
+ oper = mTagProcessor->Process(ch, &context);
+ if (oper == OPERATION_NEXT_LINE) {
+ if (IsDrawFlagSet(0x3, 0x1)) {
+ int remain = len - (context.str - str);
+ f32 width = CalcLineWidth(context.str, remain);
+ f32 offset = (textWidth - width) / 2.0f;
+ SetCursorX(context.x + offset);
+ } else if (IsDrawFlagSet(0x3, 0x2)) {
+ int remain = len - (context.str - str);
+ f32 width = CalcLineWidth(context.str, remain);
+ f32 offset = textWidth - width;
+ SetCursorX(context.x + offset);
+ } else {
+ f32 width = GetCursorX() - context.x;
+ textWidth = Max(textWidth, width);
+ SetCursorX(context.x);
+ }
+
+ if (useLimit) {
+ prevNewline = static_cast<const T *>(reader.GetCurrentPos());
+ }
+
+ charSpace = false;
+ } else if (oper == OPERATION_NO_CHAR_SPACE) {
+ charSpace = false;
+ } else if (oper == OPERATION_CHAR_SPACE) {
+ charSpace = true;
+ } else if (oper == OPERATION_END_DRAW) {
+ break;
+ }
+
+ reader.Set(context.str);
+ } else {
+ f32 baseY = GetCursorY();
+ if (useLimit && prevStream != prevNewline) {
+ f32 baseX = GetCursorX();
+ f32 space = charSpace ? GetCharSpace() : 0.0f;
+ f32 width = IsWidthFixed() ? GetFixedWidth() : GetFont()->GetCharWidth(ch) * GetScaleH();
+ if (baseX - cursorX + space + width > mWidthLimit) {
+ ch = '\n';
+ reader.Set(prevStream);
+ continue;
+ }
+ }
+
+ if (charSpace) {
+ MoveCursorX(GetCharSpace());
+ }
+
+ charSpace = true;
+
+ const Font *font = GetFont();
+ f32 adj = -font->GetBaselinePos() * GetScaleV();
+ MoveCursorY(adj);
+ CharWriter::Print(ch);
+ SetCursorY(baseY);
+ }
+
+ if (useLimit) {
+ prevStream = static_cast<const T *>(reader.GetCurrentPos());
+ }
+
+ ch = reader.Next();
+ }
+
+ f32 width = GetCursorX() - context.x;
+ textWidth = Max(textWidth, width);
+
+ if (IsDrawFlagSet(0x300, 0x100) || IsDrawFlagSet(0x300, 0x200)) {
+ SetCursorY(orgCursorY);
+ } else {
+ MoveCursorY(cursorYAdj);
+ }
+
+ return textWidth;
+}
+
+template <typename T>
+f32 TextWriterBase<T>::AdjustCursor(f32 *x1, f32 *y1, const T *str, int len) {
+ f32 textWidth = 0.0f;
+ f32 textHeight = 0.0f;
+
+ if (!IsDrawFlagSet(0x333, 0x300) && !IsDrawFlagSet(0x333, 0)) {
+ Rect rect;
+ CalcStringRect(&rect, str, len);
+
+ textWidth = rect.left + rect.right;
+ textHeight = rect.top + rect.bottom;
+
+ if (textWidth > mWidthLimit) {
+ textWidth = mWidthLimit;
+ }
+ }
+
+ if (IsDrawFlagSet(0x30, 0x10)) {
+ *x1 -= textWidth / 2;
+ } else if (IsDrawFlagSet(0x30, 0x20)) {
+ *x1 -= textWidth;
+ }
+
+ if (IsDrawFlagSet(0x300, 0x100)) {
+ *y1 -= textHeight / 2;
+ } else if (IsDrawFlagSet(0x300, 0x200)) {
+ *y1 -= textHeight;
+ }
+
+ if (IsDrawFlagSet(0x3, 0x1)) {
+ SetCursorX(*x1 + (textWidth - CalcLineWidth(str, len)) / 2);
+ } else if (IsDrawFlagSet(0x3, 0x2)) {
+ SetCursorX(*x1 + (textWidth - CalcLineWidth(str, len)));
+ } else {
+ SetCursorX(*x1);
+ }
+
+ if (IsDrawFlagSet(0x300, 0x300)) {
+ SetCursorY(*y1);
+ } else {
+ SetCursorY(*y1 + GetFontAscent());
+ }
+
+ return textWidth;
+}
+
+template struct TextWriterBase<char>;
+template struct TextWriterBase<wchar_t>;
+
+} // namespace ut
+} // namespace nw4r