//============================================================================ //---------------------------------------------------------------------------- // HouseIO.c //---------------------------------------------------------------------------- //============================================================================ #include "BitmapImage.h" #include "DialogManager.h" #include "Externs.h" #include "Environ.h" #include "FileManager.h" #include "GpIOStream.h" #include "House.h" #include "IGpLogDriver.h" #include "IGpSystemServices.h" #include "ObjectEdit.h" #include "RectUtils.h" #include "ResourceManager.h" #include "PLDialogs.h" #include "PLDrivers.h" #include "PLMovies.h" #include "PLResources.h" #include "PLStringCompare.h" #include "PLPasStr.h" #include "CombinedTimestamp.h" #include "DeflateCodec.h" #include "MacFileInfo.h" #include "GpIOStream.h" #include "GpVector.h" #include "MacBinary2.h" #include "QDPictOpcodes.h" #include "QDPixMap.h" #include "PLStandardColors.h" #include "ZipFile.h" #define kSaveChangesAlert 1002 #define kSaveChanges 1 #define kDiscardChanges 2 void OpenHouseMovie (void); void CloseHouseMovie (void); AnimationPlayer theMovie; Rect movieRect; PortabilityLayer::IResourceArchive *houseResFork; short wasHouseVersion; Boolean houseOpen, fileDirty; Boolean changeLockStateOfHouse, saveHouseLocked, houseIsReadOnly; Boolean hasMovie, tvInRoom; PortabilityLayer::CompositeFile *houseCFile; extern VFileSpec *theHousesSpecs; extern short thisHouseIndex, tvWithMovieNumber; extern short numberRooms, housesFound; extern Boolean noRoomAtAll, quitting, wardBitSet; extern Boolean phoneBitSet, bannerStarCountOn; bool ParseAndConvertSoundChecked(const THandle &handle, void const*& outDataContents, size_t &outDataSize); //============================================================== Functions //-------------------------------------------------------------- OpenHouseMovie void OpenHouseMovie (void) { #ifdef COMPILEQT VFileSpec theSpec; Handle spaceSaver; if (thisMac.hasQT) { theSpec = theHousesSpecs[thisHouseIndex]; PasStringConcat(theSpec.m_name, PSTR(".mov")); AnimationPackage *anim = AnimationPackage::Create(); if (!anim) return; PLError_t theErr = anim->Load(theSpec.m_dir, theSpec.m_name); if (theErr != PLErrors::kNone) { anim->Destroy(); if (theErr != PLErrors::kFileNotFound) YellowAlert(kYellowQTMovieNotLoaded, theErr); return; } movieRect = (*anim->GetFrame(0))->GetRect(); hasMovie = true; theMovie.SetPackage(anim); AnimationManager::GetInstance()->RegisterPlayer(&theMovie); } #endif } //-------------------------------------------------------------- CloseHouseMovie void CloseHouseMovie (void) { #ifdef COMPILEQT if ((thisMac.hasQT) && (hasMovie)) { AnimationManager::GetInstance()->RemovePlayer(&theMovie); theMovie.m_animPackage->Destroy(); theMovie.m_animPackage = nullptr; } #endif hasMovie = false; } //-------------------------------------------------------------- OpenHouse // Opens a house (whatever current selection is). Returns true if all went well. Boolean OpenHouse (Boolean read) { PLError_t theErr; if (houseOpen) { if (!CloseHouse()) return(false); } if ((housesFound < 1) || (thisHouseIndex == -1)) return(false); #ifdef COMPILEDEMO if (!StrCmp::EqualCaseInsensitive(theHousesSpecs[thisHouseIndex].name, "\pDemo House")) return (false); #endif houseCFile = PortabilityLayer::FileManager::GetInstance()->OpenCompositeFile(theHousesSpecs[thisHouseIndex].m_dir, theHousesSpecs[thisHouseIndex].m_name); if (!houseCFile) return (false); houseIsReadOnly = houseCFile->IsDataReadOnly(); GpIOStream *houseStream = nil; theErr = houseCFile->OpenData(PortabilityLayer::EFilePermission_Any, GpFileCreationDispositions::kCreateOrOpen, houseStream); if (!CheckFileError(theErr, thisHouseName)) { houseCFile->Close(); houseCFile = nil; return (false); } houseOpen = true; OpenHouseResFork(); if (hasMovie) CloseHouseMovie(); tvInRoom = false; tvWithMovieNumber = -1; OpenHouseMovie(); if (read) { Boolean readOK = ReadHouse(houseStream, theHousesSpecs[thisHouseIndex].m_dir != PortabilityLayer::VirtualDirectories::kGameData); houseStream->Close(); if (!readOK) CloseHouse(); return readOK; } houseStream->Close(); return (true); } //-------------------------------------------------------------- OpenSpecificHouse // Opens the specific house passed in. #ifndef COMPILEDEMO Boolean OpenSpecificHouse (const VFileSpec &specs) { short i; Boolean itOpened; if ((housesFound < 1) || (thisHouseIndex == -1)) return (false); itOpened = true; for (i = 0; i < housesFound; i++) { if ((theHousesSpecs[i].m_dir == specs.m_dir) && (StrCmp::EqualCaseInsensitive(theHousesSpecs[i].m_name, specs.m_name))) { thisHouseIndex = i; PasStringCopy(theHousesSpecs[thisHouseIndex].m_name, thisHouseName); if (!OpenHouse(true)) itOpened = false; break; } } return (itOpened); } #endif //-------------------------------------------------------------- ReadHouse // With a house open, this function reads in the actual bits of dataÉ // into memory. void ByteSwapPoint(Point *point) { PortabilityLayer::ByteSwap::BigInt16(point->h); PortabilityLayer::ByteSwap::BigInt16(point->v); } void ByteSwapRect(Rect *rect) { PortabilityLayer::ByteSwap::BigInt16(rect->top); PortabilityLayer::ByteSwap::BigInt16(rect->left); PortabilityLayer::ByteSwap::BigInt16(rect->bottom); PortabilityLayer::ByteSwap::BigInt16(rect->right); } template void SanitizePascalStr(uint8_t(&chars)[TSize]) { const size_t maxLength = TSize - 1; size_t strLength = chars[0]; if (strLength > maxLength) { strLength = maxLength; chars[0] = static_cast(maxLength); } for (size_t i = 1 + strLength; i < TSize; i++) chars[i] = 0; } void ByteSwapScores(scoresType *scores) { SanitizePascalStr(scores->banner); for (int i = 0; i < kMaxScores; i++) SanitizePascalStr(scores->names[i]); for (int i = 0; i < kMaxScores; i++) PortabilityLayer::ByteSwap::BigInt32(scores->scores[i]); for (int i = 0; i < kMaxScores; i++) PortabilityLayer::ByteSwap::BigUInt32(scores->timeStamps[i]); for (int i = 0; i < kMaxScores; i++) PortabilityLayer::ByteSwap::BigInt16(scores->levels[i]); } void ByteSwapSavedGame(gameType *game) { PortabilityLayer::ByteSwap::BigInt16(game->version); PortabilityLayer::ByteSwap::BigInt16(game->wasStarsLeft); PortabilityLayer::ByteSwap::BigUInt32(game->timeStamp); ByteSwapPoint(&game->where); PortabilityLayer::ByteSwap::BigInt32(game->score); PortabilityLayer::ByteSwap::BigInt32(game->unusedLong); PortabilityLayer::ByteSwap::BigInt32(game->unusedLong2); PortabilityLayer::ByteSwap::BigInt16(game->energy); PortabilityLayer::ByteSwap::BigInt16(game->bands); PortabilityLayer::ByteSwap::BigInt16(game->roomNumber); PortabilityLayer::ByteSwap::BigInt16(game->gliderState); PortabilityLayer::ByteSwap::BigInt16(game->numGliders); PortabilityLayer::ByteSwap::BigInt16(game->foil); PortabilityLayer::ByteSwap::BigInt16(game->unusedShort); } void ByteSwapBlower(blowerType *blower) { ByteSwapPoint(&blower->topLeft); PortabilityLayer::ByteSwap::BigInt16(blower->distance); } void ByteSwapFurniture(furnitureType *furniture) { ByteSwapRect(&furniture->bounds); PortabilityLayer::ByteSwap::BigInt16(furniture->pict); } void ByteSwapBonus(bonusType *bonus) { ByteSwapPoint(&bonus->topLeft); PortabilityLayer::ByteSwap::BigInt16(bonus->length); PortabilityLayer::ByteSwap::BigInt16(bonus->points); } void ByteSwapTransport(transportType *transport) { ByteSwapPoint(&transport->topLeft); PortabilityLayer::ByteSwap::BigInt16(transport->tall); PortabilityLayer::ByteSwap::BigInt16(transport->where); } void ByteSwapSwitch(switchType *sw) { ByteSwapPoint(&sw->topLeft); PortabilityLayer::ByteSwap::BigInt16(sw->delay); PortabilityLayer::ByteSwap::BigInt16(sw->where); } void ByteSwapLight(lightType *light) { ByteSwapPoint(&light->topLeft); PortabilityLayer::ByteSwap::BigInt16(light->length); } void ByteSwapAppliance(applianceType *appliance) { ByteSwapPoint(&appliance->topLeft); PortabilityLayer::ByteSwap::BigInt16(appliance->height); } void ByteSwapEnemy(enemyType *enemy) { ByteSwapPoint(&enemy->topLeft); PortabilityLayer::ByteSwap::BigInt16(enemy->length); } void ByteSwapClutter(clutterType *clutter) { ByteSwapRect(&clutter->bounds); PortabilityLayer::ByteSwap::BigInt16(clutter->pict); } void ByteSwapObject(objectType *obj, bool isSwappedAfter) { int16_t objWhat = 0; if (isSwappedAfter) objWhat = obj->what; PortabilityLayer::ByteSwap::BigInt16(obj->what); if (!isSwappedAfter) objWhat = obj->what; switch (objWhat) { case kFloorVent: case kCeilingVent: case kFloorBlower: case kCeilingBlower: case kSewerGrate: case kLeftFan: case kRightFan: case kTaper: case kCandle: case kStubby: case kTiki: case kBBQ: case kInvisBlower: case kGrecoVent: case kSewerBlower: case kLiftArea: ByteSwapBlower(&obj->data.a); break; case kTable: case kShelf: case kCabinet: case kFilingCabinet: case kWasteBasket: case kMilkCrate: case kCounter: case kDresser: case kDeckTable: case kStool: case kTrunk: case kInvisObstacle: case kManhole: case kBooks: case kInvisBounce: ByteSwapFurniture(&obj->data.b); break; case kRedClock: case kBlueClock: case kYellowClock: case kCuckoo: case kPaper: case kBattery: case kBands: case kGreaseRt: case kGreaseLf: case kFoil: case kInvisBonus: case kStar: case kSparkle: case kHelium: case kSlider: ByteSwapBonus(&obj->data.c); break; case kUpStairs: case kDownStairs: case kMailboxLf: case kMailboxRt: case kFloorTrans: case kCeilingTrans: case kDoorInLf: case kDoorInRt: case kDoorExRt: case kDoorExLf: case kWindowInLf: case kWindowInRt: case kWindowExRt: case kWindowExLf: case kInvisTrans: case kDeluxeTrans: ByteSwapTransport(&obj->data.d); break; case kLightSwitch: case kMachineSwitch: case kThermostat: case kPowerSwitch: case kKnifeSwitch: case kInvisSwitch: case kTrigger: case kLgTrigger: case kSoundTrigger: ByteSwapSwitch(&obj->data.e); break; case kCeilingLight: case kLightBulb: case kTableLamp: case kHipLamp: case kDecoLamp: case kFlourescent: case kTrackLight: case kInvisLight: ByteSwapLight(&obj->data.f); break; case kShredder: case kToaster: case kMacPlus: case kGuitar: case kTV: case kCoffee: case kOutlet: case kVCR: case kStereo: case kMicrowave: case kCinderBlock: case kFlowerBox: case kCDs: case kCustomPict: ByteSwapAppliance(&obj->data.g); break; case kBalloon: case kCopterLf: case kCopterRt: case kDartLf: case kDartRt: case kBall: case kDrip: case kFish: case kCobweb: ByteSwapEnemy(&obj->data.h); break; case kOzma: case kMirror: case kMousehole: case kFireplace: case kFlower: case kWallWindow: case kBear: case kCalendar: case kVase1: case kVase2: case kBulletin: case kCloud: case kFaucet: case kRug: case kChimes: ByteSwapClutter(&obj->data.i); break; default: break; }; } void ByteSwapRoom(roomType *room, bool isSwappedAfter) { SanitizePascalStr(room->name); PortabilityLayer::ByteSwap::BigInt16(room->bounds); PortabilityLayer::ByteSwap::BigInt16(room->background); for (int i = 0; i < kNumTiles; i++) PortabilityLayer::ByteSwap::BigInt16(room->tiles[i]); PortabilityLayer::ByteSwap::BigInt16(room->floor); PortabilityLayer::ByteSwap::BigInt16(room->suite); PortabilityLayer::ByteSwap::BigInt16(room->openings); PortabilityLayer::ByteSwap::BigInt16(room->numObjects); for (int i = 0; i < kMaxRoomObs; i++) ByteSwapObject(room->objects + i, isSwappedAfter); } bool ByteSwapHouse(housePtr house, size_t sizeInBytes, bool isSwappedAfter) { size_t nRooms = 0; if (isSwappedAfter) nRooms = house->nRooms; PortabilityLayer::ByteSwap::BigInt16(house->version); PortabilityLayer::ByteSwap::BigInt16(house->unusedShort); PortabilityLayer::ByteSwap::BigInt32(house->timeStamp); PortabilityLayer::ByteSwap::BigInt32(house->flags); ByteSwapPoint(&house->initial); SanitizePascalStr(house->banner); SanitizePascalStr(house->trailer); ByteSwapScores(&house->highScores); ByteSwapSavedGame(&house->savedGame_Unused); PortabilityLayer::ByteSwap::BigInt16(house->firstRoom); PortabilityLayer::ByteSwap::BigInt16(house->nRooms); if (!isSwappedAfter) nRooms = house->nRooms; const size_t roomDataSize = sizeInBytes - houseType::kBinaryDataSize; if (nRooms < 0 || roomDataSize / sizeof(roomType) < nRooms) return false; for (size_t i = 0; i < nRooms; i++) ByteSwapRoom(house->rooms + i, isSwappedAfter); house->padding = 0; return true; } static bool FailCheck(bool value) { return value; } static bool SucceedCheck(bool value) { return value; } static bool LCheck(bool value) { if (!value) return FailCheck(value); return value; } static bool RCheck(bool value) { if (value) return SucceedCheck(value); return value; } template bool LegalizePascalStr(uint8_t(&chars)[TSize], bool &anyRepairs) { const size_t maxLength = TSize - 1; if (chars[0] > maxLength) { chars[0] = static_cast(maxLength); anyRepairs = RCheck(true); return true; } return true; } static void LegalizeBoolean(Boolean &b) { if (b < 0 || b > 1) b = 1; } static bool LegalizeScores(scoresType *scores, bool &anyRepairs) { if (!LegalizePascalStr(scores->banner, anyRepairs)) return LCheck(false); for (int i = 0; i < kMaxScores; i++) if (!LegalizePascalStr(scores->names[i], anyRepairs)) return LCheck(false); return true; } static bool LegalizeRoomLayout(houseType *house, size_t roomNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; if (room->suite == kRoomIsEmpty) return true; if (room->suite < 0 || room->suite >= kMaxNumRoomsH) { room->suite = kRoomIsEmpty; anyRepairs = RCheck(true); return true; } if (room->floor > (kMaxNumRoomsV - kNumUndergroundFloors) || room->floor <= -kNumUndergroundFloors) { room->suite = kRoomIsEmpty; anyRepairs = RCheck(true); return true; } for (size_t ori = 0; ori < roomNum; ori++) { const roomType *otherRoom = house->rooms + ori; if (otherRoom->floor == room->floor && otherRoom->suite == room->suite) { room->suite = kRoomIsEmpty; anyRepairs = RCheck(true); return true; } } return true; } static void LegalizeTopLeft(Point &topLeft, bool &anyRepairs) { if (topLeft.h < 0) { anyRepairs = RCheck(true); topLeft.h = 0; } else if (topLeft.h >= kRoomWide) { anyRepairs = RCheck(true); topLeft.h = kRoomWide - 1; } if (topLeft.v < 0) { anyRepairs = RCheck(true); topLeft.v = 0; } else if (topLeft.v >= kTileHigh) { anyRepairs = RCheck(true); topLeft.v = kTileHigh - 1; } } template void LegalizeExpect(const TCondA &condA, const TCondB &condB, TSet &set, const TSetTo &setTo, bool &anyRepairs) { if (condA == condB && set != setTo) { set = setTo; anyRepairs = RCheck(true); } } static Boolean ForceRectInRoomRect(Rect &rect) { Rect roomRect; QSetRect(&roomRect, 0, 0, kRoomWide, kTileHigh); return ForceRectInRect(&rect, &roomRect); } static bool LegalizeBlower(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; blowerType *blower = &obj->data.a; LegalizeBoolean(blower->initial); LegalizeBoolean(blower->state); int direction = -1; for (int i = 0; i < 4; i++) { if (blower->vector & (1 << i)) { if (direction >= 0) anyRepairs = RCheck(true); else direction = i; } } static const int kDirectionUp = 0; static const int kDirectionRight = 1; static const int kDirectionDown = 2; static const int kDirectionLeft = 3; if (direction < 0 || direction > 3) direction = kDirectionUp; LegalizeTopLeft(blower->topLeft, anyRepairs); switch (obj->what) { case kFloorVent: case kFloorBlower: case kSewerGrate: case kTaper: case kCandle: case kStubby: case kTiki: case kBBQ: case kGrecoVent: case kSewerBlower: if (direction != kDirectionUp) direction = kDirectionUp; break; case kCeilingVent: case kCeilingBlower: if (direction != kDirectionDown) direction = kDirectionDown; break; case kRightFan: if (direction != kDirectionRight) direction = kDirectionRight; break; case kLeftFan: if (direction != kDirectionLeft) direction = kDirectionLeft; break; case kInvisBlower: case kLiftArea: break; default: return LCheck(false); } LegalizeExpect(obj->what, kFloorVent, blower->topLeft.v, kFloorVentTop, anyRepairs); LegalizeExpect(obj->what, kFloorBlower, blower->topLeft.v, kFloorBlowerTop, anyRepairs); LegalizeExpect(obj->what, kSewerGrate, blower->topLeft.v, kSewerGrateTop, anyRepairs); LegalizeExpect(obj->what, kCeilingVent, blower->topLeft.v, kCeilingVentTop, anyRepairs); LegalizeExpect(obj->what, kCeilingBlower, blower->topLeft.v, kCeilingBlowerTop, anyRepairs); if (blower->vector != (1 << direction)) { blower->vector = static_cast(1 << direction); // Unfortunately there's a lot of invalid directional data with fans, but because of how the code works, it has no effect //anyRepairs = RCheck(true); } if (blower->distance < 0) { blower->distance = 0; anyRepairs = RCheck(true); } if (obj->what == kLiftArea) { int maxWidth = kRoomWide - blower->topLeft.h; int maxHeight = kTileHigh - blower->topLeft.v; if (blower->distance > maxWidth) { blower->distance = maxWidth; anyRepairs = RCheck(true); } if (blower->tall * 2 > maxHeight) { blower->tall = static_cast(maxHeight / 2); anyRepairs = RCheck(true); } return true; } Rect positionedRect = srcRects[obj->what]; ZeroRectCorner(&positionedRect); OffsetRect(&positionedRect, blower->topLeft.h, blower->topLeft.v); const Rect basePositionedRect = positionedRect; if (ForceRectInRoomRect(positionedRect)) { anyRepairs = RCheck(true); blower->topLeft.h = positionedRect.left; blower->topLeft.v = positionedRect.top; } int maxDistance = 0; switch (direction) { case kDirectionUp: { int highestAllowed = BlowerTypeHasUpperLimit(obj->what) ? kUpwardVentMinY : 0; maxDistance = blower->topLeft.v - highestAllowed; } break; case kDirectionRight: maxDistance = kRoomWide - positionedRect.right; break; case kDirectionDown: maxDistance = kTileHigh - positionedRect.bottom; break; case kDirectionLeft: maxDistance = positionedRect.left; break; default: assert(false); return LCheck(false); } if (blower->distance > maxDistance) { blower->distance = maxDistance; anyRepairs = RCheck(true); } return true; } static bool LegalizeFurniture(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; furnitureType *furniture = &obj->data.b; bool isVertConstrained = false; bool isHorizConstrained = false; bool isBottomConstrained = false; int bottomConstraint = 0; switch (obj->what) { default: assert(false); return LCheck(false); case kTable: case kShelf: case kDeckTable: isVertConstrained = true; break; case kBooks: case kFilingCabinet: case kWasteBasket: case kMilkCrate: case kStool: case kTrunk: isVertConstrained = true; isHorizConstrained = true; break; case kCabinet: case kInvisObstacle: case kInvisBounce: // No constraints break; case kCounter: isBottomConstrained = true; bottomConstraint = kCounterBottom; break; case kDresser: isBottomConstrained = true; bottomConstraint = kDresserBottom; break; case kManhole: isVertConstrained = true; isHorizConstrained = true; isBottomConstrained = true; bottomConstraint = kManholeSits; break; }; const Rect baseRect = srcRects[obj->what]; if (NormalizeRect(&furniture->bounds)) anyRepairs = RCheck(true); Point topLeft = Point::Create(furniture->bounds.left, furniture->bounds.top); LegalizeTopLeft(topLeft, anyRepairs); if (obj->what == kManhole) { if (((topLeft.h - 3) % 64) != 0) { topLeft.h = (((topLeft.h + 29) / 64) * 64) + 3; anyRepairs = RCheck(true); } } uint16_t width = furniture->bounds.Width(); uint16_t height = furniture->bounds.Height(); if (width > kRoomWide) { width = kRoomWide; anyRepairs = RCheck(true); } if (height > kTileHigh) { height = kTileHigh; anyRepairs = RCheck(true); } LegalizeExpect(isVertConstrained, true, height, baseRect.Height(), anyRepairs); LegalizeExpect(isHorizConstrained, true, width, baseRect.Width(), anyRepairs); furniture->bounds.left = topLeft.h; furniture->bounds.top = topLeft.v; furniture->bounds.bottom = topLeft.v + static_cast(height); furniture->bounds.right = topLeft.h + static_cast(width); if (ForceRectInRoomRect(furniture->bounds)) anyRepairs = RCheck(true); if (isBottomConstrained && furniture->bounds.bottom != bottomConstraint) { if (furniture->bounds.top > bottomConstraint) furniture->bounds.top = bottomConstraint; furniture->bounds.bottom = bottomConstraint; anyRepairs = RCheck(true); } return true; } static bool LegalizeBonus(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; bonusType *bonus = &obj->data.c; LegalizeBoolean(bonus->state); LegalizeBoolean(bonus->initial); if (obj->what == kInvisBonus && bonus->points < 0) { anyRepairs = RCheck(true); bonus->points = 0; } LegalizeTopLeft(bonus->topLeft, anyRepairs); switch (obj->what) { default: return LCheck(false); case kRedClock: case kBlueClock: case kYellowClock: case kCuckoo: case kPaper: case kBattery: case kBands: case kGreaseRt: case kGreaseLf: case kFoil: case kInvisBonus: case kStar: case kSparkle: case kHelium: case kSlider: break; }; Rect objRect = srcRects[obj->what]; ZeroRectCorner(&objRect); if (obj->what == kSlider) { if (bonus->length < 0) { anyRepairs = RCheck(true); bonus->length = 0; } else if (bonus->length > kRoomWide) { anyRepairs = RCheck(true); bonus->length = kRoomWide; } objRect.right = bonus->length; } QOffsetRect(&objRect, bonus->topLeft.h, bonus->topLeft.v); if (ForceRectInRoomRect(objRect)) { anyRepairs = RCheck(true); bonus->topLeft.h = objRect.left; bonus->topLeft.v = objRect.top; } int maxLength = -1; if (obj->what == kGreaseRt) maxLength = kRoomWide - objRect.right; if (obj->what == kGreaseLf) maxLength = objRect.left; if (obj->what == kSlider) maxLength = kRoomWide - bonus->topLeft.h; if (maxLength >= 0) { if (bonus->length < 0) { anyRepairs = RCheck(true); bonus->length = 0; } else if (bonus->length > maxLength) { anyRepairs = RCheck(true); bonus->length = maxLength; } } return true; } static void LegalizeRoomLink(houseType *house, int16_t &where, bool &anyRepairs) { // Leftover -1 from version conversion, these are generally valid if (where == -100) where = -1; if (where < -1) { anyRepairs = RCheck(true); where = -1; } else if (where >= 0) { SInt16 floor, suite; ExtractFloorSuite(house, where, &floor, &suite); if (suite < 0 || suite >= kMaxNumRoomsH) { anyRepairs = RCheck(true); where = -1; } if (floor > (kMaxNumRoomsV - kNumUndergroundFloors) || floor <= -kNumUndergroundFloors) { anyRepairs = RCheck(true); where = -1; } } } static bool LegalizeTransport(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; transportType *transport = &obj->data.d; LegalizeTopLeft(transport->topLeft, anyRepairs); LegalizeRoomLink(house, transport->where, anyRepairs); Rect objRect = srcRects[obj->what]; ZeroRectCorner(&objRect); if (obj->what == kInvisTrans) { if (transport->tall < 0) { anyRepairs = RCheck(true); transport->tall = 0; } else if (transport->tall > kTileHigh) { anyRepairs = RCheck(true); transport->tall = kTileHigh; } objRect.right = transport->wide; objRect.bottom = transport->tall; } if (obj->what == kDeluxeTrans) { uint8_t codedWidth = static_cast((transport->tall >> 8) & 0xff); uint8_t codedHeight = static_cast((transport->tall) & 0xff); objRect.right = codedWidth * 4; objRect.bottom = codedHeight * 4; } QOffsetRect(&objRect, transport->topLeft.h, transport->topLeft.v); if (ForceRectInRoomRect(objRect)) { anyRepairs = RCheck(true); transport->topLeft.h = objRect.left; transport->topLeft.v = objRect.top; if (obj->what == kDeluxeTrans) transport->tall = static_cast(((objRect.Width() / 4) << 8) + objRect.Height() / 4); else if (obj->what == kInvisTrans) { transport->wide = static_cast(objRect.Width()); transport->tall = static_cast(objRect.Height()); } } switch (obj->what) { default: return LCheck(false); case kUpStairs: case kDownStairs: if (transport->topLeft.v != kStairsTop) { anyRepairs = RCheck(true); transport->topLeft.v = kStairsTop; } break; case kFloorTrans: if (transport->topLeft.v != kFloorTransTop) { anyRepairs = RCheck(true); transport->topLeft.v = kFloorTransTop; } break; case kCeilingTrans: if (transport->topLeft.v != kCeilingTransTop) { anyRepairs = RCheck(true); transport->topLeft.v = kCeilingTransTop; } break; case kDoorInLf: if (transport->topLeft.h != kDoorInLfLeft) { anyRepairs = RCheck(true); transport->topLeft.h = kCeilingTransTop; } if (transport->topLeft.v != kDoorInTop) { anyRepairs = RCheck(true); transport->topLeft.v = kDoorInTop; } break; case kDoorInRt: if (transport->topLeft.h != kDoorInRtLeft) { anyRepairs = RCheck(true); transport->topLeft.h = kDoorInRtLeft; } if (transport->topLeft.v != kDoorInTop) { anyRepairs = RCheck(true); transport->topLeft.v = kDoorInTop; } break; case kDoorExRt: if (transport->topLeft.h != kDoorExRtLeft) { anyRepairs = RCheck(true); transport->topLeft.h = kDoorExRtLeft; } if (transport->topLeft.v != kDoorExTop) { anyRepairs = RCheck(true); transport->topLeft.v = kDoorExTop; } break; case kDoorExLf: if (transport->topLeft.h != kDoorExLfLeft) { anyRepairs = RCheck(true); transport->topLeft.h = kDoorExLfLeft; } if (transport->topLeft.v != kDoorExTop) { anyRepairs = RCheck(true); transport->topLeft.v = kDoorExTop; } break; case kWindowInLf: if (transport->topLeft.h != kWindowInLfLeft) { anyRepairs = RCheck(true); transport->topLeft.h = kWindowInLfLeft; } if (transport->topLeft.v != kWindowInTop) { anyRepairs = RCheck(true); transport->topLeft.v = kWindowInTop; } break; case kWindowInRt: if (transport->topLeft.h != kWindowInRtLeft) { anyRepairs = RCheck(true); transport->topLeft.h = kWindowInRtLeft; } if (transport->topLeft.v != kWindowInTop) { anyRepairs = RCheck(true); transport->topLeft.v = kWindowInTop; } break; case kWindowExRt: if (transport->topLeft.h != kWindowExRtLeft) { anyRepairs = RCheck(true); transport->topLeft.h = kWindowExRtLeft; } if (transport->topLeft.v != kWindowExTop) { anyRepairs = RCheck(true); transport->topLeft.v = kWindowExTop; } break; case kWindowExLf: if (transport->topLeft.h != kWindowExLfLeft) { anyRepairs = RCheck(true); transport->topLeft.h = kWindowExLfLeft; } if (transport->topLeft.v != kWindowExTop) { anyRepairs = RCheck(true); transport->topLeft.v = kWindowExTop; } break; case kInvisTrans: case kDeluxeTrans: case kMailboxLf: case kMailboxRt: break; } return true; } static bool LegalizeSwitch(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; switchType *sw = &obj->data.e; if (sw->delay < 0) { sw->delay = 0; anyRepairs = RCheck(true); } bool isSwitch = false; bool isTrigger = false; switch (obj->what) { default: return RCheck(false); case kLightSwitch: case kMachineSwitch: case kThermostat: case kPowerSwitch: case kKnifeSwitch: case kInvisSwitch: isSwitch = true; LegalizeRoomLink(house, sw->where, anyRepairs); break; case kTrigger: case kLgTrigger: LegalizeRoomLink(house, sw->where, anyRepairs); break; case kSoundTrigger: break; }; if (isSwitch) { switch (sw->type) { case kToggle: case kForceOn: case kForceOff: break; default: anyRepairs = RCheck(true); sw->type = kToggle; break; } } if (isTrigger) { if (sw->type != kOneShot) { anyRepairs = RCheck(true); sw->type = kOneShot; } } LegalizeTopLeft(sw->topLeft, anyRepairs); Rect bounds = srcRects[obj->what]; ZeroRectCorner(&bounds); QOffsetRect(&bounds, sw->topLeft.h, sw->topLeft.v); if (ForceRectInRoomRect(bounds)) { anyRepairs = RCheck(true); sw->topLeft.h = bounds.left; sw->topLeft.v = bounds.top; } return true; } static bool LegalizeLight(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; lightType *light = &obj->data.f; LegalizeBoolean(light->initial); LegalizeBoolean(light->state); LegalizeTopLeft(light->topLeft, anyRepairs); Rect bounds = srcRects[obj->what]; ZeroRectCorner(&bounds); if (obj->what == kTrackLight || obj->what == kFlourescent) { if (light->length < 0) { anyRepairs = RCheck(true); light->length = 0; } else if (light->length > kRoomWide) { anyRepairs = RCheck(true); light->length = kRoomWide; } bounds.right = light->length; } QOffsetRect(&bounds, light->topLeft.h, light->topLeft.v); if (ForceRectInRoomRect(bounds)) { anyRepairs = RCheck(true); light->topLeft.h = bounds.left; light->topLeft.v = bounds.top; } switch (obj->what) { case kCeilingLight: if (light->topLeft.v != kCeilingLightTop) { anyRepairs = RCheck(true); light->topLeft.v = kCeilingLightTop; } break; case kHipLamp: if (light->topLeft.v != kHipLampTop) { anyRepairs = RCheck(true); light->topLeft.v = kHipLampTop; } break; case kDecoLamp: if (light->topLeft.v != kDecoLampTop) { anyRepairs = RCheck(true); light->topLeft.v = kDecoLampTop; } break; case kFlourescent: if (light->topLeft.v != kFlourescentTop) { anyRepairs = RCheck(true); light->topLeft.v = kFlourescentTop; } break; case kTrackLight: if (light->topLeft.v != kTrackLightTop) { anyRepairs = RCheck(true); light->topLeft.v = kTrackLightTop; } break; case kLightBulb: case kTableLamp: case kInvisLight: break; } return true; } static bool LegalizeAppliance(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; applianceType *appl = &obj->data.g; LegalizeBoolean(appl->initial); LegalizeBoolean(appl->state); LegalizeTopLeft(appl->topLeft, anyRepairs); if (obj->what != kCustomPict) { Rect bounds = srcRects[obj->what]; ZeroRectCorner(&bounds); QOffsetRect(&bounds, appl->topLeft.h, appl->topLeft.v); if (ForceRectInRoomRect(bounds)) { anyRepairs = RCheck(true); appl->topLeft.h = bounds.left; appl->topLeft.v = bounds.top; } switch (obj->what) { case kToaster: { int maxHeight = bounds.top; if (appl->height < 0) { anyRepairs = RCheck(true); appl->height = 0; } else if (appl->height > maxHeight) { anyRepairs = RCheck(true); appl->height = maxHeight; } } break; case kShredder: case kMacPlus: case kGuitar: case kTV: case kCoffee: case kOutlet: case kVCR: case kStereo: case kMicrowave: case kCinderBlock: case kFlowerBox: case kCDs: break; } } return true; } static bool LegalizeEnemy(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; enemyType *enemy = &obj->data.h; LegalizeBoolean(enemy->initial); LegalizeBoolean(enemy->state); LegalizeTopLeft(enemy->topLeft, anyRepairs); Rect bounds = srcRects[obj->what]; ZeroRectCorner(&bounds); QOffsetRect(&bounds, enemy->topLeft.h, enemy->topLeft.v); if (ForceRectInRoomRect(bounds)) { anyRepairs = RCheck(true); enemy->topLeft.h = bounds.left; enemy->topLeft.v = bounds.top; } switch (obj->what) { case kBalloon: case kCopterLf: case kCopterRt: { int expectedV = (kTileHigh / 2) - HalfRectTall(&bounds); if (enemy->topLeft.v != expectedV) { anyRepairs = RCheck(true); enemy->topLeft.v = expectedV; } } break; case kDartLf: { int expectedH = kRoomWide - RectWide(&bounds); if (enemy->topLeft.h != expectedH) { anyRepairs = RCheck(true); enemy->topLeft.h = expectedH; } } break; case kDartRt: if (enemy->topLeft.h != 0) { anyRepairs = RCheck(true); enemy->topLeft.h = 0; } break; case kBall: case kFish: { int maxLength = bounds.top; if (enemy->length < 0) { anyRepairs = RCheck(true); enemy->length = 0; } else if (enemy->length > maxLength) { anyRepairs = RCheck(true); enemy->length = maxLength; } } break; case kDrip: { int maxLength = kTileHigh - bounds.bottom; if (enemy->length < 0) { anyRepairs = RCheck(true); enemy->length = 0; } else if (enemy->length > maxLength) { anyRepairs = RCheck(true); enemy->length = maxLength; } } break; case kCobweb: break; } return true; } static bool LegalizeClutter(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; clutterType *blower = &obj->data.i; PL_NotYetImplemented_TODO("Validate"); return true; } static bool LegalizeObject(houseType *house, size_t roomNum, size_t objectNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; objectType *obj = room->objects + objectNum; switch (obj->what) { default: obj->what = kObjectIsEmpty; anyRepairs = RCheck(true); break; case kObjectIsEmpty: break; case kFloorVent: case kCeilingVent: case kFloorBlower: case kCeilingBlower: case kSewerGrate: case kLeftFan: case kRightFan: case kTaper: case kCandle: case kStubby: case kTiki: case kBBQ: case kInvisBlower: case kGrecoVent: case kSewerBlower: case kLiftArea: return LegalizeBlower(house, roomNum, objectNum, anyRepairs); case kTable: case kShelf: case kCabinet: case kFilingCabinet: case kWasteBasket: case kMilkCrate: case kCounter: case kDresser: case kDeckTable: case kStool: case kTrunk: case kInvisObstacle: case kManhole: case kBooks: case kInvisBounce: return LegalizeFurniture(house, roomNum, objectNum, anyRepairs); case kRedClock: case kBlueClock: case kYellowClock: case kCuckoo: case kPaper: case kBattery: case kBands: case kGreaseRt: case kGreaseLf: case kFoil: case kInvisBonus: case kStar: case kSparkle: case kHelium: case kSlider: return LegalizeBonus(house, roomNum, objectNum, anyRepairs); case kUpStairs: case kDownStairs: case kMailboxLf: case kMailboxRt: case kFloorTrans: case kCeilingTrans: case kDoorInLf: case kDoorInRt: case kDoorExRt: case kDoorExLf: case kWindowInLf: case kWindowInRt: case kWindowExRt: case kWindowExLf: case kInvisTrans: case kDeluxeTrans: return LegalizeTransport(house, roomNum, objectNum, anyRepairs); case kLightSwitch: case kMachineSwitch: case kThermostat: case kPowerSwitch: case kKnifeSwitch: case kInvisSwitch: case kTrigger: case kLgTrigger: case kSoundTrigger: return LegalizeSwitch(house, roomNum, objectNum, anyRepairs); case kCeilingLight: case kLightBulb: case kTableLamp: case kHipLamp: case kDecoLamp: case kFlourescent: case kTrackLight: case kInvisLight: return LegalizeLight(house, roomNum, objectNum, anyRepairs); case kShredder: case kToaster: case kMacPlus: case kGuitar: case kTV: case kCoffee: case kOutlet: case kVCR: case kStereo: case kMicrowave: case kCinderBlock: case kFlowerBox: case kCDs: case kCustomPict: return LegalizeAppliance(house, roomNum, objectNum, anyRepairs); case kBalloon: case kCopterLf: case kCopterRt: case kDartLf: case kDartRt: case kBall: case kDrip: case kFish: case kCobweb: return LegalizeEnemy(house, roomNum, objectNum, anyRepairs); case kOzma: case kMirror: case kMousehole: case kFireplace: case kFlower: case kWallWindow: case kBear: case kCalendar: case kVase1: case kVase2: case kBulletin: case kCloud: case kFaucet: case kRug: case kChimes: return LegalizeClutter(house, roomNum, objectNum, anyRepairs); } return true; } static bool LegalizeRoom(houseType *house, size_t roomNum, bool &anyRepairs) { roomType *room = house->rooms + roomNum; if (room->suite == kRoomIsEmpty) return true; if (!LegalizePascalStr(room->name, anyRepairs)) return LCheck(false); LegalizeBoolean(room->visited); for (int i = 0; i < kNumTiles; i++) { if (room->tiles[i] < 0) { room->tiles[i] = 0; anyRepairs = RCheck(true); } else if (room->tiles[i] >= kNumTiles) { room->tiles[i] = kNumTiles - 1; anyRepairs = RCheck(true); } } // Enforce object type caps enum CapType { CapType_Candle, CapType_Tiki, CapType_Coals, CapType_Pendulum, CapType_RubberBands, CapType_Star, CapType_DynamicObj, CapType_SoundTrigger, CapType_UpStairs, CapType_DownStairs, CapType_Grease, CapType_Count, }; int caps[CapType_Count]; caps[CapType_Candle] = kMaxCandles; caps[CapType_Tiki] = kMaxTikis; caps[CapType_Coals] = kMaxCoals; caps[CapType_Pendulum] = kMaxPendulums; caps[CapType_RubberBands] = kMaxRubberBands; caps[CapType_Star] = kMaxStars; caps[CapType_DynamicObj] = kMaxDynamicObs; caps[CapType_SoundTrigger] = kMaxSoundTriggers; caps[CapType_UpStairs] = kMaxStairs; caps[CapType_DownStairs] = kMaxStairs; caps[CapType_Grease] = kMaxGrease; int counts[CapType_Count]; for (int i = 0; i < CapType_Count; i++) counts[i] = 0; for (size_t i = 0; i < kMaxRoomObs; i++) { CapType capType = CapType_Count; objectType *obj = room->objects + i; switch (obj->what) { case kTaper: case kCandle: case kStubby: capType = CapType_Candle; break; case kTiki: capType = CapType_Tiki; break; case kBBQ: capType = CapType_Coals; break; case kCuckoo: capType = CapType_Pendulum; break; case kBands: capType = CapType_RubberBands; break; case kStar: capType = CapType_Star; break; case kSparkle: case kToaster: case kMacPlus: case kTV: case kCoffee: case kOutlet: case kVCR: case kStereo: case kMicrowave: case kBalloon: case kCopterLf: case kCopterRt: case kDartLf: case kDartRt: case kBall: case kDrip: case kFish: capType = CapType_DynamicObj; break; case kSoundTrigger: capType = CapType_SoundTrigger; break; case kUpStairs: capType = CapType_UpStairs; break; case kDownStairs: capType = CapType_DownStairs; break; case kGreaseLf: case kGreaseRt: capType = CapType_Grease; break; default: break; }; if (capType != CapType_Count) { if (counts[capType] == caps[capType]) { obj->what = kObjectIsEmpty; anyRepairs = RCheck(true); } else counts[capType]++; } } // Check all objects for (size_t i = 0; i < kMaxRoomObs; i++) { if (!LegalizeObject(house, roomNum, i, anyRepairs)) return LCheck(false); } int16_t numObjects = kMaxRoomObs; for (size_t i = 0; i < kMaxRoomObs; i++) { objectType *obj = room->objects + i; if (obj->what == kObjectIsEmpty) numObjects--; } if (numObjects != room->numObjects) { anyRepairs = RCheck(true); room->numObjects = numObjects; } return true; } static bool LegalizeHouse(houseType *house, bool &anyRepairs) { size_t nRooms = house->nRooms; if (!LegalizeScores(&house->highScores, anyRepairs)) return false; PL_NotYetImplemented_TODO("Validate initial pos"); // Repair room layout for (size_t i = 0; i < nRooms; i++) { if (!LegalizeRoomLayout(house, i, anyRepairs)) return LCheck(false); } // Repair firstRoom if (house->firstRoom < 0 || house->firstRoom >= house->nRooms || house->rooms[house->firstRoom].suite == kRoomIsEmpty) { if (nRooms != 0) { bool repairedOK = false; for (size_t i = 0; i < nRooms; i++) { if (house->rooms[i].suite == kRoomIsEmpty) continue; house->firstRoom = static_cast(i); repairedOK = true; anyRepairs = RCheck(true); } if (!repairedOK) return LCheck(false); } } for (size_t i = 0; i < nRooms; i++) { if (!LegalizeRoom(house, i, anyRepairs)) return LCheck(false); } return true; } Boolean ReadHouse (GpIOStream *houseStream, bool untrusted) { short whichRoom; // There should be no padding remaining the house type GP_STATIC_ASSERT(sizeof(houseType) - sizeof(roomType) == houseType::kBinaryDataSize + 2); if (!houseOpen) { YellowAlert(kYellowUnaccounted, 2); return (false); } const GpUFilePos_t byteCount = houseStream->Size(); #ifdef COMPILEDEMO if (byteCount != 16526L) return (false); #endif if (thisHouse != nil) { thisHouse.Dispose(); thisHouse = nil; } if (byteCount < houseType::kBinaryDataSize) { YellowAlert(kYellowHouseDamaged, 1); return (false); } const size_t roomDataSize = static_cast(byteCount) - houseType::kBinaryDataSize; if (roomDataSize % sizeof(roomType) != 0) { YellowAlert(kYellowHouseDamaged, 2); return (false); } const size_t roomCountFromDataSize = roomDataSize / sizeof(roomType); // GP: Correct for padding const size_t alignmentPadding = sizeof(houseType) - sizeof(roomType) - houseType::kBinaryDataSize; thisHouse = NewHandle(byteCount + alignmentPadding).StaticCast(); if (thisHouse == nil) { YellowAlert(kYellowNoMemory, 10); return(false); } if (!houseStream->SeekStart(0)) { CheckFileError(PLErrors::kIOError, thisHouseName); return(false); } const size_t readByteCount = houseStream->Read(*thisHouse, byteCount); if (readByteCount != byteCount || readByteCount < houseType::kBinaryDataSize) { CheckFileError(PLErrors::kIOError, thisHouseName); return(false); } if (alignmentPadding != 0) { // GP: Correct for padding const size_t roomDataSize = byteCount - houseType::kBinaryDataSize; uint8_t *houseDataBytes = reinterpret_cast(*thisHouse); memmove((*thisHouse)->rooms, houseDataBytes + houseType::kBinaryDataSize, roomDataSize); } ByteSwapHouse(*thisHouse, static_cast(byteCount), false); numberRooms = (*thisHouse)->nRooms; if (numberRooms < 0 || static_cast(numberRooms) > roomCountFromDataSize) { YellowAlert(kYellowHouseDamaged, 3); return (false); } bool anyRepairs = false; if (untrusted) { if (!LegalizeHouse(*thisHouse, anyRepairs)) { YellowAlert(kYellowHouseDamaged, 4); return (false); } if (anyRepairs) YellowAlert(kYellowHouseRepaired, 0); } #ifdef COMPILEDEMO if (numberRooms != 45) return (false); #endif if ((numberRooms < 1) || (byteCount == 0L)) { numberRooms = 0; noRoomAtAll = true; YellowAlert(kYellowNoRooms, 0); } wasHouseVersion = (*thisHouse)->version; if (wasHouseVersion >= kNewHouseVersion) { YellowAlert(kYellowNewerVersion, 0); return(false); } houseUnlocked = (((*thisHouse)->timeStamp & 0x00000001) == 0); #ifdef COMPILEDEMO if (houseUnlocked) return (false); #endif changeLockStateOfHouse = false; saveHouseLocked = false; whichRoom = (*thisHouse)->firstRoom; #ifdef COMPILEDEMO if (whichRoom != 0) return (false); #endif wardBitSet = (((*thisHouse)->flags & 0x00000001) == 0x00000001); phoneBitSet = (((*thisHouse)->flags & 0x00000002) == 0x00000002); bannerStarCountOn = (((*thisHouse)->flags & 0x00000004) == 0x00000000); noRoomAtAll = (RealRoomNumberCount() == 0); thisRoomNumber = -1; previousRoom = -1; if (!noRoomAtAll) CopyRoomToThisRoom(whichRoom); if (houseIsReadOnly) { houseUnlocked = false; if (ReadScoresFromDisk()) { } } objActive = kNoObjectSelected; ReflectCurrentRoom(true); fileDirty = false; UpdateMenus(false); return (true); } //-------------------------------------------------------------- WriteHouse // This function writes out the house data to disk. Boolean WriteHouse (Boolean checkIt) { UInt32 timeStamp; long byteCount; PLError_t theErr; if ((housesFound < 1) || (thisHouseIndex == -1)) return(false); if (!houseOpen) { YellowAlert(kYellowUnaccounted, 4); return (false); } if (!houseCFile) { YellowAlert(kYellowUnaccounted, 4); return (false); } GpIOStream *houseStream = nil; theErr = houseCFile->OpenData(PortabilityLayer::EFilePermission_Write, GpFileCreationDispositions::kCreateOrOverwrite, houseStream); if (theErr != PLErrors::kNone) return (false); CopyThisRoomToRoom(); if (checkIt) CheckHouseForProblems(); byteCount = GetHandleSize(thisHouse.StaticCast()); if (fileDirty) { int64_t currentTime = PLDrivers::GetSystemServices()->GetTime(); if (currentTime > 0x7fffffff) currentTime = 0x7fffffff; if (changeLockStateOfHouse) houseUnlocked = !saveHouseLocked; if (houseUnlocked) // house unlocked timeStamp &= 0x7FFFFFFE; else timeStamp |= 0x00000001; (*thisHouse)->timeStamp = (long)timeStamp; (*thisHouse)->version = wasHouseVersion; } long headerSize = houseType::kBinaryDataSize; long roomsSize = sizeof(roomType) * (*thisHouse)->nRooms; ByteSwapHouse(*thisHouse, static_cast(byteCount), true); if (houseStream->Write(*thisHouse, headerSize) != headerSize) { CheckFileError(PLErrors::kIOError, thisHouseName); ByteSwapHouse(*thisHouse, static_cast(byteCount), false); houseStream->Close(); return(false); } if (houseStream->Write((*thisHouse)->rooms, roomsSize) != roomsSize) { CheckFileError(PLErrors::kIOError, thisHouseName); ByteSwapHouse(*thisHouse, static_cast(byteCount), false); houseStream->Close(); return(false); } ByteSwapHouse(*thisHouse, static_cast(byteCount), false); houseStream->Close(); if (changeLockStateOfHouse) { changeLockStateOfHouse = false; ReflectCurrentRoom(true); } fileDirty = false; UpdateMenus(false); return (true); } //-------------------------------------------------------------- CloseHouse // This function closes the current house that is open. Boolean CloseHouse (void) { if (!houseOpen) return (true); if (fileDirty) { #ifndef COMPILEDEMO if (!QuerySaveChanges()) // false signifies user canceled return(false); #endif } CloseHouseResFork(); CloseHouseMovie(); if (houseCFile) { houseCFile->Close(); houseCFile = nil; } houseOpen = false; return (true); } //-------------------------------------------------------------- OpenHouseResFork // Opens the resource fork of the current house that is open. void OpenHouseResFork (void) { PortabilityLayer::ResourceManager *rm = PortabilityLayer::ResourceManager::GetInstance(); if (houseResFork == nullptr) { houseResFork = rm->LoadResFile(houseCFile); if (!houseResFork) YellowAlert(kYellowFailedResOpen, PLErrors::kResourceError); } } //-------------------------------------------------------------- CloseHouseResFork // Closes the resource fork of the current house that is open. void CloseHouseResFork (void) { if (houseResFork) { PortabilityLayer::ResourceManager *rm = PortabilityLayer::ResourceManager::GetInstance(); houseResFork->Destroy(); houseResFork = nullptr; } } //-------------------------------------------------------------- QuerySaveChanges // If changes were made, this function will present the user with aÉ // dialog asking them if they would like to save the changes. #ifndef COMPILEDEMO Boolean QuerySaveChanges (void) { short hitWhat; Boolean whoCares; if (!fileDirty) return(true); InitCursor(); // CenterAlert(kSaveChangesAlert); DialogTextSubstitutions substitutions(thisHouseName); hitWhat = PortabilityLayer::DialogManager::GetInstance()->DisplayAlert(kSaveChangesAlert, &substitutions); if (hitWhat == kSaveChanges) { if (wasHouseVersion < kHouseVersion) ConvertHouseVer1To2(); wasHouseVersion = kHouseVersion; if (WriteHouse(true)) return (true); else return (false); } else if (hitWhat == kDiscardChanges) { fileDirty = false; if (!quitting) { whoCares = CloseHouse(); OpenHouse(true); } UpdateMenus(false); return (true); } else return (false); } #endif //-------------------------------------------------------------- YellowAlert // This is a dialog used to present an error code and explanationÉ // to the user when a non-lethal error has occurred. Ideally, ofÉ // course, this never is called. void YellowAlert (short whichAlert, short identifier) { IGpLogDriver *logger = PLDrivers::GetLogDriver(); if (logger) logger->Printf(IGpLogDriver::Category_Warning, "Yellow alert %i identifier %i", static_cast(whichAlert), static_cast(identifier)); #define kYellowAlert 1006 Str255 errStr, errNumStr; short whoCares; InitCursor(); GetIndString(errStr, kYellowAlert, whichAlert); NumToString((long)identifier, errNumStr); // CenterAlert(kYellowAlert); DialogTextSubstitutions substitutions(errStr, errNumStr); whoCares = PortabilityLayer::DialogManager::GetInstance()->DisplayAlert(kYellowAlert, &substitutions); } //-------------------------------------------------------------- LoadHousePicture THandle LoadHouseResource(const PortabilityLayer::ResTypeID &resTypeID, int16_t resID) { THandle hdl = houseResFork->LoadResource(resTypeID, resID); if (hdl != nullptr) return hdl; return PortabilityLayer::ResourceManager::GetInstance()->GetAppResource(resTypeID, resID); } //-------------------------------------------------------------- ExportHouse namespace ExportHouseResults { enum ExportHouseResult { kOK, kStreamFailed, kIOError, kMemError, kResourceError, kInternalError, }; } typedef ExportHouseResults::ExportHouseResult ExportHouseResult_t; struct SimpleResource { PortabilityLayer::ResTypeID m_resType; int m_resourceID; PortabilityLayer::PascalStr<255> m_name; size_t m_offsetInResData; uint8_t m_attributes; }; static bool AppendRaw(GpVector &bytes, const void *data, size_t size) { for (size_t i = 0; i < size; i++) { if (!bytes.Append(static_cast(data)[i])) return false; } return true; } template static bool AppendRawStruct(GpVector &bytes, const T &value) { return AppendRaw(bytes, &value, sizeof(T)); } static ExportHouseResult_t TryExportSound(GpVector &resData, const THandle &resHandle) { const void *dataContents = nullptr; size_t dataSize = 0; if (!ParseAndConvertSoundChecked(resHandle, dataContents, dataSize)) return ExportHouseResults::kResourceError; // Don't ask... const uint8_t commandStreamPrefix[20] = { 0, 1, 0, 1, 0, 5, 0, 0, 0, 0xa0, 0, 1, 0x80, 0x51, 0, 0, 0, 0, 0, 0x14 }; struct BufferHeader { BEUInt32_t m_samplePtr; BEUInt32_t m_length; BEFixed32_t m_sampleRate; BEUInt32_t m_loopStart; BEUInt32_t m_loopEnd; uint8_t m_encoding; uint8_t m_baseFrequency; }; BufferHeader bufferHeader; bufferHeader.m_samplePtr = 0; bufferHeader.m_length = static_cast(dataSize); bufferHeader.m_sampleRate.m_intPart = 0x56ee; bufferHeader.m_sampleRate.m_fracPart = 0x8ba3; bufferHeader.m_loopStart = static_cast(dataSize - 2); bufferHeader.m_loopEnd = static_cast(dataSize - 1); bufferHeader.m_encoding = 0; bufferHeader.m_baseFrequency = 0x3c; if (!resData.Resize(sizeof(bufferHeader) + sizeof(commandStreamPrefix) + dataSize)) return ExportHouseResults::kMemError; memcpy(&resData[0], commandStreamPrefix, sizeof(commandStreamPrefix)); memcpy(&resData[sizeof(commandStreamPrefix)], &bufferHeader, sizeof(bufferHeader)); if (dataSize > 0) memcpy(&resData[sizeof(commandStreamPrefix) + sizeof(bufferHeader)], dataContents, dataSize); return ExportHouseResults::kOK; } static void BitSwap4(GpVector &vec, size_t count) { for (size_t i = 0; i < count; i++) { uint8_t v = vec[i]; v = (((v >> 4) & 0xf) | ((v << 4) & 0xf)); vec[i] = v; } } static void BitSwap2(GpVector &vec, size_t count) { for (size_t i = 0; i < count; i++) { uint8_t v = vec[i]; v = (((v >> 2) & 0x33) | ((v << 2) & 0xcc)); vec[i] = v; } } static void BitSwap1(GpVector &vec, size_t count) { for (size_t i = 0; i < count; i++) { uint8_t v = vec[i]; v = (((v >> 1) & 0x55) | ((v << 1) & 0xaa)); vec[i] = v; } } namespace RLEEncoder { static const size_t kMaxRepeat = 129; static const size_t kMaxLiteral = 128; bool EmitSymbol(GpVector &compressedData, uint8_t sym) { return compressedData.Append(sym); } bool EmitSymbol(GpVector &compressedData, uint16_t sym) { return compressedData.Append((sym >> 8) & 0xff) && compressedData.Append(sym & 0xff); } template bool EmitLiterals(GpVector &compressedData, const T *symbols, size_t length) { if (length == 0) return true; assert(length <= kMaxLiteral); if (!compressedData.Append(static_cast(length - 1))) return false; for (size_t i = 0; i < length; i++) { if (!EmitSymbol(compressedData, symbols[i])) return false; } return true; } template bool EmitRepeat(GpVector &compressedData, const T &symbol, size_t length) { if (length < 2) return EmitLiterals(compressedData, &symbol, length); assert(length <= kMaxRepeat); if (!compressedData.Append(static_cast(257 - length))) return false; if (!EmitSymbol(compressedData, symbol)) return false; return true; } template bool PackRLE(GpVector &compressedData, const GpVector &uncompressedData) { size_t numUncompressed = uncompressedData.Count(); const T *uncompressedSymbols = uncompressedData.Buffer(); if (!compressedData.Resize(0)) return false; size_t literalStartLoc = 0; size_t repeatStartLoc = 0; size_t readPos = 0; // Loop/exit invariants: // repeatStartLoc - literalStartLoc <= kMaxLiteral // i - repeatStartLoc < kMaxRepeat for (size_t i = 0; i < numUncompressed; i++) { T b = uncompressedSymbols[i]; if (b != uncompressedSymbols[repeatStartLoc]) { // Run terminates at i const size_t repeatLength = i - repeatStartLoc; const size_t literalLength = repeatStartLoc - literalStartLoc; // Determine if we should flush the repeat or fold it into the literal span. // There are several situations that can happen here: // Repeat length is 1: // Literal span is at limit: // Emit literal span, start new literal span at repeatStartLoc // Literal span is below limit: // Do nothing // Repeat length is 2: // Literal span is 0: // Emit repeat // Literal span is non-zero and appending repeat to literal span would be at or exceed limit: // Emit literal span and repeat // Otherwise: // Do nothing // Repeat length is 3+: // Emit literal span and repeat if (repeatLength == 1) { if (literalLength == kMaxLiteral) { if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) return false; literalStartLoc = repeatStartLoc; } } else if (repeatLength == 2) { if (literalLength == 0 || (literalLength + repeatLength >= kMaxLiteral)) { if (literalLength != 0) { if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) return false; } if (!EmitRepeat(compressedData, uncompressedSymbols[repeatStartLoc], repeatLength)) return false; literalStartLoc = i; } } else if (repeatLength >= 3) { if (literalLength != 0) { if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) return false; } if (!EmitRepeat(compressedData, uncompressedSymbols[repeatStartLoc], repeatLength)) return false; literalStartLoc = i; } repeatStartLoc = i; } else { // i is a repeat character const size_t repeatLength = i + 1 - repeatStartLoc; const size_t literalLength = repeatStartLoc - literalStartLoc; if (repeatLength == kMaxRepeat) { if (literalLength != 0) { if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) return false; } if (!EmitRepeat(compressedData, uncompressedSymbols[repeatStartLoc], repeatLength)) return false; literalStartLoc = i + 1; repeatStartLoc = i + 1; } } } // Final flush size_t repeatLength = numUncompressed - repeatStartLoc; size_t literalLength = repeatStartLoc - literalStartLoc; if (repeatLength == 1) { if (literalLength < kMaxLiteral) { literalLength++; repeatLength = 0; } } if (literalLength != 0) { if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) return false; } if (repeatLength == 1) { if (!EmitLiterals(compressedData, uncompressedSymbols + repeatStartLoc, 1)) return false; } else if (repeatLength >= 2) { if (!EmitRepeat(compressedData, uncompressedSymbols[repeatStartLoc], repeatLength)) return false; } return true; } } static ExportHouseResult_t TryExportPictFromSurface(GpVector &resData, DrawSurface *surface) { bool couldBe16Bit = true; bool couldBe8Bit = true; int numUniqueColors = 0; PortabilityLayer::RGBAColor uniqueColors[256]; for (int i = 0; i < 256; i++) uniqueColors[i] = PortabilityLayer::RGBAColor::Create(0, 0, 0, 255); const Rect rect = surface->m_port.GetRect(); const size_t width = rect.Width(); const size_t height = rect.Height(); THandle pixMapHdl = surface->m_port.GetPixMap(); const PixMap *pixMap = *pixMapHdl; const uint8_t *imageData = static_cast(pixMap->m_data); const size_t pixelDataPitch = pixMap->m_pitch; assert(pixMap->m_pixelFormat == GpPixelFormats::kRGB32); for (size_t row = 0; row < height; row++) { const uint8_t *rowData = imageData + pixMap->m_pitch * row; if (!couldBe8Bit && !couldBe16Bit) break; for (size_t col = 0; col < width; col++) { const uint8_t *pixelData = rowData + col * 4; if (couldBe16Bit) { for (int ch = 0; ch < 3; ch++) { uint8_t channelData = pixelData[ch]; if ((channelData >> 2) != (channelData & 0x7)) { couldBe16Bit = false; break; } } } if (couldBe8Bit) { PortabilityLayer::RGBAColor rgbaColor = PortabilityLayer::RGBAColor::Create(pixelData[0], pixelData[1], pixelData[2], pixelData[3]); bool matchedColor = false; for (int i = 0; i < numUniqueColors; i++) { if (uniqueColors[i] == rgbaColor) { matchedColor = true; break; } } if (!matchedColor) { if (numUniqueColors == 256) couldBe8Bit = false; else uniqueColors[numUniqueColors++] = rgbaColor; } } } } bool isBWBitmap = false; if (numUniqueColors <= 2) { isBWBitmap = true; for (int c = 0; c < numUniqueColors; c++) { if (uniqueColors[c] != StdColors::Black() && uniqueColors[c] != StdColors::White()) isBWBitmap = false; } if (isBWBitmap) { numUniqueColors = 2; uniqueColors[0] = StdColors::White(); uniqueColors[1] = StdColors::Black(); } } int bpp = 0; if (isBWBitmap) bpp = 1; else if (couldBe8Bit) { if (numUniqueColors <= 2) bpp = 1; else if (numUniqueColors <= 4) bpp = 2; else if (numUniqueColors <= 16) bpp = 4; else bpp = 8; } else if (couldBe16Bit) bpp = 16; else bpp = 32; // The typical structure of a PICT is header, ClipRegion, then a raster op. // We use V1 pict for 1bpp and V2 for all others. struct PictHeader { uint8_t m_size[2]; BERect m_rect; }; BERect beRect; beRect.top = beRect.left = 0; beRect.right = static_cast(width); beRect.bottom = static_cast(height); PictHeader pictHeader; pictHeader.m_size[0] = 0; pictHeader.m_size[1] = 0; pictHeader.m_rect = beRect; if (!AppendRawStruct(resData, pictHeader)) return ExportHouseResults::kMemError; int pictVersion = 1; if (isBWBitmap) { const uint8_t versionTag[2] = { 0x11, 0x01 }; if (!AppendRaw(resData, versionTag, 2)) return ExportHouseResults::kMemError; } else { pictVersion = 2; struct PictV2Header { BEUInt16_t m_versionTag; BEUInt16_t m_versionOp; BEUInt16_t m_headerOp; BEInt16_t m_v2Version; BEInt16_t m_reserved1; BEFixed32_t m_top; BEFixed32_t m_left; BEFixed32_t m_bottom; BEFixed32_t m_right; BEUInt32_t m_reserved2; }; GP_STATIC_ASSERT(sizeof(PictV2Header) == 30); PictV2Header v2Header; v2Header.m_versionTag = 0x0011; v2Header.m_versionOp = 0x02ff; v2Header.m_headerOp = 0x0c00; v2Header.m_v2Version = -1; v2Header.m_reserved1 = -1; v2Header.m_top.m_intPart = beRect.top; v2Header.m_top.m_fracPart = 0; v2Header.m_left.m_intPart = beRect.left; v2Header.m_left.m_fracPart = 0; v2Header.m_bottom.m_intPart = beRect.bottom; v2Header.m_bottom.m_fracPart = 0; v2Header.m_right.m_intPart = beRect.right; v2Header.m_right.m_fracPart = 0; if (!AppendRawStruct(resData, v2Header)) return ExportHouseResults::kMemError; } // Emit ClipRgn opcode if (pictVersion == 1) { const uint8_t clipRgnOpcode[1] = { PortabilityLayer::QDOpcodes::kClipRegion }; if (!AppendRaw(resData, clipRgnOpcode, 1)) return ExportHouseResults::kMemError; } else if (pictVersion == 2) { const uint8_t clipRgnOpcode[2] = { 0, PortabilityLayer::QDOpcodes::kClipRegion }; if (!AppendRaw(resData, clipRgnOpcode, 2)) return ExportHouseResults::kMemError; } struct ClipRgnData { BEUInt16_t m_structureSize; BERect m_rect; }; GP_STATIC_ASSERT(sizeof(ClipRgnData) == 10); ClipRgnData clipRgnData; clipRgnData.m_structureSize = sizeof(ClipRgnData); clipRgnData.m_rect = beRect; if (!AppendRawStruct(resData, clipRgnData)) return ExportHouseResults::kMemError; // Emit image const size_t bitsPerRow = width * bpp; const size_t bytesPerRow = (bitsPerRow + 7) / 8; const size_t expansionCapacity = bytesPerRow + (bytesPerRow / 128) + 16; // Worst-case scenario for RLE failure to compress uint16_t bitmapOpcode = 0; int packType = 0; bool isDirect = false; if (bpp <= 8) { if (bytesPerRow < 8) { packType = 1; bitmapOpcode = PortabilityLayer::QDOpcodes::kBitsRect; } else { packType = 0; bitmapOpcode = PortabilityLayer::QDOpcodes::kPackBitsRect; } } else { isDirect = true; bitmapOpcode = PortabilityLayer::QDOpcodes::kDirectBitsRect; if (bpp == 16) packType = 3; else if (bpp == 32) packType = 4; else return ExportHouseResults::kInternalError; } if (pictVersion == 1) { const uint8_t opcode = static_cast(bitmapOpcode); if (!AppendRaw(resData, &opcode, 1)) return ExportHouseResults::kMemError; } else if (pictVersion == 2) { const BEUInt16_t opcode(bitmapOpcode); if (!AppendRaw(resData, &opcode, 2)) return ExportHouseResults::kMemError; } // Write prelude const bool isPixmap = !isBWBitmap; { if (isDirect) { struct DirectPrelude { BEUInt32_t m_baseAddress; BEUInt16_t m_rowSize; }; DirectPrelude prelude; prelude.m_baseAddress = 0; prelude.m_rowSize = static_cast(0x8000 | bytesPerRow); if (!AppendRawStruct(resData, prelude)) return ExportHouseResults::kMemError; } else { uint16_t rowSize = static_cast(bytesPerRow); if (!isBWBitmap) rowSize |= 0x8000; BEUInt16_t rowSizeBE(rowSize); if (!AppendRawStruct(resData, rowSizeBE)) return ExportHouseResults::kMemError; } } // Do actual image packing, we need to do this now so we have the pack size available GpVector packedImage(PLDrivers::GetAlloc()); GpVector uncompressedRowData8(PLDrivers::GetAlloc()); GpVector uncompressedRowData16(PLDrivers::GetAlloc()); GpVector compressedRowData(PLDrivers::GetAlloc()); if (packType == 4) { if (!uncompressedRowData8.Resize(bytesPerRow)) return ExportHouseResults::kMemError; } else { if (!uncompressedRowData16.Resize(bytesPerRow / 2)) return ExportHouseResults::kMemError; } if (!compressedRowData.Resize(expansionCapacity)) return ExportHouseResults::kMemError; for (size_t row = 0; row < height; row++) { if (!uncompressedRowData8.Resize(0)) return ExportHouseResults::kMemError; if (!uncompressedRowData16.Resize(0)) return ExportHouseResults::kMemError; const uint8_t *srcRowStart = imageData + pixelDataPitch * row; if (packType == 4) { // RGB24 images for (size_t ch = 0; ch < 3; ch++) { for (size_t col = 0; col < width; col++) { const uint8_t *srcPixelStart = srcRowStart + col * 4; if (!uncompressedRowData8.Append(srcPixelStart[ch])) return ExportHouseResults::kMemError; } } } else if (couldBe16Bit) { assert(packType == 3); for (size_t col = 0; col < width; col++) { const uint8_t *srcPixelStart = srcRowStart + col * 4; uint16_t packed = ((srcPixelStart[0] << 7) & 0x7c00) | ((srcPixelStart[1] << 2) & 0x3e0) | ((srcPixelStart[2] >> 3) & 0x1f); if (!uncompressedRowData16.Append(packed)) return ExportHouseResults::kMemError; } } else { assert(couldBe8Bit); int numBitsSpooled = 0; uint8_t spooledBits = 0; for (size_t col = 0; col < width; col++) { const uint8_t *srcPixelStart = srcRowStart + col * 4; PortabilityLayer::RGBAColor color = PortabilityLayer::RGBAColor::Create(srcPixelStart[0], srcPixelStart[1], srcPixelStart[2], srcPixelStart[3]); int colorIndex = -1; for (int ci = 0; ci < numUniqueColors; ci++) { if (color == uniqueColors[ci]) { colorIndex = ci; break; } } assert(colorIndex >= 0); spooledBits <<= bpp; spooledBits |= colorIndex; numBitsSpooled += bpp; if (numBitsSpooled == 8) { if (!uncompressedRowData8.Append(spooledBits)) return ExportHouseResults::kMemError; numBitsSpooled = 0; spooledBits = 0; } } if (numBitsSpooled != 0) { spooledBits <<= (8 - numBitsSpooled); if (!uncompressedRowData8.Append(spooledBits)) return ExportHouseResults::kMemError; } } if (!compressedRowData.Resize(0)) return ExportHouseResults::kMemError; bool needsLengthMarker = false; switch (packType) { case 0: case 4: // 8-bit RLE if (!RLEEncoder::PackRLE(compressedRowData, uncompressedRowData8)) return ExportHouseResults::kMemError; needsLengthMarker = true; break; case 1: // Uncompressed if (!compressedRowData.Resize(bytesPerRow)) return ExportHouseResults::kMemError; for (size_t i = 0; i < bytesPerRow; i++) compressedRowData[i] = uncompressedRowData8[i]; needsLengthMarker = false; break; case 3: // 16-bit RLE if (!RLEEncoder::PackRLE(compressedRowData, uncompressedRowData16)) return ExportHouseResults::kMemError; needsLengthMarker = true; break; default: assert(false); return ExportHouseResults::kInternalError; }; const size_t compressedSize = compressedRowData.Count(); if (needsLengthMarker) { if (bytesPerRow > 250) packedImage.Append((compressedSize >> 8) & 0xff); packedImage.Append(compressedSize & 0xff); } for (size_t i = 0; i < compressedSize; i++) packedImage.Append(compressedRowData[i]); } // Write BitMap/PixMap { if (isBWBitmap) { struct BitMapData { BEBitMap m_bitMap; BERect m_srcRect; BERect m_destRect; BEUInt16_t m_transferMode; }; BitMapData bmData; bmData.m_bitMap.m_bounds = beRect; bmData.m_srcRect = beRect; bmData.m_destRect = beRect; bmData.m_transferMode = 0; if (!AppendRawStruct(resData, bmData)) return ExportHouseResults::kMemError; } else { BEPixMap pixMap; pixMap.m_bounds = beRect; pixMap.m_version = 0; pixMap.m_packType = packType; pixMap.m_packSize = static_cast(packedImage.Count()); pixMap.m_hRes = 0x480000; pixMap.m_vRes = 0x480000; pixMap.m_pixelType = (isDirect ? 16 : 0); pixMap.m_pixelSize = bpp; pixMap.m_componentCount = isDirect ? 3 : 1; if (bpp == 32) pixMap.m_componentSize = 8; else if (bpp == 16) pixMap.m_componentSize = 5; else pixMap.m_componentSize = bpp; pixMap.m_planeSizeBytes = 0; pixMap.m_clutHandle = 0; pixMap.m_unused = 0; if (!AppendRawStruct(resData, pixMap)) return ExportHouseResults::kMemError; if (isDirect) { } else { BEColorTableHeader clutHeader; clutHeader.m_resourceID = 0; clutHeader.m_flags = 0; clutHeader.m_numItemsMinusOne = numUniqueColors - 1; if (!AppendRawStruct(resData, clutHeader)) return ExportHouseResults::kMemError; for (int i = 0; i < numUniqueColors; i++) { BEColorTableItem item; item.m_index = i; item.m_red[0] = item.m_red[1] = uniqueColors[i].r; item.m_green[0] = item.m_green[1] = uniqueColors[i].g; item.m_blue[0] = item.m_blue[1] = uniqueColors[i].b; if (!AppendRawStruct(resData, item)) return ExportHouseResults::kMemError; } } struct TransferData { BERect m_srcRect; BERect m_destRect; BEUInt16_t m_transferMode; }; TransferData transferData; transferData.m_srcRect = beRect; transferData.m_destRect = beRect; transferData.m_transferMode = 0; if (!AppendRawStruct(resData, transferData)) return ExportHouseResults::kMemError; } } // Write image contents if (!AppendRaw(resData, &packedImage[0], packedImage.Count())) return ExportHouseResults::kMemError; // Write pad byte (??) if (!isBWBitmap && (resData.Count() & 1) != 0) { if (!resData.Append(0)) return ExportHouseResults::kMemError; } // Emit EOP opcode if (pictVersion == 1) { const uint8_t eopOpcode[1] = { PortabilityLayer::QDOpcodes::kEndOfPicture }; if (!AppendRaw(resData, eopOpcode, 1)) return ExportHouseResults::kMemError; } else if (pictVersion == 2) { const uint8_t eopOpcode[2] = { 0, PortabilityLayer::QDOpcodes::kEndOfPicture }; if (!AppendRaw(resData, eopOpcode, 2)) return ExportHouseResults::kMemError; } return ExportHouseResults::kOK; } static ExportHouseResult_t TryExportPICT(GpVector &resData, const THandle &resHandle) { // Parse bitmap header const THandle bmpHandle = resHandle.StaticCast(); const BitmapImage *bmp = *bmpHandle; const Rect rect = bmp->GetRect(); DrawSurface *surface = nullptr; if (NewGWorld(&surface, GpPixelFormats::kRGB32, &rect, nullptr) != PLErrors::kNone) return ExportHouseResults::kMemError; surface->DrawPicture(bmpHandle, rect, false); ExportHouseResult_t result = TryExportPictFromSurface(resData, surface); DisposeGWorld(surface); return result; } static ExportHouseResult_t TryExportResource(GpVector &resData, PortabilityLayer::IResourceArchive *resArchive, const PortabilityLayer::ResTypeID &resTypeID, int16_t resID) { THandle resHandle = resArchive->LoadResource(resTypeID, resID); if (!resHandle) return ExportHouseResults::kMemError; if (resTypeID == PortabilityLayer::ResTypeID('PICT')) { ExportHouseResult_t exportResult = TryExportPICT(resData, resHandle); resHandle.Dispose(); return exportResult; } if (resTypeID == PortabilityLayer::ResTypeID('snd ')) { ExportHouseResult_t exportResult = TryExportSound(resData, resHandle); resHandle.Dispose(); return exportResult; } const size_t size = resHandle.MMBlock()->m_size; if (!resData.Resize(size)) { resHandle.Dispose(); return ExportHouseResults::kMemError; } if (size > 0) memcpy(&resData[0], *resHandle, size); resHandle.Dispose(); return ExportHouseResults::kOK; } ExportHouseResult_t TryExportResources(GpIOStream *stream, PortabilityLayer::IResourceArchive *resArchive) { if (!resArchive) return ExportHouseResults::kOK; IGpAllocator *alloc = PLDrivers::GetAlloc(); GpVector resources(alloc); GpUFilePos_t resForkStart = stream->Tell(); PortabilityLayer::IResourceIterator *iterator = resArchive->EnumerateResources(); if (!iterator) return ExportHouseResults::kMemError; const GpUFilePos_t resForkHeaderPos = stream->Tell(); GpUFilePos_t resForkDataStart = 0; PortabilityLayer::ResTypeID resTypeID; int16_t resID = 0; bool isFirstResource = true; while (iterator->GetOne(resTypeID, resID)) { if (resTypeID != PortabilityLayer::ResTypeID('PICT') && resTypeID != PortabilityLayer::ResTypeID('snd ')) continue; if (isFirstResource) { // Seems to want this much scratch space... uint8_t headerData[256]; memset(headerData, 0, sizeof(headerData)); if (!stream->WriteExact(headerData, sizeof(headerData))) return ExportHouseResults::kIOError; resForkDataStart = stream->Tell(); isFirstResource = false; } SimpleResource res; bool isPurgeable = true; res.m_name.Set(0, nullptr); res.m_resourceID = resID; res.m_resType = resTypeID; res.m_attributes = 0; res.m_offsetInResData = stream->Tell() - resForkDataStart; if (isPurgeable) res.m_attributes |= (1 << 5); if (!resources.Append(static_cast(res))) { iterator->Destroy(); return ExportHouseResults::kMemError; } GpVector resData(alloc); ExportHouseResult_t exportResResult = TryExportResource(resData, resArchive, resTypeID, resID); if (exportResResult != ExportHouseResults::kOK) return exportResResult; BEUInt32_t packedLen(static_cast(resData.Count())); if (!stream->WriteExact(&packedLen, sizeof(packedLen))) { iterator->Destroy(); return ExportHouseResults::kIOError; } if (resData.Count() > 0) { if (!stream->WriteExact(&resData[0], resData.Count())) { iterator->Destroy(); return ExportHouseResults::kIOError; } } const unsigned int unpaddedExcess = ((stream->Tell() - resForkStart) & 0x3); if (unpaddedExcess > 0) { uint8_t padding[4] = { 0, 0, 0, 0 }; if (!stream->WriteExact(padding, 4 - unpaddedExcess)) return ExportHouseResults::kIOError; } } iterator->Destroy(); if (!resources.Count()) return ExportHouseResults::kOK; GpVector uniqueResTypes(alloc); GpVector resTypeCounts(alloc); // Generate res map for (size_t i = 0; i < resources.Count(); i++) { const SimpleResource &res = resources[i]; size_t uniqueResTypeIndex = uniqueResTypes.Count(); for (size_t uri = 0; uri < uniqueResTypes.Count(); uri++) { if (uniqueResTypes[uri] == res.m_resType) { uniqueResTypeIndex = uri; break; } } if (uniqueResTypeIndex == uniqueResTypes.Count()) { if (!uniqueResTypes.Append(res.m_resType)) return ExportHouseResults::kMemError; if (!resTypeCounts.Append(1)) return ExportHouseResults::kMemError; } else resTypeCounts[uniqueResTypeIndex]++; } const GpUFilePos_t resMapPos = stream->Tell(); const GpUFilePos_t resDataSize = resMapPos - resForkDataStart; // Reserved space for resource header copy (16), handle to next res map (4), file ref number (2) { char resHeaderCopy[22]; memset(resHeaderCopy, 0, sizeof(resHeaderCopy)); if (!stream->WriteExact(resHeaderCopy, sizeof(resHeaderCopy))) return ExportHouseResults::kIOError; } uint16_t resForkAttributes = 0; // We don't use any of these const size_t typeListEntrySize = 8; const size_t refListEntrySize = 12; const size_t resourceTypeListStartLoc = 28; const size_t resourceTypeListSize = 2 + uniqueResTypes.Count() * typeListEntrySize; const size_t resourceRefListStartLoc = resourceTypeListStartLoc + resourceTypeListSize; const size_t resourceNameListStartLoc = resourceRefListStartLoc + resources.Count() * refListEntrySize; struct ResForkHeaderData { BEUInt16_t m_attributes; BEUInt16_t m_resourceTypeListStartLoc; BEUInt16_t m_resourceNameListStartLoc; BEUInt16_t m_numResTypesMinusOne; }; ResForkHeaderData headerData; headerData.m_attributes = resForkAttributes; headerData.m_resourceTypeListStartLoc = static_cast(resourceTypeListStartLoc); headerData.m_resourceNameListStartLoc = static_cast(resourceNameListStartLoc); headerData.m_numResTypesMinusOne = static_cast(uniqueResTypes.Count() - 1); if (!stream->WriteExact(&headerData, sizeof(headerData))) return ExportHouseResults::kIOError; GpVector refListStartForType(alloc); if (!refListStartForType.Resize(resTypeCounts.Count())) return ExportHouseResults::kMemError; if (resTypeCounts.Count() > 0) { refListStartForType[0] = 0; for (size_t i = 1; i < refListStartForType.Count(); i++) refListStartForType[i] = refListStartForType[i - 1] + resTypeCounts[i - 1]; } struct ResTypeData { char m_resType[4]; BEUInt16_t m_resCountMinusOne; BEUInt16_t m_refListStart; }; // Write resource type list for (size_t i = 0; i < uniqueResTypes.Count(); i++) { ResTypeData resTypeData; uniqueResTypes[i].ExportAsChars(resTypeData.m_resType); resTypeData.m_resCountMinusOne = resTypeCounts[i] - 1; resTypeData.m_refListStart = static_cast(refListStartForType[i] * refListEntrySize + resourceTypeListSize); if (!stream->WriteExact(&resTypeData, sizeof(resTypeData))) return ExportHouseResults::kIOError; } struct RefListEntry { BEInt16_t m_resID; BEInt16_t m_nameOffset; uint8_t m_attribs; uint8_t m_resDataStart[3]; BEUInt32_t m_reserved; }; // Write reference lists for (size_t ti = 0; ti < uniqueResTypes.Count(); ti++) { PortabilityLayer::ResTypeID resType = uniqueResTypes[ti]; for (size_t i = 0; i < resources.Count(); i++) { const SimpleResource &res = resources[i]; if (res.m_resType != resType) continue; RefListEntry refListEntry; refListEntry.m_resID = static_cast(res.m_resourceID); refListEntry.m_nameOffset = -1; refListEntry.m_attribs = res.m_attributes; const size_t resDataStart = res.m_offsetInResData; refListEntry.m_resDataStart[0] = static_cast((resDataStart >> 16) & 0xff); refListEntry.m_resDataStart[1] = static_cast((resDataStart >> 8) & 0xff); refListEntry.m_resDataStart[2] = static_cast((resDataStart >> 0) & 0xff); refListEntry.m_reserved = 0; if (!stream->WriteExact(&refListEntry, sizeof(refListEntry))) return ExportHouseResults::kIOError; } } const GpUFilePos_t resForkEnd = stream->Tell(); const GpUFilePos_t resMapSize = resForkEnd - resMapPos; struct ResForkHeader { BEUInt32_t m_resForkDataStart; BEUInt32_t m_resMapPos; BEUInt32_t m_resDataSize; BEUInt32_t m_resMapSize; }; ResForkHeader header; header.m_resForkDataStart = static_cast(resForkDataStart - resForkStart); header.m_resMapPos = static_cast(resMapPos - resForkStart); header.m_resDataSize = static_cast(resDataSize); header.m_resMapSize = static_cast(resMapSize); // Write header at the start of the file if (!stream->SeekStart(resForkHeaderPos)) return ExportHouseResults::kIOError; if (!stream->WriteExact(&header, sizeof(header))) return ExportHouseResults::kIOError; // Write header at the start of the resource map if (!stream->SeekStart(resMapPos)) return ExportHouseResults::kIOError; if (!stream->WriteExact(&header, sizeof(header))) return ExportHouseResults::kIOError; // Return to the end of the file if (!stream->SeekStart(resForkEnd)) return ExportHouseResults::kIOError; return ExportHouseResults::kOK; } ExportHouseResult_t TryExportHouseToStream(GpIOStream *stream) { uint8_t mb2Header[PortabilityLayer::MacBinary2::kHeaderSize]; memset(mb2Header, 0, sizeof(mb2Header)); // Write MacBinary header if (!stream->WriteExact(mb2Header, sizeof(mb2Header))) return ExportHouseResults::kIOError; houseType *house = *thisHouse; const size_t houseSize = thisHouse.MMBlock()->m_size; const size_t nRooms = house->nRooms; const size_t houseDataSize = houseType::kBinaryDataSize + sizeof(roomType) * nRooms; ByteSwapHouse(house, houseSize, true); if (!stream->WriteExact(house, houseType::kBinaryDataSize)) { ByteSwapHouse(house, houseSize, false); return ExportHouseResults::kIOError; } if (!stream->WriteExact(house->rooms, sizeof(roomType) * nRooms)) { ByteSwapHouse(house, houseSize, false); return ExportHouseResults::kIOError; } ByteSwapHouse(house, houseSize, false); char padding[128]; memset(padding, 0, sizeof(padding)); const GpUFilePos_t dataAlignExcess = stream->Tell() % 128; if (dataAlignExcess != 0) { if (!stream->WriteExact(padding, 128 - dataAlignExcess)) return ExportHouseResults::kIOError; } const GpUFilePos_t resForkPos = stream->Tell(); // Serialize resources if (houseResFork != nullptr) { ExportHouseResult_t resExportResult = TryExportResources(stream, houseResFork); if (resExportResult != ExportHouseResults::kOK) return resExportResult; } const GpUFilePos_t resForkSize = stream->Tell() - resForkPos; const GpUFilePos_t resAlignExcess = resForkSize % 128; if (resForkSize != 0) { if (!stream->WriteExact(padding, 128 - resAlignExcess)) return ExportHouseResults::kIOError; } PortabilityLayer::MacFileInfo fileInfo; fileInfo.m_fileName.Set(thisHouseName[0], reinterpret_cast(thisHouseName + 1)); fileInfo.m_commentSize = 0; fileInfo.m_dataForkSize = static_cast(houseDataSize); fileInfo.m_resourceForkSize = resForkSize; memcpy(fileInfo.m_properties.m_fileType, "gliH", 4); memcpy(fileInfo.m_properties.m_fileCreator, "ozm5", 4); fileInfo.m_properties.m_xPos = 0; fileInfo.m_properties.m_yPos = 0; fileInfo.m_properties.m_finderFlags = 0; fileInfo.m_properties.m_protected = 0; fileInfo.m_properties.m_createdTimeMacEpoch = fileInfo.m_properties.m_modifiedTimeMacEpoch = PLDrivers::GetSystemServices()->GetTime(); PortabilityLayer::MacBinary2::SerializeHeader(mb2Header, fileInfo); if (!stream->SeekStart(0)) return ExportHouseResults::kIOError; if (!stream->WriteExact(mb2Header, PortabilityLayer::MacBinary2::kHeaderSize)) return ExportHouseResults::kIOError; return ExportHouseResults::kOK; } ExportHouseResult_t TryExportHouse(void) { GpIOStream *stream = nullptr; if (PortabilityLayer::FileManager::GetInstance()->OpenNonCompositeFile(PortabilityLayer::VirtualDirectories::kSourceExport, thisHouseName, ".bin", PortabilityLayer::EFilePermission_Write, GpFileCreationDispositions::kCreateOrOverwrite, stream)) return ExportHouseResults::kStreamFailed; ExportHouseResult_t result = TryExportHouseToStream(stream); stream->Close(); return result; } void ExportHouse(void) { ExportHouseResult_t result = TryExportHouse(); switch (result) { case ExportHouseResults::kOK: break; case ExportHouseResults::kMemError: YellowAlert(kYellowNoMemory, 0); break; case ExportHouseResults::kInternalError: YellowAlert(kYellowUnaccounted, 0); break; case ExportHouseResults::kIOError: case ExportHouseResults::kStreamFailed: YellowAlert(kYellowFailedWrite, 0); break; case ExportHouseResults::kResourceError: YellowAlert(kYellowFailedResOpen, 0); break; } } ExportHouseResult_t TryDownloadHouseToStream(GpIOStream *stream) { PortabilityLayer::MacFileProperties mfp; mfp.m_createdTimeMacEpoch = PLDrivers::GetSystemServices()->GetTime(); memcpy(mfp.m_fileCreator, "ozm5", 4); memcpy(mfp.m_fileType, "gliH", 4); mfp.m_finderFlags = 0; mfp.m_modifiedTimeMacEpoch = mfp.m_createdTimeMacEpoch; mfp.m_protected = 0; mfp.m_xPos = 0; mfp.m_yPos = 0; PortabilityLayer::MacFilePropertiesSerialized mfps; mfps.Serialize(mfp); unsigned int year, month, day, hour, minute, second; PortabilityLayer::CombinedTimestamp ts; PLDrivers::GetSystemServices()->GetLocalDateTime(year, month, day, hour, minute, second); ts.SetLocalYear(year); ts.m_localDay = day; ts.m_localHour = hour; ts.m_localMinute = minute; ts.m_localMonth = month; ts.m_localSecond = second; uint16_t dosDate, dosTime; ts.GetAsMSDOSTimestamp(dosDate, dosTime); const uint32_t metaSize = sizeof(mfps.m_data); const uint32_t metaCRC = PortabilityLayer::DeflateContext::CRC32(0, mfps.m_data, metaSize); const char *metaPackagedName = PortabilityLayer::MacFilePropertiesSerialized::GetPackagedName(); GpUFilePos_t metaLHPos = 0; PortabilityLayer::ZipFileLocalHeader metaLH; metaLH.m_signature = PortabilityLayer::ZipFileLocalHeader::kSignature; metaLH.m_versionRequired = PortabilityLayer::ZipConstants::kStoredRequiredVersion; metaLH.m_flags = 0; metaLH.m_method = PortabilityLayer::ZipConstants::kStoredMethod; metaLH.m_modificationTime = dosTime; metaLH.m_modificationDate = dosDate; metaLH.m_crc = metaCRC; metaLH.m_compressedSize = metaSize; metaLH.m_uncompressedSize = metaSize; metaLH.m_fileNameLength = strlen(metaPackagedName); metaLH.m_extraFieldLength = 0; if (!stream->WriteExact(&metaLH, sizeof(metaLH))) return ExportHouseResults::kIOError; if (!stream->WriteExact(metaPackagedName, strlen(metaPackagedName))) return ExportHouseResults::kIOError; if (!stream->WriteExact(mfps.m_data, metaSize)) return ExportHouseResults::kIOError; houseType *house = *thisHouse; const size_t houseSize = thisHouse.MMBlock()->m_size; const size_t nRooms = house->nRooms; const size_t houseDataSize = houseType::kBinaryDataSize + sizeof(roomType) * nRooms; ByteSwapHouse(house, houseSize, true); uint32_t houseCRC = PortabilityLayer::DeflateContext::CRC32(0, house, houseType::kBinaryDataSize); houseCRC = PortabilityLayer::DeflateContext::CRC32(houseCRC, house->rooms, sizeof(roomType) * nRooms); const uint32_t totalhouseSize = houseType::kBinaryDataSize + sizeof(roomType) * nRooms; GpUFilePos_t houseLHPos = 0; const char *dataPackagedName = "!data"; PortabilityLayer::ZipFileLocalHeader houseLH; houseLH.m_signature = PortabilityLayer::ZipFileLocalHeader::kSignature; houseLH.m_versionRequired = PortabilityLayer::ZipConstants::kStoredRequiredVersion; houseLH.m_flags = 0; houseLH.m_method = PortabilityLayer::ZipConstants::kStoredMethod; houseLH.m_modificationTime = dosTime; houseLH.m_modificationDate = dosDate; houseLH.m_crc = houseCRC; houseLH.m_compressedSize = totalhouseSize; houseLH.m_uncompressedSize = totalhouseSize; houseLH.m_fileNameLength = strlen(dataPackagedName); houseLH.m_extraFieldLength = 0; if (!stream->WriteExact(&houseLH, sizeof(houseLH))) { ByteSwapHouse(house, houseSize, false); return ExportHouseResults::kIOError; } if (!stream->WriteExact(dataPackagedName, strlen(dataPackagedName))) { ByteSwapHouse(house, houseSize, false); return ExportHouseResults::kIOError; } if (!stream->WriteExact(house, houseType::kBinaryDataSize)) { ByteSwapHouse(house, houseSize, false); return ExportHouseResults::kIOError; } if (!stream->WriteExact(house->rooms, sizeof(roomType) * nRooms)) { ByteSwapHouse(house, houseSize, false); return ExportHouseResults::kIOError; } ByteSwapHouse(house, houseSize, false); GpUFilePos_t cdirStart = stream->Tell(); PortabilityLayer::ZipCentralDirectoryFileHeader metaCDir; metaCDir.m_signature = PortabilityLayer::ZipCentralDirectoryFileHeader::kSignature; metaCDir.m_versionCreated = PortabilityLayer::ZipConstants::kCompressedRequiredVersion;; metaCDir.m_versionRequired = PortabilityLayer::ZipConstants::kStoredRequiredVersion; metaCDir.m_flags = 0; metaCDir.m_method = PortabilityLayer::ZipConstants::kStoredMethod; metaCDir.m_modificationTime = dosTime; metaCDir.m_modificationDate = dosDate; metaCDir.m_crc = metaCRC; metaCDir.m_compressedSize = metaSize; metaCDir.m_uncompressedSize = metaSize; metaCDir.m_fileNameLength = strlen(metaPackagedName); metaCDir.m_extraFieldLength = 0; metaCDir.m_commentLength = 0; metaCDir.m_diskNumber = 0; metaCDir.m_internalAttributes = 0; metaCDir.m_externalAttributes = PortabilityLayer::ZipConstants::kArchivedAttributes; metaCDir.m_localHeaderOffset = static_cast(metaLHPos); if (!stream->WriteExact(&metaCDir, sizeof(metaCDir))) return ExportHouseResults::kIOError; if (!stream->WriteExact(metaPackagedName, strlen(metaPackagedName))) return ExportHouseResults::kIOError; PortabilityLayer::ZipCentralDirectoryFileHeader dataCDir; dataCDir.m_signature = PortabilityLayer::ZipCentralDirectoryFileHeader::kSignature; dataCDir.m_versionCreated = PortabilityLayer::ZipConstants::kCompressedRequiredVersion;; dataCDir.m_versionRequired = PortabilityLayer::ZipConstants::kStoredRequiredVersion; dataCDir.m_flags = 0; dataCDir.m_method = PortabilityLayer::ZipConstants::kStoredMethod; dataCDir.m_modificationTime = dosTime; dataCDir.m_modificationDate = dosDate; dataCDir.m_crc = metaCRC; dataCDir.m_compressedSize = houseDataSize; dataCDir.m_uncompressedSize = houseDataSize; dataCDir.m_fileNameLength = strlen(dataPackagedName); dataCDir.m_extraFieldLength = 0; dataCDir.m_commentLength = 0; dataCDir.m_diskNumber = 0; dataCDir.m_internalAttributes = 0; dataCDir.m_externalAttributes = PortabilityLayer::ZipConstants::kArchivedAttributes; dataCDir.m_localHeaderOffset = static_cast(houseLHPos); if (!stream->WriteExact(&dataCDir, sizeof(dataCDir))) return ExportHouseResults::kIOError; if (!stream->WriteExact(dataPackagedName, strlen(dataPackagedName))) return ExportHouseResults::kIOError; PortabilityLayer::ZipEndOfCentralDirectoryRecord eocd; eocd.m_signature = PortabilityLayer::ZipEndOfCentralDirectoryRecord::kSignature; eocd.m_thisDiskNumber = 0; eocd.m_centralDirDisk = 0; eocd.m_numCentralDirRecordsThisDisk = 2; eocd.m_numCentralDirRecords = 2; eocd.m_centralDirectorySizeBytes = static_cast(stream->Tell() - cdirStart); eocd.m_centralDirStartOffset = static_cast(cdirStart); eocd.m_commentLength = 0; if (!stream->WriteExact(&eocd, sizeof(eocd))) return ExportHouseResults::kIOError; return ExportHouseResults::kOK; } ExportHouseResult_t TryDownloadHouse(void) { GpIOStream *stream = nullptr; if (PortabilityLayer::FileManager::GetInstance()->OpenNonCompositeFile(PortabilityLayer::VirtualDirectories::kSourceExport, thisHouseName, ".gpf", PortabilityLayer::EFilePermission_Write, GpFileCreationDispositions::kCreateOrOverwrite, stream)) return ExportHouseResults::kStreamFailed; ExportHouseResult_t result = TryDownloadHouseToStream(stream); stream->Close(); return result; } void DownloadHouse(void) { ExportHouseResult_t result = TryDownloadHouse(); switch (result) { case ExportHouseResults::kOK: break; case ExportHouseResults::kMemError: YellowAlert(kYellowNoMemory, 0); break; case ExportHouseResults::kInternalError: YellowAlert(kYellowUnaccounted, 0); break; case ExportHouseResults::kIOError: case ExportHouseResults::kStreamFailed: YellowAlert(kYellowFailedWrite, 0); break; case ExportHouseResults::kResourceError: YellowAlert(kYellowFailedResOpen, 0); break; } }