From 32116bc17ed3e610107533aeec002e673637d298 Mon Sep 17 00:00:00 2001 From: elasota Date: Fri, 3 Jan 2020 00:13:41 -0500 Subject: [PATCH] Fix up some file handling, add support for initializing a house file --- GpApp/House.cpp | 34 +- GpApp/SelectHouse.cpp | 5 +- GpCommon/GpFileCreationDisposition.h | 15 + GpCommon/GpWindows.h | 2 + GpD3D/GpD3D.vcxproj | 1 + GpD3D/GpD3D.vcxproj.filters | 3 + GpD3D/GpFileSystem_Win32.cpp | 171 +++++- GpD3D/GpFileSystem_Win32.h | 8 +- PortabilityLayer/FileManager.cpp | 90 +-- PortabilityLayer/FileManager.h | 8 +- PortabilityLayer/FontFamily.cpp | 2 +- PortabilityLayer/HostFileSystem.h | 9 +- PortabilityLayer/MenuManager.cpp | 15 +- PortabilityLayer/PLCore.cpp | 2 +- PortabilityLayer/PLResourceManager.cpp | 26 +- PortabilityLayer/PLResources.cpp | 6 - PortabilityLayer/PLResources.h | 2 - PortabilityLayer/ResourceFile.cpp | 782 ++++++++++++------------- PortabilityLayer/ResourceManager.h | 2 + 19 files changed, 708 insertions(+), 475 deletions(-) create mode 100644 GpCommon/GpFileCreationDisposition.h diff --git a/GpApp/House.cpp b/GpApp/House.cpp index 009cdad..3bd5a14 100644 --- a/GpApp/House.cpp +++ b/GpApp/House.cpp @@ -11,11 +11,14 @@ #include "PLPasStr.h" #include "PLResources.h" #include "PLSound.h" +#include "PLSysCalls.h" #include "DialogUtils.h" #include "Externs.h" #include "FileManager.h" +#include "HostFileSystem.h" #include "House.h" #include "RectUtils.h" +#include "ResourceManager.h" #define kGoToDialogID 1043 @@ -51,32 +54,23 @@ Boolean CreateNewHouse (void) AEKeyword theKeyword; DescType actualType; Size actualSize; - NavReplyRecord theReply; - NavDialogOptions dialogOptions; VFileSpec tempSpec; VFileSpec theSpec; PLError_t theErr; - - theErr = NavGetDefaultDialogOptions(&dialogOptions); - theErr = NavPutFile(nil, &theReply, &dialogOptions, nil, 'gliH', 'ozm5', nil); - if (theErr == PLErrors::kUserCancelled_TEMP) - return false; - if (!theReply.validRecord) - return (false); - - theErr = AEGetNthPtr(&(theReply.selection), 1, typeFSS, &theKeyword, - &actualType, &theSpec, sizeof(VFileSpec), &actualSize); PortabilityLayer::FileManager *fm = PortabilityLayer::FileManager::GetInstance(); - if (theReply.replacing) - { - if (fm->FileExists(theSpec.m_dir, theSpec.m_name)) - { - CheckFileError(PLErrors::kFileNotFound, theSpec.m_name); - return (false); - } + theSpec.m_dir = PortabilityLayer::VirtualDirectories::kUserData; + PasStringCopy(PSTR("My House"), theSpec.m_name); + char savePath[sizeof(theSpec.m_name) + 1]; + size_t savePathLength = 0; + + if (!fm->PromptSaveFile(theSpec.m_dir, savePath, savePathLength, sizeof(theSpec.m_name), PSTR("My House"))) + return false; + + if (fm->FileExists(theSpec.m_dir, theSpec.m_name)) + { if (!fm->DeleteFile(theSpec.m_dir, theSpec.m_name)) { CheckFileError(PLErrors::kAccessDenied, theSpec.m_name); @@ -94,7 +88,7 @@ Boolean CreateNewHouse (void) if (!CheckFileError(theErr, PSTR("New House"))) return (false); - theErr = HCreateResFile(theSpec.m_dir, theSpec.m_name); + theErr = PortabilityLayer::ResourceManager::GetInstance()->CreateBlankResFile(theSpec.m_dir, theSpec.m_name); if (theErr != PLErrors::kNone) YellowAlert(kYellowFailedResCreate, theErr); diff --git a/GpApp/SelectHouse.cpp b/GpApp/SelectHouse.cpp index ffa12e6..540cdc1 100644 --- a/GpApp/SelectHouse.cpp +++ b/GpApp/SelectHouse.cpp @@ -572,8 +572,9 @@ void DoDirSearch (void) for (i = 0; i < kMaxDirectories; i++) theDirs[i] = PortabilityLayer::VirtualDirectories::kUnspecified; currentDir = 0; - theDirs[currentDir] = PortabilityLayer::VirtualDirectories::kGameData; - numDirs = 1; + theDirs[0] = PortabilityLayer::VirtualDirectories::kGameData; + theDirs[1] = PortabilityLayer::VirtualDirectories::kUserData; + numDirs = 2; PortabilityLayer::FileManager *fm = PortabilityLayer::FileManager::GetInstance(); diff --git a/GpCommon/GpFileCreationDisposition.h b/GpCommon/GpFileCreationDisposition.h new file mode 100644 index 0000000..875d141 --- /dev/null +++ b/GpCommon/GpFileCreationDisposition.h @@ -0,0 +1,15 @@ +#pragma once + +namespace GpFileCreationDispositions +{ + enum GpFileCreationDisposition + { + kCreateOrOverwrite, // If exists: Overwrite. If not exists: Create. + kCreateNew, // If exists: Fail. If not exists: Create. + kCreateOrOpen, // If exists: Open. If not exists: Create. + kOpenExisting, // If exists: Open. If not exists: Fail. + kOverwriteExisting, // If exists: Overwrite. If not exists: Fail. + }; +} + +typedef GpFileCreationDispositions::GpFileCreationDisposition GpFileCreationDisposition_t; diff --git a/GpCommon/GpWindows.h b/GpCommon/GpWindows.h index b9213a6..4ba6743 100644 --- a/GpCommon/GpWindows.h +++ b/GpCommon/GpWindows.h @@ -7,6 +7,8 @@ #include #undef CreateMutex +#undef DeleteFile + struct IGpFiber; struct IGpColorCursor_Win32; diff --git a/GpD3D/GpD3D.vcxproj b/GpD3D/GpD3D.vcxproj index 54109df..ef61767 100644 --- a/GpD3D/GpD3D.vcxproj +++ b/GpD3D/GpD3D.vcxproj @@ -170,6 +170,7 @@ + diff --git a/GpD3D/GpD3D.vcxproj.filters b/GpD3D/GpD3D.vcxproj.filters index cdf2cb8..0196bae 100644 --- a/GpD3D/GpD3D.vcxproj.filters +++ b/GpD3D/GpD3D.vcxproj.filters @@ -194,6 +194,9 @@ Header Files + + Header Files + diff --git a/GpD3D/GpFileSystem_Win32.cpp b/GpD3D/GpFileSystem_Win32.cpp index 55f41ba..9201eaa 100644 --- a/GpD3D/GpFileSystem_Win32.cpp +++ b/GpD3D/GpFileSystem_Win32.cpp @@ -7,6 +7,8 @@ #include #include #include +#include + #include class GpDirectoryCursor_Win32 final : public PortabilityLayer::HostDirectoryCursor @@ -112,12 +114,17 @@ GpFileSystem_Win32::GpFileSystem_Win32() } m_prefsDir.append(L"\\GlidePort"); + + m_userHousesDir = m_prefsDir + L"\\Houses"; m_scoresDir = m_prefsDir + L"\\Scores"; CreateDirectoryW(m_prefsDir.c_str(), nullptr); CreateDirectoryW(m_scoresDir.c_str(), nullptr); + CreateDirectoryW(m_userHousesDir.c_str(), nullptr); + m_prefsDir.append(L"\\"); m_scoresDir.append(L"\\"); + m_userHousesDir.append(L"\\"); } DWORD modulePathSize = GetModuleFileNameW(nullptr, m_executablePath, MAX_PATH); @@ -192,7 +199,7 @@ bool GpFileSystem_Win32::FileLocked(PortabilityLayer::VirtualDirectory_t virtual return (attribs & FILE_ATTRIBUTE_READONLY) != 0; } -PortabilityLayer::IOStream *GpFileSystem_Win32::OpenFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, bool create) +PortabilityLayer::IOStream *GpFileSystem_Win32::OpenFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, GpFileCreationDisposition_t createDisposition) { wchar_t winPath[MAX_PATH + 1]; @@ -200,15 +207,58 @@ PortabilityLayer::IOStream *GpFileSystem_Win32::OpenFile(PortabilityLayer::Virtu return false; const DWORD desiredAccess = writeAccess ? (GENERIC_WRITE | GENERIC_READ) : GENERIC_READ; - const DWORD creationDisposition = create ? OPEN_ALWAYS : OPEN_EXISTING; + DWORD winCreationDisposition = 0; - HANDLE h = CreateFileW(winPath, desiredAccess, FILE_SHARE_READ, nullptr, creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr); + switch (createDisposition) + { + case GpFileCreationDispositions::kCreateOrOverwrite: + winCreationDisposition = CREATE_ALWAYS; + break; + case GpFileCreationDispositions::kCreateNew: + winCreationDisposition = CREATE_NEW; + break; + case GpFileCreationDispositions::kCreateOrOpen: + winCreationDisposition = OPEN_ALWAYS; + break; + case GpFileCreationDispositions::kOpenExisting: + winCreationDisposition = OPEN_EXISTING; + break; + case GpFileCreationDispositions::kOverwriteExisting: + winCreationDisposition = TRUNCATE_EXISTING; + break; + default: + return false; + } + + HANDLE h = CreateFileW(winPath, desiredAccess, FILE_SHARE_READ, nullptr, winCreationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr); if (h == INVALID_HANDLE_VALUE) return false; return new GpFileStream_Win32(h, true, writeAccess, true); } +bool GpFileSystem_Win32::DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &existed) +{ + wchar_t winPath[MAX_PATH + 1]; + + if (!ResolvePath(virtualDirectory, path, winPath)) + return false; + + if (DeleteFileW(winPath)) + { + existed = true; + return true; + } + + DWORD err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) + existed = false; + else + existed = true; + + return false; +} + PortabilityLayer::HostDirectoryCursor *GpFileSystem_Win32::ScanDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory) { wchar_t winPath[MAX_PATH + 2]; @@ -225,6 +275,118 @@ PortabilityLayer::HostDirectoryCursor *GpFileSystem_Win32::ScanDirectory(Portabi return GpDirectoryCursor_Win32::Create(ff, findData); } +bool GpFileSystem_Win32::PromptSaveFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, char *path, size_t &outPathLength, size_t pathCapacity, const char *initialFileName) +{ + wchar_t baseFN[MAX_PATH + 5]; + wchar_t baseDir[MAX_PATH + 5]; + + const size_t existingPathLen = strlen(initialFileName); + if (existingPathLen >= MAX_PATH) + return false; + + for (size_t i = 0; i < existingPathLen; i++) + baseFN[i] = static_cast(initialFileName[i]); + baseFN[existingPathLen] = 0; + + if (!ResolvePath(virtualDirectory, "", baseDir)) + return false; + + OPENFILENAMEW ofn; + memset(&ofn, 0, sizeof(ofn)); + + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFilter = L"GlidePort File (*.gpf)\0*.gpf\0"; + ofn.lpstrFile = baseFN; + ofn.lpstrDefExt = L"gpf"; + ofn.nMaxFile = MAX_PATH; + ofn.lpstrInitialDir = baseDir; + ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT; + + if (!GetSaveFileNameW(&ofn)) + return false; + + if (ofn.Flags & OFN_EXTENSIONDIFFERENT) + { + MessageBeep(MB_ICONERROR); + MessageBoxW(nullptr, L"Save file failed: Saved files must have the '.gpf' extension", L"Invalid file path", MB_OK); + return false; + } + + const wchar_t *fn = ofn.lpstrFile + ofn.nFileOffset; + size_t fnLengthWithoutExt = wcslen(fn); + if (ofn.nFileExtension - 1 > ofn.nFileOffset) // Off by 1 because extension doesn't include . + fnLengthWithoutExt = ofn.nFileExtension - ofn.nFileOffset - 1; + + if (fnLengthWithoutExt >= pathCapacity) + { + wchar_t msg[256]; + wsprintfW(msg, L"Save file failed: File name is too long. Limit is %i characters.", static_cast(pathCapacity)); + MessageBeep(MB_ICONERROR); + MessageBoxW(nullptr, msg, L"Invalid file path", MB_OK); + return false; + } + + if (ofn.nFileOffset != wcslen(baseDir) || memcmp(ofn.lpstrFile, baseDir, ofn.nFileOffset * sizeof(wchar_t))) + { + wchar_t msg[256 + MAX_PATH]; + wsprintfW(msg, L"Save file failed: File can't be saved here, it must be saved in %s", baseDir); + MessageBeep(MB_ICONERROR); + MessageBoxW(nullptr, msg, L"Invalid file path", MB_OK); + return false; + } + + const wchar_t *unsupportedCharMsg = L"File name contains unsupported characters."; + + for (size_t i = 0; i < fnLengthWithoutExt; i++) + { + if (fn[i] < static_cast(0) || fn[i] >= static_cast(128)) + { + MessageBeep(MB_ICONERROR); + MessageBoxW(nullptr, unsupportedCharMsg, L"Invalid file path", MB_OK); + return false; + } + + path[i] = static_cast(fn[i]); + } + + if (!ValidateFilePath(path, fnLengthWithoutExt)) + { + MessageBeep(MB_ICONERROR); + MessageBoxW(nullptr, unsupportedCharMsg, L"Invalid file path", MB_OK); + return false; + } + + outPathLength = fnLengthWithoutExt; + + return true; +} + +bool GpFileSystem_Win32::ValidateFilePath(const char *str, size_t length) const +{ + for (size_t i = 0; i < length; i++) + { + const char c = str[i]; + if (c >= '0' && c <= '9') + continue; + + if (c == '_' || c == '.' || c == '\'') + continue; + + if (c == ' ' && i != 0 && i != length - 1) + continue; + + if (c >= 'a' && c <= 'z') + continue; + + if (c >= 'A' && c <= 'Z') + continue; + + return false; + } + + return true; +} + const wchar_t *GpFileSystem_Win32::GetBasePath() const { return m_executablePath; @@ -247,6 +409,9 @@ bool GpFileSystem_Win32::ResolvePath(PortabilityLayer::VirtualDirectory_t virtua case PortabilityLayer::VirtualDirectories::kGameData: baseDir = m_housesDir.c_str(); break; + case PortabilityLayer::VirtualDirectories::kUserData: + baseDir = m_userHousesDir.c_str(); + break; case PortabilityLayer::VirtualDirectories::kPrefs: baseDir = m_prefsDir.c_str(); break; diff --git a/GpD3D/GpFileSystem_Win32.h b/GpD3D/GpFileSystem_Win32.h index bf1febe..847544c 100644 --- a/GpD3D/GpFileSystem_Win32.h +++ b/GpD3D/GpFileSystem_Win32.h @@ -14,9 +14,14 @@ public: bool FileExists(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path) override; bool FileLocked(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool *exists) override; - PortabilityLayer::IOStream *OpenFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, bool create) override; + PortabilityLayer::IOStream *OpenFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, GpFileCreationDisposition_t createDisposition) override; + bool DeleteFile(PortabilityLayer::VirtualDirectory_t virtualDirectory, const char *path, bool &existed) override; PortabilityLayer::HostDirectoryCursor *ScanDirectory(PortabilityLayer::VirtualDirectory_t virtualDirectory) override; + bool PromptSaveFile(PortabilityLayer::VirtualDirectory_t dirID, char *path, size_t &outPathLength, size_t pathCapacity, const char *initialFileName) override; + + bool ValidateFilePath(const char *path, size_t sz) const override; + const wchar_t *GetBasePath() const; static GpFileSystem_Win32 *GetInstance(); @@ -28,6 +33,7 @@ private: std::wstring m_scoresDir; std::wstring m_packagedDir; std::wstring m_housesDir; + std::wstring m_userHousesDir; std::wstring m_resourcesDir; wchar_t m_executablePath[MAX_PATH]; diff --git a/PortabilityLayer/FileManager.cpp b/PortabilityLayer/FileManager.cpp index 3f6b635..e662a47 100644 --- a/PortabilityLayer/FileManager.cpp +++ b/PortabilityLayer/FileManager.cpp @@ -29,8 +29,10 @@ namespace PortabilityLayer PLError_t OpenFileResources(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, IOStream *&outRefNum) override; bool ReadFileProperties(VirtualDirectory_t dirID, const PLPasStr &filename, MacFileProperties &properties) override; - PLError_t RawOpenFileData(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, bool ignoreMeta, IOStream *&outStream) override; - PLError_t RawOpenFileResources(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, bool ignoreMeta, IOStream *&outStream) override; + PLError_t RawOpenFileData(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, bool ignoreMeta, GpFileCreationDisposition_t creationDisposition, IOStream *&outStream) override; + PLError_t RawOpenFileResources(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, bool ignoreMeta, GpFileCreationDisposition_t creationDisposition, IOStream *&outStream) override; + + bool PromptSaveFile(VirtualDirectory_t dirID, char *path, size_t &outPathLength, size_t pathCapacity, const PLPasStr &initialFileName) override; static FileManagerImpl *GetInstance(); @@ -38,7 +40,7 @@ namespace PortabilityLayer typedef char ExtendedFileName_t[64 + 4]; PLError_t OpenFileFork(VirtualDirectory_t dirID, const PLPasStr &filename, const char *ext, EFilePermission permission, IOStream *&outRefNum); - PLError_t RawOpenFileFork(VirtualDirectory_t dirID, const PLPasStr &filename, const char *ext, EFilePermission permission, bool ignoreMeta, bool create, IOStream *&outStream); + PLError_t RawOpenFileFork(VirtualDirectory_t dirID, const PLPasStr &filename, const char *ext, EFilePermission permission, bool ignoreMeta, GpFileCreationDisposition_t createDisposition, IOStream *&outStream); static bool ConstructFilename(ExtendedFileName_t& extFN, const PLPasStr &fn, const char *extension); @@ -74,12 +76,28 @@ namespace PortabilityLayer bool FileManagerImpl::DeleteFile(VirtualDirectory_t dirID, const PLPasStr &filename) { - ExtendedFileName_t extFN; - if (!ConstructFilename(extFN, filename, ".gpf")) - return false; + const size_t numExts = 3; - // PL_NotYetImplemented_TODO("FileSystem") - return false; + const char *exts[numExts] = { ".gpr", ".gpd", ".gpf" }; + const bool extMayNotExist[numExts] = { true, true, false }; + + for (int extIndex = 0; extIndex < numExts; extIndex++) + { + ExtendedFileName_t extFN; + if (!ConstructFilename(extFN, filename, exts[extIndex])) + return true; + + bool existed = false; + if (!PortabilityLayer::HostFileSystem::GetInstance()->DeleteFile(dirID, extFN, existed)) + { + if (extMayNotExist[extIndex] && !existed) + continue; + else + return false; + } + } + + return true; } PLError_t FileManagerImpl::CreateFile(VirtualDirectory_t dirID, const PLPasStr &filename, const MacFileProperties &mfp) @@ -92,7 +110,7 @@ namespace PortabilityLayer return PLErrors::kBadFileName; IOStream *stream = nullptr; - PLError_t err = RawOpenFileFork(dirID, filename, ".gpf", EFilePermission_Write, true, true, stream); + PLError_t err = RawOpenFileFork(dirID, filename, ".gpf", EFilePermission_Write, true, GpFileCreationDispositions::kCreateOrOverwrite, stream); if (err) return err; @@ -130,7 +148,7 @@ namespace PortabilityLayer bool FileManagerImpl::ReadFileProperties(VirtualDirectory_t dirID, const PLPasStr &filename, MacFileProperties &properties) { IOStream *stream = nullptr; - PLError_t err = RawOpenFileFork(dirID, filename, ".gpf", EFilePermission_Read, true, false, stream); + PLError_t err = RawOpenFileFork(dirID, filename, ".gpf", EFilePermission_Read, true, GpFileCreationDispositions::kOpenExisting, stream); if (err) return false; @@ -144,14 +162,23 @@ namespace PortabilityLayer return readOk; } - PLError_t FileManagerImpl::RawOpenFileData(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission permission, bool ignoreMeta, IOStream *&outStream) + PLError_t FileManagerImpl::RawOpenFileData(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission permission, bool ignoreMeta, GpFileCreationDisposition_t createDisposition, IOStream *&outStream) { - return RawOpenFileFork(dirID, filename, ".gpd", permission, ignoreMeta, false, outStream); + return RawOpenFileFork(dirID, filename, ".gpd", permission, ignoreMeta, createDisposition, outStream); } - PLError_t FileManagerImpl::RawOpenFileResources(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission permission, bool ignoreMeta, IOStream *&outStream) + PLError_t FileManagerImpl::RawOpenFileResources(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission permission, bool ignoreMeta, GpFileCreationDisposition_t createDisposition, IOStream *&outStream) { - return RawOpenFileFork(dirID, filename, ".gpr", permission, ignoreMeta, false, outStream); + return RawOpenFileFork(dirID, filename, ".gpr", permission, ignoreMeta, createDisposition, outStream); + } + + bool FileManagerImpl::PromptSaveFile(VirtualDirectory_t dirID, char *path, size_t &outPathLength, size_t pathCapacity, const PLPasStr &initialFileName) + { + ExtendedFileName_t extFN; + if (!ConstructFilename(extFN, initialFileName, "")) + return false; + + return PortabilityLayer::HostFileSystem::GetInstance()->PromptSaveFile(dirID, path, outPathLength, pathCapacity, extFN); } FileManagerImpl *FileManagerImpl::GetInstance() @@ -162,8 +189,9 @@ namespace PortabilityLayer PLError_t FileManagerImpl::OpenFileFork(VirtualDirectory_t dirID, const PLPasStr &filename, const char *extension, EFilePermission permission, IOStream *&outStream) { bool isWriteAccess = (permission == EFilePermission_Any || permission == EFilePermission_ReadWrite || permission == EFilePermission_Write); + GpFileCreationDisposition_t createDisposition = isWriteAccess ? GpFileCreationDispositions::kCreateOrOpen : GpFileCreationDispositions::kOpenExisting; IOStream *stream = nullptr; - PLError_t openError = RawOpenFileFork(dirID, filename, extension, permission, false, isWriteAccess, stream); + PLError_t openError = RawOpenFileFork(dirID, filename, extension, permission, false, createDisposition, stream); if (openError != PLErrors::kNone) return openError; @@ -172,7 +200,7 @@ namespace PortabilityLayer return PLErrors::kNone; } - PLError_t FileManagerImpl::RawOpenFileFork(VirtualDirectory_t dirID, const PLPasStr &filename, const char *ext, EFilePermission permission, bool ignoreMeta, bool create, IOStream *&outStream) + PLError_t FileManagerImpl::RawOpenFileFork(VirtualDirectory_t dirID, const PLPasStr &filename, const char *ext, EFilePermission permission, bool ignoreMeta, GpFileCreationDisposition_t createDisposition, IOStream *&outStream) { ExtendedFileName_t gpfExtFN; ExtendedFileName_t extFN; @@ -192,27 +220,25 @@ namespace PortabilityLayer if (!ConstructFilename(extFN, filename, ext)) return PLErrors::kBadFileName; - const bool needToCreate = create && !HostFileSystem::GetInstance()->FileExists(dirID, extFN); - IOStream *fstream = nullptr; switch (permission) { case EFilePermission_Any: - fstream = HostFileSystem::GetInstance()->OpenFile(dirID, extFN, true, needToCreate); + fstream = HostFileSystem::GetInstance()->OpenFile(dirID, extFN, true, createDisposition); if (fstream) permission = EFilePermission_ReadWrite; else { permission = EFilePermission_Read; - fstream = HostFileSystem::GetInstance()->OpenFile(dirID, extFN, false, needToCreate); + fstream = HostFileSystem::GetInstance()->OpenFile(dirID, extFN, false, createDisposition); } break; case EFilePermission_Read: - fstream = HostFileSystem::GetInstance()->OpenFile(dirID, extFN, false, needToCreate); + fstream = HostFileSystem::GetInstance()->OpenFile(dirID, extFN, false, createDisposition); break; case EFilePermission_ReadWrite: case EFilePermission_Write: - fstream = HostFileSystem::GetInstance()->OpenFile(dirID, extFN, true, needToCreate); + fstream = HostFileSystem::GetInstance()->OpenFile(dirID, extFN, true, createDisposition); break; } @@ -233,26 +259,8 @@ namespace PortabilityLayer memcpy(extFN, fn.Chars(), fnameSize); memcpy(extFN + fnameSize, extension, strlen(extension) + 1); - for (size_t i = 0; i < fnameSize; i++) - { - const char c = extFN[i]; - if (c >= '0' && c <= '9') - continue; - - if (c == '_' || c == '.' || c == '\'') - continue; - - if (c == ' ' && i != 0 && i != fnameSize - 1) - continue; - - if (c >= 'a' && c <= 'z') - continue; - - if (c >= 'A' && c <= 'Z') - continue; - + if (!PortabilityLayer::HostFileSystem::GetInstance()->ValidateFilePath(extFN, fnameSize)) return false; - } return true; } diff --git a/PortabilityLayer/FileManager.h b/PortabilityLayer/FileManager.h index ab33a80..be551e9 100644 --- a/PortabilityLayer/FileManager.h +++ b/PortabilityLayer/FileManager.h @@ -3,6 +3,7 @@ #include "FilePermission.h" #include "CoreDefs.h" #include "FilePos.h" +#include "GpFileCreationDisposition.h" #include "PLErrorCodes.h" #include "VirtualDirectory.h" @@ -26,12 +27,15 @@ namespace PortabilityLayer virtual PLError_t CreateFile(VirtualDirectory_t dirID, const PLPasStr &filename, const MacFileProperties &mfp) = 0; virtual PLError_t CreateFileAtCurrentTime(VirtualDirectory_t dirID, const PLPasStr &filename, const ResTypeID &fileCreator, const ResTypeID &fileType) = 0; + // OpenFileData + OpenFileResources require that the file already exists (i.e. has a .gpf), but the fork may not virtual PLError_t OpenFileData(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, IOStream *&outStream) = 0; virtual PLError_t OpenFileResources(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, IOStream *&outStream) = 0; virtual bool ReadFileProperties(VirtualDirectory_t dirID, const PLPasStr &filename, MacFileProperties &properties) = 0; - virtual PLError_t RawOpenFileData(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, bool ignoreMeta, IOStream *&outStream) = 0; - virtual PLError_t RawOpenFileResources(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, bool ignoreMeta, IOStream *&outStream) = 0; + virtual PLError_t RawOpenFileData(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, bool ignoreMeta, GpFileCreationDisposition_t createDisposition, IOStream *&outStream) = 0; + virtual PLError_t RawOpenFileResources(VirtualDirectory_t dirID, const PLPasStr &filename, EFilePermission filePermission, bool ignoreMeta, GpFileCreationDisposition_t createDisposition, IOStream *&outStream) = 0; + + virtual bool PromptSaveFile(VirtualDirectory_t dirID, char *path, size_t &outPathLength, size_t pathCapacity, const PLPasStr &initialFileName) = 0; static FileManager *GetInstance(); }; diff --git a/PortabilityLayer/FontFamily.cpp b/PortabilityLayer/FontFamily.cpp index 205761d..5d0e269 100644 --- a/PortabilityLayer/FontFamily.cpp +++ b/PortabilityLayer/FontFamily.cpp @@ -11,7 +11,7 @@ namespace PortabilityLayer { void FontFamily::AddFont(int flags, const char *path, FontHacks fontHacks) { - PortabilityLayer::IOStream *sysFontStream = PortabilityLayer::HostFileSystem::GetInstance()->OpenFile(PortabilityLayer::VirtualDirectories::kFonts, path, false, false); + PortabilityLayer::IOStream *sysFontStream = PortabilityLayer::HostFileSystem::GetInstance()->OpenFile(PortabilityLayer::VirtualDirectories::kFonts, path, false, GpFileCreationDispositions::kOpenExisting); if (!sysFontStream) return; diff --git a/PortabilityLayer/HostFileSystem.h b/PortabilityLayer/HostFileSystem.h index 89710a3..56c070a 100644 --- a/PortabilityLayer/HostFileSystem.h +++ b/PortabilityLayer/HostFileSystem.h @@ -1,7 +1,10 @@ #pragma once +#include "GpFileCreationDisposition.h" #include "VirtualDirectory.h" +#include + namespace PortabilityLayer { class IOStream; @@ -12,9 +15,13 @@ namespace PortabilityLayer public: virtual bool FileExists(VirtualDirectory_t virtualDirectory, const char *path) = 0; virtual bool FileLocked(VirtualDirectory_t virtualDirectory, const char *path, bool *exists) = 0; - virtual IOStream *OpenFile(VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, bool create) = 0; + virtual IOStream *OpenFile(VirtualDirectory_t virtualDirectory, const char *path, bool writeAccess, GpFileCreationDisposition_t createDisposition) = 0; + virtual bool DeleteFile(VirtualDirectory_t virtualDirectory, const char *path, bool &existed) = 0; virtual HostDirectoryCursor *ScanDirectory(VirtualDirectory_t virtualDirectory) = 0; + virtual bool PromptSaveFile(VirtualDirectory_t virtualDirectory, char *path, size_t &outPathLength, size_t pathCapacity, const char *initialFileName) = 0; + virtual bool ValidateFilePath(const char *path, size_t pathLen) const = 0; + static HostFileSystem *GetInstance(); static void SetInstance(HostFileSystem *instance); diff --git a/PortabilityLayer/MenuManager.cpp b/PortabilityLayer/MenuManager.cpp index bc19483..6d1419b 100644 --- a/PortabilityLayer/MenuManager.cpp +++ b/PortabilityLayer/MenuManager.cpp @@ -366,6 +366,8 @@ namespace PortabilityLayer m_firstMenu = insertingMenu; existingMenuPtr->prevMenu = insertingMenu; + + DrawMenuBar(); } void MenuManagerImpl::InsertMenuAfter(const THandle &insertingMenu, const THandle &existingMenu) @@ -384,6 +386,8 @@ namespace PortabilityLayer m_lastMenu = insertingMenu; existingMenuPtr->nextMenu = insertingMenu; + + DrawMenuBar(); } void MenuManagerImpl::InsertMenuAtEnd(const THandle &insertingMenu) @@ -399,6 +403,8 @@ namespace PortabilityLayer (*m_lastMenu)->nextMenu = insertingMenu; (*insertingMenu)->prevMenu = m_lastMenu; m_lastMenu = insertingMenu; + + DrawMenuBar(); } void MenuManagerImpl::InsertMenuAtBeginning(const THandle &insertingMenu) @@ -414,12 +420,12 @@ namespace PortabilityLayer (*m_firstMenu)->prevMenu = insertingMenu; (*insertingMenu)->nextMenu = m_firstMenu; m_firstMenu = insertingMenu; + + DrawMenuBar(); } void MenuManagerImpl::RemoveMenu(const THandle &menu) { - DrawMenuBar(); - Menu *menuPtr = *menu; if (menuPtr->stringBlobHandle) PortabilityLayer::MemoryManager::GetInstance()->ReleaseHandle(menuPtr->stringBlobHandle); @@ -446,6 +452,8 @@ namespace PortabilityLayer Menu *menu = *menuHandle; menu->enabled = enabled; + + DrawMenuBar(); } void MenuManagerImpl::SetItemEnabled(const THandle &menuHandle, unsigned int index, bool enabled) @@ -456,6 +464,8 @@ namespace PortabilityLayer return; menu->menuItems[index].enabled = enabled; + + DrawMenuBar(); } void MenuManagerImpl::SetItemChecked(const THandle &menuHandle, unsigned int index, bool checked) @@ -473,7 +483,6 @@ namespace PortabilityLayer return point.m_y >= 0 && static_cast(point.m_y) < kMenuBarHeight; } - void MenuManagerImpl::MenuSelect(const Vec2i &initialPoint, int16_t *outMenu, uint16_t *outItem) { RefreshMenuBarLayout(); diff --git a/PortabilityLayer/PLCore.cpp b/PortabilityLayer/PLCore.cpp index 93659b5..233de15 100644 --- a/PortabilityLayer/PLCore.cpp +++ b/PortabilityLayer/PLCore.cpp @@ -540,7 +540,7 @@ DirectoryFileListEntry *GetDirectoryFiles(PortabilityLayer::VirtualDirectory_t d if (!strcmp(&filename[fnLen - 4], ".gpf")) { const size_t dotPos = fnLen - 4; - PortabilityLayer::IOStream *stream = fs->OpenFile(dirID, filename, false, false); + PortabilityLayer::IOStream *stream = fs->OpenFile(dirID, filename, false, GpFileCreationDispositions::kOpenExisting); if (!stream) continue; diff --git a/PortabilityLayer/PLResourceManager.cpp b/PortabilityLayer/PLResourceManager.cpp index eb9952b..a3f2d72 100644 --- a/PortabilityLayer/PLResourceManager.cpp +++ b/PortabilityLayer/PLResourceManager.cpp @@ -32,6 +32,7 @@ namespace PortabilityLayer short OpenResFork(VirtualDirectory_t virtualDir, const PLPasStr &filename) override; void CloseResFile(short ref) override; + PLError_t CreateBlankResFile(VirtualDirectory_t virtualDir, const PLPasStr &filename) override; THandle GetResource(const ResTypeID &resType, int id) override; @@ -161,7 +162,7 @@ namespace PortabilityLayer return -1; IOStream *fStream = nullptr; - if (FileManager::GetInstance()->RawOpenFileResources(virtualDir, filename, EFilePermission_Read, true, fStream) != PLErrors::kNone) + if (FileManager::GetInstance()->RawOpenFileResources(virtualDir, filename, EFilePermission_Read, true, GpFileCreationDispositions::kOpenExisting, fStream) != PLErrors::kNone) return -1; ResourceFile *resFile = new ResourceFile(); @@ -224,6 +225,29 @@ namespace PortabilityLayer m_currentResFile = m_lastResFile; } + PLError_t ResourceManagerImpl::CreateBlankResFile(VirtualDirectory_t virtualDir, const PLPasStr &filename) + { + const uint8_t blankResFileData[] = { + 0, 0, 0, 16, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 30, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 0, 30, 255, 255 }; + + PortabilityLayer::IOStream *stream = nullptr; + PLError_t error = FileManager::GetInstance()->RawOpenFileResources(virtualDir, filename, EFilePermission_Write, true, GpFileCreationDispositions::kCreateOrOverwrite, stream); + if (error) + return error; + + if (stream->Write(blankResFileData, sizeof(blankResFileData)) != sizeof(blankResFileData)) + { + stream->Close(); + return PLErrors::kIOError; + } + + stream->Close(); + + return PLErrors::kNone; + } + THandle ResourceManagerImpl::GetResource(const ResTypeID &resType, int id) { short searchIndex = m_currentResFile; diff --git a/PortabilityLayer/PLResources.cpp b/PortabilityLayer/PLResources.cpp index 4303998..7d34b82 100644 --- a/PortabilityLayer/PLResources.cpp +++ b/PortabilityLayer/PLResources.cpp @@ -45,12 +45,6 @@ int Count1Resources(UInt32 resType) return 0; } -PLError_t HCreateResFile(PortabilityLayer::VirtualDirectory_t dirID, const PLPasStr &name) -{ - PL_NotYetImplemented(); - return PLErrors::kNone; -} - void SetResLoad(Boolean load) { PortabilityLayer::ResourceManager::GetInstance()->SetResLoad(load != 0); diff --git a/PortabilityLayer/PLResources.h b/PortabilityLayer/PLResources.h index c8e508b..4c718ca 100644 --- a/PortabilityLayer/PLResources.h +++ b/PortabilityLayer/PLResources.h @@ -13,8 +13,6 @@ Handle Get1Resource(UInt32 resID, int index); Handle Get1IndResource(UInt32 resID, int index); int Count1Resources(UInt32 resType); -PLError_t HCreateResFile(PortabilityLayer::VirtualDirectory_t dirID, const PLPasStr &name); - void SetResLoad(Boolean load); // Sets whether resources should be loaded when requested long GetMaxResourceSize(Handle res); diff --git a/PortabilityLayer/ResourceFile.cpp b/PortabilityLayer/ResourceFile.cpp index e7fd70e..9aab110 100644 --- a/PortabilityLayer/ResourceFile.cpp +++ b/PortabilityLayer/ResourceFile.cpp @@ -1,396 +1,396 @@ -#include "ResourceFile.h" -#include "BinarySearch.h" -#include "ByteSwap.h" -#include "MacFileMem.h" -#include "MemoryManager.h" -#include "MemReaderStream.h" -#include "ResourceCompiledRef.h" -#include "ResourceCompiledTypeList.h" - -#include - -namespace PortabilityLayer -{ - ResourceFile::ResourceFile() - : m_resDataBlob(nullptr) - , m_resDataBlobSize(0) - , m_resNameBlob(nullptr) - , m_resNameBlobSize(0) - , m_compiledRefBlob(nullptr) - , m_numResources(0) - , m_compiledTypeListBlob(nullptr) - , m_numResourceTypes(0) - { - } - - ResourceFile::~ResourceFile() - { - if (m_resNameBlob) - delete[] m_resNameBlob; - - if (m_resDataBlob) - delete[] m_resDataBlob; - - if (m_compiledRefBlob) - delete[] m_compiledRefBlob; - - if (m_compiledTypeListBlob) - delete[] m_compiledTypeListBlob; - } - - bool ResourceFile::Load(IOStream *stream) - { - struct ResourceHeader - { - uint32_t m_resDataOffset; - uint32_t m_resMapOffset; - uint32_t m_resDataSize; - uint32_t m_resMapSize; - }; - - const UFilePos_t streamSize = stream->Size(); - if (streamSize > UINT32_MAX) - return false; - - uint32_t resForkSize = static_cast(streamSize); - - ResourceHeader resourceHeader; - - if (stream->Read(&resourceHeader, sizeof(ResourceHeader)) != sizeof(ResourceHeader)) - return false; - - ByteSwap::BigUInt32(resourceHeader.m_resDataOffset); - ByteSwap::BigUInt32(resourceHeader.m_resMapOffset); - ByteSwap::BigUInt32(resourceHeader.m_resDataSize); - ByteSwap::BigUInt32(resourceHeader.m_resMapSize); - - if (resourceHeader.m_resDataOffset > resForkSize) - return false; - - if (resourceHeader.m_resMapOffset > resForkSize) - return false; - - if (resForkSize - resourceHeader.m_resDataOffset < resourceHeader.m_resDataSize) - return false; - - if (resForkSize - resourceHeader.m_resMapOffset < resourceHeader.m_resMapSize) - return false; - - if (!stream->SeekStart(resourceHeader.m_resDataOffset)) - return false; - - m_resDataBlob = new uint8_t[resourceHeader.m_resDataSize]; - m_resDataBlobSize = resourceHeader.m_resDataSize; - - if (stream->Read(m_resDataBlob, resourceHeader.m_resDataSize) != resourceHeader.m_resDataSize) - return false; - - // The format of this is slightly different from the documented format: - // The type list offset is the offset of the type COUNT, which takes up 2 bytes. - // Usually the offset is 28, even though the size of the resource map including the - // count would appear to be 30. - - struct ResourceMap - { - uint8_t m_reserved[16 + 4 + 2]; - uint16_t m_attributes; - uint16_t m_typeListOffset; - uint16_t m_nameListOffset; - }; - - struct ResourceTypeListEntry - { - ResTypeID m_resType; - uint16_t m_numResourcesMinusOne; - uint16_t m_refListOffset; - }; - - struct ResourceRefListEntry - { - int16_t m_resID; - int16_t m_resourceNameOffset; - uint8_t m_attributes; - uint8_t m_packedResDataOffset[3]; - uint32_t m_reserved; - }; - - if (!stream->SeekStart(resourceHeader.m_resMapOffset)) - return false; - - ResourceMap resourceMap; - if (stream->Read(&resourceMap, sizeof(ResourceMap)) != sizeof(ResourceMap)) - return false; - - ByteSwap::BigUInt16(resourceMap.m_attributes); - ByteSwap::BigUInt16(resourceMap.m_typeListOffset); - ByteSwap::BigUInt16(resourceMap.m_nameListOffset); - - const size_t sizeFromStartOfResMap = resForkSize - resourceHeader.m_resMapOffset; - if (resourceMap.m_typeListOffset > sizeFromStartOfResMap) - return false; - - if (resourceMap.m_nameListOffset > sizeFromStartOfResMap) - return false; - - const size_t typeListOffset = resourceHeader.m_resMapOffset + resourceMap.m_typeListOffset; - const size_t sizeFromStartOfTypeList = resForkSize - typeListOffset; - - // First pass: Count the number of references we need - if (!stream->SeekStart(typeListOffset)) - return false; - - uint16_t numTypesMinusOne; - if (stream->Read(&numTypesMinusOne, 2) != 2) - return false; - ByteSwap::BigUInt16(numTypesMinusOne); - - // numTypesMinusOne can sometimes be -1, in which case there are no resources - m_numResourceTypes = (numTypesMinusOne + 1) & 0xffff; - - if (sizeFromStartOfTypeList < 2 || (sizeFromStartOfTypeList - 2) / 8 < m_numResourceTypes) - return false; - - uint32_t numResourcesTotal = 0; - - for (uint32_t i = 0; i < m_numResourceTypes; i++) - { - ResourceTypeListEntry tlEntry; - if (stream->Read(&tlEntry, sizeof(ResourceTypeListEntry)) != sizeof(ResourceTypeListEntry)) - return false; - - ByteSwap::BigUInt16(tlEntry.m_numResourcesMinusOne); - - const uint32_t numResourcesOfThisType = tlEntry.m_numResourcesMinusOne + 1; - - if (UINT32_MAX - numResourcesTotal < numResourcesOfThisType) - return false; - - numResourcesTotal += numResourcesOfThisType; - } - - if (numResourcesTotal > 0) - m_compiledRefBlob = new ResourceCompiledRef[numResourcesTotal]; - - m_numResources = numResourcesTotal; - - // Second pass: Compile references - ResourceCompiledRef *refToCompile = m_compiledRefBlob; - - if (m_numResourceTypes > 0) - m_compiledTypeListBlob = new ResourceCompiledTypeList[m_numResourceTypes]; - - for (uint32_t i = 0; i < m_numResourceTypes; i++) - { - if (!stream->SeekStart(typeListOffset + 2 + i * 8)) - return false; - - ResourceTypeListEntry tlEntry; - if (stream->Read(&tlEntry, sizeof(ResourceTypeListEntry)) != sizeof(ResourceTypeListEntry)) - return false; - - ByteSwap::BigUInt16(tlEntry.m_numResourcesMinusOne); - ByteSwap::BigUInt16(tlEntry.m_refListOffset); - - ResourceCompiledTypeList &ctl = m_compiledTypeListBlob[i]; - ctl.m_resType = tlEntry.m_resType; - ctl.m_firstRef = refToCompile; - ctl.m_numRefs = tlEntry.m_numResourcesMinusOne + 1; - - if (sizeFromStartOfTypeList < tlEntry.m_refListOffset) - return false; - - // Start reading the ref list - if (!stream->SeekStart(typeListOffset + tlEntry.m_refListOffset)) - return false; - - const uint32_t numResourcesOfThisType = tlEntry.m_numResourcesMinusOne + 1; - - ResourceCompiledRef *firstRefOfThisType = refToCompile; - - for (uint32_t i = 0; i < numResourcesOfThisType; i++) - { - ResourceRefListEntry refListEntry; - if (stream->Read(&refListEntry, sizeof(ResourceRefListEntry)) != sizeof(ResourceRefListEntry)) - return false; - - ByteSwap::BigInt16(refListEntry.m_resID); - ByteSwap::BigInt16(refListEntry.m_resourceNameOffset); - - const uint8_t *packedOffset = refListEntry.m_packedResDataOffset; - - const size_t dataSizeOffset = (packedOffset[0] << 16) + (packedOffset[1] << 8) + packedOffset[2]; - - if (dataSizeOffset > resourceHeader.m_resDataSize) - return false; - - // Needs to be at least size 4 to decode the resource size - if (resourceHeader.m_resDataSize - dataSizeOffset < 4) - return false; - - const size_t dataOffset = dataSizeOffset + 4; - - refToCompile->m_attributes = refListEntry.m_attributes; - refToCompile->m_resData = m_resDataBlob + dataOffset; - refToCompile->m_resID = refListEntry.m_resID; - refToCompile->m_resNameOffset = refListEntry.m_resourceNameOffset; - refToCompile->m_handle = nullptr; - - uint32_t resSize; - memcpy(&resSize, m_resDataBlob + dataSizeOffset, 4); - - ByteSwap::BigUInt32(resSize); - - if (resSize > resourceHeader.m_resDataSize) - return false; - - if (resourceHeader.m_resDataSize - resSize < dataOffset) - return false; - - refToCompile++; - } - - std::sort(firstRefOfThisType, refToCompile, CompiledRefSortPredicate); - - for (uint32_t i = 1; i < numResourcesOfThisType; i++) - { - if (firstRefOfThisType[i - 1].m_resID == firstRefOfThisType[i].m_resID) - return false; - } - } - - if (m_numResources > 0) - { - bool anyNamed = false; - - size_t lastNameStart = 0; - for (size_t i = 0; i < m_numResources; i++) - { - const ResourceCompiledRef &ref = m_compiledRefBlob[i]; - if (ref.m_resNameOffset < 0) - { - if (ref.m_resNameOffset != -1) - return false; // Non-compliant - } - else - { - anyNamed = true; - - const size_t candidateOffset = static_cast(ref.m_resNameOffset); - if (candidateOffset > lastNameStart) - lastNameStart = candidateOffset; - } - } - - if (anyNamed) - { - const size_t nameListCapacity = sizeFromStartOfResMap - resourceMap.m_nameListOffset; - if (lastNameStart >= nameListCapacity) - return false; - - m_resNameBlobSize = lastNameStart + 1 + 256; - m_resNameBlob = new uint8_t[m_resNameBlobSize]; - memset(m_resNameBlob, 0, m_resNameBlobSize); - - if (!stream->SeekStart(resourceHeader.m_resMapOffset + resourceMap.m_nameListOffset)) - return false; - - if (stream->Read(m_resNameBlob, lastNameStart + 1) != lastNameStart + 1) - return false; - - // Figure out the length of the final string - const size_t lastStringLength = m_resNameBlob[lastNameStart]; - if (lastStringLength > 0) - { - if (stream->Read(m_resNameBlob + lastNameStart + 1, lastStringLength) != lastStringLength) - return false; - } - } - } - - std::sort(m_compiledTypeListBlob, m_compiledTypeListBlob + m_numResourceTypes, CompiledTypeListSortPredicate); - - for (uint32_t i = 1; i < m_numResourceTypes; i++) - { - if (m_compiledTypeListBlob[i - 1].m_resType == m_compiledTypeListBlob[i].m_resType) - return false; - } - - return true; - } - - bool ResourceFile::CompiledRefSortPredicate(const ResourceCompiledRef &a, const ResourceCompiledRef &b) - { - return a.m_resID < b.m_resID; - } - - bool ResourceFile::CompiledTypeListSortPredicate(const ResourceCompiledTypeList &a, const ResourceCompiledTypeList &b) - { - return memcmp(&a.m_resType, &b.m_resType, sizeof(ResTypeID)) < 0; - } - - int ResourceFile::CompiledRefSearchPredicate(int resID, const ResourceCompiledRef &ref) - { - return resID - ref.m_resID; - } - - int ResourceFile::CompiledTypeListSearchPredicate(const ResTypeID &resTypeID, const ResourceCompiledTypeList &typeList) - { - return memcmp(&resTypeID, &typeList.m_resType, 4); +#include "ResourceFile.h" +#include "BinarySearch.h" +#include "ByteSwap.h" +#include "MacFileMem.h" +#include "MemoryManager.h" +#include "MemReaderStream.h" +#include "ResourceCompiledRef.h" +#include "ResourceCompiledTypeList.h" + +#include + +namespace PortabilityLayer +{ + ResourceFile::ResourceFile() + : m_resDataBlob(nullptr) + , m_resDataBlobSize(0) + , m_resNameBlob(nullptr) + , m_resNameBlobSize(0) + , m_compiledRefBlob(nullptr) + , m_numResources(0) + , m_compiledTypeListBlob(nullptr) + , m_numResourceTypes(0) + { + } + + ResourceFile::~ResourceFile() + { + if (m_resNameBlob) + delete[] m_resNameBlob; + + if (m_resDataBlob) + delete[] m_resDataBlob; + + if (m_compiledRefBlob) + delete[] m_compiledRefBlob; + + if (m_compiledTypeListBlob) + delete[] m_compiledTypeListBlob; + } + + bool ResourceFile::Load(IOStream *stream) + { + struct ResourceHeader + { + uint32_t m_resDataOffset; + uint32_t m_resMapOffset; + uint32_t m_resDataSize; + uint32_t m_resMapSize; + }; + + const UFilePos_t streamSize = stream->Size(); + if (streamSize > UINT32_MAX) + return false; + + uint32_t resForkSize = static_cast(streamSize); + + ResourceHeader resourceHeader; + + if (stream->Read(&resourceHeader, sizeof(ResourceHeader)) != sizeof(ResourceHeader)) + return false; + + ByteSwap::BigUInt32(resourceHeader.m_resDataOffset); + ByteSwap::BigUInt32(resourceHeader.m_resMapOffset); + ByteSwap::BigUInt32(resourceHeader.m_resDataSize); + ByteSwap::BigUInt32(resourceHeader.m_resMapSize); + + if (resourceHeader.m_resDataOffset > resForkSize) + return false; + + if (resourceHeader.m_resMapOffset > resForkSize) + return false; + + if (resForkSize - resourceHeader.m_resDataOffset < resourceHeader.m_resDataSize) + return false; + + if (resForkSize - resourceHeader.m_resMapOffset < resourceHeader.m_resMapSize) + return false; + + if (!stream->SeekStart(resourceHeader.m_resDataOffset)) + return false; + + m_resDataBlob = new uint8_t[resourceHeader.m_resDataSize]; + m_resDataBlobSize = resourceHeader.m_resDataSize; + + if (stream->Read(m_resDataBlob, resourceHeader.m_resDataSize) != resourceHeader.m_resDataSize) + return false; + + // The format of this is slightly different from the documented format: + // The type list offset is the offset of the type COUNT, which takes up 2 bytes. + // Usually the offset is 28, even though the size of the resource map including the + // count would appear to be 30. + + struct ResourceMap + { + uint8_t m_reserved[16 + 4 + 2]; + uint16_t m_attributes; + uint16_t m_typeListOffset; + uint16_t m_nameListOffset; + }; + + struct ResourceTypeListEntry + { + ResTypeID m_resType; + uint16_t m_numResourcesMinusOne; + uint16_t m_refListOffset; + }; + + struct ResourceRefListEntry + { + int16_t m_resID; + int16_t m_resourceNameOffset; + uint8_t m_attributes; + uint8_t m_packedResDataOffset[3]; + uint32_t m_reserved; + }; + + if (!stream->SeekStart(resourceHeader.m_resMapOffset)) + return false; + + ResourceMap resourceMap; + if (stream->Read(&resourceMap, sizeof(ResourceMap)) != sizeof(ResourceMap)) + return false; + + ByteSwap::BigUInt16(resourceMap.m_attributes); + ByteSwap::BigUInt16(resourceMap.m_typeListOffset); + ByteSwap::BigUInt16(resourceMap.m_nameListOffset); + + const size_t sizeFromStartOfResMap = resForkSize - resourceHeader.m_resMapOffset; + if (resourceMap.m_typeListOffset > sizeFromStartOfResMap) + return false; + + if (resourceMap.m_nameListOffset > sizeFromStartOfResMap) + return false; + + const size_t typeListOffset = resourceHeader.m_resMapOffset + resourceMap.m_typeListOffset; + const size_t sizeFromStartOfTypeList = resForkSize - typeListOffset; + + // First pass: Count the number of references we need + if (!stream->SeekStart(typeListOffset)) + return false; + + uint16_t numTypesMinusOne; + if (stream->Read(&numTypesMinusOne, 2) != 2) + return false; + ByteSwap::BigUInt16(numTypesMinusOne); + + // numTypesMinusOne can sometimes be -1, in which case there are no resources + m_numResourceTypes = (numTypesMinusOne + 1) & 0xffff; + + if (sizeFromStartOfTypeList < 2 || (sizeFromStartOfTypeList - 2) / 8 < m_numResourceTypes) + return false; + + uint32_t numResourcesTotal = 0; + + for (uint32_t i = 0; i < m_numResourceTypes; i++) + { + ResourceTypeListEntry tlEntry; + if (stream->Read(&tlEntry, sizeof(ResourceTypeListEntry)) != sizeof(ResourceTypeListEntry)) + return false; + + ByteSwap::BigUInt16(tlEntry.m_numResourcesMinusOne); + + const uint32_t numResourcesOfThisType = tlEntry.m_numResourcesMinusOne + 1; + + if (UINT32_MAX - numResourcesTotal < numResourcesOfThisType) + return false; + + numResourcesTotal += numResourcesOfThisType; + } + + if (numResourcesTotal > 0) + m_compiledRefBlob = new ResourceCompiledRef[numResourcesTotal]; + + m_numResources = numResourcesTotal; + + // Second pass: Compile references + ResourceCompiledRef *refToCompile = m_compiledRefBlob; + + if (m_numResourceTypes > 0) + m_compiledTypeListBlob = new ResourceCompiledTypeList[m_numResourceTypes]; + + for (uint32_t i = 0; i < m_numResourceTypes; i++) + { + if (!stream->SeekStart(typeListOffset + 2 + i * 8)) + return false; + + ResourceTypeListEntry tlEntry; + if (stream->Read(&tlEntry, sizeof(ResourceTypeListEntry)) != sizeof(ResourceTypeListEntry)) + return false; + + ByteSwap::BigUInt16(tlEntry.m_numResourcesMinusOne); + ByteSwap::BigUInt16(tlEntry.m_refListOffset); + + ResourceCompiledTypeList &ctl = m_compiledTypeListBlob[i]; + ctl.m_resType = tlEntry.m_resType; + ctl.m_firstRef = refToCompile; + ctl.m_numRefs = tlEntry.m_numResourcesMinusOne + 1; + + if (sizeFromStartOfTypeList < tlEntry.m_refListOffset) + return false; + + // Start reading the ref list + if (!stream->SeekStart(typeListOffset + tlEntry.m_refListOffset)) + return false; + + const uint32_t numResourcesOfThisType = tlEntry.m_numResourcesMinusOne + 1; + + ResourceCompiledRef *firstRefOfThisType = refToCompile; + + for (uint32_t i = 0; i < numResourcesOfThisType; i++) + { + ResourceRefListEntry refListEntry; + if (stream->Read(&refListEntry, sizeof(ResourceRefListEntry)) != sizeof(ResourceRefListEntry)) + return false; + + ByteSwap::BigInt16(refListEntry.m_resID); + ByteSwap::BigInt16(refListEntry.m_resourceNameOffset); + + const uint8_t *packedOffset = refListEntry.m_packedResDataOffset; + + const size_t dataSizeOffset = (packedOffset[0] << 16) + (packedOffset[1] << 8) + packedOffset[2]; + + if (dataSizeOffset > resourceHeader.m_resDataSize) + return false; + + // Needs to be at least size 4 to decode the resource size + if (resourceHeader.m_resDataSize - dataSizeOffset < 4) + return false; + + const size_t dataOffset = dataSizeOffset + 4; + + refToCompile->m_attributes = refListEntry.m_attributes; + refToCompile->m_resData = m_resDataBlob + dataOffset; + refToCompile->m_resID = refListEntry.m_resID; + refToCompile->m_resNameOffset = refListEntry.m_resourceNameOffset; + refToCompile->m_handle = nullptr; + + uint32_t resSize; + memcpy(&resSize, m_resDataBlob + dataSizeOffset, 4); + + ByteSwap::BigUInt32(resSize); + + if (resSize > resourceHeader.m_resDataSize) + return false; + + if (resourceHeader.m_resDataSize - resSize < dataOffset) + return false; + + refToCompile++; + } + + std::sort(firstRefOfThisType, refToCompile, CompiledRefSortPredicate); + + for (uint32_t i = 1; i < numResourcesOfThisType; i++) + { + if (firstRefOfThisType[i - 1].m_resID == firstRefOfThisType[i].m_resID) + return false; + } + } + + if (m_numResources > 0) + { + bool anyNamed = false; + + size_t lastNameStart = 0; + for (size_t i = 0; i < m_numResources; i++) + { + const ResourceCompiledRef &ref = m_compiledRefBlob[i]; + if (ref.m_resNameOffset < 0) + { + if (ref.m_resNameOffset != -1) + return false; // Non-compliant + } + else + { + anyNamed = true; + + const size_t candidateOffset = static_cast(ref.m_resNameOffset); + if (candidateOffset > lastNameStart) + lastNameStart = candidateOffset; + } + } + + if (anyNamed) + { + const size_t nameListCapacity = sizeFromStartOfResMap - resourceMap.m_nameListOffset; + if (lastNameStart >= nameListCapacity) + return false; + + m_resNameBlobSize = lastNameStart + 1 + 256; + m_resNameBlob = new uint8_t[m_resNameBlobSize]; + memset(m_resNameBlob, 0, m_resNameBlobSize); + + if (!stream->SeekStart(resourceHeader.m_resMapOffset + resourceMap.m_nameListOffset)) + return false; + + if (stream->Read(m_resNameBlob, lastNameStart + 1) != lastNameStart + 1) + return false; + + // Figure out the length of the final string + const size_t lastStringLength = m_resNameBlob[lastNameStart]; + if (lastStringLength > 0) + { + if (stream->Read(m_resNameBlob + lastNameStart + 1, lastStringLength) != lastStringLength) + return false; + } + } + } + + std::sort(m_compiledTypeListBlob, m_compiledTypeListBlob + m_numResourceTypes, CompiledTypeListSortPredicate); + + for (uint32_t i = 1; i < m_numResourceTypes; i++) + { + if (m_compiledTypeListBlob[i - 1].m_resType == m_compiledTypeListBlob[i].m_resType) + return false; + } + + return true; + } + + bool ResourceFile::CompiledRefSortPredicate(const ResourceCompiledRef &a, const ResourceCompiledRef &b) + { + return a.m_resID < b.m_resID; + } + + bool ResourceFile::CompiledTypeListSortPredicate(const ResourceCompiledTypeList &a, const ResourceCompiledTypeList &b) + { + return memcmp(&a.m_resType, &b.m_resType, sizeof(ResTypeID)) < 0; + } + + int ResourceFile::CompiledRefSearchPredicate(int resID, const ResourceCompiledRef &ref) + { + return resID - ref.m_resID; + } + + int ResourceFile::CompiledTypeListSearchPredicate(const ResTypeID &resTypeID, const ResourceCompiledTypeList &typeList) + { + return memcmp(&resTypeID, &typeList.m_resType, 4); } void ResourceFile::GetAllResourceTypeLists(ResourceCompiledTypeList *&outTypeLists, size_t &outCount) const { outTypeLists = m_compiledTypeListBlob; - outCount = m_numResourceTypes; - } - - const ResourceCompiledTypeList *ResourceFile::GetResourceTypeList(const ResTypeID &resType) - { - const ResourceCompiledTypeList *tlStart = m_compiledTypeListBlob; - const ResourceCompiledTypeList *tlEnd = tlStart + m_numResourceTypes; - - const ResourceCompiledTypeList *tl = BinarySearch(tlStart, tlEnd, resType, CompiledTypeListSearchPredicate); - if (tl == tlEnd) - return nullptr; - - return tl; - } - - MMHandleBlock *ResourceFile::GetResource(const ResTypeID &resType, int id, bool load) - { - const ResourceCompiledTypeList *tl = GetResourceTypeList(resType); - if (tl == nullptr) - return nullptr; - - ResourceCompiledRef *refStart = tl->m_firstRef; - ResourceCompiledRef *refEnd = refStart + tl->m_numRefs; - ResourceCompiledRef *ref = BinarySearch(refStart, refEnd, id, CompiledRefSearchPredicate); - - if (ref == refEnd) - return nullptr; - - MMHandleBlock *handle = nullptr; - if (ref->m_handle != nullptr) - handle = ref->m_handle; - else - { - handle = MemoryManager::GetInstance()->AllocHandle(0); - handle->m_rmSelfRef = ref; - ref->m_handle = handle; - } - - if (handle->m_contents == nullptr && load) - { - const uint32_t resSize = ref->GetSize(); - if (resSize > 0) - { - void *contents = MemoryManager::GetInstance()->Alloc(resSize); - handle->m_contents = contents; - handle->m_size = resSize; - memcpy(handle->m_contents, ref->m_resData, resSize); - } - } - - return handle; - } -} + outCount = m_numResourceTypes; + } + + const ResourceCompiledTypeList *ResourceFile::GetResourceTypeList(const ResTypeID &resType) + { + const ResourceCompiledTypeList *tlStart = m_compiledTypeListBlob; + const ResourceCompiledTypeList *tlEnd = tlStart + m_numResourceTypes; + + const ResourceCompiledTypeList *tl = BinarySearch(tlStart, tlEnd, resType, CompiledTypeListSearchPredicate); + if (tl == tlEnd) + return nullptr; + + return tl; + } + + MMHandleBlock *ResourceFile::GetResource(const ResTypeID &resType, int id, bool load) + { + const ResourceCompiledTypeList *tl = GetResourceTypeList(resType); + if (tl == nullptr) + return nullptr; + + ResourceCompiledRef *refStart = tl->m_firstRef; + ResourceCompiledRef *refEnd = refStart + tl->m_numRefs; + ResourceCompiledRef *ref = BinarySearch(refStart, refEnd, id, CompiledRefSearchPredicate); + + if (ref == refEnd) + return nullptr; + + MMHandleBlock *handle = nullptr; + if (ref->m_handle != nullptr) + handle = ref->m_handle; + else + { + handle = MemoryManager::GetInstance()->AllocHandle(0); + handle->m_rmSelfRef = ref; + ref->m_handle = handle; + } + + if (handle->m_contents == nullptr && load) + { + const uint32_t resSize = ref->GetSize(); + if (resSize > 0) + { + void *contents = MemoryManager::GetInstance()->Alloc(resSize); + handle->m_contents = contents; + handle->m_size = resSize; + memcpy(handle->m_contents, ref->m_resData, resSize); + } + } + + return handle; + } +} diff --git a/PortabilityLayer/ResourceManager.h b/PortabilityLayer/ResourceManager.h index d35c1eb..85a4030 100644 --- a/PortabilityLayer/ResourceManager.h +++ b/PortabilityLayer/ResourceManager.h @@ -1,6 +1,7 @@ #pragma once #include "VirtualDirectory.h" +#include "PLErrorCodes.h" #include "PLHandle.h" class PLPasStr; @@ -21,6 +22,7 @@ namespace PortabilityLayer virtual short OpenResFork(VirtualDirectory_t virtualDir, const PLPasStr &filename) = 0; virtual void CloseResFile(short ref) = 0; + virtual PLError_t CreateBlankResFile(VirtualDirectory_t virtualDir, const PLPasStr &filename) = 0; virtual THandle GetResource(const ResTypeID &resType, int id) = 0;