Skip to content

Instantly share code, notes, and snippets.

@vfig
Created February 6, 2019 17:17
Show Gist options
  • Save vfig/94b353fa41e5e38ac912df91ffbb07e8 to your computer and use it in GitHub Desktop.
Save vfig/94b353fa41e5e38ac912df91ffbb07e8 to your computer and use it in GitHub Desktop.
Sunless Skies qualities/events parser (rough and ready)
# Use Unity Asset Extractor to save the qualities and events assets to "qualities.dat" and "events.dat" respectively.
# Then run this script to explore the data.
#
# Needs Python 3.6
import struct, textwrap
# For indenting a string:
def indent(s):
return '\n'.join((' ' + l) for l in str(s).splitlines())
# For saner hexifying
def hexify(b):
s = b.hex()
return " ".join([s[i:i+2] for i in range(0, len(s), 2)])
#-----------------------------
class BinaryReader:
def __init__(self, data):
self.data = data[:]
self.offset = 0
def debug_dump(self, description=''):
size = 16
pre_ofs = max(0, self.offset-size)
post_ofs = min(self.offset+size, len(self.data))
b = self.data[pre_ofs:post_ofs]
print()
print(f":: {description} @ 0x{self.offset:08x}:")
print(hexify(b))
print((" " * (self.offset - pre_ofs)) + "^^ ")
def Seek(self, offset):
self.offset = offset
def _read_single_fmt(fmt, name=''):
def _read(self):
d_len = struct.calcsize(fmt)
d = struct.unpack_from(fmt, self.data, self.offset)
self.offset += d_len
return d[0]
if name:
_read.__name__ = ('Read' + name)
return _read
ReadUint8 = _read_single_fmt('@B', 'Uint8')
ReadUint16 = _read_single_fmt('@H', 'Uint16')
ReadUint32 = _read_single_fmt('@L', 'Uint32')
ReadUint64 = _read_single_fmt('@Q', 'Uint64')
ReadInt8 = _read_single_fmt('@b', 'Int8')
ReadInt16 = _read_single_fmt('@h', 'Int16')
ReadInt32 = _read_single_fmt('@l', 'Int32')
ReadInt64 = _read_single_fmt('@q', 'Int64')
ReadSingle = _read_single_fmt('@f', 'Single')
ReadDouble = _read_single_fmt('@d', 'Double')
def ReadBoolean(self):
return (self.ReadUint8() != 0)
def ReadString(self):
_start = self.offset
d_len = self._ReadVarInt()
d = self._ReadBytes(d_len)
try:
return d.decode('utf8')
except UnicodeDecodeError:
# For wrapping printed output
wrapper = textwrap.TextWrapper()
wrapper.width = 80
wrapper.initial_indent = ' '
wrapper.subsequent_indent = ' '
wrapper.break_on_hyphens = False
print(f"Cannot decode string at offset 0x{_start:x}:")
print(wrapper.fill(hexify(d)))
raise
def _ReadVarInt(self):
accum = 0
shift = 0
while True:
b = self.ReadUint8()
last_byte = ((b & 0x80) == 0)
accum |= ((b & 0x7F) << shift)
shift += 7
if last_byte:
break
return accum
def _ReadBytes(self, n):
d_len = n
d = self.data[self.offset:self.offset+n]
self.offset += d_len
return d
class Area:
pass
class BinarySerializer_Area:
@classmethod
def Deserialize(cls, bs):
area = Area()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
area.Description = bs.ReadString()
if bs.ReadBoolean():
area.ImageName = bs.ReadString()
if bs.ReadBoolean():
area.World = BinarySerializer_World.Deserialize(bs)
area.MarketAccessPermitted = bs.ReadBoolean()
if bs.ReadBoolean():
area.MoveMessage = bs.ReadString()
area.HideName = bs.ReadBoolean()
area.RandomPostcard = bs.ReadBoolean()
area.MapX = bs.ReadInt32()
area.MapY = bs.ReadInt32()
if bs.ReadBoolean():
area.UnlocksWithQuality = BinarySerializer_Quality.Deserialize(bs)
area.ShowOps = bs.ReadBoolean()
area.PremiumSubRequired = bs.ReadBoolean()
if bs.ReadBoolean():
area.Name = bs.ReadString()
area.Id = bs.ReadInt32()
return area
@classmethod
def DeserializeCollection(cls, bs):
l = []
num = bs.ReadInt32()
for i in range(num):
l.append(cls.Deserialize(bs))
return l
class AspectQPossession:
pass
class BinarySerializer_AspectQPossession:
@classmethod
def Deserialize(cls, bs):
aspectQPossession = AspectQPossession()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
aspectQPossession.Quality = BinarySerializer_Quality.Deserialize(bs)
aspectQPossession.XP = bs.ReadInt32()
aspectQPossession.EffectiveLevelModifier = bs.ReadInt32()
if bs.ReadBoolean():
aspectQPossession.TargetQuality = BinarySerializer_Quality.Deserialize(bs)
if bs.ReadBoolean():
aspectQPossession.TargetLevel = bs.ReadInt32()
if bs.ReadBoolean():
aspectQPossession.CompletionMessage = bs.ReadString()
aspectQPossession.Level = bs.ReadInt32()
if bs.ReadBoolean():
aspectQPossession.AssociatedQuality = BinarySerializer_Quality.Deserialize(bs)
aspectQPossession.Id = bs.ReadInt32()
return aspectQPossession
class Branch:
def __init__(self):
self.SuccessEvent = None
self.DefaultEvent = None
self.RareDefaultEvent = None
self.RareDefaultEventChance = 0
self.RareSuccessEvent = None
self.RareSuccessEventChance = 0
self.ParentEvent = None
self.QualitiesRequired = []
self.Image = ''
self.Description = ''
self.OwnerName = ''
self.DateTimeCreated = 0 # DateTime
self.CurrencyCost = 0
self.Archived = False
self.RenameQualityCategory = 0
self.ButtonText = ''
self.Ordering = 0
self.Act = None
self.ActionCost = 0
self.Name = ''
self.Id = 0
def __str__(self):
lines = [f"Branch {self.Id}: \"{self.Name}\""]
for r in self.QualitiesRequired:
lines.append(f" requires: {r}")
if self.DefaultEvent:
lines.append(f" Default:\n{indent(self.DefaultEvent)}")
if self.RareDefaultEvent:
lines.append(f" RareDefault ({self.RareDefaultEventChance}):\n{indent(self.RareDefaultEvent)}")
if self.SuccessEvent:
lines.append(f" Success:\n{indent(self.SuccessEvent)}")
if self.RareSuccessEvent:
lines.append(f" RareSuccess ({self.RareSuccessEventChance}):\n{indent(self.RareSuccessEvent)}")
return '\n'.join(lines)
def __repr__(self):
return str(self)
class BinarySerializer_Branch:
@classmethod
def Deserialize(cls, bs):
branch = Branch()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
branch.SuccessEvent = BinarySerializer_Event.Deserialize(bs)
if bs.ReadBoolean():
branch.DefaultEvent = BinarySerializer_Event.Deserialize(bs)
if bs.ReadBoolean():
branch.RareDefaultEvent = BinarySerializer_Event.Deserialize(bs)
branch.RareDefaultEventChance = bs.ReadInt32()
if bs.ReadBoolean():
branch.RareSuccessEvent = BinarySerializer_Event.Deserialize(bs)
branch.RareSuccessEventChance = bs.ReadInt32()
if bs.ReadBoolean():
branch.ParentEvent = BinarySerializer_Event.Deserialize(bs)
if bs.ReadBoolean():
branch.QualitiesRequired = []
num = bs.ReadInt32()
for i in range(num):
branch.QualitiesRequired.append(BinarySerializer_BranchQRequirement.Deserialize(bs))
if bs.ReadBoolean():
branch.Image = bs.ReadString()
if bs.ReadBoolean():
branch.Description = bs.ReadString()
if bs.ReadBoolean():
branch.OwnerName = bs.ReadString()
branch.DateTimeCreated = BinarySerializer_DateTime.Deserialize(bs)
branch.CurrencyCost = bs.ReadInt32()
branch.Archived = bs.ReadBoolean()
if bs.ReadBoolean():
branch.RenameQualityCategory = BinarySerializer_Category.Deserialize(bs)
if bs.ReadBoolean():
branch.ButtonText = bs.ReadString()
branch.Ordering = bs.ReadInt32()
if bs.ReadBoolean():
branch.Act = BinarySerializer_Act.Deserialize(bs)
branch.ActionCost = bs.ReadInt32()
if bs.ReadBoolean():
branch.Name = bs.ReadString()
branch.Id = bs.ReadInt32()
return branch
class BranchQRequirement:
def __str__(self):
Quality = getattr(self, 'AssociatedQuality', None)
QualityId = getattr(Quality, 'Id', 0)
MinLevel = getattr(self, 'MinLevel', -1)
MaxLevel = getattr(self, 'MaxLevel', -1)
MinAdvanced = getattr(self, 'MinAdvanced', -1)
MaxAdvanced = getattr(self, 'MaxAdvanced', -1)
return f"Requires Quality {QualityId}, Min: {MinLevel}, Max: {MaxLevel}, MinAdvanced: {MinAdvanced}, MaxAdvanced: {MaxAdvanced}"
def __repr__(self):
return str(self)
class BinarySerializer_BranchQRequirement:
@classmethod
def Deserialize(cls, bs):
branchQRequirement = BranchQRequirement()
if not bs.ReadBoolean():
return null
if bs.ReadBoolean():
branchQRequirement.DifficultyLevel = bs.ReadInt32()
if bs.ReadBoolean():
branchQRequirement.DifficultyAdvanced = bs.ReadString()
branchQRequirement.VisibleWhenRequirementFailed = bs.ReadBoolean()
if bs.ReadBoolean():
branchQRequirement.CustomLockedMessage = bs.ReadString()
if bs.ReadBoolean():
branchQRequirement.CustomUnlockedMessage = bs.ReadString()
branchQRequirement.IsCostRequirement = bs.ReadBoolean()
if bs.ReadBoolean():
branchQRequirement.MinLevel = bs.ReadInt32()
if bs.ReadBoolean():
branchQRequirement.MaxLevel = bs.ReadInt32()
if bs.ReadBoolean():
branchQRequirement.MinAdvanced = bs.ReadString()
if bs.ReadBoolean():
branchQRequirement.MaxAdvanced = bs.ReadString()
if bs.ReadBoolean():
branchQRequirement.AssociatedQuality = BinarySerializer_Quality.Deserialize(bs)
branchQRequirement.Id = bs.ReadInt32()
return branchQRequirement
class BinarySerializer_Category:
@classmethod
def Deserialize(cls, bs):
category = 0 # Category.Unspecified
return bs.ReadInt32()
class BinarySerializer_DateTime:
@classmethod
def Deserialize(cls, bs):
return 0 # HACK: I don't know why this is not implemented, but it appears to be the case!
class Deck:
pass
class BinarySerializer_Deck:
@classmethod
def Deserialize(cls, bs):
deck = Deck()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
deck.World = BinarySerializer_World.Deserialize(bs)
if bs.ReadBoolean():
deck.Name = bs.ReadString()
if bs.ReadBoolean():
deck.ImageName = bs.ReadString()
deck.Ordering = bs.ReadInt32()
if bs.ReadBoolean():
deck.Description = bs.ReadString()
deck.Availability = BinarySerializer_Frequency.Deserialize(bs)
deck.DrawSize = bs.ReadInt32()
deck.MaxCards = bs.ReadInt32()
deck.Id = bs.ReadInt32()
return deck
class BinarySerializer_DifficultyTestType:
@classmethod
def Deserialize(self, bs):
difficultyTestType = 0 # DifficultyTestType.Broad
return bs.ReadInt32()
class Event:
def __init__(self):
self.Id = 0 # From Entity
self.Name = '' # from EntityWithName
self._moveToArea = None # Area
self._moveToDomicile = None # Domicile
self._switchToSetting = None # Setting
self._fatePointsChange = 0
self._bootyValue = 0
self.ChildBranches = []
self.ParentBranch = None # Branch
self.QualitiesAffected = []
self.QualitiesRequired = []
self.Image = ''
self.SecondImage = ''
self.Description = ''
self.Tag = ''
self.ExoticEffects = ''
self.Note = ''
self.ChallengeLevel = 0
self.UnclearedEditAt = None # DateTime?
self.LastEditedBy = None # User
self.Ordering = 0.0
self.ShowAsMessage = False
self.LivingStory = None # LivingStory
self.LinkToEvent = None # Event
self.Deck = None # Deck
self.Category = 0 # EventCategory
self.LimitedToArea = None # Area
self.World = None # World
self.Transient = False
self.Stickiness = 0
self.MoveToAreaId = 0
self.LogInJournalAgainstQuality = None # Quality
self.Setting = None # Setting
self.Urgency = None # Urgency
self.OwnerName = ''
self.DateTimeCreated = 0 # DateTime
self.Distribution = 0
self.Autofire = False
self.CanGoBack = False
def __str__(self):
lines = [f"[{self.Id}: {self.Name}]"]
for r in self.QualitiesRequired:
lines.append(f" requires: {r}")
for a in self.QualitiesAffected:
lines.append(f" affects: {a}")
if self.LinkToEvent:
lines.append(f" LINK TO: {self.LinkToEvent}")
for i, b in enumerate(self.ChildBranches):
lines.append(f"> {i}:\n{indent(b)}")
return '\n'.join(lines)
def __repr__(self):
return str(self)
class BinarySerializer_Event:
@classmethod
def Deserialize(cls, bs, debug=False):
if debug: bs.debug_dump('Event begins')
event = Event()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
event.ChildBranches = []
num = bs.ReadInt32()
for i in range(num):
event.ChildBranches.append(BinarySerializer_Branch.Deserialize(bs))
if bs.ReadBoolean():
event.ParentBranch = BinarySerializer_Branch.Deserialize(bs)
if bs.ReadBoolean():
event.QualitiesAffected = []
num2 = bs.ReadInt32()
for j in range(num2):
event.QualitiesAffected.append(BinarySerializer_EventQEffect.Deserialize(bs))
if bs.ReadBoolean():
event.QualitiesRequired = []
num3 = bs.ReadInt32()
for k in range(num3):
event.QualitiesRequired.append(BinarySerializer_EventQRequirement.Deserialize(bs))
if bs.ReadBoolean():
event.Image = bs.ReadString()
if bs.ReadBoolean():
event.SecondImage = bs.ReadString()
if bs.ReadBoolean():
event.Description = bs.ReadString()
if debug: print(f"Description: {event.Description}")
if bs.ReadBoolean():
event.Tag = bs.ReadString()
if bs.ReadBoolean():
event.ExoticEffects = bs.ReadString()
if bs.ReadBoolean():
event.Note = bs.ReadString()
event.ChallengeLevel = bs.ReadInt32()
if bs.ReadBoolean():
event.UnclearedEditAt = BinarySerializer_DateTime.Deserialize(bs)
if bs.ReadBoolean():
event.LastEditedBy = BinarySerializer_User.Deserialize(bs)
event.Ordering = bs.ReadSingle()
event.ShowAsMessage = bs.ReadBoolean()
if bs.ReadBoolean():
event.LivingStory = BinarySerializer_LivingStory.Deserialize(bs)
if bs.ReadBoolean():
event.LinkToEvent = cls.Deserialize(bs)
if bs.ReadBoolean():
event.Deck = BinarySerializer_Deck.Deserialize(bs)
event.Category = BinarySerializer_EventCategory.Deserialize(bs)
if bs.ReadBoolean():
event.LimitedToArea = BinarySerializer_Area.Deserialize(bs)
if bs.ReadBoolean():
event.World = BinarySerializer_World.Deserialize(bs)
event.Transient = bs.ReadBoolean()
event.Stickiness = bs.ReadInt32()
event.MoveToAreaId = bs.ReadInt32()
if bs.ReadBoolean():
event.MoveToArea = BinarySerializer_Area.Deserialize(bs)
if bs.ReadBoolean():
event.MoveToDomicile = BinarySerializer_Domicile.Deserialize(bs)
if bs.ReadBoolean():
event.SwitchToSetting = BinarySerializer_Setting.Deserialize(bs)
event.FatePointsChange = bs.ReadInt32()
event.BootyValue = bs.ReadInt32()
if bs.ReadBoolean():
event.LogInJournalAgainstQuality = BinarySerializer_Quality.Deserialize(bs)
if bs.ReadBoolean():
event.Setting = BinarySerializer_Setting.Deserialize(bs)
event.Urgency = BinarySerializer_Urgency.Deserialize(bs)
if bs.ReadBoolean():
event.Teaser = bs.ReadString()
if bs.ReadBoolean():
event.OwnerName = bs.ReadString()
if debug: print(f"OwnerName: {event.OwnerName}")
if debug: bs.debug_dump('Datetime here:')
event.DateTimeCreated = BinarySerializer_DateTime.Deserialize(bs)
event.Distribution = bs.ReadInt32()
event.Autofire = bs.ReadBoolean()
event.CanGoBack = bs.ReadBoolean()
if bs.ReadBoolean():
event.Name = bs.ReadString()
event.Id = bs.ReadInt32()
if debug: print(f"----- Deserialized Event {event.Id}")
return event
@classmethod
def DeserializeCollection(cls, bs):
l = []
num = bs.ReadInt32()
for i in range(num):
l.append(cls.Deserialize(bs))
return l
class BinarySerializer_EventCategory:
@classmethod
def Deserialize(cls, bs):
eventCategory = 0 #EventCategory.Unspecialised
return bs.ReadInt32()
class EventQEffect:
def __init__(self):
self.Priority = 0
self.ForceEquip = False
self.OnlyIfNoMoreThanAdvanced = ''
self.OnlyIfAtLeast = 0
self.OnlyIfNoMoreThan = 0
self.SetToExactlyAdvanced = ''
self.ChangeByAdvanced = ''
self.OnlyIfAtLeastAdvanced = ''
self.SetToExactly = 0
self.TargetQuality = None
self.TargetLevel = 0
self.CompletionMessage = ''
self.Level = 0
self.AssociatedQuality = None
self.Id = 0
def __str__(self):
bits = [f"{self.AssociatedQuality}:"]
if self.Level:
sign = ('' if (self.Level < 0) else '+')
bits.append(f" {sign}{self.Level}")
if self.SetToExactly:
bits.append(f" = {self.SetToExactly}")
if self.SetToExactlyAdvanced:
bits.append(f" = {self.SetToExactlyAdvanced}")
if self.OnlyIfAtLeast:
bits.append(f" (only if at least {self.OnlyIfAtLeast})")
if self.OnlyIfAtLeastAdvanced:
bits.append(f" (only if no more than {self.OnlyIfAtLeastAdvanced})")
if self.OnlyIfNoMoreThan:
bits.append(f" (only if no more than {self.OnlyIfNoMoreThan})")
if self.OnlyIfNoMoreThanAdvanced:
bits.append(f" (only if no more than {self.OnlyIfNoMoreThanAdvanced})")
if self.TargetQuality:
bits.append(f" (target: {self.TargetQuality} at level {self.TargetLevel})")
return "".join(bits)
def __repr__(self):
return str(self)
class BinarySerializer_EventQEffect:
@classmethod
def Deserialize(cls, bs):
eventQEffect = EventQEffect()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
eventQEffect.Priority = bs.ReadInt32()
eventQEffect.ForceEquip = bs.ReadBoolean()
if bs.ReadBoolean():
eventQEffect.OnlyIfNoMoreThanAdvanced = bs.ReadString()
if bs.ReadBoolean():
eventQEffect.OnlyIfAtLeast = bs.ReadInt32()
if bs.ReadBoolean():
eventQEffect.OnlyIfNoMoreThan = bs.ReadInt32()
if bs.ReadBoolean():
eventQEffect.SetToExactlyAdvanced = bs.ReadString()
if bs.ReadBoolean():
eventQEffect.ChangeByAdvanced = bs.ReadString()
if bs.ReadBoolean():
eventQEffect.OnlyIfAtLeastAdvanced = bs.ReadString()
if bs.ReadBoolean():
eventQEffect.SetToExactly = bs.ReadInt32()
if bs.ReadBoolean():
eventQEffect.TargetQuality = BinarySerializer_Quality.Deserialize(bs)
if bs.ReadBoolean():
eventQEffect.TargetLevel = bs.ReadInt32()
if bs.ReadBoolean():
eventQEffect.CompletionMessage = bs.ReadString()
eventQEffect.Level = bs.ReadInt32()
if bs.ReadBoolean():
eventQEffect.AssociatedQuality = BinarySerializer_Quality.Deserialize(bs)
eventQEffect.Id = bs.ReadInt32()
return eventQEffect
class EventQRequirement:
def __str__(self):
Quality = getattr(self, 'AssociatedQuality', None)
QualityId = getattr(Quality, 'Id', 0)
MinLevel = getattr(self, 'MinLevel', -1)
MaxLevel = getattr(self, 'MaxLevel', -1)
MinAdvanced = getattr(self, 'MinAdvanced', -1)
MaxAdvanced = getattr(self, 'MaxAdvanced', -1)
return f"Requires Quality {QualityId}, Min: {MinLevel}, Max: {MaxLevel}, MinAdvanced: {MinAdvanced}, MaxAdvanced: {MaxAdvanced}"
def __repr__(self):
return str(self)
class BinarySerializer_EventQRequirement:
@classmethod
def Deserialize(cls, bs):
eventQRequirement = EventQRequirement()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
eventQRequirement.MinLevel = bs.ReadInt32()
if bs.ReadBoolean():
eventQRequirement.MaxLevel = bs.ReadInt32()
if bs.ReadBoolean():
eventQRequirement.MinAdvanced = bs.ReadString()
if bs.ReadBoolean():
eventQRequirement.MaxAdvanced = bs.ReadString()
if bs.ReadBoolean():
eventQRequirement.AssociatedQuality = BinarySerializer_Quality.Deserialize(bs)
eventQRequirement.Id = bs.ReadInt32()
return eventQRequirement
class BinarySerializer_Frequency:
@classmethod
def Deserialize(cls, bs):
frequency = 0 # Frequency.Sometimes
return bs.ReadInt32()
class BinarySerializer_Genre:
@classmethod
def Deserialize(cls, bs):
genre = 0 # Genre.NotYetSpecified=
return bs.ReadInt32()
class BinarySerializer_LoggedInVia:
@classmethod
def Deserialize(cls, bs):
loggedInVia = 0 # LoggedInVia.Web
return bs.ReadInt32()
class BinarySerializer_Nature:
@classmethod
def Deserialize(cls, bs):
nature = 0 # Nature.Unspecified
return bs.ReadInt32()
class BinarySerializer_PrivilegeLevel:
@classmethod
def Deserialize(cls, bs):
privilegeLevel = 0 # PrivilegeLevel.User
return bs.ReadInt32()
class BinarySerializer_PublishState:
@classmethod
def Deserialize(cls, bs):
publishState = 0 # PublishState.UnPublished
return bs.ReadInt32()
class QEnhancement:
def __init__(self):
self.Level = 0
self.AssociatedQuality = None
self.Id = 0
def __str__(self):
sign = ('' if (self.Level < 0) else '+')
return f"Enhancement: {self.AssociatedQuality} {sign}{self.Level}"
def __repr__(self):
return __str__(self)
class BinarySerializer_QEnhancement:
@classmethod
def Deserialize(cls, bs):
qEnhancement = QEnhancement()
if not bs.ReadBoolean():
return None
qEnhancement.Level = bs.ReadInt32()
if bs.ReadBoolean():
qEnhancement.AssociatedQuality = BinarySerializer_Quality.Deserialize(bs)
qEnhancement.Id = bs.ReadInt32()
return qEnhancement
# public enum QualityAllowedOn
# {
# Unspecified,
# Character,
# QualityAndCharacter,
# Event,
# Branch,
# Persona,
# User
# }
# public enum DifficultyTestType
# {
# Broad,
# Narrow
# }
# public enum Nature
# {
# Unspecified,
# Status,
# Thing
# }
# public enum Category
# {
# Academic = 16000,
# Accomplishment = 5050,
# Acquaintance = 5025,
# Advantage = 160,
# Affiliation = 13000,
# Ambition = 7000,
# Avatar = 39000,
# BasicAbility = 1000,
# Boots = 105,
# Cartography = 17000,
# Circumstance = 37000,
# Clothing = 107,
# Club = 12000,
# Companion = 106,
# ConstantCompanion = 11000,
# Contacts = 6000,
# Contraband = 18000,
# Curiosity = 150,
# Currency = 1,
# Destiny = 60000,
# Document = 170,
# Dreams = 5002,
# Elder = 19000,
# Gloves = 104,
# Goods = 200,
# GreatGame = 70001,
# Hat = 103,
# HomeComfort = 15000,
# Infernal = 20000,
# Influence = 21000,
# Intrigue = 5001,
# Key = 45000,
# Knowledge = 50000,
# Legal = 29000,
# Literature = 22000,
# Lodgings = 22500,
# Luminosity = 23000,
# MajorLateral = 34000,
# Menace = 5500,
# MinorLateral = 36000,
# Modfier = 70000,
# Mysteries = 24000,
# Nostalgia = 25000,
# Objective = 40000,
# Profession = 3000,
# Progress = 5200,
# Quest = 35000,
# Quirk = 5004,
# RagTrade = 26000,
# Ratness = 27000,
# Reputation = 5003,
# Route = 8000,
# Rubbery = 32000,
# Rumour = 28000,
# Sustenance = 70003,
# Seasonal = 9000,
# Ship = 10000,
# SidebarAbility = 33000,
# SpecificAbility = 2000,
# Story = 5000,
# Timer = 13999,
# Transportation = 14000,
# Unspecified = 0,
# Venture = 5100,
# Weapon = 101,
# WildWords = 30000,
# Wines = 31000,
# Hidden = 6661,
# Randomizer = 6662,
# ZeeTreasures = 70002,
# Bridge = 70004,
# Plating = 70005,
# Auxiliary = 70006,
# SmallWeapon = 70007,
# LargeWeapon = 70008,
# Scout = 70009
# }
class Quality:
def __init__(self):
self.Id = 0 # From Entity
self.Name = '' # from EntityWithName
self.QualitiesPossessedList = []
self.RelationshipCapable = False
self.PluralName = ''
self.OwnerName = ''
self.Description = ''
self.Image = ''
self.Notes = ''
self.Tag = ''
self.Cap = 0 # int?
self.CapAdvanced = ''
self.HimbleLevel = 0
self.UsePyramidNumbers = False
self.PyramidNumberIncreaseLimit = 0
self.AvailableAt = ''
self.PreventNaming = False
self.CssClasses = ''
self.QEffectPriority = 0
self.QEffectMinimalLimit = 0 # int?
self.World = None # World
self.Ordering = 0
self.IsSlot = False
self.LimitedToArea = None # Area
self.AssignToSlot = None # Quality
self.ParentQuality = None # Quality
self.Persistent = False
self.Visible = False
self.Enhancements = []
self.EnhancementsDescription = ''
self.SecondChanceQuality = None # Quality
self.UseEvent = None # Event
self.DifficultyTestType = 0 # DifficultyTestType
self.DifficultyScaler = 0
self.AllowedOn = 0 # QualityAllowedOn
self.Nature = 0 # Nature
self.Category = 0 # Category
self.LevelDescriptionText = ''
self.ChangeDescriptionText = ''
self.DescendingChangeDescriptionText = ''
self.LevelImageText = ''
self.VariableDescriptionText = ''
def __str__(self):
return f"[{self.Id}: {self.Name}]"
def __repr__(self):
return str(self)
class BinarySerializer_Quality:
@classmethod
def Deserialize(cls, bs, debug=False):
if debug: bs.debug_dump('Quality begins')
quality = Quality()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
quality.QualitiesPossessedList = []
num = bs.ReadInt32()
for i in range(num):
quality.QualitiesPossessedList.append(BinarySerializer_AspectQPossession.Deserialize(bs))
quality.RelationshipCapable = bs.ReadBoolean()
if bs.ReadBoolean():
quality.PluralName = bs.ReadString()
if debug: print(f"PluralName: {quality.PluralName}")
if bs.ReadBoolean():
quality.OwnerName = bs.ReadString()
if debug: print(f"OwnerName: {quality.OwnerName}")
if bs.ReadBoolean():
quality.Description = bs.ReadString()
if debug: print(f"Description: {quality.Description}")
if bs.ReadBoolean():
quality.Image = bs.ReadString()
if debug: print(f"Image: {quality.Image}")
if bs.ReadBoolean():
quality.Notes = bs.ReadString()
if debug: print(f"Notes: {quality.Notes}")
if bs.ReadBoolean():
quality.Tag = bs.ReadString()
if debug: print(f"Tag: {quality.Tag}")
if bs.ReadBoolean():
quality.Cap = bs.ReadInt32()
if debug: print(f"Cap: {quality.Cap}")
if bs.ReadBoolean():
quality.CapAdvanced = bs.ReadString()
if debug: print(f"CapAdvanced: {quality.CapAdvanced}")
quality.HimbleLevel = bs.ReadInt32()
if debug: print(f"HimbleLevel: {quality.HimbleLevel}")
if debug: bs.debug_dump('checkpoint')
quality.UsePyramidNumbers = bs.ReadBoolean()
if debug: print(f"UsePyramidNumbers: {quality.UsePyramidNumbers}")
quality.PyramidNumberIncreaseLimit = bs.ReadInt32()
if debug: print(f"PyramidNumberIncreaseLimit: {quality.PyramidNumberIncreaseLimit}")
if bs.ReadBoolean():
quality.AvailableAt = bs.ReadString()
if debug: print(f"AvailableAt: {quality.AvailableAt}")
quality.PreventNaming = bs.ReadBoolean()
if debug: print(f"PreventNaming: {quality.PreventNaming}")
if bs.ReadBoolean():
quality.CssClasses = bs.ReadString()
if debug: print(f"CssClasses: {quality.CssClasses}")
quality.QEffectPriority = bs.ReadInt32()
if debug: print(f"QEffectPriority: {quality.QEffectPriority}")
if bs.ReadBoolean():
quality.QEffectMinimalLimit = bs.ReadInt32()
if debug: print(f"QEffectMinimalLimit: {quality.QEffectMinimalLimit}")
if bs.ReadBoolean():
quality.World = BinarySerializer_World.Deserialize(bs)
if debug: print(f"World: {quality.World}")
quality.Ordering = bs.ReadInt32()
if debug: print(f"Ordering: {quality.Ordering}")
quality.IsSlot = bs.ReadBoolean()
if debug: print(f"IsSlot: {quality.IsSlot}")
if bs.ReadBoolean():
quality.LimitedToArea = BinarySerializer_Area.Deserialize(bs)
if debug: print(f"LimitedToArea: {quality.LimitedToArea}")
if bs.ReadBoolean():
if debug: bs.debug_dump('AssignToSlot quality!')
quality.AssignToSlot = cls.Deserialize(bs)
if debug: print(f"AssignToSlot: {quality.AssignToSlot}")
if bs.ReadBoolean():
if debug: bs.debug_dump('ParentQuality thing!')
quality.ParentQuality = cls.Deserialize(bs)
if debug: print(f"ParentQuality: {quality.ParentQuality}")
quality.Persistent = bs.ReadBoolean()
if debug: print(f"Persistent: {quality.Persistent}")
quality.Visible = bs.ReadBoolean()
if debug: print(f"Visible: {quality.Visible}")
if bs.ReadBoolean():
quality.Enhancements = []
num2 = bs.ReadInt32()
if debug: print(f"Enhancements (count): {num2}")
for j in range(num2):
quality.Enhancements.append(BinarySerializer_QEnhancement.Deserialize(bs))
if bs.ReadBoolean():
quality.EnhancementsDescription = bs.ReadString()
if debug: print(f"EnhancementsDescription: {quality.EnhancementsDescription}")
if bs.ReadBoolean():
quality.SecondChanceQuality = BinarySerializer_Quality.Deserialize(bs)
if debug: print(f"SecondChanceQuality: {quality.SecondChanceQuality}")
if bs.ReadBoolean():
if debug: bs.debug_dump('UseEvent thing!')
quality.UseEvent = BinarySerializer_Event.Deserialize(bs, debug=debug)
if debug: print(f"UseEvent: {quality.UseEvent}")
quality.DifficultyTestType = BinarySerializer_DifficultyTestType.Deserialize(bs)
if debug: print(f"DifficultyTestType: {quality.DifficultyTestType}")
quality.DifficultyScaler = bs.ReadInt32()
if debug: print(f"DifficultyScaler: {quality.DifficultyScaler}")
quality.AllowedOn = BinarySerializer_QualityAllowedOn.Deserialize(bs)
if debug: print(f"AllowedOn: {quality.AllowedOn}")
quality.Nature = BinarySerializer_Nature.Deserialize(bs)
if debug: print(f"Nature: {quality.Nature}")
quality.Category = BinarySerializer_Category.Deserialize(bs)
if debug: print(f"Category: {quality.Category}")
if debug: bs.debug_dump('just about to break')
if bs.ReadBoolean():
quality.LevelDescriptionText = bs.ReadString()
if debug: print(f"LevelDescriptionText: {quality.LevelDescriptionText}")
if bs.ReadBoolean():
quality.ChangeDescriptionText = bs.ReadString()
if debug: print(f"ChangeDescriptionText: {quality.ChangeDescriptionText}")
if bs.ReadBoolean():
quality.DescendingChangeDescriptionText = bs.ReadString()
if debug: print(f"DescendingChangeDescriptionText: {quality.DescendingChangeDescriptionText}")
if bs.ReadBoolean():
quality.LevelImageText = bs.ReadString()
if debug: print(f"LevelImageText: {quality.LevelImageText}")
if bs.ReadBoolean():
quality.VariableDescriptionText = bs.ReadString()
if debug: print(f"VariableDescriptionText: {quality.VariableDescriptionText}")
if bs.ReadBoolean():
quality.Name = bs.ReadString()
if debug: print(f"Name: {quality.Name}")
quality.Id = bs.ReadInt32()
if debug: print(f"Id: {quality.Id}")
return quality
@classmethod
def DeserializeCollection(cls, bs):
l = []
num = bs.ReadInt32()
for i in range(num):
_start = bs.offset
l.append(cls.Deserialize(bs, debug=False))
q = l[-1]
return l
class BinarySerializer_QualityAllowedOn:
@classmethod
def Deserialize(cls, bs):
qualityAllowedOn = 0 # QualityAllowedOn.Unspecified
return bs.ReadInt32()
class Setting:
pass
class BinarySerializer_Setting:
@classmethod
def Deserialize(cls, bs):
setting = Setting()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
setting.World = BinarySerializer_World.Deserialize(bs)
if bs.ReadBoolean():
setting.OwnerName = bs.ReadString()
if bs.ReadBoolean():
setting.Personae = []
num = bs.ReadInt32()
for i in range(num):
setting.Personae.append(BinarySerializer_Persona.Deserialize(bs))
if bs.ReadBoolean():
setting.StartingArea = BinarySerializer_Area.Deserialize(bs)
if bs.ReadBoolean():
setting.StartingDomicile = BinarySerializer_Domicile.Deserialize(bs)
setting.ItemsUsableHere = bs.ReadBoolean()
if bs.ReadBoolean():
setting.Exchange = BinarySerializer_Exchange.Deserialize(bs)
setting.TurnLengthSeconds = bs.ReadInt32()
setting.MaxActionsAllowed = bs.ReadInt32()
setting.MaxCardsAllowed = bs.ReadInt32()
setting.ActionsInPeriodBeforeExhaustion = bs.ReadInt32()
if bs.ReadBoolean():
setting.Description = bs.ReadString()
if bs.ReadBoolean():
setting.Name = bs.ReadString()
setting.Id = bs.ReadInt32()
return setting
@classmethod
def DeserializeCollection(cls, bs):
l = []
num = bs.ReadInt32()
for i in range(num):
l.append(cls.Deserialize(bs))
return l
class BinarySerializer_Urgency:
@classmethod
def Deserialize(cls, bs):
urgency = 0 #Urgency.Normal
return bs.ReadInt32()
class User:
pass
class BinarySerializer_User:
@classmethod
def Deserialize(cls, bs):
user = User()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
user.QualitiesPossessedList = []
num = bs.ReadInt32()
for i in range(num):
user.QualitiesPossessedList.append(BinarySerializer_UserQPossession.Deserialize(bs))
if bs.ReadBoolean():
user.Name = bs.ReadString()
if bs.ReadBoolean():
user.StartedInWorld = BinarySerializer_World.Deserialize(bs)
if bs.ReadBoolean():
user.EmailAddress = bs.ReadString()
if bs.ReadBoolean():
user.FacebookEmail = bs.ReadString()
if bs.ReadBoolean():
user.PasswordHash = bs.ReadString()
if bs.ReadBoolean():
user.ConfirmationCode = bs.ReadString()
if bs.ReadBoolean():
user.TwitterId = bs.ReadInt64()
if bs.ReadBoolean():
user.FacebookId = bs.ReadInt64()
if bs.ReadBoolean():
user.GoogleId = bs.ReadString()
if bs.ReadBoolean():
user.GoogleAuthToken = bs.ReadString()
if bs.ReadBoolean():
user.GoogleAuthTokenSecret = bs.ReadString()
if bs.ReadBoolean():
user.GoogleEmail = bs.ReadString()
if bs.ReadBoolean():
user.TwitterAuthToken = bs.ReadString()
if bs.ReadBoolean():
user.TwitterAuthTokenSecret = bs.ReadString()
if bs.ReadBoolean():
user.FacebookAuthToken = bs.ReadString()
if bs.ReadBoolean():
user.FacebookAuthTokenSecret = bs.ReadString()
if bs.ReadBoolean():
user.Source = bs.ReadString()
user.EnteredViaContentId = bs.ReadInt32()
user.EnteredViaCharacterId = bs.ReadInt32()
user.Status = BinarySerializer_UserStatus.Deserialize(bs)
user.EmailVerified = bs.ReadBoolean()
user.EchoViaNetwork = BinarySerializer_ViaNetwork.Deserialize(bs)
user.MessageViaNetwork = BinarySerializer_ViaNetwork.Deserialize(bs)
user.MessageAboutNastiness = bs.ReadBoolean()
user.MessageAboutNiceness = bs.ReadBoolean()
user.MessageAboutAnnouncements = bs.ReadBoolean()
user.StoryEventMessage = bs.ReadBoolean()
user.DefaultPrivilegeLevel = BinarySerializer_PrivilegeLevel.Deserialize(bs)
user.LoggedInVia = BinarySerializer_LoggedInVia.Deserialize(bs)
user.IsBroadcastTarget = bs.ReadBoolean()
user.MysteryPrizeTracking = bs.ReadInt32()
user.Recruited = bs.ReadInt32()
if bs.ReadBoolean():
user.TempId = bs.ReadString()
user.CreatedAt = BinarySerializer_DateTime.Deserialize(bs)
if bs.ReadBoolean():
user.LastLoggedInAt = BinarySerializer_DateTime.Deserialize(bs)
if bs.ReadBoolean():
user.LastActiveAt = BinarySerializer_DateTime.Deserialize(bs)
if bs.ReadBoolean():
user.IP = bs.ReadString()
if bs.ReadBoolean():
user.LastAccessCode = bs.ReadString()
if bs.ReadBoolean():
user.WorldPrivileges = []
num2 = bs.ReadInt32()
for j in range(num2):
user.WorldPrivileges.append(BinarySerializer_UserWorldPrivilege.Deserialize(bs))
user.SRPurchasedNexInLifetime = bs.ReadInt32()
user.FatePointsGainedThroughGameInLifetime = bs.ReadInt32()
user.Nex = bs.ReadInt32()
user.Id = bs.ReadInt32()
#######
print()
print(f"*** USER {user.Id}:")
for k in sorted(user.__dict__.keys()):
print(" %s: %s" % (k, user.__dict__[k]))
print()
#######
return user
class UserQPossession:
pass
class BinarySerializer_UserQPossession:
@classmethod
def Deserialize(cls, bs):
userQPossession = UserQPossession()
if not bs.ReadBoolean():
return None
userQPossession.XP = bs.ReadInt32()
userQPossession.EffectiveLevelModifier = bs.ReadInt32()
if bs.ReadBoolean():
userQPossession.TargetQuality = BinarySerializer_Quality.Deserialize(bs)
if bs.ReadBoolean():
userQPossession.TargetLevel = bs.ReadInt32()
if bs.ReadBoolean():
userQPossession.CompletionMessage = bs.ReadString()
userQPossession.Level = bs.ReadInt32()
if bs.ReadBoolean():
userQPossession.AssociatedQuality = BinarySerializer_Quality.Deserialize(bs)
userQPossession.Id = bs.ReadInt32()
return userQPossession
class BinarySerializer_UserStatus:
@classmethod
def Deserialize(cls, bs):
userStatus = 0 # UserStatus.AwaitingEmailConfirmation
return bs.ReadInt32()
class UserWorldPrivilege:
pass
class BinarySerializer_UserWorldPrivilege:
@classmethod
def Deserialize(cls, bs):
userWorldPrivilege = UserWorldPrivilege()
if not bs.ReadBoolean():
return None
if bs.ReadBoolean():
userWorldPrivilege.World = BinarySerializer_World.Deserialize(bs)
userWorldPrivilege.PrivilegeLevel = BinarySerializer_PrivilegeLevel.Deserialize(bs)
if bs.ReadBoolean():
userWorldPrivilege.User = BinarySerializer_User.Deserialize(bs)
userWorldPrivilege.Id = bs.ReadInt32()
return userWorldPrivilege
class BinarySerializer_ViaNetwork:
@classmethod
def Deserialize(cls, bs):
viaNetwork = 0 # ViaNetwork.None
return bs.ReadInt32()
class World:
pass
class BinarySerializer_World:
@classmethod
def Deserialize(cls, bs):
world = World()
if not bs.ReadBoolean():
return None
world.GeneralQualityCatalogue = bs.ReadBoolean()
world.ShowCardTitles = bs.ReadBoolean()
if bs.ReadBoolean():
world.CharacterCreationPageText = bs.ReadString()
if bs.ReadBoolean():
world.EndPageText = bs.ReadString()
if bs.ReadBoolean():
world.FrontPageText = bs.ReadString()
if bs.ReadBoolean():
world.CustomCss = bs.ReadString()
if bs.ReadBoolean():
world.Credits = bs.ReadString()
if bs.ReadBoolean():
world.Description = bs.ReadString()
if bs.ReadBoolean():
world.Name = bs.ReadString()
if bs.ReadBoolean():
world.Domain = bs.ReadString()
world.Promoted = bs.ReadInt32()
if bs.ReadBoolean():
world.DefaultSetting = BinarySerializer_Setting.Deserialize(bs)
world.FacebookAuth = bs.ReadBoolean()
world.TwitterAuth = bs.ReadBoolean()
world.EmailAuth = bs.ReadBoolean()
if bs.ReadBoolean():
world.FacebookAPIKey = bs.ReadString()
if bs.ReadBoolean():
world.FacebookAppId = bs.ReadString()
if bs.ReadBoolean():
world.FacebookAppSecret = bs.ReadString()
if bs.ReadBoolean():
world.GameUserTwitterAuthToken = bs.ReadString()
if bs.ReadBoolean():
world.GameUserTwitterAuthTokenSecret = bs.ReadString()
if bs.ReadBoolean():
world.TwitterConsumerKey = bs.ReadString()
if bs.ReadBoolean():
world.TwitterConsumerSecret = bs.ReadString()
if bs.ReadBoolean():
world.TwitterCallbackUrl = bs.ReadString()
if bs.ReadBoolean():
world.AmazonHostedImageUrl = bs.ReadString()
if bs.ReadBoolean():
world.AmazonBucketName = bs.ReadString()
if bs.ReadBoolean():
world.StyleSheet = bs.ReadString()
if bs.ReadBoolean():
world.LogoImage = bs.ReadString()
if bs.ReadBoolean():
world.DefaultStartingSetting = BinarySerializer_Setting.Deserialize(bs)
if bs.ReadBoolean():
world.Owner = BinarySerializer_User.Deserialize(bs)
world.IsPortalWorld = bs.ReadBoolean()
world.Monetizes = bs.ReadBoolean()
if bs.ReadBoolean():
world.PaymentEmailAddress = bs.ReadString()
if bs.ReadBoolean():
world.SupportEmailAddress = bs.ReadString()
if bs.ReadBoolean():
world.SystemFromEmailAddress = bs.ReadString()
world.LastUpdated = BinarySerializer_DateTime.Deserialize(bs)
if bs.ReadBoolean():
world.UpdateNotes = bs.ReadString()
world.PublishState = BinarySerializer_PublishState.Deserialize(bs)
world.Genre = BinarySerializer_Genre.Deserialize(bs)
world.Id = bs.ReadInt32()
#######
print()
print(f"*** WORLD {world.Id}:")
for k in sorted(world.__dict__.keys()):
print(" %s: %s" % (k, world.__dict__[k]))
print()
#######
return world
def main():
# Qualities
data = open('qualities.dat', 'rb').read()
bs = BinaryReader(data)
bs.Seek(0x14) # Skip Unity resource bits
qualities = BinarySerializer_Quality.DeserializeCollection(bs)
# Events
data = open('events.dat', 'rb').read()
bs = BinaryReader(data)
bs.Seek(0x10) # Skip Unity resource bits
events = BinarySerializer_Event.DeserializeCollection(bs)
def get_quality(id):
for q in qualities:
if q.Id == id:
return q
return None
def get_event(id):
for e in events:
if e.Id == id:
return e
return None
def save():
with open('qualities.txt', 'w') as output:
for q in qualities:
output.write(str(q))
output.write('\n')
with open('events.txt', 'w') as output:
for e in events:
output.write(str(e))
output.write('\n')
print("Qualities and events saved.")
print("call save() to save qualities and events as text files.")
print("or explore 'events' and 'qualities' lists interactively.")
print()
# Shell
import code
variables = globals().copy()
variables.update(locals())
shell = code.InteractiveConsole(variables)
shell.interact()
if __name__ == '__main__': main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment