Files
Aerofoil/HouseTool/HouseTool.cpp
2024-07-31 20:23:34 +12:00

1819 lines
39 KiB
C++

#include <stdint.h>
#include "PLCore.h"
#include "PLBigEndian.h"
#include "MacRomanConversion.h"
#include "GpUnicode.h"
#include <vector>
#ifdef _WIN32
#include "WindowsUnicodeToolShim.h"
#else
#include "UnixCompat.h"
#endif
typedef uint8_t Boolean;
#define kMaxScores 10
#define kMaxRoomObs 24
#define kNumTiles 8
// Object types
#define kFloorVent 0x01 // Blowers
#define kCeilingVent 0x02
#define kFloorBlower 0x03
#define kCeilingBlower 0x04
#define kSewerGrate 0x05
#define kLeftFan 0x06
#define kRightFan 0x07
#define kTaper 0x08
#define kCandle 0x09
#define kStubby 0x0A
#define kTiki 0x0B
#define kBBQ 0x0C
#define kInvisBlower 0x0D
#define kGrecoVent 0x0E
#define kSewerBlower 0x0F
#define kLiftArea 0x10
#define kTable 0x11 // Furniture
#define kShelf 0x12
#define kCabinet 0x13
#define kFilingCabinet 0x14
#define kWasteBasket 0x15
#define kMilkCrate 0x16
#define kCounter 0x17
#define kDresser 0x18
#define kDeckTable 0x19
#define kStool 0x1A
#define kTrunk 0x1B
#define kInvisObstacle 0x1C
#define kManhole 0x1D
#define kBooks 0x1E
#define kInvisBounce 0x1F
#define kRedClock 0x21 // Prizes
#define kBlueClock 0x22
#define kYellowClock 0x23
#define kCuckoo 0x24
#define kPaper 0x25
#define kBattery 0x26
#define kBands 0x27
#define kGreaseRt 0x28
#define kGreaseLf 0x29
#define kFoil 0x2A
#define kInvisBonus 0x2B
#define kStar 0x2C
#define kSparkle 0x2D
#define kHelium 0x2E
#define kSlider 0x2F
#define kUpStairs 0x31 // Transport
#define kDownStairs 0x32
#define kMailboxLf 0x33
#define kMailboxRt 0x34
#define kFloorTrans 0x35
#define kCeilingTrans 0x36
#define kDoorInLf 0x37
#define kDoorInRt 0x38
#define kDoorExRt 0x39
#define kDoorExLf 0x3A
#define kWindowInLf 0x3B
#define kWindowInRt 0x3C
#define kWindowExRt 0x3D
#define kWindowExLf 0x3E
#define kInvisTrans 0x3F
#define kDeluxeTrans 0x40
#define kLightSwitch 0x41 // Switches
#define kMachineSwitch 0x42
#define kThermostat 0x43
#define kPowerSwitch 0x44
#define kKnifeSwitch 0x45
#define kInvisSwitch 0x46
#define kTrigger 0x47
#define kLgTrigger 0x48
#define kSoundTrigger 0x49
#define kCeilingLight 0x51 // Lights
#define kLightBulb 0x52
#define kTableLamp 0x53
#define kHipLamp 0x54
#define kDecoLamp 0x55
#define kFlourescent 0x56
#define kTrackLight 0x57
#define kInvisLight 0x58
#define kShredder 0x61 // Appliances
#define kToaster 0x62
#define kMacPlus 0x63
#define kGuitar 0x64
#define kTV 0x65
#define kCoffee 0x66
#define kOutlet 0x67
#define kVCR 0x68
#define kStereo 0x69
#define kMicrowave 0x6A
#define kCinderBlock 0x6B
#define kFlowerBox 0x6C
#define kCDs 0x6D
#define kCustomPict 0x6E
#define kBalloon 0x71 // Enemies
#define kCopterLf 0x72
#define kCopterRt 0x73
#define kDartLf 0x74
#define kDartRt 0x75
#define kBall 0x76
#define kDrip 0x77
#define kFish 0x78
#define kCobweb 0x79
#define kOzma 0x81 // Clutter
#define kMirror 0x82
#define kMousehole 0x83
#define kFireplace 0x84
#define kFlower 0x85
#define kWallWindow 0x86
#define kBear 0x87
#define kCalendar 0x88
#define kVase1 0x89
#define kVase2 0x8A
#define kBulletin 0x8B
#define kCloud 0x8C
#define kFaucet 0x8D
#define kRug 0x8E
#define kChimes 0x8F
struct EnumDef
{
int m_value;
const char *m_name;
};
struct Bool8
{
uint8_t m_value;
};
template<class T, int TSize>
struct StaticArray
{
static const int kSize = TSize;
T m_data[TSize];
};
template<int TSize>
struct PascalStr
{
static const int kSize = TSize;
uint8_t m_length;
uint8_t m_chars[TSize];
};
typedef PascalStr<15> PStr15_t;
typedef PascalStr<27> PStr27_t;
typedef PascalStr<31> PStr31_t;
typedef PascalStr<255> PStr255_t;
typedef struct
{
BEPoint topLeft; // 4
BEInt16_t distance; // 2
Bool8 initial; // 1
Bool8 state; // 1 F. lf. dn. rt. up
uint8_t vector; // 1 | x | x | x | x | 8 | 4 | 2 | 1 |
uint8_t tall; // 1
} blowerType; // total = 10
typedef struct
{
BERect bounds; // 8
BEInt16_t pict; // 2
} furnitureType; // total = 10
typedef struct
{
BEPoint topLeft; // 4
BEInt16_t length; // 2 grease spill
BEInt16_t points; // 2 invis bonus
Bool8 state; // 1
Bool8 initial; // 1
} bonusType; // total = 10
typedef struct
{
BEPoint topLeft; // 4
BEInt16_t tall; // 2 invis transport
BEInt16_t where; // 2
uint8_t who; // 1
uint8_t wide; // 1
} transportType; // total = 10
typedef struct
{
BEPoint topLeft; // 4
BEInt16_t delay; // 2
BEInt16_t where; // 2
uint8_t who; // 1
uint8_t type; // 1
} switchType; // total = 10
typedef struct
{
BEPoint topLeft; // 4
BEInt16_t length; // 2
uint8_t byte0; // 1
uint8_t byte1; // 1
Bool8 initial; // 1
Bool8 state; // 1
} lightType; // total = 10
typedef struct
{
BEPoint topLeft; // 4
BEInt16_t height; // 2 toaster, pict ID
uint8_t byte0; // 1
uint8_t delay; // 1
Bool8 initial; // 1
Bool8 state; // 1
} applianceType; // total = 10
typedef struct
{
BEPoint topLeft; // 4
BEInt16_t length; // 2
uint8_t delay; // 1
uint8_t byte0; // 1
Bool8 initial; // 1
Bool8 state; // 1
} enemyType; // total = 10
typedef struct
{
BERect bounds; // 8
BEInt16_t pict; // 2
} clutterType; // total = 10
struct objectType
{
union u
{
blowerType a;
furnitureType b;
bonusType c;
transportType d;
switchType e;
lightType f;
applianceType g;
enemyType h;
clutterType i;
StaticArray<uint8_t, 10> pad;
u();
u(const u &other);
~u();
};
BEInt16_t what; // 2
u data; // 10
}; // total = 12
objectType::u::u()
{
static_assert(sizeof(*this) == 10, "Wrong size");
}
objectType::u::~u()
{
}
objectType::u::u(const u &other)
{
memcpy(this, &other, sizeof(*this));
}
typedef objectType *objectPtr;
typedef struct
{
PStr31_t banner; // 32 = 32
StaticArray<PStr15_t, kMaxScores> names; // 16 * 10 = 160
StaticArray<BEInt32_t, kMaxScores> scores; // 4 * 10 = 40
StaticArray<BEUInt32_t, kMaxScores> timeStamps; // 4 * 10 = 40
StaticArray<BEInt16_t, kMaxScores> levels; // 2 * 10 = 20
} scoresType; // total = 292
typedef struct
{
BEInt16_t version; // 2
BEInt16_t wasStarsLeft; // 2
BEUInt32_t timeStamp; // 4
BEPoint where; // 4
BEInt32_t score; // 4
BEInt32_t unusedLong; // 4
BEInt32_t unusedLong2; // 4
BEInt16_t energy; // 2
BEInt16_t bands; // 2
BEInt16_t roomNumber; // 2
BEInt16_t gliderState; // 2
BEInt16_t numGliders; // 2
BEInt16_t foil; // 2
BEInt16_t unusedShort; // 2
Bool8 facing; // 1
Bool8 showFoil; // 1
} gameType; // total = 40
typedef struct
{
BEInt16_t unusedShort; // 2
uint8_t unusedByte; // 1
Bool8 visited; // 1
objectType objects[kMaxRoomObs]; // 24 * 12
} savedRoom, *saveRoomPtr; // total = 292
typedef struct
{
PStr27_t name; // 28
BEInt16_t bounds; // 2
uint8_t leftStart; // 1
uint8_t rightStart; // 1
uint8_t unusedByte; // 1
Bool8 visited; // 1
BEInt16_t background; // 2
StaticArray<BEInt16_t, kNumTiles> tiles; // 2 * 8
BEInt16_t floor, suite; // 2 + 2
BEInt16_t openings; // 2
BEInt16_t numObjects; // 2
StaticArray<objectType, kMaxRoomObs> objects; // 24 * 12
} roomType, *roomPtr; // total = 348
struct houseType
{
BEInt16_t version; // 2
BEInt16_t unusedShort; // 2
BEInt32_t timeStamp; // 4
BEInt32_t flags; // 4 (bit 0 = wardBit)
BEPoint initial; // 4
PStr255_t banner; // 256
PStr255_t trailer; // 256
scoresType highScores; // 292
gameType savedGame_Unused; // 40
Bool8 hasGame; // 1
Bool8 unusedBoolean; // 1
BEInt16_t firstRoom; // 2
BEInt16_t nRooms; // 2
static const size_t kBinaryDataSize = 866;
};
static const size_t houseSize = sizeof(houseType);
#define ENUM_VALUE_DEF(k) { k, #k }
EnumDef g_objectTypeEnum[] =
{
{ -1, "kObjectIsEmpty" },
ENUM_VALUE_DEF(kFloorVent),
ENUM_VALUE_DEF(kCeilingVent),
ENUM_VALUE_DEF(kFloorBlower),
ENUM_VALUE_DEF(kCeilingBlower),
ENUM_VALUE_DEF(kSewerGrate),
ENUM_VALUE_DEF(kLeftFan),
ENUM_VALUE_DEF(kRightFan),
ENUM_VALUE_DEF(kTaper),
ENUM_VALUE_DEF(kCandle),
ENUM_VALUE_DEF(kStubby),
ENUM_VALUE_DEF(kTiki),
ENUM_VALUE_DEF(kBBQ),
ENUM_VALUE_DEF(kInvisBlower),
ENUM_VALUE_DEF(kGrecoVent),
ENUM_VALUE_DEF(kSewerBlower),
ENUM_VALUE_DEF(kLiftArea),
ENUM_VALUE_DEF(kTable),
ENUM_VALUE_DEF(kShelf),
ENUM_VALUE_DEF(kCabinet),
ENUM_VALUE_DEF(kFilingCabinet),
ENUM_VALUE_DEF(kWasteBasket),
ENUM_VALUE_DEF(kMilkCrate),
ENUM_VALUE_DEF(kCounter),
ENUM_VALUE_DEF(kDresser),
ENUM_VALUE_DEF(kDeckTable),
ENUM_VALUE_DEF(kStool),
ENUM_VALUE_DEF(kTrunk),
ENUM_VALUE_DEF(kInvisObstacle),
ENUM_VALUE_DEF(kManhole),
ENUM_VALUE_DEF(kBooks),
ENUM_VALUE_DEF(kInvisBounce),
ENUM_VALUE_DEF(kRedClock),
ENUM_VALUE_DEF(kBlueClock),
ENUM_VALUE_DEF(kYellowClock),
ENUM_VALUE_DEF(kCuckoo),
ENUM_VALUE_DEF(kPaper),
ENUM_VALUE_DEF(kBattery),
ENUM_VALUE_DEF(kBands),
ENUM_VALUE_DEF(kGreaseRt),
ENUM_VALUE_DEF(kGreaseLf),
ENUM_VALUE_DEF(kFoil),
ENUM_VALUE_DEF(kInvisBonus),
ENUM_VALUE_DEF(kStar),
ENUM_VALUE_DEF(kSparkle),
ENUM_VALUE_DEF(kHelium),
ENUM_VALUE_DEF(kSlider),
ENUM_VALUE_DEF(kUpStairs),
ENUM_VALUE_DEF(kDownStairs),
ENUM_VALUE_DEF(kMailboxLf),
ENUM_VALUE_DEF(kMailboxRt),
ENUM_VALUE_DEF(kFloorTrans),
ENUM_VALUE_DEF(kCeilingTrans),
ENUM_VALUE_DEF(kDoorInLf),
ENUM_VALUE_DEF(kDoorInRt),
ENUM_VALUE_DEF(kDoorExRt),
ENUM_VALUE_DEF(kDoorExLf),
ENUM_VALUE_DEF(kWindowInLf),
ENUM_VALUE_DEF(kWindowInRt),
ENUM_VALUE_DEF(kWindowExRt),
ENUM_VALUE_DEF(kWindowExLf),
ENUM_VALUE_DEF(kInvisTrans),
ENUM_VALUE_DEF(kDeluxeTrans),
ENUM_VALUE_DEF(kLightSwitch),
ENUM_VALUE_DEF(kMachineSwitch),
ENUM_VALUE_DEF(kThermostat),
ENUM_VALUE_DEF(kPowerSwitch),
ENUM_VALUE_DEF(kKnifeSwitch),
ENUM_VALUE_DEF(kInvisSwitch),
ENUM_VALUE_DEF(kTrigger),
ENUM_VALUE_DEF(kLgTrigger),
ENUM_VALUE_DEF(kSoundTrigger),
ENUM_VALUE_DEF(kCeilingLight),
ENUM_VALUE_DEF(kLightBulb),
ENUM_VALUE_DEF(kTableLamp),
ENUM_VALUE_DEF(kHipLamp),
ENUM_VALUE_DEF(kDecoLamp),
ENUM_VALUE_DEF(kFlourescent),
ENUM_VALUE_DEF(kTrackLight),
ENUM_VALUE_DEF(kInvisLight),
ENUM_VALUE_DEF(kShredder),
ENUM_VALUE_DEF(kToaster),
ENUM_VALUE_DEF(kMacPlus),
ENUM_VALUE_DEF(kGuitar),
ENUM_VALUE_DEF(kTV),
ENUM_VALUE_DEF(kCoffee),
ENUM_VALUE_DEF(kOutlet),
ENUM_VALUE_DEF(kVCR),
ENUM_VALUE_DEF(kStereo),
ENUM_VALUE_DEF(kMicrowave),
ENUM_VALUE_DEF(kCinderBlock),
ENUM_VALUE_DEF(kFlowerBox),
ENUM_VALUE_DEF(kCDs),
ENUM_VALUE_DEF(kCustomPict),
ENUM_VALUE_DEF(kBalloon),
ENUM_VALUE_DEF(kCopterLf),
ENUM_VALUE_DEF(kCopterRt),
ENUM_VALUE_DEF(kDartLf),
ENUM_VALUE_DEF(kDartRt),
ENUM_VALUE_DEF(kBall),
ENUM_VALUE_DEF(kDrip),
ENUM_VALUE_DEF(kFish),
ENUM_VALUE_DEF(kCobweb),
ENUM_VALUE_DEF(kOzma),
ENUM_VALUE_DEF(kMirror),
ENUM_VALUE_DEF(kMousehole),
ENUM_VALUE_DEF(kFireplace),
ENUM_VALUE_DEF(kFlower),
ENUM_VALUE_DEF(kWallWindow),
ENUM_VALUE_DEF(kBear),
ENUM_VALUE_DEF(kCalendar),
ENUM_VALUE_DEF(kVase1),
ENUM_VALUE_DEF(kVase2),
ENUM_VALUE_DEF(kBulletin),
ENUM_VALUE_DEF(kCloud),
ENUM_VALUE_DEF(kFaucet),
ENUM_VALUE_DEF(kRug),
ENUM_VALUE_DEF(kChimes),
};
const size_t g_objectTypeEnumSize = sizeof(g_objectTypeEnum) / sizeof(g_objectTypeEnum[0]);
struct IDataVisitor
{
virtual IDataVisitor *EnterIndex(int index) = 0;
virtual IDataVisitor *EnterNamed(const char *name) = 0;
virtual void Exit() = 0;
virtual void VisitLPStr(uint8_t &length, uint8_t *chars, int capacity) = 0;
virtual void VisitInt32(int32_t &v) = 0;
virtual void VisitUInt32(uint32_t &v) = 0;
virtual void VisitInt16(int16_t &v) = 0;
virtual void VisitBool8(uint8_t &v) = 0;
virtual void VisitByte(uint8_t &v) = 0;
virtual bool VisitEnum(const EnumDef *enumDefs, size_t numDefs, int &v) = 0;
virtual bool VisitStaticCapacity(size_t size) = 0;
virtual void VisitDynamicCapacity(size_t &size) = 0;
};
bool VisitEnum(const EnumDef *enumDefs, size_t numDefs, IDataVisitor *visitor);
void Visit(roomType &v, IDataVisitor *visitor);
void Visit(objectType &v, IDataVisitor *visitor);
void Visit(blowerType &v, IDataVisitor *visitor);
void Visit(furnitureType &v, IDataVisitor *visitor);
void Visit(bonusType &v, IDataVisitor *visitor);
void Visit(transportType &v, IDataVisitor *visitor);
void Visit(switchType &v, IDataVisitor *visitor);
void Visit(lightType &v, IDataVisitor *visitor);
void Visit(applianceType &v, IDataVisitor *visitor);
void Visit(enemyType &v, IDataVisitor *visitor);
void Visit(clutterType &v, IDataVisitor *visitor);
void Visit(scoresType &v, IDataVisitor *visitor);
void Visit(gameType &v, IDataVisitor *visitor);
void Visit(Bool8 &v, IDataVisitor *visitor);
void Visit(uint8_t &v, IDataVisitor *visitor);
void Visit(BEInt16_t &v, IDataVisitor *visitor);
void Visit(BEInt32_t &v, IDataVisitor *visitor);
void Visit(BEUInt32_t &v, IDataVisitor *visitor);
void Visit(BEPoint &v, IDataVisitor *visitor);
void Visit(BERect &v, IDataVisitor *visitor);
template<int TSize>
void Visit(PascalStr<TSize> &pstr, IDataVisitor *visitor)
{
visitor->VisitLPStr(pstr.m_length, pstr.m_chars, TSize);
}
template<class T, int TSize>
void Visit(StaticArray<T, TSize> &arr, IDataVisitor *visitor)
{
if (!visitor->VisitStaticCapacity(TSize))
return;
for (int i = 0; i < TSize; i++)
{
IDataVisitor *subscriptVisitor = visitor->EnterIndex(i);
if (subscriptVisitor)
{
Visit(arr.m_data[i], visitor);
subscriptVisitor->Exit();
}
}
}
template<class T>
void VisitField(const char *subscriptName, T &subscript, IDataVisitor *visitor)
{
IDataVisitor *subscriptVisitor = visitor->EnterNamed(subscriptName);
if (subscriptVisitor)
{
Visit(subscript, visitor);
subscriptVisitor->Exit();
}
}
template<class T>
void VisitArray(T *arr, int size, IDataVisitor *visitor)
{
if (!visitor->VisitStaticCapacity(static_cast<size_t>(size)))
return;
for (int i = 0; i < size; i++)
{
IDataVisitor *subscriptVisitor = visitor->EnterIndex(i);
if (subscriptVisitor)
{
Visit(arr[i], visitor);
subscriptVisitor->Exit();
}
}
}
bool VisitEnum(const EnumDef *enumDefs, size_t numDefs, int &v, IDataVisitor *visitor)
{
return visitor->VisitEnum(enumDefs, numDefs, v);
}
void Visit(houseType &house, std::vector<roomType> &rooms, IDataVisitor *visitor)
{
VisitField("version", house.version, visitor);
VisitField("unusedShort", house.unusedShort, visitor);
VisitField("timeStamp", house.timeStamp, visitor);
VisitField("flags", house.flags, visitor);
VisitField("initial", house.initial, visitor);
VisitField("banner", house.banner, visitor);
VisitField("trailer", house.trailer, visitor);
VisitField("highScores", house.highScores, visitor);
VisitField("savedGame", house.savedGame_Unused, visitor);
VisitField("hasGame", house.hasGame, visitor);
VisitField("unusedBoolean", house.unusedBoolean, visitor);
VisitField("firstRoom", house.firstRoom, visitor);
VisitField("nRooms", house.nRooms, visitor);
IDataVisitor *roomsSubscript = visitor->EnterNamed("rooms");
if (roomsSubscript)
{
size_t size = rooms.size();
roomsSubscript->VisitDynamicCapacity(size);
if (size > 0x7fff)
size = 0x7fff;
if (size < rooms.size())
rooms.resize(size);
else if (size > rooms.size())
{
roomType emptyRoom;
memset(&emptyRoom, 0, sizeof(emptyRoom));
rooms.reserve(size);
while (size > rooms.size())
rooms.push_back(emptyRoom);
}
house.nRooms = static_cast<int16_t>(size);
for (size_t i = 0; i < size; i++)
{
IDataVisitor *roomSubscript = roomsSubscript->EnterIndex(static_cast<int>(i));
if (roomSubscript)
{
Visit(rooms[i], roomSubscript);
roomSubscript->Exit();
}
}
roomsSubscript->Exit();
}
}
void Visit(blowerType &v, IDataVisitor *visitor)
{
VisitField("topLeft", v.topLeft, visitor);
VisitField("distance", v.distance, visitor);
VisitField("initial", v.initial, visitor);
VisitField("state", v.state, visitor);
VisitField("vector", v.vector, visitor);
VisitField("tall", v.tall, visitor);
}
void Visit(furnitureType &v, IDataVisitor *visitor)
{
VisitField("bounds", v.bounds, visitor);
VisitField("pict", v.pict, visitor);
}
void Visit(bonusType &v, IDataVisitor *visitor)
{
VisitField("topLeft", v.topLeft, visitor);
VisitField("length", v.length, visitor);
VisitField("points", v.points, visitor);
VisitField("state", v.state, visitor);
VisitField("initial", v.initial, visitor);
}
void Visit(transportType &v, IDataVisitor *visitor)
{
VisitField("topLeft", v.topLeft, visitor);
VisitField("tall", v.tall, visitor);
VisitField("where", v.where, visitor);
VisitField("who", v.who, visitor);
VisitField("wide", v.wide, visitor);
}
void Visit(switchType &v, IDataVisitor *visitor)
{
VisitField("topLeft", v.topLeft, visitor);
VisitField("delay", v.delay, visitor);
VisitField("where", v.where, visitor);
VisitField("who", v.who, visitor);
VisitField("type", v.type, visitor);
}
void Visit(lightType &v, IDataVisitor *visitor)
{
VisitField("topLeft", v.topLeft, visitor);
VisitField("length", v.length, visitor);
VisitField("byte0", v.byte0, visitor);
VisitField("byte1", v.byte1, visitor);
VisitField("initial", v.initial, visitor);
VisitField("state", v.state, visitor);
}
void Visit(applianceType &v, IDataVisitor *visitor)
{
VisitField("topLeft", v.topLeft, visitor);
VisitField("height", v.height, visitor);
VisitField("byte0", v.byte0, visitor);
VisitField("delay", v.delay, visitor);
VisitField("initial", v.initial, visitor);
VisitField("state", v.state, visitor);
}
void Visit(enemyType &v, IDataVisitor *visitor)
{
VisitField("topLeft", v.topLeft, visitor);
VisitField("length", v.length, visitor);
VisitField("delay", v.delay, visitor);
VisitField("byte0", v.byte0, visitor);
VisitField("initial", v.initial, visitor);
VisitField("state", v.state, visitor);
}
void Visit(clutterType &v, IDataVisitor *visitor)
{
VisitField("bounds", v.bounds, visitor);
VisitField("pict", v.pict, visitor);
}
void Visit(objectType &v, IDataVisitor *visitor)
{
IDataVisitor *whatVisitor = visitor->EnterNamed("what");
if (whatVisitor)
{
int ev = v.what;
if (!VisitEnum(g_objectTypeEnum, g_objectTypeEnumSize, ev, visitor))
Visit(v.what, visitor);
whatVisitor->Exit();
}
switch (static_cast<int16_t>(v.what))
{
default:
//VisitField("pad", v.data.pad, visitor);
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:
Visit(v.data.a, visitor);
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:
Visit(v.data.b, visitor);
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:
Visit(v.data.c, visitor);
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:
Visit(v.data.d, visitor);
break;
case kLightSwitch:
case kMachineSwitch:
case kThermostat:
case kPowerSwitch:
case kKnifeSwitch:
case kInvisSwitch:
case kTrigger:
case kLgTrigger:
case kSoundTrigger:
Visit(v.data.e, visitor);
break;
case kCeilingLight:
case kLightBulb:
case kTableLamp:
case kHipLamp:
case kDecoLamp:
case kFlourescent:
case kTrackLight:
case kInvisLight:
Visit(v.data.f, visitor);
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:
Visit(v.data.g, visitor);
break;
case kBalloon:
case kCopterLf:
case kCopterRt:
case kDartLf:
case kDartRt:
case kBall:
case kDrip:
case kFish:
case kCobweb:
Visit(v.data.h, visitor);
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:
Visit(v.data.i, visitor);
break;
}
}
void Visit(roomType &v, IDataVisitor *visitor)
{
VisitField("name", v.name, visitor);
VisitField("bounds", v.bounds, visitor);
VisitField("leftStart", v.leftStart, visitor);
VisitField("rightStart", v.rightStart, visitor);
VisitField("unusedByte", v.unusedByte, visitor);
VisitField("visited", v.visited, visitor);
VisitField("background", v.background, visitor);
VisitField("tiles", v.tiles, visitor);
VisitField("floor", v.floor, visitor);
VisitField("suite", v.suite, visitor);
VisitField("openings", v.openings, visitor);
VisitField("numObjects", v.numObjects, visitor);
VisitField("objects", v.objects, visitor);
}
void Visit(scoresType &v, IDataVisitor *visitor)
{
VisitField("banner", v.banner, visitor);
VisitField("names", v.names, visitor);
VisitField("scores", v.scores, visitor);
VisitField("timeStamps", v.timeStamps, visitor);
VisitField("levels", v.levels, visitor);
}
void Visit(gameType &v, IDataVisitor *visitor)
{
VisitField("version", v.version, visitor);
VisitField("starsLeft", v.wasStarsLeft, visitor);
VisitField("timeStamp", v.timeStamp, visitor);
VisitField("where", v.where, visitor);
VisitField("score", v.score, visitor);
VisitField("unusedLong", v.unusedLong, visitor);
VisitField("unusedLong2", v.unusedLong2, visitor);
VisitField("energy", v.energy, visitor);
VisitField("bands", v.bands, visitor);
VisitField("roomNumber", v.roomNumber, visitor);
VisitField("gliderState", v.gliderState, visitor);
VisitField("numGliders", v.numGliders, visitor);
VisitField("foil", v.foil, visitor);
VisitField("unusedShort", v.unusedShort, visitor);
VisitField("facing", v.facing, visitor);
VisitField("showFoil", v.showFoil, visitor);
}
void Visit(Bool8 &v, IDataVisitor *visitor)
{
visitor->VisitBool8(v.m_value);
}
void Visit(uint8_t &v, IDataVisitor *visitor)
{
visitor->VisitByte(v);
}
void Visit(BEInt16_t &v, IDataVisitor *visitor)
{
int16_t vi = v;
visitor->VisitInt16(vi);
v = vi;
}
void Visit(BEInt32_t &v, IDataVisitor *visitor)
{
int32_t vi = v;
visitor->VisitInt32(vi);
v = vi;
}
void Visit(BEUInt32_t &v, IDataVisitor *visitor)
{
uint32_t vi = v;
visitor->VisitUInt32(vi);
v = vi;
}
void Visit(BEPoint &v, IDataVisitor *visitor)
{
VisitField("h", v.h, visitor);
VisitField("v", v.v, visitor);
}
void Visit(BERect &v, IDataVisitor *visitor)
{
VisitField("top", v.top, visitor);
VisitField("left", v.left, visitor);
VisitField("bottom", v.bottom, visitor);
VisitField("right", v.right, visitor);
}
int PrintUsage()
{
fprintf(stderr, "Usage:\n");
fprintf(stderr, "HouseTool decompile <house.gpd> <out.txt>\n");
fprintf(stderr, "HouseTool patch <house.gpd> <data def> <replacement value>\n");
return -1;
}
bool ReadHouseFromPath(const char *path, houseType &house, std::vector<roomType> &rooms)
{
FILE *f = fopen_utf8(path, "rb");
if (!f)
return false;
if (fread(&house, 1, houseType::kBinaryDataSize, f) != houseType::kBinaryDataSize)
{
fclose(f);
return false;
}
int16_t nRooms = house.nRooms;
if (nRooms < 0)
{
fclose(f);
return false;
}
rooms.resize(nRooms);
if (nRooms > 0)
{
if (fread(&rooms[0], 1, sizeof(roomType) * nRooms, f) != sizeof(roomType) * nRooms)
{
rooms.clear();
fclose(f);
return false;
}
}
fclose(f);
return true;
}
class PatchVisitor final : public IDataVisitor
{
public:
PatchVisitor(const char *scope, const char *replacementValue);
IDataVisitor *EnterIndex(int index) override;
IDataVisitor *EnterNamed(const char *name) override;
void Exit() override;
void VisitLPStr(uint8_t &length, uint8_t *chars, int capacity) override;
void VisitInt32(int32_t &v) override;
void VisitUInt32(uint32_t &v) override;
void VisitInt16(int16_t &v) override;
void VisitBool8(uint8_t &v) override;
void VisitByte(uint8_t &v) override;
bool VisitEnum(const EnumDef *enumDefs, size_t numDefs, int &v) override;
bool VisitStaticCapacity(size_t size) override;
void VisitDynamicCapacity(size_t &size) override;
bool PatchedOK() const;
private:
static bool DecodeInteger(const std::string &scopeStr, size_t startPos, int &outInteger, size_t &outEndPos);
static bool DecodeQuotedString(const std::string &scopeStr, size_t startPos, std::string &outStr, size_t &outEndPos);
std::string m_scope;
std::string m_nextScope;
int m_scopeInteger;
bool m_nextScopeIsInteger;
const char *m_replacementValue;
void EnterScope();
bool TryEnterScope();
bool m_isDone;
bool m_haveScope;
bool m_patchedOK;
};
PatchVisitor::PatchVisitor(const char *scope, const char *replacementValue)
: m_scopeInteger(0)
, m_nextScopeIsInteger(false)
, m_replacementValue(replacementValue)
, m_nextScope(scope)
, m_isDone(false)
, m_haveScope(true)
, m_patchedOK(false)
{
EnterScope();
}
IDataVisitor *PatchVisitor::EnterIndex(int index)
{
if (m_isDone || !m_haveScope || !m_nextScopeIsInteger)
return nullptr;
if (m_scopeInteger == index)
{
EnterScope();
return this;
}
return nullptr;
}
IDataVisitor *PatchVisitor::EnterNamed(const char *name)
{
if (m_isDone || !m_haveScope || m_nextScopeIsInteger)
return nullptr;
if (m_scope == name)
{
EnterScope();
return this;
}
return nullptr;
}
void PatchVisitor::Exit()
{
m_isDone = true;
}
void PatchVisitor::VisitLPStr(uint8_t &length, uint8_t *chars, int capacity)
{
if (m_isDone || m_haveScope)
return;
const uint8_t *replacementUTF8 = reinterpret_cast<const uint8_t *>(m_replacementValue);
size_t rLen = strlen(m_replacementValue);
length = 0;
while (rLen > 0)
{
uint32_t codePoint = 0;
size_t charsDigested = 0;
if (!GpUnicode::UTF8::Decode(replacementUTF8, rLen, charsDigested, codePoint))
break;
rLen -= charsDigested;
replacementUTF8 += charsDigested;
uint8_t macRomanChar = 0;
if (codePoint > 0xffff)
macRomanChar = '?';
else
{
if (!MacRoman::FromUnicode(macRomanChar, static_cast<uint16_t>(codePoint)))
macRomanChar = '?';
}
chars[length++] = macRomanChar;
if (length == capacity)
break;
}
for (int i = length; i < capacity; i++)
chars[i] = 0;
m_patchedOK = true;
m_isDone = true;
}
void PatchVisitor::VisitInt32(int32_t &v)
{
if (m_isDone || m_haveScope)
return;
int integer = 0;
if (sscanf(m_replacementValue, "%i", &integer) == 1)
{
m_patchedOK = true;
m_isDone = true;
v = integer;
}
}
void PatchVisitor::VisitUInt32(uint32_t &v)
{
if (m_isDone || m_haveScope)
return;
unsigned int integer = 0;
if (sscanf(m_replacementValue, "%u", &integer) == 1)
{
m_patchedOK = true;
m_isDone = true;
v = integer;
}
}
void PatchVisitor::VisitInt16(int16_t &v)
{
if (m_isDone || m_haveScope)
return;
int integer = 0;
if (sscanf(m_replacementValue, "%i", &integer) == 1)
{
m_patchedOK = true;
m_isDone = true;
v = integer;
}
}
void PatchVisitor::VisitBool8(uint8_t &v)
{
if (m_isDone || m_haveScope)
return;
if (!strcmp(m_replacementValue, "true"))
{
m_patchedOK = true;
m_isDone = true;
v = 1;
}
else if (!strcmp(m_replacementValue, "false"))
{
m_patchedOK = true;
m_isDone = true;
v = 0;
}
else
{
int integer = 0;
if (sscanf(m_replacementValue, "%i", &integer) == 1)
{
m_patchedOK = true;
m_isDone = true;
v = integer;
}
}
}
void PatchVisitor::VisitByte(uint8_t &v)
{
if (m_isDone || m_haveScope)
return;
int integer = 0;
if (sscanf(m_replacementValue, "%i", &integer) == 1)
{
m_patchedOK = true;
m_isDone = true;
v = integer;
}
}
bool PatchVisitor::VisitEnum(const EnumDef *enumDefs, size_t numDefs, int &v)
{
if (m_isDone || m_haveScope)
return false;
for (size_t i = 0; i < numDefs; i++)
{
if (!strcmp(enumDefs[i].m_name, m_replacementValue))
{
m_patchedOK = true;
m_isDone = true;
v = enumDefs[i].m_value;
return true;
}
}
return false;
}
bool PatchVisitor::VisitStaticCapacity(size_t size)
{
return true;
}
void PatchVisitor::VisitDynamicCapacity(size_t &size)
{
}
bool PatchVisitor::PatchedOK() const
{
return m_patchedOK;
}
bool PatchVisitor::DecodeInteger(const std::string &scopeStr, size_t startPos, int &outInteger, size_t &outEndPos)
{
size_t len = scopeStr.length();
if (startPos >= len)
return false;
bool isNegative = false;
char firstChar = scopeStr[startPos];
if (firstChar == '-')
{
isNegative = true;
startPos++;
if (startPos >= len)
return false;
}
int resultInteger = 0;
for (size_t i = startPos; i < len; i++)
{
char decChar = scopeStr[i];
if (decChar >= '0' && decChar <= '9')
{
int digit = decChar - '0';
if (isNegative)
resultInteger = resultInteger * 10 - digit;
else
resultInteger = resultInteger * 10 + digit;
}
else
{
if (i == startPos)
return false;
outInteger = resultInteger;
outEndPos = i;
return true;
}
}
outInteger = resultInteger;
outEndPos = len;
return true;
}
bool PatchVisitor::DecodeQuotedString(const std::string &scopeStr, size_t startPos, std::string &outString, size_t &outEndPos)
{
size_t len = scopeStr.length();
if (startPos >= len || scopeStr[startPos] != '\"')
return false;
std::vector<char> decoded;
for (size_t i = startPos + 1; i < len; i++)
{
char decChar = scopeStr[i];
if (decChar == '\"')
{
if (decoded.size() == 0)
outString = std::string();
else
outString = std::string(&decoded[0], decoded.size());
outEndPos = i + 1;
return true;
}
if (decChar == '\\')
{
i++;
if (i == len)
return false;
char escChar = scopeStr[i];
if (escChar == '\"')
decoded.push_back('\"');
else if (escChar == '\\')
decoded.push_back('\\');
else if (escChar == '/')
decoded.push_back('/');
else if (escChar == 'b')
decoded.push_back('\b');
else if (escChar == 'f')
decoded.push_back('\f');
else if (escChar == 'n')
decoded.push_back('\n');
else if (escChar == 'r')
decoded.push_back('\r');
else if (escChar == 't')
decoded.push_back('\t');
else if (escChar == 'u')
{
int nibbles[4];
for (int ni = 0; ni < 4; ni++)
{
i++;
if (i == len)
return false;
char nibbleChar = scopeStr[i];
if (nibbleChar >= '0' && nibbleChar <= '9')
nibbles[ni] = nibbleChar - '0';
else if (nibbleChar >= 'A' && nibbleChar <= 'F')
nibbles[ni] = nibbleChar - 'A' + 0xa;
else if (nibbleChar >= 'a' && nibbleChar <= 'f')
nibbles[ni] = nibbleChar - 'a' + 0xa;
else
return false;
}
const uint16_t unicodeCodePoint = (nibbles[0] << 12) + (nibbles[1] << 8) + (nibbles[2] << 4) + nibbles[3];
uint8_t encoded[GpUnicode::UTF8::kMaxEncodedBytes];
size_t emitted = 0;
GpUnicode::UTF8::Encode(encoded, emitted, unicodeCodePoint);
for (size_t ei = 0; ei < emitted; ei++)
decoded.push_back(static_cast<char>(encoded[ei]));
}
}
else
{
if (decChar < ' ' || decChar > '~')
return false;
decoded.push_back(decChar);
}
}
outEndPos = len;
return false;
}
void PatchVisitor::EnterScope()
{
if (!m_haveScope)
{
m_isDone = true;
return;
}
bool shouldHaveScope = (m_nextScope.size() > 0);
if (!shouldHaveScope)
{
m_haveScope = false;
return;
}
if (!TryEnterScope())
{
m_haveScope = false;
m_isDone = true;
}
}
bool PatchVisitor::TryEnterScope()
{
if (m_nextScope.size() == 0)
return false;
if (m_nextScope[0] == '.')
{
size_t lastFieldChar = 1;
while (lastFieldChar < m_nextScope.size())
{
char c = m_nextScope[lastFieldChar];
if ((c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9')
|| c == '_')
lastFieldChar++;
else
break;
}
if (lastFieldChar == 1)
return false;
m_nextScopeIsInteger = false;
m_scope = m_nextScope.substr(1, lastFieldChar - 1);
m_nextScope = m_nextScope.substr(lastFieldChar);
return true;
}
if (m_nextScope[0] == '[')
{
if (m_nextScope.size() == 1)
return false;
if (m_nextScope[1] == '\"')
{
std::string quotedStr;
size_t endPos = 0;
if (!DecodeQuotedString(m_nextScope, 1, quotedStr, endPos))
return false;
if (endPos >= m_nextScope.size())
return false;
if (m_nextScope[endPos] != ']')
return false;
m_nextScopeIsInteger = false;
m_scope = quotedStr;
m_nextScope = m_nextScope.substr(endPos + 1);
return true;
}
else
{
int integer = 0;
size_t endPos = 0;
if (!DecodeInteger(m_nextScope, 1, integer, endPos))
return false;
if (endPos >= m_nextScope.size())
return false;
if (m_nextScope[endPos] != ']')
return false;
m_nextScopeIsInteger = true;
m_scope = "";
m_scopeInteger = integer;
m_nextScope = m_nextScope.substr(endPos + 1);
return true;
}
}
return false;
}
int PatchMain(int argc, const char **argv)
{
if (argc != 5)
return PrintUsage();
houseType house;
std::vector<roomType> rooms;
if (!ReadHouseFromPath(argv[2], house, rooms))
{
fprintf(stderr, "Failed to read house\n");
return -1;
}
PatchVisitor patcher(argv[3], argv[4]);
Visit(house, rooms, &patcher);
if (!patcher.PatchedOK())
fprintf(stderr, "Patch failed\n");
else
{
FILE *houseOutF = fopen_utf8(argv[2], "wb");
if (!houseOutF)
{
fprintf(stderr, "Failed to open output house\n");
return -1;
}
fwrite(&house, 1, houseType::kBinaryDataSize, houseOutF);
if (rooms.size() > 0)
fwrite(&rooms[0], 1, sizeof(roomType) * rooms.size(), houseOutF);
fclose(houseOutF);
}
return 0;
}
class DecompileVisitor final : public IDataVisitor
{
public:
explicit DecompileVisitor(FILE *f);
IDataVisitor *EnterIndex(int index) override;
IDataVisitor *EnterNamed(const char *name) override;
void Exit() override;
void VisitLPStr(uint8_t &length, uint8_t *chars, int capacity) override;
void VisitInt32(int32_t &v) override;
void VisitUInt32(uint32_t &v) override;
void VisitInt16(int16_t &v) override;
void VisitBool8(uint8_t &v) override;
void VisitByte(uint8_t &v) override;
bool VisitEnum(const EnumDef *enumDefs, size_t numDefs, int &v) override;
bool VisitStaticCapacity(size_t size) override;
void VisitDynamicCapacity(size_t &size) override;
private:
enum ScopeType
{
ScopeType_Array,
ScopeType_Map,
ScopeType_Unknown,
};
FILE *m_f;
int m_indentLevel;
bool m_isInNamed;
bool m_isInArray;
std::vector<ScopeType> m_scopes;
ScopeType GetTopScope();
void SetTopScope(ScopeType scopeType);
void PopScope();
void PushScope();
void WriteIndent();
};
DecompileVisitor::DecompileVisitor(FILE *f)
: m_f(f)
{
m_scopes.push_back(ScopeType_Unknown);
}
IDataVisitor *DecompileVisitor::EnterIndex(int index)
{
ScopeType topScope = GetTopScope();
if (topScope == ScopeType_Array)
{
fprintf(m_f, ",\n");
WriteIndent();
}
else
{
fprintf(m_f, "[\n");
WriteIndent();
SetTopScope(ScopeType_Array);
}
PushScope();
fprintf(m_f, "[%i] = ", index);
return this;
}
IDataVisitor *DecompileVisitor::EnterNamed(const char *name)
{
ScopeType topScope = GetTopScope();
if (topScope == ScopeType_Map)
{
fprintf(m_f, ",\n");
WriteIndent();
}
else
{
fprintf(m_f, "{\n");
WriteIndent();
SetTopScope(ScopeType_Map);
}
PushScope();
fprintf(m_f, "%s = ", name);
return this;
}
void DecompileVisitor::Exit()
{
ScopeType topScope = GetTopScope();
if (topScope == ScopeType_Map)
{
fprintf(m_f, "\n");
PopScope();
WriteIndent();
fprintf(m_f, "}");
}
else if (topScope == ScopeType_Array)
{
fprintf(m_f, "\n");
PopScope();
WriteIndent();
fprintf(m_f, "]");
}
else
PopScope();
}
void DecompileVisitor::VisitLPStr(uint8_t &length, uint8_t *chars, int capacity)
{
fputc('\"', m_f);
for (size_t i = 0; i < length; i++)
{
uint16_t unicodeChar = MacRoman::ToUnicode(chars[i]);
if (unicodeChar == '\\')
fputs("\\\\", m_f);
else if (unicodeChar == '\"')
fputs("\\\"", m_f);
else if (unicodeChar == '\r')
fputs("\\r", m_f);
else if (unicodeChar == '\n')
fputs("\\n", m_f);
else if (unicodeChar < 32 || unicodeChar > 126)
fprintf(m_f, "\\u%04x", unicodeChar);
else
{
uint8_t ubyte = static_cast<uint8_t>(unicodeChar);
fwrite(&ubyte, 1, 1, m_f);
}
}
fputc('\"', m_f);
}
void DecompileVisitor::VisitInt32(int32_t &v)
{
fprintf(m_f, "%i", static_cast<int>(v));
}
void DecompileVisitor::VisitUInt32(uint32_t &v)
{
fprintf(m_f, "%u", static_cast<unsigned int>(v));
}
void DecompileVisitor::VisitInt16(int16_t &v)
{
fprintf(m_f, "%i", static_cast<int>(v));
}
void DecompileVisitor::VisitBool8(uint8_t &v)
{
if (v == 0)
fprintf(m_f, "false");
else if (v == 1)
fprintf(m_f, "true");
else
fprintf(m_f, "%i", static_cast<int>(v));
}
void DecompileVisitor::VisitByte(uint8_t &v)
{
fprintf(m_f, "%i", static_cast<int>(v));
}
bool DecompileVisitor::VisitEnum(const EnumDef *enumDefs, size_t numDefs, int &v)
{
for (size_t i = 0; i < numDefs; i++)
{
if (enumDefs[i].m_value == v)
{
fprintf(m_f, "\"%s\"", enumDefs[i].m_name);
return true;
}
}
return false;
}
bool DecompileVisitor::VisitStaticCapacity(size_t size)
{
return true;
}
void DecompileVisitor::VisitDynamicCapacity(size_t &size)
{
}
DecompileVisitor::ScopeType DecompileVisitor::GetTopScope()
{
return m_scopes[m_scopes.size() - 1];
}
void DecompileVisitor::SetTopScope(ScopeType scopeType)
{
m_scopes[m_scopes.size() - 1] = scopeType;
}
void DecompileVisitor::PopScope()
{
m_scopes.pop_back();
}
void DecompileVisitor::PushScope()
{
m_scopes.push_back(ScopeType_Unknown);
}
void DecompileVisitor::WriteIndent()
{
for (size_t i = 0; i < m_scopes.size(); i++)
fputc('\t', m_f);
}
int DecompileMain(int argc, const char **argv)
{
if (argc != 4)
return PrintUsage();
houseType house;
std::vector<roomType> rooms;
if (!ReadHouseFromPath(argv[2], house, rooms))
{
fprintf(stderr, "Failed to read house\n");
return -1;
}
FILE *outF = fopen_utf8(argv[3], "wb");
if (!outF)
{
fprintf(stderr, "Failed to open output house\n");
return -1;
}
DecompileVisitor decompiler(outF);
Visit(house, rooms, &decompiler);
fclose(outF);
return 0;
}
int toolMain(int argc, const char **argv)
{
static const size_t houseSize = sizeof(houseType);
static_assert(sizeof(houseType) >= houseType::kBinaryDataSize, "House type is too small");
if (argc < 3)
return PrintUsage();
std::string operation = argv[1];
if (operation == "patch")
return PatchMain(argc, argv);
if (operation == "decompile")
return DecompileMain(argc, argv);
return 0;
}