Repository: xivapi/SaintCoinach Branch: master Commit: 0b22b78f9795 Files: 1641 Total size: 2.3 MB Directory structure: gitextract_w62xzu50/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── RELEASE.md │ └── workflows/ │ ├── build.yml │ └── json-validation.yaml ├── .gitignore ├── DotSquish/ │ ├── Alpha.cs │ ├── ClusterFit.cs │ ├── ColourBlock.cs │ ├── ColourFit.cs │ ├── ColourSet.cs │ ├── DotSquish.csproj │ ├── Flags.cs │ ├── Maths.cs │ └── Squish.cs ├── Godbert/ │ ├── App.config │ ├── App.xaml │ ├── App.xaml.cs │ ├── Commands/ │ │ └── DelegateCommand.cs │ ├── Controls/ │ │ ├── AttachedImage.cs │ │ ├── ColumnFactory.cs │ │ ├── INavigatable.cs │ │ ├── IRawDataColumn.cs │ │ ├── RawDataGrid.cs │ │ ├── RawDataGridColorColumn.cs │ │ ├── RawDataGridImageColumn.cs │ │ ├── RawDataGridKeyColumn.cs │ │ ├── RawDataGridTextColumn.cs │ │ └── RawDataItemsSource.cs │ ├── EngineHelper.cs │ ├── EngineInstance.cs │ ├── Godbert.csproj │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Models/ │ │ ├── ModelCharaHierarchy.cs │ │ ├── ModelCharaMain.cs │ │ ├── ModelCharaSub.cs │ │ └── ModelCharaVariant.cs │ ├── NaturalComparer.cs │ ├── ObservableBase.cs │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── Settings.cs │ ├── ViewModels/ │ │ ├── BookmarkViewModel.cs │ │ ├── DataViewModel.cs │ │ ├── DemihumanViewModel.cs │ │ ├── EquipmentViewModel.cs │ │ ├── FurnitureViewModel.cs │ │ ├── MainViewModel.cs │ │ ├── MapsViewModel.cs │ │ ├── MonstersViewModel.cs │ │ └── TerritoryViewModel.cs │ ├── Views/ │ │ ├── DataView.xaml │ │ ├── DataView.xaml.cs │ │ ├── DemihumansView.xaml │ │ ├── DemihumansView.xaml.cs │ │ ├── EquipmentView.xaml │ │ ├── EquipmentView.xaml.cs │ │ ├── FurnitureView.xaml │ │ ├── FurnitureView.xaml.cs │ │ ├── Help3DView.xaml │ │ ├── Help3DView.xaml.cs │ │ ├── MapsView.xaml │ │ ├── MapsView.xaml.cs │ │ ├── MonstersView.xaml │ │ ├── MonstersView.xaml.cs │ │ ├── TerritoryView.xaml │ │ └── TerritoryView.xaml.cs │ └── WpfHelper.cs ├── LICENSE ├── NewAdditionsByPatch.txt ├── README.md ├── SaintCoinach/ │ ├── ARealmReversed.cs │ ├── ByteArrayExtensions.cs │ ├── Definitions/ │ │ ├── AOZArrangement.json │ │ ├── AOZBoss.json │ │ ├── AOZContent.json │ │ ├── AOZContentBriefingBNpc.json │ │ ├── AOZContentBriefingObject.json │ │ ├── AOZReport.json │ │ ├── AOZScore.json │ │ ├── Achievement.json │ │ ├── AchievementCategory.json │ │ ├── AchievementHideCondition.json │ │ ├── AchievementKind.json │ │ ├── AchievementTarget.json │ │ ├── Action.json │ │ ├── ActionCastTimeline.json │ │ ├── ActionCastVFX.json │ │ ├── ActionCategory.json │ │ ├── ActionComboRoute.json │ │ ├── ActionIndirection.json │ │ ├── ActionParam.json │ │ ├── ActionProcStatus.json │ │ ├── ActionTimeline.json │ │ ├── ActionTimelineMove.json │ │ ├── ActionTimelineReplace.json │ │ ├── ActionTransient.json │ │ ├── ActivityFeedButtons.json │ │ ├── ActivityFeedCaptions.json │ │ ├── ActivityFeedGroupCaptions.json │ │ ├── ActivityFeedImages.json │ │ ├── Addon.json │ │ ├── Adventure.json │ │ ├── AdventureExPhase.json │ │ ├── AetherCurrent.json │ │ ├── AetherCurrentCompFlgSet.json │ │ ├── AetherialWheel.json │ │ ├── Aetheryte.json │ │ ├── AetheryteSystemDefine.json │ │ ├── AetheryteTransient.json │ │ ├── AirshipExplorationLevel.json │ │ ├── AirshipExplorationLog.json │ │ ├── AirshipExplorationParamType.json │ │ ├── AirshipExplorationPart.json │ │ ├── AirshipExplorationPoint.json │ │ ├── AkatsukiNote.json │ │ ├── AkatsukiNoteString.json │ │ ├── AnimaWeapon5.json │ │ ├── AnimaWeapon5Param.json │ │ ├── AnimaWeapon5PatternGroup.json │ │ ├── AnimaWeapon5SpiritTalk.json │ │ ├── AnimaWeapon5SpiritTalkParam.json │ │ ├── AnimaWeapon5TradeItem.json │ │ ├── AnimaWeaponFUITalk.json │ │ ├── AnimaWeaponFUITalkParam.json │ │ ├── AnimaWeaponIcon.json │ │ ├── AnimaWeaponItem.json │ │ ├── AnimationLOD.json │ │ ├── AozAction.json │ │ ├── AozActionTransient.json │ │ ├── AquariumFish.json │ │ ├── AquariumWater.json │ │ ├── ArchiveItem.json │ │ ├── ArrayEventHandler.json │ │ ├── AttackType.json │ │ ├── Attract.json │ │ ├── BGM.json │ │ ├── BGMFade.json │ │ ├── BGMFadeType.json │ │ ├── BGMScene.json │ │ ├── BGMSituation.json │ │ ├── BGMSwitch.json │ │ ├── BGMSystemDefine.json │ │ ├── BNpcAnnounceIcon.json │ │ ├── BNpcBase.json │ │ ├── BNpcBasePopVfx.json │ │ ├── BNpcCustomize.json │ │ ├── BNpcName.json │ │ ├── BNpcParts.json │ │ ├── BNpcState.json │ │ ├── BacklightColor.json │ │ ├── Ballista.json │ │ ├── Balloon.json │ │ ├── BannerBg.json │ │ ├── BannerCondition.json │ │ ├── BannerDecoration.json │ │ ├── BannerDesignPreset.json │ │ ├── BannerFacial.json │ │ ├── BannerFrame.json │ │ ├── BannerObtainHintType.json │ │ ├── BannerPreset.json │ │ ├── BannerTimeline.json │ │ ├── BaseParam.json │ │ ├── BattleLeve.json │ │ ├── BattleLeveRule.json │ │ ├── BeastRankBonus.json │ │ ├── BeastReputationRank.json │ │ ├── BeastTribe.json │ │ ├── Behavior.json │ │ ├── BehaviorPath.json │ │ ├── BenchmarkOverrideEquipment.json │ │ ├── Booster.json │ │ ├── Buddy.json │ │ ├── BuddyAction.json │ │ ├── BuddyEquip.json │ │ ├── BuddyItem.json │ │ ├── BuddyRank.json │ │ ├── BuddySkill.json │ │ ├── CSBonusContent.json │ │ ├── CSBonusContentIdentifier.json │ │ ├── CSBonusContentType.json │ │ ├── CSBonusMission.json │ │ ├── CSBonusMissionType.json │ │ ├── CSBonusSeason.json │ │ ├── CSBonusTextData.json │ │ ├── Cabinet.json │ │ ├── CabinetCategory.json │ │ ├── Calendar.json │ │ ├── Carry.json │ │ ├── Channeling.json │ │ ├── CharaCardBase.json │ │ ├── CharaCardDecoration.json │ │ ├── CharaCardDesignCategory.json │ │ ├── CharaCardDesignPreset.json │ │ ├── CharaCardDesignType.json │ │ ├── CharaCardHeader.json │ │ ├── CharaCardPlayStyle.json │ │ ├── CharaMakeClassEquip.json │ │ ├── CharaMakeCustomize.json │ │ ├── CharaMakeName.json │ │ ├── CharaMakeType.json │ │ ├── ChocoboRace.json │ │ ├── ChocoboRaceAbility.json │ │ ├── ChocoboRaceAbilityType.json │ │ ├── ChocoboRaceItem.json │ │ ├── ChocoboRaceRank.json │ │ ├── ChocoboRaceStatus.json │ │ ├── ChocoboRaceTerritory.json │ │ ├── ChocoboRaceTutorial.json │ │ ├── ChocoboRaceWeather.json │ │ ├── ChocoboTaxi.json │ │ ├── ChocoboTaxiStand.json │ │ ├── CircleActivity.json │ │ ├── ClassJob.json │ │ ├── ClassJobActionSort.json │ │ ├── ClassJobCategory.json │ │ ├── CollectablesShop.json │ │ ├── CollectablesShopItem.json │ │ ├── CollectablesShopItemGroup.json │ │ ├── CollectablesShopRefine.json │ │ ├── CollectablesShopRewardItem.json │ │ ├── CollectablesShopRewardScrip.json │ │ ├── Companion.json │ │ ├── CompanionMove.json │ │ ├── CompanionTransient.json │ │ ├── CompanyAction.json │ │ ├── CompanyCraftDraft.json │ │ ├── CompanyCraftDraftCategory.json │ │ ├── CompanyCraftManufactoryState.json │ │ ├── CompanyCraftPart.json │ │ ├── CompanyCraftProcess.json │ │ ├── CompanyCraftSequence.json │ │ ├── CompanyCraftSupplyItem.json │ │ ├── CompanyCraftType.json │ │ ├── CompanyLeve.json │ │ ├── CompanyLeveRule.json │ │ ├── CompleteJournal.json │ │ ├── CompleteJournalCategory.json │ │ ├── Completion.json │ │ ├── Condition.json │ │ ├── ConfigKey.json │ │ ├── ContentCloseCycle.json │ │ ├── ContentEventItem.json │ │ ├── ContentExAction.json │ │ ├── ContentFinderCondition.json │ │ ├── ContentFinderConditionTransient.json │ │ ├── ContentGauge.json │ │ ├── ContentGaugeColor.json │ │ ├── ContentMemberType.json │ │ ├── ContentNpc.json │ │ ├── ContentNpcTalk.json │ │ ├── ContentRandomSelect.json │ │ ├── ContentRoulette.json │ │ ├── ContentRouletteOpenRule.json │ │ ├── ContentRouletteRoleBonus.json │ │ ├── ContentTalk.json │ │ ├── ContentTalkParam.json │ │ ├── ContentType.json │ │ ├── ContentsNote.json │ │ ├── ContentsTutorial.json │ │ ├── ContentsTutorialPage.json │ │ ├── CraftAction.json │ │ ├── CraftLeve.json │ │ ├── CraftLeveTalk.json │ │ ├── CraftLevelDifference.json │ │ ├── CraftType.json │ │ ├── Credit.json │ │ ├── CreditBackImage.json │ │ ├── CreditCast.json │ │ ├── CreditList.json │ │ ├── CreditListText.json │ │ ├── CustomTalk.json │ │ ├── CustomTalkDefineClient.json │ │ ├── CustomTalkNestHandlers.json │ │ ├── CutSceneIncompQuest.json │ │ ├── CutScreenImage.json │ │ ├── Cutscene.json │ │ ├── CutsceneMotion.json │ │ ├── CutsceneWorkIndex.json │ │ ├── CycleTime.json │ │ ├── DailySupplyItem.json │ │ ├── DawnContent.json │ │ ├── DawnContentParticipable.json │ │ ├── DawnGrowMember.json │ │ ├── DawnMember.json │ │ ├── DawnMemberUIParam.json │ │ ├── DawnQuestMember.json │ │ ├── DeepDungeon.json │ │ ├── DeepDungeonBan.json │ │ ├── DeepDungeonDanger.json │ │ ├── DeepDungeonDemiclone.json │ │ ├── DeepDungeonEquipment.json │ │ ├── DeepDungeonFloorEffectUI.json │ │ ├── DeepDungeonItem.json │ │ ├── DeepDungeonLayer.json │ │ ├── DeepDungeonMagicStone.json │ │ ├── DeepDungeonMap5X.json │ │ ├── DeepDungeonRoom.json │ │ ├── DeepDungeonStatus.json │ │ ├── DefaultTalk.json │ │ ├── DefaultTalkLipSyncType.json │ │ ├── DeliveryQuest.json │ │ ├── Description.json │ │ ├── DescriptionPage.json │ │ ├── DescriptionSection.json │ │ ├── DescriptionString.json │ │ ├── DisposalShop.json │ │ ├── DisposalShopFilterType.json │ │ ├── DisposalShopItem.json │ │ ├── DpsChallenge.json │ │ ├── DpsChallengeOfficer.json │ │ ├── DpsChallengeTransient.json │ │ ├── DynamicEvent.json │ │ ├── DynamicEventEnemyType.json │ │ ├── DynamicEventSingleBattle.json │ │ ├── DynamicEventType.json │ │ ├── ENpcBase.json │ │ ├── ENpcDressUp.json │ │ ├── ENpcDressUpDress.json │ │ ├── ENpcResident.json │ │ ├── EObj.json │ │ ├── EObjName.json │ │ ├── EmjAddon.json │ │ ├── EmjDani.json │ │ ├── Emote.json │ │ ├── EmoteCategory.json │ │ ├── EmoteMode.json │ │ ├── EquipRaceCategory.json │ │ ├── EquipSlotCategory.json │ │ ├── EurekaAetherItem.json │ │ ├── EurekaAethernet.json │ │ ├── EurekaDungeonPortal.json │ │ ├── EurekaGrowData.json │ │ ├── EurekaLogosMixerProbability.json │ │ ├── EurekaMagiaAction.json │ │ ├── EurekaMagiciteItem.json │ │ ├── EurekaMagiciteItemType.json │ │ ├── EurekaSphereElementAdjust.json │ │ ├── EventAction.json │ │ ├── EventCustomIconType.json │ │ ├── EventIconPriority.json │ │ ├── EventIconPriorityPair.json │ │ ├── EventIconType.json │ │ ├── EventItem.json │ │ ├── EventItemCastTimeline.json │ │ ├── EventItemHelp.json │ │ ├── EventItemTimeline.json │ │ ├── EventPathMove.json │ │ ├── EventSystemDefine.json │ │ ├── ExVersion.json │ │ ├── ExportedGatheringPoint.json │ │ ├── ExportedSG.json │ │ ├── ExtraCommand.json │ │ ├── FCActivity.json │ │ ├── FCActivityCategory.json │ │ ├── FCAuthority.json │ │ ├── FCAuthorityCategory.json │ │ ├── FCChestName.json │ │ ├── FCCrestSymbol.json │ │ ├── FCHierarchy.json │ │ ├── FCProfile.json │ │ ├── FCRank.json │ │ ├── FCReputation.json │ │ ├── FCRights.json │ │ ├── FGSAddon.json │ │ ├── FGSStageUI.json │ │ ├── FashionCheckThemeCategory.json │ │ ├── FashionCheckWeeklyTheme.json │ │ ├── Fate.json │ │ ├── FateEvent.json │ │ ├── FateMode.json │ │ ├── FateProgressUI.json │ │ ├── FateShop.json │ │ ├── FateTokenType.json │ │ ├── FccShop.json │ │ ├── Festival.json │ │ ├── FieldMarker.json │ │ ├── FishParameter.json │ │ ├── FishingBaitParameter.json │ │ ├── FishingNoteInfo.json │ │ ├── FishingRecordType.json │ │ ├── FishingRecordTypeTransient.json │ │ ├── FishingSpot.json │ │ ├── FittingShop.json │ │ ├── FittingShopCategory.json │ │ ├── FittingShopCategoryItem.json │ │ ├── FittingShopItemSet.json │ │ ├── Frontline03.json │ │ ├── FurnitureCatalogCategory.json │ │ ├── FurnitureCatalogItemList.json │ │ ├── GCRankGridaniaFemaleText.json │ │ ├── GCRankGridaniaMaleText.json │ │ ├── GCRankLimsaFemaleText.json │ │ ├── GCRankLimsaMaleText.json │ │ ├── GCRankUldahFemaleText.json │ │ ├── GCRankUldahMaleText.json │ │ ├── GCScripShopCategory.json │ │ ├── GCScripShopItem.json │ │ ├── GCShop.json │ │ ├── GCShopItemCategory.json │ │ ├── GCSupplyDuty.json │ │ ├── GCSupplyDutyReward.json │ │ ├── GFATE.json │ │ ├── GFateClimbing2.json │ │ ├── GFateClimbing2Content.json │ │ ├── GFateClimbing2TotemType.json │ │ ├── GFateRideShooting.json │ │ ├── GFateType.json │ │ ├── GameRewardObtainType.json │ │ ├── GardeningSeed.json │ │ ├── GathererCrafterTool.json │ │ ├── GathererReductionReward.json │ │ ├── GatheringCondition.json │ │ ├── GatheringExp.json │ │ ├── GatheringItem.json │ │ ├── GatheringItemLevelConvertTable.json │ │ ├── GatheringItemPoint.json │ │ ├── GatheringLeve.json │ │ ├── GatheringLeveRoute.json │ │ ├── GatheringLeveRule.json │ │ ├── GatheringNotebookList.json │ │ ├── GatheringPoint.json │ │ ├── GatheringPointBase.json │ │ ├── GatheringPointBonus.json │ │ ├── GatheringPointBonusType.json │ │ ├── GatheringPointName.json │ │ ├── GatheringPointTransient.json │ │ ├── GatheringRarePopTimeTable.json │ │ ├── GatheringSubCategory.json │ │ ├── GatheringType.json │ │ ├── GcArmyCaptureTactics.json │ │ ├── GcArmyEquipPreset.json │ │ ├── GcArmyExpedition.json │ │ ├── GcArmyExpeditionMemberBonus.json │ │ ├── GcArmyExpeditionType.json │ │ ├── GcArmyMemberGrow.json │ │ ├── GcArmyTraining.json │ │ ├── GeneralAction.json │ │ ├── GilShop.json │ │ ├── GilShopItem.json │ │ ├── GimmickAccessor.json │ │ ├── GimmickJump.json │ │ ├── GimmickRect.json │ │ ├── Glasses.json │ │ ├── GlassesStyle.json │ │ ├── GoldSaucerArcadeMachine.json │ │ ├── GoldSaucerTextData.json │ │ ├── GrandCompany.json │ │ ├── GrandCompanyRank.json │ │ ├── GroupPoseFrame.json │ │ ├── GroupPoseStamp.json │ │ ├── GroupPoseStampCategory.json │ │ ├── GuardianDeity.json │ │ ├── Guide.json │ │ ├── GuidePage.json │ │ ├── GuidePageString.json │ │ ├── GuideTitle.json │ │ ├── GuildOrder.json │ │ ├── GuildOrderGuide.json │ │ ├── GuildOrderOfficer.json │ │ ├── GuildleveAssignment.json │ │ ├── GuildleveAssignmentCategory.json │ │ ├── GuildleveAssignmentTalk.json │ │ ├── HWDAnnounce.json │ │ ├── HWDCrafterSupply.json │ │ ├── HWDCrafterSupplyReward.json │ │ ├── HWDCrafterSupplyTerm.json │ │ ├── HWDDevLayerControl.json │ │ ├── HWDDevLevelUI.json │ │ ├── HWDDevLively.json │ │ ├── HWDDevProgress.json │ │ ├── HWDGathereInspectTerm.json │ │ ├── HWDGathererInspection.json │ │ ├── HWDGathererInspectionReward.json │ │ ├── HWDInfoBoardArticle.json │ │ ├── HWDInfoBoardArticleTransient.json │ │ ├── HWDInfoBoardArticleType.json │ │ ├── HWDLevelChangeDeception.json │ │ ├── HWDSharedGroup.json │ │ ├── HWDSharedGroupControlParam.json │ │ ├── HairMakeType.json │ │ ├── HouseRetainerPose.json │ │ ├── HousingAethernet.json │ │ ├── HousingAppeal.json │ │ ├── HousingEmploymentNpcList.json │ │ ├── HousingEmploymentNpcRace.json │ │ ├── HousingExterior.json │ │ ├── HousingFurniture.json │ │ ├── HousingLandSet.json │ │ ├── HousingMapMarkerInfo.json │ │ ├── HousingMerchantPose.json │ │ ├── HousingPlacement.json │ │ ├── HousingPreset.json │ │ ├── HousingUnitedExterior.json │ │ ├── HousingYardObject.json │ │ ├── HowTo.json │ │ ├── HowToCategory.json │ │ ├── HowToPage.json │ │ ├── HugeCraftworksNpc.json │ │ ├── HugeCraftworksRank.json │ │ ├── IKDContentBonus.json │ │ ├── IKDFishParam.json │ │ ├── IKDRoute.json │ │ ├── IKDRouteTable.json │ │ ├── IKDSpot.json │ │ ├── IconLanguage.json │ │ ├── InclusionShop.json │ │ ├── InclusionShopCategory.json │ │ ├── InclusionShopSeries.json │ │ ├── InclusionShopWelcom.json │ │ ├── InclusionShopWelcomText.json │ │ ├── IndividualWeather.json │ │ ├── InstanceContent.json │ │ ├── InstanceContentBuff.json │ │ ├── InstanceContentGuide.json │ │ ├── InstanceContentQICData.json │ │ ├── InstanceContentTextData.json │ │ ├── Item.json │ │ ├── ItemAction.json │ │ ├── ItemActionTelepo.json │ │ ├── ItemBarterCheck.json │ │ ├── ItemFood.json │ │ ├── ItemLevel.json │ │ ├── ItemRepairPrice.json │ │ ├── ItemRepairResource.json │ │ ├── ItemRetainerLevelUp.json │ │ ├── ItemSearchCategory.json │ │ ├── ItemSeries.json │ │ ├── ItemSortCategory.json │ │ ├── ItemSpecialBonus.json │ │ ├── ItemStainCondition.json │ │ ├── ItemUICategory.json │ │ ├── Jingle.json │ │ ├── JobHudManual.json │ │ ├── JobHudManualPriority.json │ │ ├── JournalCategory.json │ │ ├── JournalGenre.json │ │ ├── JournalSection.json │ │ ├── KineDriverOffGroup.json │ │ ├── Knockback.json │ │ ├── LegacyQuest.json │ │ ├── Leve.json │ │ ├── LeveAssignmentType.json │ │ ├── LeveClient.json │ │ ├── LeveRewardItem.json │ │ ├── LeveRewardItemGroup.json │ │ ├── LeveString.json │ │ ├── LeveVfx.json │ │ ├── Level.json │ │ ├── Lobby.json │ │ ├── LogFilter.json │ │ ├── LogKind.json │ │ ├── LogMessage.json │ │ ├── LotteryExchangeShop.json │ │ ├── MJIAnimals.json │ │ ├── MJIBuilding.json │ │ ├── MJIBuildingPlace.json │ │ ├── MJICraftworksObject.json │ │ ├── MJICraftworksObjectTheme.json │ │ ├── MJICraftworksPopularity.json │ │ ├── MJICraftworksPopularityType.json │ │ ├── MJICraftworksRankRatio.json │ │ ├── MJICraftworksSupplyDefine.json │ │ ├── MJICraftworksTension.json │ │ ├── MJICropSeed.json │ │ ├── MJIDisposalShopItem.json │ │ ├── MJIDisposalShopUICategory.json │ │ ├── MJIFarmPastureRank.json │ │ ├── MJIFunction.json │ │ ├── MJIGardenscaping.json │ │ ├── MJIGathering.json │ │ ├── MJIGatheringItem.json │ │ ├── MJIGatheringObject.json │ │ ├── MJIGatheringTool.json │ │ ├── MJIHudMode.json │ │ ├── MJIItemCategory.json │ │ ├── MJIItemPouch.json │ │ ├── MJIKeyItem.json │ │ ├── MJILandmark.json │ │ ├── MJILandmarkPlace.json │ │ ├── MJILivelyActor.json │ │ ├── MJIMinionPopAreas.json │ │ ├── MJIName.json │ │ ├── MJINekomimiRequest.json │ │ ├── MJIProgress.json │ │ ├── MJIRank.json │ │ ├── MJIRecipe.json │ │ ├── MJIRecipeMaterial.json │ │ ├── MJIStockyardManagementArea.json │ │ ├── MJIStockyardManagementTable.json │ │ ├── MJIText.json │ │ ├── MJIVillageAppearanceSG.json │ │ ├── MJIVillageAppearanceUI.json │ │ ├── MJIVillageDevelopment.json │ │ ├── MKDLore.json │ │ ├── MYCTemporaryItem.json │ │ ├── MYCTemporaryItemUICategory.json │ │ ├── MYCWarResultNotebook.json │ │ ├── MacroIcon.json │ │ ├── MacroIconRedirectOld.json │ │ ├── MainCommand.json │ │ ├── MainCommandCategory.json │ │ ├── MandervilleWeaponEnhance.json │ │ ├── ManeuversArmor.json │ │ ├── Map.json │ │ ├── MapCondition.json │ │ ├── MapExclusive.json │ │ ├── MapMarker.json │ │ ├── MapMarkerRegion.json │ │ ├── MapReplace.json │ │ ├── MapSymbol.json │ │ ├── MapTransientPvPMap.json │ │ ├── MapType.json │ │ ├── Marker.json │ │ ├── Materia.json │ │ ├── MateriaGrade.json │ │ ├── MateriaJoinRate.json │ │ ├── MateriaJoinRateGatherCraft.json │ │ ├── McGuffin.json │ │ ├── McGuffinUIData.json │ │ ├── MiniGameRA.json │ │ ├── MiniGameTurnBreakAction.json │ │ ├── MiniGameTurnBreakConst.json │ │ ├── MiniGameTurnBreakEnemy.json │ │ ├── MiniGameTurnBreakPop.json │ │ ├── MiniGameTurnBreakPopOffset.json │ │ ├── MiniGameTurnBreakStage.json │ │ ├── MiniGameTurnBreakStatus.json │ │ ├── MinionRace.json │ │ ├── MinionRules.json │ │ ├── MinionSkillType.json │ │ ├── MirageStoreSetItem.json │ │ ├── MirageStoreSetItemLookup.json │ │ ├── MobHuntOrder.json │ │ ├── MobHuntOrderType.json │ │ ├── MobHuntReward.json │ │ ├── MobHuntRewardCap.json │ │ ├── MobHuntTarget.json │ │ ├── ModelChara.json │ │ ├── ModelSkeleton.json │ │ ├── ModelState.json │ │ ├── MonsterNote.json │ │ ├── MonsterNoteTarget.json │ │ ├── MotionTimeline.json │ │ ├── MotionTimelineBlendTable.json │ │ ├── Mount.json │ │ ├── MountAction.json │ │ ├── MountCustomize.json │ │ ├── MountFlyingCondition.json │ │ ├── MountSpeed.json │ │ ├── MountTransient.json │ │ ├── MoveTimeline.json │ │ ├── MoveVfx.json │ │ ├── MovieStaffList.json │ │ ├── MovieSubtitle.json │ │ ├── MovieSubtitle500.json │ │ ├── MovieSubtitleVoyage.json │ │ ├── MultipleHelp.json │ │ ├── MultipleHelpPage.json │ │ ├── MultipleHelpString.json │ │ ├── NotebookDivision.json │ │ ├── NotebookDivisionCategory.json │ │ ├── NotoriousMonster.json │ │ ├── NotoriousMonsterTerritory.json │ │ ├── NpcEquip.json │ │ ├── NpcYell.json │ │ ├── Omen.json │ │ ├── Omikuji.json │ │ ├── OmikujiGuidance.json │ │ ├── OnlineStatus.json │ │ ├── OpenContent.json │ │ ├── OpenContentCandidateName.json │ │ ├── Opening.json │ │ ├── Orchestrion.json │ │ ├── OrchestrionCategory.json │ │ ├── OrchestrionPath.json │ │ ├── OrchestrionUiparam.json │ │ ├── Ornament.json │ │ ├── OrnamentAction.json │ │ ├── ParamGrow.json │ │ ├── PartyContent.json │ │ ├── PartyContentCutscene.json │ │ ├── PartyContentTextData.json │ │ ├── PatchMark.json │ │ ├── Perform.json │ │ ├── PerformGroup.json │ │ ├── PerformTransient.json │ │ ├── Pet.json │ │ ├── PetAction.json │ │ ├── PetMirage.json │ │ ├── PhysicsGroup.json │ │ ├── PhysicsWind.json │ │ ├── Picture.json │ │ ├── PlaceName.json │ │ ├── PlantPotFlowerSeed.json │ │ ├── PlayerSearchLocation.json │ │ ├── PlayerSearchSubLocation.json │ │ ├── PreHandler.json │ │ ├── PresetCamera.json │ │ ├── PresetCameraAdjust.json │ │ ├── PreviewableItems.json │ │ ├── PublicContent.json │ │ ├── PublicContentCutscene.json │ │ ├── PublicContentTextData.json │ │ ├── PvPAction.json │ │ ├── PvPActionSort.json │ │ ├── PvPBaseParamValue.json │ │ ├── PvPRank.json │ │ ├── PvPSelectTrait.json │ │ ├── PvPSeries.json │ │ ├── PvPSeriesLevel.json │ │ ├── PvPTrait.json │ │ ├── Quest.json │ │ ├── QuestAcceptAdditionCondition.json │ │ ├── QuestBattle.json │ │ ├── QuestChapter.json │ │ ├── QuestClassJobReward.json │ │ ├── QuestClassJobSupply.json │ │ ├── QuestDefineClient.json │ │ ├── QuestDerivedClass.json │ │ ├── QuestEffect.json │ │ ├── QuestEffectDefine.json │ │ ├── QuestEventAreaEntranceInfo.json │ │ ├── QuestLinkMarker.json │ │ ├── QuestLinkMarkerIcon.json │ │ ├── QuestLinkMarkerSet.json │ │ ├── QuestRedo.json │ │ ├── QuestRedoChapterUI.json │ │ ├── QuestRedoChapterUICategory.json │ │ ├── QuestRedoChapterUITab.json │ │ ├── QuestRedoIncompChapter.json │ │ ├── QuestRepeatFlag.json │ │ ├── QuestRewardOther.json │ │ ├── QuestSelectTitle.json │ │ ├── QuestSetDefine.json │ │ ├── QuickChat.json │ │ ├── QuickChatTransient.json │ │ ├── RPParameter.json │ │ ├── Race.json │ │ ├── RacingChocoboItem.json │ │ ├── RacingChocoboName.json │ │ ├── RacingChocoboNameCategory.json │ │ ├── RacingChocoboNameInfo.json │ │ ├── RacingChocoboParam.json │ │ ├── RaidFinderParam.json │ │ ├── ReactionEventObject.json │ │ ├── ReactionEventObjectInfo.json │ │ ├── RecastNavimesh.json │ │ ├── Recipe.json │ │ ├── RecipeLevelTable.json │ │ ├── RecipeLookup.json │ │ ├── RecipeNotebookList.json │ │ ├── RecommendContents.json │ │ ├── Relic.json │ │ ├── Relic3.json │ │ ├── RelicItem.json │ │ ├── RelicNote.json │ │ ├── RelicNoteCategory.json │ │ ├── ReplaceAction.json │ │ ├── Resident.json │ │ ├── ResistanceWeaponAdjust.json │ │ ├── RetainerFortuneRewardRange.json │ │ ├── RetainerTask.json │ │ ├── RetainerTaskLvRange.json │ │ ├── RetainerTaskNormal.json │ │ ├── RetainerTaskParameter.json │ │ ├── RetainerTaskRandom.json │ │ ├── RideShooting.json │ │ ├── RideShootingTargetType.json │ │ ├── RideShootingTextData.json │ │ ├── SatisfactionArbitration.json │ │ ├── SatisfactionBonusGuarantee.json │ │ ├── SatisfactionNpc.json │ │ ├── SatisfactionSupply.json │ │ ├── SatisfactionSupplyReward.json │ │ ├── ScenarioTree.json │ │ ├── ScenarioTreeTips.json │ │ ├── ScenarioTreeTipsClassQuest.json │ │ ├── ScenarioType.json │ │ ├── ScreenImage.json │ │ ├── SecretRecipeBook.json │ │ ├── SharlayanCraftWorks.json │ │ ├── SharlayanCraftWorksSupply.json │ │ ├── ShellFixedFromCommand.json │ │ ├── SkyIsland2Mission.json │ │ ├── SkyIsland2MissionDetail.json │ │ ├── SkyIsland2MissionType.json │ │ ├── SkyIsland2RangeType.json │ │ ├── Snipe.json │ │ ├── SnipeTalk.json │ │ ├── SnipeTalkName.json │ │ ├── SpearfishingComboTarget.json │ │ ├── SpearfishingItem.json │ │ ├── SpearfishingNotebook.json │ │ ├── SpearfishingRecordPage.json │ │ ├── SpearfishingSilhouette.json │ │ ├── SpecialShop.json │ │ ├── SpecialShopItemCategory.json │ │ ├── Stain.json │ │ ├── StainTransient.json │ │ ├── StanceChange.json │ │ ├── Status.json │ │ ├── StatusHitEffect.json │ │ ├── StatusLoopVFX.json │ │ ├── Story.json │ │ ├── SubmarineExploration.json │ │ ├── SubmarineMap.json │ │ ├── SubmarinePart.json │ │ ├── SubmarineRank.json │ │ ├── SwitchTalk.json │ │ ├── SwitchTalkVariation.json │ │ ├── TelepoRelay.json │ │ ├── TerritoryType.json │ │ ├── TerritoryTypeTelepo.json │ │ ├── TerritoryTypeTransient.json │ │ ├── TextCommand.json │ │ ├── TextCommandParam.json │ │ ├── TiltParam.json │ │ ├── Title.json │ │ ├── TofuEditParam.json │ │ ├── TofuObject.json │ │ ├── TofuObjectCategory.json │ │ ├── TofuPreset.json │ │ ├── TofuPresetCategory.json │ │ ├── TofuPresetObject.json │ │ ├── Tomestones.json │ │ ├── TomestonesItem.json │ │ ├── TopicSelect.json │ │ ├── Town.json │ │ ├── Trait.json │ │ ├── TraitRecast.json │ │ ├── TraitTransient.json │ │ ├── Transformation.json │ │ ├── Treasure.json │ │ ├── TreasureHuntRank.json │ │ ├── TreasureModel.json │ │ ├── TreasureSpot.json │ │ ├── Tribe.json │ │ ├── TripleTriad.json │ │ ├── TripleTriadCard.json │ │ ├── TripleTriadCardObtain.json │ │ ├── TripleTriadCardRarity.json │ │ ├── TripleTriadCardResident.json │ │ ├── TripleTriadCardType.json │ │ ├── TripleTriadCompetition.json │ │ ├── TripleTriadResident.json │ │ ├── TripleTriadRule.json │ │ ├── Tutorial.json │ │ ├── TutorialDPS.json │ │ ├── TutorialHealer.json │ │ ├── TutorialTank.json │ │ ├── UDS_Event.json │ │ ├── UDS_Property.json │ │ ├── UIColor.json │ │ ├── UIConst.json │ │ ├── UILevelLookup.json │ │ ├── VFX.json │ │ ├── VVDData.json │ │ ├── VVDNotebookContents.json │ │ ├── VVDNotebookSeries.json │ │ ├── VVDRouteData.json │ │ ├── VVDVariantAction.json │ │ ├── VaseFlower.json │ │ ├── Warp.json │ │ ├── WarpCondition.json │ │ ├── WarpLogic.json │ │ ├── WeaponTimeline.json │ │ ├── Weather.json │ │ ├── WeatherGroup.json │ │ ├── WeatherRate.json │ │ ├── WeatherReportReplace.json │ │ ├── WebGuidance.json │ │ ├── WebURL.json │ │ ├── WeddingBGM.json │ │ ├── WeeklyBingoOrderData.json │ │ ├── WeeklyBingoRewardData.json │ │ ├── WeeklyBingoText.json │ │ ├── WeeklyLotBonus.json │ │ ├── World.json │ │ ├── WorldDCGroupType.json │ │ ├── YKW.json │ │ ├── YardCatalogCategory.json │ │ ├── YardCatalogItemList.json │ │ ├── ZoneSharedGroup.json │ │ └── game.ver │ ├── EorzeaDateTime.Formatter.cs │ ├── EorzeaDateTime.cs │ ├── Ex/ │ │ ├── Column.cs │ │ ├── DataReader.cs │ │ ├── DataReaders/ │ │ │ ├── DelegateDataReader.cs │ │ │ ├── PackedBooleanDataReader.cs │ │ │ └── StringDataReader.cs │ │ ├── DataRowBase.cs │ │ ├── DataSheet.Enumerator.cs │ │ ├── DataSheet.cs │ │ ├── ExCollection.cs │ │ ├── Header.cs │ │ ├── IDataRow.cs │ │ ├── IDataSheet.Generic.cs │ │ ├── IDataSheet.cs │ │ ├── IMultiRow.cs │ │ ├── IMultiSheet.Generic.cs │ │ ├── IMultiSheet.cs │ │ ├── IRow.cs │ │ ├── ISheet.Generic.cs │ │ ├── ISheet.cs │ │ ├── Language.cs │ │ ├── MultiRow.cs │ │ ├── MultiSheet.Enumerator.cs │ │ ├── MultiSheet.cs │ │ ├── PartialDataSheet.Enumerator.cs │ │ ├── PartialDataSheet.cs │ │ ├── Relational/ │ │ │ ├── Definition/ │ │ │ │ ├── GroupDataDefinition.cs │ │ │ │ ├── IDataDefinition.cs │ │ │ │ ├── PositionedDataDefinition.cs │ │ │ │ ├── RelationDefinition.cs │ │ │ │ ├── RepeatDataDefinition.cs │ │ │ │ ├── SheetDefinition.cs │ │ │ │ ├── SingleDataDefinition.cs │ │ │ │ └── ViewDefinition.cs │ │ │ ├── IRelationalDataRow.cs │ │ │ ├── IRelationalDataSheet.Generic.cs │ │ │ ├── IRelationalDataSheet.cs │ │ │ ├── IRelationalMultiRow.cs │ │ │ ├── IRelationalMultiSheet.Generic.cs │ │ │ ├── IRelationalMultiSheet.cs │ │ │ ├── IRelationalRow.cs │ │ │ ├── IRelationalSheet.Generic.cs │ │ │ ├── IRelationalSheet.cs │ │ │ ├── IValueConverter.cs │ │ │ ├── RelationalColumn.cs │ │ │ ├── RelationalDataIndex.cs │ │ │ ├── RelationalDataSheet.cs │ │ │ ├── RelationalExCollection.cs │ │ │ ├── RelationalHeader.cs │ │ │ ├── RelationalMultiRow.cs │ │ │ ├── RelationalMultiSheet.cs │ │ │ ├── RelationalPartialDataSheet.cs │ │ │ ├── Update/ │ │ │ │ ├── ChangeType.cs │ │ │ │ ├── Changes/ │ │ │ │ │ ├── DefinitionMoved.cs │ │ │ │ │ ├── DefinitionRemoved.cs │ │ │ │ │ ├── FieldChanged.cs │ │ │ │ │ ├── RowAdded.cs │ │ │ │ │ ├── RowRemoved.cs │ │ │ │ │ ├── SheetLanguageAdded.cs │ │ │ │ │ ├── SheetLanguageRemoved.cs │ │ │ │ │ ├── SheetRemoved.cs │ │ │ │ │ └── SheetTypeChanged.cs │ │ │ │ ├── ColumnComparer.cs │ │ │ │ ├── Comparer.cs │ │ │ │ ├── DefinitionUpdater.cs │ │ │ │ ├── IChange.cs │ │ │ │ ├── RelationUpdater.cs │ │ │ │ ├── SheetComparer.cs │ │ │ │ ├── SheetUpdater.cs │ │ │ │ └── UpdateProgress.cs │ │ │ └── ValueConverters/ │ │ │ ├── ColorConverter.cs │ │ │ ├── ComplexLinkConverter.cs │ │ │ ├── GenericReferenceConverter.cs │ │ │ ├── IconConverter.cs │ │ │ ├── MultiReferenceConverter.cs │ │ │ ├── QuadConverter.cs │ │ │ ├── SheetLinkConverter.cs │ │ │ └── TomestoneOrItemReferenceConverter.cs │ │ ├── Variant1/ │ │ │ ├── DataRow.cs │ │ │ └── RelationalDataRow.cs │ │ └── Variant2/ │ │ ├── DataRow.cs │ │ ├── RelationalDataRow.cs │ │ └── SubRow.cs │ ├── Graphics/ │ │ ├── Bone.cs │ │ ├── BoundingBox.cs │ │ ├── ColorMap.cs │ │ ├── Exports/ │ │ │ ├── ModelExport.cs │ │ │ └── Obj.cs │ │ ├── ImcFile.cs │ │ ├── ImcPart.cs │ │ ├── ImcVariant.cs │ │ ├── Lgb/ │ │ │ ├── ILgbEntry.cs │ │ │ ├── LgbENpcEntry.cs │ │ │ ├── LgbEntryType.cs │ │ │ ├── LgbEventObjectEntry.cs │ │ │ ├── LgbFile.cs │ │ │ ├── LgbGimmickEntry.cs │ │ │ ├── LgbGroup.cs │ │ │ ├── LgbLightEntry.cs │ │ │ ├── LgbModelEntry.cs │ │ │ └── LgbVfxEntry.cs │ │ ├── Material.cs │ │ ├── MaterialDefinition.cs │ │ ├── MaterialHeader.cs │ │ ├── MaterialMetadataHeader.cs │ │ ├── MaterialTextureParameter.cs │ │ ├── Mesh.cs │ │ ├── MeshHeader.cs │ │ ├── MeshPart.cs │ │ ├── MeshPartHeader.cs │ │ ├── Model.cs │ │ ├── ModelAttribute.cs │ │ ├── ModelBoundingBoxes.cs │ │ ├── ModelDefinition.cs │ │ ├── ModelDefinitionHeader.cs │ │ ├── ModelFile.cs │ │ ├── ModelHeader.cs │ │ ├── ModelQuality.cs │ │ ├── ModelVariantIdentifier.cs │ │ ├── PapAnimation.cs │ │ ├── PapFile.cs │ │ ├── Pcb/ │ │ │ ├── IPcbBlockData.cs │ │ │ ├── PcbBlockData.cs │ │ │ ├── PcbBlockDataType.cs │ │ │ ├── PcbBlockEntry.cs │ │ │ └── PcbFile.cs │ │ ├── Sgb/ │ │ │ ├── ISgbData.cs │ │ │ ├── ISgbGroupEntry.cs │ │ │ ├── SgbDataType.cs │ │ │ ├── SgbFile.cs │ │ │ ├── SgbGimmickEntry.cs │ │ │ ├── SgbGroup.cs │ │ │ ├── SgbGroup1CEntry.cs │ │ │ ├── SgbGroupEntryType.cs │ │ │ ├── SgbLightEntry.cs │ │ │ ├── SgbModelEntry.cs │ │ │ └── SgbVfxEntry.cs │ │ ├── ShPk/ │ │ │ ├── Parameter.cs │ │ │ ├── ParameterHeader.cs │ │ │ ├── ParameterType.cs │ │ │ ├── ShPkFile.cs │ │ │ ├── ShPkHeader.cs │ │ │ ├── ShaderHeader.cs │ │ │ ├── ShaderParameterReference.cs │ │ │ └── ShaderType.cs │ │ ├── SklbFile.cs │ │ ├── Territory.cs │ │ ├── TerritoryParts/ │ │ │ └── Terrain.cs │ │ ├── TransformedModel.cs │ │ ├── Unknowns/ │ │ │ ├── BoneIndices.cs │ │ │ ├── BoneList.cs │ │ │ ├── MaterialStruct1.cs │ │ │ ├── MaterialStruct2.cs │ │ │ ├── ModelStruct1.cs │ │ │ ├── ModelStruct2.cs │ │ │ ├── ModelStruct3.cs │ │ │ ├── ModelStruct5.cs │ │ │ ├── ModelStruct6.cs │ │ │ └── ModelStruct7.cs │ │ ├── Vector2.cs │ │ ├── Vector3.cs │ │ ├── Vector4.cs │ │ ├── Vertex.cs │ │ ├── VertexAttribute.cs │ │ ├── VertexDataType.cs │ │ ├── VertexFormat.cs │ │ ├── VertexFormatElement.cs │ │ └── VertexReader.cs │ ├── HalfHelper.cs │ ├── IO/ │ │ ├── Directory.Enumeration.cs │ │ ├── Directory.cs │ │ ├── EmptyFile.cs │ │ ├── File.cs │ │ ├── FileCommonHeader.cs │ │ ├── FileDefault.cs │ │ ├── FileFactory.cs │ │ ├── FileType.cs │ │ ├── Hash.cs │ │ ├── IIndexFile.cs │ │ ├── IPackSource.cs │ │ ├── Index.cs │ │ ├── Index2.cs │ │ ├── Index2File.cs │ │ ├── Index2Header.cs │ │ ├── Index2Source.Enumerator.cs │ │ ├── Index2Source.cs │ │ ├── IndexDirectory.cs │ │ ├── IndexFile.cs │ │ ├── IndexHeader.cs │ │ ├── IndexSource.Enumerator.cs │ │ ├── IndexSource.cs │ │ ├── Pack.cs │ │ ├── PackCollection.cs │ │ └── PackIdentifier.cs │ ├── Imaging/ │ │ ├── GftdEntry.cs │ │ ├── GraphicsFileTextureDefinition.cs │ │ ├── IconHelper.cs │ │ ├── ImageConverter.cs │ │ ├── ImageFile.cs │ │ ├── ImageFormat.cs │ │ └── ImageHeader.cs │ ├── Libra/ │ │ ├── Achievement.cs │ │ ├── AchievementCategory.cs │ │ ├── AchievementKind.cs │ │ ├── Action.cs │ │ ├── BNpcName.Parse.cs │ │ ├── BNpcName.cs │ │ ├── BNpcName_PlaceName.cs │ │ ├── BaseParam.cs │ │ ├── BeastTribe.cs │ │ ├── ClassJob.cs │ │ ├── ClassJobCategory.cs │ │ ├── ClassJob_ClassJobCategory.cs │ │ ├── Colosseum.cs │ │ ├── ContentRoulette.cs │ │ ├── ContentType.cs │ │ ├── CraftType.cs │ │ ├── ENpcResident.Parse.cs │ │ ├── ENpcResident.cs │ │ ├── ENpcResident_PlaceName.cs │ │ ├── ENpcResident_Quest.cs │ │ ├── Emote.cs │ │ ├── Entities.cs │ │ ├── FCHierarchy.cs │ │ ├── FCRank.cs │ │ ├── FCReputation.cs │ │ ├── Frontline01.cs │ │ ├── GCRankGridaniaFemaleText.cs │ │ ├── GCRankGridaniaMaleText.cs │ │ ├── GCRankLimsaFemaleText.cs │ │ ├── GCRankLimsaMaleText.cs │ │ ├── GCRankUldahFemaleText.cs │ │ ├── GCRankUldahMaleText.cs │ │ ├── Gathering.cs │ │ ├── GatheringType.cs │ │ ├── GeneralAction.cs │ │ ├── GrandCompany.cs │ │ ├── GuardianDeity.cs │ │ ├── GuildOrder.cs │ │ ├── InstanceContent.cs │ │ ├── InstanceContentType.cs │ │ ├── Item.Parse.cs │ │ ├── Item.cs │ │ ├── ItemCategory.cs │ │ ├── ItemSery.cs │ │ ├── ItemSpecialBonu.cs │ │ ├── ItemUICategory.cs │ │ ├── ItemUIKind.cs │ │ ├── Item_ClassJob.cs │ │ ├── JournalCategory.cs │ │ ├── JournalGenre.cs │ │ ├── JsonReaderExtensions.cs │ │ ├── LibraModel.Context.cs │ │ ├── LibraModel.Context.tt │ │ ├── LibraModel.Designer.cs │ │ ├── LibraModel.cs │ │ ├── LibraModel.edmx │ │ ├── LibraModel.edmx.diagram │ │ ├── LibraModel.tt │ │ ├── LodestoneSystemDefine.cs │ │ ├── NotebookDivision.cs │ │ ├── PlaceName.cs │ │ ├── Quest.cs │ │ ├── QuestWebEx.cs │ │ ├── QuestWebType.cs │ │ ├── Quest_ClassJob.cs │ │ ├── Race.cs │ │ ├── Recipe.cs │ │ ├── RecipeElement.cs │ │ ├── Shop.cs │ │ ├── Status.cs │ │ ├── Title.cs │ │ ├── Tomestone.cs │ │ └── Town.cs │ ├── OrderedBitConverter.cs │ ├── Range.cs │ ├── SaintCoinach.csproj │ ├── Sound/ │ │ ├── ScdAdpcmEntry.cs │ │ ├── ScdCodec.cs │ │ ├── ScdEntry.cs │ │ ├── ScdEntryHeader.cs │ │ ├── ScdFile.cs │ │ ├── ScdHeader.cs │ │ ├── ScdOggCryptType.cs │ │ └── ScdOggEntry.cs │ ├── Text/ │ │ ├── DecodeExpressionType.cs │ │ ├── DefaultEvaluationFunctionProvider.cs │ │ ├── EvaluationHelper.cs │ │ ├── EvaluationParameters.cs │ │ ├── Expressions/ │ │ │ ├── CloseTag.cs │ │ │ ├── ExpressionCollection.cs │ │ │ ├── GenericExpression.cs │ │ │ ├── IValueExpression.cs │ │ │ ├── ObjectWithDisplay.cs │ │ │ ├── OpenTag.cs │ │ │ └── SurroundedExpression.cs │ │ ├── IEvaluationFunctionProvider.cs │ │ ├── IExpression.cs │ │ ├── INode.cs │ │ ├── IntegerType.cs │ │ ├── NodeFlags.cs │ │ ├── Nodes/ │ │ │ ├── ArgumentCollection.cs │ │ │ ├── CloseTag.cs │ │ │ ├── Comparison.cs │ │ │ ├── DefaultElement.cs │ │ │ ├── EmptyElement.cs │ │ │ ├── GenericElement.cs │ │ │ ├── IConditionalNode.cs │ │ │ ├── IExpressionNode.cs │ │ │ ├── INodeVisitor.cs │ │ │ ├── INodeWithArguments.cs │ │ │ ├── INodeWithChildren.cs │ │ │ ├── IStaticNode.cs │ │ │ ├── IfElement.cs │ │ │ ├── IfEqualsElement.cs │ │ │ ├── OpenTag.cs │ │ │ ├── Parameter.cs │ │ │ ├── StaticByteArray.cs │ │ │ ├── StaticInteger.cs │ │ │ ├── StaticString.cs │ │ │ ├── SwitchElement.cs │ │ │ └── TopLevelParameter.cs │ │ ├── Parameters/ │ │ │ ├── ObjectParameters.cs │ │ │ ├── ParameterBase.cs │ │ │ └── PlayerParameters.cs │ │ ├── Processor/ │ │ │ ├── Expressions/ │ │ │ │ ├── EmptyExpression.cs │ │ │ │ ├── ExpressionCollection.cs │ │ │ │ └── StaticExpression.cs │ │ │ └── IExpression.cs │ │ ├── StringTokens.cs │ │ ├── TagType.cs │ │ ├── XivString.cs │ │ └── XivStringDecoder.cs │ ├── UpdateReport.cs │ ├── Xiv/ │ │ ├── Achievement.cs │ │ ├── AchievementCategory.cs │ │ ├── AchievementKind.cs │ │ ├── Action.cs │ │ ├── ActionBase.cs │ │ ├── ActionCategory.cs │ │ ├── ActionTransient.cs │ │ ├── Addon.cs │ │ ├── Adventure.cs │ │ ├── AetherCurrent.cs │ │ ├── AirshipExplorationLevel.cs │ │ ├── AirshipExplorationLog.cs │ │ ├── AirshipExplorationParamType.cs │ │ ├── AirshipExplorationPoint.cs │ │ ├── BGM.cs │ │ ├── BNpc.cs │ │ ├── BNpcBase.cs │ │ ├── BNpcData.cs │ │ ├── BNpcLocation.cs │ │ ├── BNpcName.cs │ │ ├── BaseParam.cs │ │ ├── BeastReputationRank.cs │ │ ├── BeastTribe.cs │ │ ├── BuddyAction.cs │ │ ├── BuddyEquip.cs │ │ ├── CharaMakeCustomize.cs │ │ ├── CharaMakeType.cs │ │ ├── ChocoboRace.cs │ │ ├── ChocoboRaceAbility.cs │ │ ├── ChocoboRaceAbilityType.cs │ │ ├── ChocoboRaceItem.cs │ │ ├── ChocoboRaceRank.cs │ │ ├── ChocoboRaceStatus.cs │ │ ├── ChocoboRaceTerritory.cs │ │ ├── ClassJob.cs │ │ ├── ClassJobActionBase.cs │ │ ├── ClassJobCategory.cs │ │ ├── Collections/ │ │ │ ├── BNpcCollection.cs │ │ │ ├── ClassJobActionCollection.cs │ │ │ ├── ENpcCollection.cs │ │ │ ├── EquipSlotCollection.cs │ │ │ ├── ItemCollection.cs │ │ │ └── ShopCollection.cs │ │ ├── Companion.cs │ │ ├── CompanionTransient.cs │ │ ├── CompanyAction.cs │ │ ├── CompanyCraftDraft.cs │ │ ├── CompanyCraftDraftCategory.cs │ │ ├── CompanyCraftDraftCategoryTxt.cs │ │ ├── CompanyCraftManufactoryState.cs │ │ ├── CompanyCraftPart.cs │ │ ├── CompanyCraftProcess.Request.cs │ │ ├── CompanyCraftProcess.cs │ │ ├── CompanyCraftSequence.cs │ │ ├── CompanyCraftSupplyItem.cs │ │ ├── CompanyCraftType.cs │ │ ├── ContentBase.cs │ │ ├── ContentFinderCondition.cs │ │ ├── ContentMemberType.cs │ │ ├── ContentReward.cs │ │ ├── ContentRoulette.cs │ │ ├── ContentType.cs │ │ ├── CraftAction.cs │ │ ├── CraftLeve.cs │ │ ├── CraftLeveItem.cs │ │ ├── CraftLevelDifference.cs │ │ ├── CraftType.cs │ │ ├── CustomTalk.cs │ │ ├── ENpc.cs │ │ ├── ENpcBase.cs │ │ ├── ENpcResident.cs │ │ ├── EObj.cs │ │ ├── Emote.cs │ │ ├── EmoteCategory.cs │ │ ├── EquipSlot.cs │ │ ├── EquipSlotCategory.cs │ │ ├── EventAction.cs │ │ ├── EventItem.cs │ │ ├── FCRank.cs │ │ ├── Fate.cs │ │ ├── FccShop.cs │ │ ├── FishParameter.cs │ │ ├── FishingSpot.cs │ │ ├── GCScripShopCategory.cs │ │ ├── GCScripShopItem.cs │ │ ├── GCShop.cs │ │ ├── GCShopItemCategory.cs │ │ ├── GatheringCondition.cs │ │ ├── GatheringExp.cs │ │ ├── GatheringItem.cs │ │ ├── GatheringItemBase.cs │ │ ├── GatheringNotebookList.cs │ │ ├── GatheringNotebookRegion.cs │ │ ├── GatheringPoint.cs │ │ ├── GatheringPointBase.cs │ │ ├── GatheringPointBonus.cs │ │ ├── GatheringPointBonusType.cs │ │ ├── GatheringPointName.cs │ │ ├── GatheringSubCategory.cs │ │ ├── GatheringType.cs │ │ ├── GeneralAction.cs │ │ ├── GenericLocation.cs │ │ ├── GilShop.cs │ │ ├── GilShopItem.cs │ │ ├── GoldSaucerTextData.cs │ │ ├── GrandCompany.cs │ │ ├── GrandCompanyRank.cs │ │ ├── GuardianDeity.cs │ │ ├── HousingFurniture.cs │ │ ├── HousingItem.cs │ │ ├── HousingItemCategory.cs │ │ ├── HousingLayoutLimit.cs │ │ ├── HousingYardObject.cs │ │ ├── IContentReward.cs │ │ ├── IItemSource.cs │ │ ├── ILocatable.cs │ │ ├── ILocation.cs │ │ ├── IParameterObject.cs │ │ ├── IQuantifiable.cs │ │ ├── IQuantifiableXivString.cs │ │ ├── IShop.cs │ │ ├── IShopListing.cs │ │ ├── IShopListingItem.cs │ │ ├── IXivRow.cs │ │ ├── IXivSheet.Generic.cs │ │ ├── IXivSheet.cs │ │ ├── IXivSubRow.cs │ │ ├── InstanceContent.cs │ │ ├── InstanceContentData.Fight.cs │ │ ├── InstanceContentData.RewardItem.cs │ │ ├── InstanceContentData.Treasure.cs │ │ ├── InstanceContentData.cs │ │ ├── Item.cs │ │ ├── ItemAction.cs │ │ ├── ItemActions/ │ │ │ ├── AchievementScroll.cs │ │ │ ├── AdventureBook.cs │ │ │ ├── AetherytePendulum.cs │ │ │ ├── AttributeReset.cs │ │ │ ├── BuddyEquipUnlock.cs │ │ │ ├── BuddySummon.cs │ │ │ ├── ChocoboActionReset.cs │ │ │ ├── ChocoboFeed.cs │ │ │ ├── ChocoboLevelCapIncrease.cs │ │ │ ├── CompanionUnlock.cs │ │ │ ├── CompanyEffect.cs │ │ │ ├── CustomizeUnlock.cs │ │ │ ├── DesynthSkillReset.cs │ │ │ ├── Enhancement.cs │ │ │ ├── EquipmentCoffer.cs │ │ │ ├── EternalBondInvitation.cs │ │ │ ├── EternityRing.cs │ │ │ ├── Fantasia.cs │ │ │ ├── FateContentAction.cs │ │ │ ├── FieldNoteUnlock.cs │ │ │ ├── Fireworks.cs │ │ │ ├── FolkloreBook.cs │ │ │ ├── Food.cs │ │ │ ├── FriendlyEffect.cs │ │ │ ├── GpRecovery.cs │ │ │ ├── Heavenscracker.cs │ │ │ ├── HostileEffect.cs │ │ │ ├── HpMpRecovery.cs │ │ │ ├── HpRecovery.cs │ │ │ ├── ItemRoulette.cs │ │ │ ├── MgpCard.cs │ │ │ ├── MountUnlock.cs │ │ │ ├── MpRecovery.cs │ │ │ ├── OrchestrionRollUnlock.cs │ │ │ ├── OrnamentUnlock.cs │ │ │ ├── PointRecovery.cs │ │ │ ├── Raise.cs │ │ │ ├── RealmRebornRed.cs │ │ │ ├── RecipeBookUnlock.cs │ │ │ ├── Sanction.cs │ │ │ ├── SphereScroll.cs │ │ │ ├── StatusRemoval.cs │ │ │ ├── SustainPotion.cs │ │ │ ├── TeleportationTicket.cs │ │ │ ├── TpRecovery.cs │ │ │ └── TripleTriadCardUnlock.cs │ │ ├── ItemBase.cs │ │ ├── ItemComparer.cs │ │ ├── ItemFood.cs │ │ ├── ItemLevel.cs │ │ ├── ItemSearchCategory.cs │ │ ├── ItemSeries.cs │ │ ├── ItemSpecialBonus.cs │ │ ├── ItemUICategory.cs │ │ ├── Items/ │ │ │ ├── Accessory.cs │ │ │ ├── Armour.cs │ │ │ ├── CraftingTool.cs │ │ │ ├── Equipment.cs │ │ │ ├── GatheringTool.cs │ │ │ ├── Housing.cs │ │ │ ├── MagicWeapon.cs │ │ │ ├── MateriaItem.cs │ │ │ ├── PhysicalWeapon.cs │ │ │ ├── Shield.cs │ │ │ ├── SoulCrystal.cs │ │ │ ├── Usable.cs │ │ │ └── Weapon.cs │ │ ├── JournalCategory.cs │ │ ├── JournalGenre.cs │ │ ├── JournalSection.cs │ │ ├── Leve.cs │ │ ├── LeveAssignmentType.cs │ │ ├── LeveClient.cs │ │ ├── LeveRewardItem.cs │ │ ├── LeveRewardItemGroup.cs │ │ ├── LeveVfx.cs │ │ ├── Level.cs │ │ ├── LogFilter.cs │ │ ├── LogKind.cs │ │ ├── LogKindCategoryText.cs │ │ ├── LogMessage.cs │ │ ├── MYCWarResultNotebook.cs │ │ ├── MainCommand.cs │ │ ├── MainCommandCategory.cs │ │ ├── Map.cs │ │ ├── MasterpieceSupplyDuty.CollectableItem.cs │ │ ├── MasterpieceSupplyDuty.cs │ │ ├── Materia.cs │ │ ├── MinionRace.cs │ │ ├── MinionSkillType.cs │ │ ├── ModelChara.cs │ │ ├── MonsterNote.cs │ │ ├── MonsterNoteTarget.cs │ │ ├── Mount.cs │ │ ├── NpcEquip.cs │ │ ├── OnlineStatus.cs │ │ ├── Ornament.cs │ │ ├── ParamGrow.cs │ │ ├── Parameter.cs │ │ ├── ParameterCollection.cs │ │ ├── ParameterType.cs │ │ ├── ParameterValue.cs │ │ ├── ParameterValueFixed.cs │ │ ├── ParameterValueRelative.cs │ │ ├── ParameterValueRelativeLimited.cs │ │ ├── PlaceName.cs │ │ ├── PrerequisiteQuestsRequirement.cs │ │ ├── PrerequisiteQuestsRequirementType.cs │ │ ├── ProbabilityPair.cs │ │ ├── Quad.cs │ │ ├── Quest.cs │ │ ├── QuestRepeatInterval.cs │ │ ├── QuestRequirements.ClassJobRequirement.cs │ │ ├── QuestRequirements.InstanceContentRequirement.cs │ │ ├── QuestRequirements.PreviousQuestRequirement.cs │ │ ├── QuestRequirements.cs │ │ ├── QuestRewardGroupType.cs │ │ ├── QuestRewardItem.cs │ │ ├── QuestRewardItemGroup.cs │ │ ├── QuestRewardOther.cs │ │ ├── QuestRewards.cs │ │ ├── Race.cs │ │ ├── RacingChocoboItem.cs │ │ ├── RacingChocoboName.cs │ │ ├── RacingChocoboNameCategory.cs │ │ ├── RacingChocoboNameInfo.cs │ │ ├── RacingChocoboParam.cs │ │ ├── Recipe.cs │ │ ├── RecipeElement.cs │ │ ├── RecipeIngredient.cs │ │ ├── RecipeIngredientType.cs │ │ ├── RecipeLevel.cs │ │ ├── RecipeLevelTable.cs │ │ ├── RelicNote.cs │ │ ├── RelicNoteCategory.cs │ │ ├── RelicNoteCategoryText.cs │ │ ├── RetainerTask.cs │ │ ├── RetainerTaskBase.cs │ │ ├── RetainerTaskNormal.cs │ │ ├── RetainerTaskRandom.cs │ │ ├── Salvage.cs │ │ ├── Sheets/ │ │ │ ├── InventoryItemSheet.cs │ │ │ └── ItemActionSheet.cs │ │ ├── ShopListingItem.cs │ │ ├── SpearfishingItem.cs │ │ ├── SpecialShop.cs │ │ ├── SpecialShopListing.cs │ │ ├── Stain.cs │ │ ├── Status.cs │ │ ├── TerritoryType.cs │ │ ├── Title.cs │ │ ├── Tomestone.cs │ │ ├── TomestonesItem.cs │ │ ├── Trait.cs │ │ ├── Tribe.cs │ │ ├── TripleTriad.cs │ │ ├── TripleTriadCard.cs │ │ ├── TripleTriadCardRarity.cs │ │ ├── TripleTriadCardResident.cs │ │ ├── TripleTriadCardType.cs │ │ ├── TripleTriadCompetition.cs │ │ ├── TripleTriadRule.cs │ │ ├── Weather.cs │ │ ├── WeatherRate.cs │ │ ├── XivCollection.cs │ │ ├── XivRow.cs │ │ ├── XivSheet.Enumerator.cs │ │ ├── XivSheet.cs │ │ ├── XivSheet2.Enumerator.cs │ │ ├── XivSheet2.cs │ │ ├── XivSheetAttribute.cs │ │ └── XivSubRow.cs │ ├── app.config │ └── packages.config ├── SaintCoinach.Cmd/ │ ├── App.config │ ├── Commands/ │ │ ├── AllExdCommand.cs │ │ ├── AllExdRawCommand.cs │ │ ├── BgmCommand.cs │ │ ├── ExdCommand.cs │ │ ├── ExdHeaderCommand.cs │ │ ├── FurnitureExpCommand.cs │ │ ├── HDUiCommand.cs │ │ ├── ImageCommand.cs │ │ ├── LanguageCommand.cs │ │ ├── MapCommand.cs │ │ ├── RawCommand.cs │ │ ├── RawExdCommand.cs │ │ ├── SqlExport.cs │ │ └── UiCommand.cs │ ├── ExdHelper.cs │ ├── Program.cs │ ├── Properties/ │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ └── SaintCoinach.Cmd.csproj ├── SaintCoinach.Graphics.Viewer/ │ ├── AnimatedModel.cs │ ├── Animation.cs │ ├── AnimationContainer.cs │ ├── AnimationPlayer.cs │ ├── Camera.cs │ ├── Component.cs │ ├── ComponentContainer.cs │ ├── Content/ │ │ ├── BgColorChangeMaterial.cs │ │ ├── BgMaterial.cs │ │ ├── CharacterMaterial.cs │ │ ├── ContentMesh.cs │ │ ├── ContentMeshPart.cs │ │ ├── ContentModel.cs │ │ ├── ContentSgb.cs │ │ ├── ContentTerritory.cs │ │ ├── CrystalMaterial.cs │ │ ├── Cube.cs │ │ ├── HairMaterial.cs │ │ ├── IrisMaterial.cs │ │ ├── MaterialBase.cs │ │ ├── PrimitiveMesh.cs │ │ ├── PrimitiveModel.cs │ │ └── SkinMaterial.cs │ ├── Data/ │ │ ├── CustomizeParameters.cs │ │ └── ParametersBase.cs │ ├── Drawable3DComponent.cs │ ├── EffectFactory.cs │ ├── Effects/ │ │ ├── BgColorChangeEffect.cs │ │ ├── BgEffect.cs │ │ ├── CharacterEffect.cs │ │ ├── CrystalEffect.cs │ │ ├── CustomizeParameterEffectVariable.cs │ │ ├── DirectionalLight.cs │ │ ├── EffectBase.cs │ │ ├── EffectDirectionalLightVariable.cs │ │ ├── EffectTextureVariable.cs │ │ ├── HLSL/ │ │ │ ├── BasicEffect.fx │ │ │ ├── Bg.fx │ │ │ ├── BgColorChange.fx │ │ │ ├── BgUvScroll.fx │ │ │ ├── Character.fx │ │ │ ├── Common.Skinned.fxh │ │ │ ├── Common.fxh │ │ │ ├── Crystal.fx │ │ │ ├── CustomizeParameter.fxh │ │ │ ├── Hair.fx │ │ │ ├── Iris.fx │ │ │ ├── Lighting.fxh │ │ │ ├── Skin.fx │ │ │ └── Structures.fxh │ │ ├── HairEffect.cs │ │ ├── IrisEffect.cs │ │ ├── SkinEffect.cs │ │ └── SkinnedEffect.cs │ ├── Engine.cs │ ├── EngineTime.cs │ ├── FormEngine.cs │ ├── FormInputService.cs │ ├── IComponent.cs │ ├── IContentComponent.cs │ ├── IDrawable3DComponent.cs │ ├── IImageRendererSource.cs │ ├── IInputService.cs │ ├── IUpdateableComponent.cs │ ├── ImageRenderer.cs │ ├── Interop/ │ │ ├── FbxExport.cs │ │ ├── HavokInterop.cs │ │ ├── InteropAnimation.cs │ │ ├── InteropMesh.cs │ │ ├── InteropTransform.cs │ │ ├── InteropVector4.cs │ │ └── InteropVertex.cs │ ├── Keyboard.cs │ ├── MaterialFactory.cs │ ├── ModelFactory.cs │ ├── Mouse.cs │ ├── MouseState.cs │ ├── ParametersExtensions.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── RendererSources/ │ │ ├── BaseImageRendererSource.cs │ │ ├── EquipmentImageRendererSource.cs │ │ └── MonsterImageRendererSource.cs │ ├── SaintCoinach.Graphics.Viewer.csproj │ ├── Skeleton.cs │ ├── TextureFactory.cs │ ├── VectorConverter.cs │ ├── Vertex3D.cs │ └── app.config └── SaintCoinach.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true # Code [*.cs] indent_size = 4 indent_style = space csharp_new_line_before_open_brace = all:warning ================================================ FILE: .gitattributes ================================================ # Auto detect text files and perform LF normalization * text=auto # Custom for Visual Studio *.cs diff=csharp *.sln merge=union *.csproj merge=union *.vbproj merge=union *.fsproj merge=union *.dbproj merge=union # Standard to msysgit *.doc diff=astextplain *.DOC diff=astextplain *.docx diff=astextplain *.DOCX diff=astextplain *.dot diff=astextplain *.DOT diff=astextplain *.pdf diff=astextplain *.PDF diff=astextplain *.rtf diff=astextplain *.RTF diff=astextplain ================================================ FILE: .github/RELEASE.md ================================================ This is an automated SaintCoinach release based on recent changes. Please note that your Anti-Virus program may detect SaintCoinach as a false positive. ================================================ FILE: .github/workflows/build.yml ================================================ name: Build SaintCoinach on: [push, pull_request] concurrency: build-${{ github.ref }} jobs: build: name: Build on Windows runs-on: windows-2025 steps: - uses: actions/checkout@v2 - name: Setup .NET uses: actions/setup-dotnet@v1 with: dotnet-version: '7.x.x' - name: Define Version id: define-version run: | $env:COMMIT = $env:GITHUB_SHA.Substring(0, 7) echo "::set-output name=commit::$env:COMMIT" - name: Restore dependencies run: dotnet restore - name: Build run: dotnet build -c Release - name: Create Release ZIPs if: ${{ github.ref == 'refs/heads/master' }} run: | mkdir release-out Compress-Archive -Path .\Godbert\bin\Release\net7.0-windows\* -DestinationPath .\release-out\Godbert.zip -Force Compress-Archive -Path .\SaintCoinach.Cmd\bin\Release\net7.0\* -DestinationPath .\release-out\SaintCoinach.Cmd.zip -Force - name: Create Release uses: softprops/action-gh-release@v1 if: ${{ github.ref == 'refs/heads/master' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: release-out/** name: Release for ${{ steps.define-version.outputs.commit }} body_path: .github\RELEASE.md prerelease: false tag_name: ${{ steps.define-version.outputs.commit }} ================================================ FILE: .github/workflows/json-validation.yaml ================================================ name: Validate ex JSON definitions # This workflow is triggered on pushes to the repository. on: [push, pull_request] jobs: build: name: validate-json runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Install jsonlint run: sudo npm install jsonlint -g - name: Validate schemas run: set -e; for f in $(find SaintCoinach/Definitions/ -name *.json -print); do echo -n "$f - "; jsonlint $f -q; echo "OK!"; done ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Roslyn cache directories *.ide/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* #NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile .vs/ # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # If using the old MSBuild-Integrated Package Restore, uncomment this: #!**/packages/repositories.config # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Others sql/ *.Cache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # ========================= # Operating System Files # ========================= # OSX # ========================= .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk # Windows # ========================= # Windows image file caches Thumbs.db ehthumbs.db # Folder config file Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msm *.msp # DX Things *.tkb *.StyleCop # JetBrains .idea SaintCoinach.Cmd/Properties/launchSettings.json ================================================ FILE: DotSquish/Alpha.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DotSquish { internal static class Alpha { #region DXT3 private static int FloatToInt(float a, int limit) { // Use ANSI round-to-zero behaviour to get round-to-nearest. var i = (int)(a + .5f); if (i < 0) i = 0; else if (i > limit) i = limit; return i; } public static void CompressAlphaDxt3(byte[] rgba, byte[] target, int targetOffset, int mask) { // Quantise and pack alpha values pairwise. for (int i = 0; i < 8; ++i) { // Qnatise down to 4 bits. var alpha1 = rgba[8 * i + 3] * (15f / 255f); var alpha2 = rgba[8 * i + 7] * (15f / 255f); var quant1 = FloatToInt(alpha1, 15); var quant2 = FloatToInt(alpha2, 15); // Set alpha to zero where masked. var bit1 = 1 << (2 * i); var bit2 = 1 << (2 * i + 1); if ((mask & bit1) == 0) quant1 = 0; if ((mask & bit2) == 0) quant2 = 0; // Pack into the byte. target[targetOffset + i] = (byte)(quant1 | (quant2 << 4)); } } public static void DecompressAlphaDxt3(byte[] block, int blockOffset, byte[] target, int targetOffset) { // Unpack the alpha values pairwise. for (int i = 0; i < 8; ++i) { // Quantise down to 4 bits. var quant = block[blockOffset + i]; // Unpack the values. var lo = quant & 0x0f; var hi = quant & 0xf0; // Convert back up to bytes. target[targetOffset + 8 * i + 3] = (byte)(lo | (lo << 4)); target[targetOffset + 8 * i + 7] = (byte)(hi | (hi >> 4)); } } #endregion #region DXT5 private static void FixRange(ref int min, ref int max, int steps) { if (max - min < steps) max = Math.Min(min + steps, 255); if (max - min < steps) min = Math.Max(0, max - steps); } private static int FitCodes(byte[] rgba, int mask, byte[] codes, out byte[] indices) { indices = new byte[16]; // Fit each alpha value to the codebook. var err = 0; for (int i = 0; i < 16; ++i) { // Check this pixel is valid. var bit = 1 << i; if ((mask & bit) == 0) { // Use the first code. indices[i] = 0; continue; } // Find the least error and corresponding index. var value = rgba[4 * i + 3]; var least = int.MaxValue; var index = 0; for (int j = 0; j < 8; ++j) { // Get the squared error from this code. var dist = ((int)value) - ((int)codes[j]); dist *= dist; // Compare with the best so far. if (dist < least) { least = dist; index = j; } } // Save this index and accumulate the error. indices[i] = (byte)index; err += least; } // Return the total error. return err; } private static void WriteAlphaBlock(int alpha0, int alpha1, byte[] indices, byte[] target, int targetOffset) { // Write the first two bytes. target[targetOffset + 0] = (byte)alpha0; target[targetOffset + 1] = (byte)alpha1; var indOff = 0; var retOff = 2; for (int i = 0; i < 2; ++i) { // Pack 8 3-bit values. var value = 0; for (int j = 0; j < 8; ++j) { var index = indices[indOff++]; value |= (index << 3 * j); } // Store in 3 bytes for (int j = 0; j < 3; ++j) { var b = (value >> (8 * j)) & 0xFF; target[targetOffset + retOff++] = (byte)b; } } } private static void WriteAlphaBlock5(int alpha0, int alpha1, byte[] indices, byte[] target, int targetOffset) { // Check the relative values of the endpoints. if (alpha0 > alpha1) { var swapped = new byte[16]; for (int i = 0; i < 16; ++i) { var index = indices[i]; if (index == 0) swapped[i] = 1; else if (index == 1) swapped[i] = 0; else if (index <= 5) swapped[i] = (byte)(7 - index); else swapped[i] = index; } // Write the block. WriteAlphaBlock(alpha1, alpha0, swapped, target, targetOffset); } else { // Write the block. WriteAlphaBlock(alpha0, alpha1, indices, target, targetOffset); } } private static void WriteAlphaBlock7(int alpha0, int alpha1, byte[] indices, byte[] target, int targetOffset) { // Check the relative values of the endpoints. if (alpha0 > alpha1) { var swapped = new byte[16]; for (int i = 0; i < 16; ++i) { var index = indices[i]; if (index == 0) swapped[i] = 1; else if (index == 1) swapped[i] = 0; else swapped[i] = (byte)(9 - index); } // Write the block. WriteAlphaBlock(alpha1, alpha0, swapped, target, targetOffset); } else { // Write the block. WriteAlphaBlock(alpha0, alpha1, indices, target, targetOffset); } } public static void CompressAlphaDxt5(byte[] rgba, int mask, byte[] target, int targetOffset) { // Get the range for 5-alpha and 7-alpha interpolation. int min5 = 255, max5 = 0; int min7 = 255, max7 = 0; for (int i = 0; i < 16; ++i) { // Check this pixel is valid. var bit = 1 << i; if ((mask & bit) == 0) continue; // Incorporate into the min/max. int value = rgba[4 * i + 3]; if (value < min7) min7 = value; if (value > max7) max7 = value; if (value != 0 && value < min5) min5 = value; if (value != 255 && value > max5) max5 = value; } // Handle the case that no valid range was found. if (min5 > max5) min5 = max5; if (min7 > max7) min7 = max7; // Fix the range to be the minimum in each case. FixRange(ref min5, ref max5, 5); FixRange(ref min7, ref max7, 7); // Set up the 5-alpha code book. var codes5 = new byte[8]; codes5[0] = (byte)min5; codes5[1] = (byte)max5; for (int i = 1; i < 5; ++i) codes5[i + 1] = (byte)(((5 - i) * min5 + i * max5) / 5); codes5[6] = 0; codes5[7] = 255; // Set up the 7-alpha code book. var codes7 = new byte[8]; codes7[0] = (byte)min7; codes7[1] = (byte)max7; for (int i = 1; i < 7; ++i) codes7[i + 1] = (byte)(((7 - i) * min7 + i * max7) / 7); // Fit the data to both code books. byte[] indices5, indices7; var err5 = FitCodes(rgba, mask, codes5, out indices5); var err7 = FitCodes(rgba, mask, codes7, out indices7); // Save the block with least error. if (err5 <= err7) WriteAlphaBlock5(min5, max5, indices5, target, targetOffset); else WriteAlphaBlock7(min7, max7, indices7, target, targetOffset); } public static void DecompressAlphaDxt5(byte[] block, int blockOffset, byte[] target, int targetOffset) { // Get the two alpha values. var alpha0 = block[blockOffset + 0]; var alpha1 = block[blockOffset + 1]; // Compare the values to build the codebook. var codes = new byte[8]; codes[0] = alpha0; codes[1] = alpha1; if (alpha0 <= alpha1) { // Use the 5-alpha codebook. for (int i = 1; i < 5; ++i) codes[1 + i] = (byte)(((5 - i) * alpha0 + i * alpha1) / 5); codes[6] = 0; codes[7] = 255; } else { // Use the 7-alpha codebook. for (int i = 1; i < 7; ++i) codes[1 + i] = (byte)(((7 - i) * alpha0 + i * alpha1) / 7); } // Decode the incdices var indices = new byte[16]; var blOff = 2; var indOff = 0; for (int i = 0; i < 2; ++i) { // Grab 3 bytes int value = 0; for (int j = 0; j < 3; ++j) { var b = block[blockOffset + blOff++]; value |= (b << 8 * j); } // Unpack 8 3-bit values from it for (int j = 0; j < 8; ++j) { var index = (value >> 3 * j) & 0x7; indices[indOff++] = (byte)index; } } // Write out the index codebook values. for (int i = 0; i < 16; ++i) target[targetOffset + 4 * i + 3] = codes[indices[i]]; } #endregion } } ================================================ FILE: DotSquish/ClusterFit.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DotSquish { internal class ClusterFit : ColourFit { const int MaxIterations = 8; #region Fields private int _IterationCount; private Vector3 _Principle; private byte[] _Order = new byte[16 * MaxIterations]; private Vector4[] _PointsWeight = new Vector4[16]; private Vector4 _XSumWSum; private Vector4 _Metric; private Vector4 _BestError; #endregion #region Constructor protected ClusterFit(ColourSet colours, SquishOptions flags) : base(colours, flags) { // Set the iteration count. this._IterationCount = flags.HasFlag(SquishOptions.ColourIterativeClusterFit) ? MaxIterations : 1; // Initialise the best error. this._BestError = new Vector4(float.MaxValue); // Initialize the metric var perceptual = flags.HasFlag(SquishOptions.ColourMetricPerceptual); if (perceptual) this._Metric = new Vector4(0.2126f, 0.7152f, 0.0722f, 0.0f); else this._Metric = new Vector4(1.0f); // Get the covariance matrix. var covariance = Sym3x3.ComputeWeightedCovariance(colours.Count, colours.Points, colours.Weights); // Compute the principle component this._Principle = Sym3x3.ComputePrincipledComponent(covariance); } #endregion #region Methods protected bool ConstructOrdering(Vector3 axis, int iteration) { // Build the list of dot products. var dps = new float[16]; var ordOff = 16 * iteration; for (int i = 0; i < _Colours.Count; ++i) { dps[i] = Vector3.Dot(_Colours.Points[i], axis); this._Order[ordOff + i] = (byte)i; } // Stable sort using them. for (int i = 0; i < _Colours.Count; ++i) { for (int j = i; j > 0 && dps[j] < dps[j - 1]; --j) { var _dps = dps[j]; var _order = _Order[ordOff + j]; dps[j] = dps[j - 1]; dps[j - 1] = _dps; _Order[ordOff + j] = _Order[ordOff + j - 1]; _Order[ordOff + j - 1] = _order; } } // Check this ordering is unique for (int it = 0; it < iteration; ++it) { var prevOff = 16 * it; var same = true; for (int i = 0; i < _Colours.Count; ++i) { if (_Order[ordOff + i] != _Order[prevOff + i]) { same = false; break; } } if (same) return false; } // Copy the ordering and weight all the points this._XSumWSum = new Vector4(0f); for (int i = 0; i < _Colours.Count; ++i) { var j = _Order[ordOff + i]; var p = new Vector4(_Colours.Points[j].X, _Colours.Points[j].Y, _Colours.Points[j].Z, 1f); var w = new Vector4(_Colours.Weights[j]); var x = p * w; this._PointsWeight[i] = x; this._XSumWSum += x; } return true; } protected override void Compress3(byte[] block) { // Declare variables var count = _Colours.Count; var zero = new Vector4(0f); var half = new Vector4(.5f); var one = new Vector4(1f); var two = new Vector4(2f); var half_half2 = new Vector4(.5f, .5f, .5f, .25f); var grid = new Vector4(31f, 63f, 31f, 0f); var gridrcp = new Vector4(1f / 31f, 1f / 63f, 1f / 31f, 0f); // Prepare the ordering using the principle axis. ConstructOrdering(_Principle, 0); // Check all possible clusters and iterate on the total order. var bestStart = zero; var bestEnd = zero; var bestError = this._BestError; var bestIndices = new byte[16]; //var bestIteration = 0; //int besti = 0, bestj = 0; //// Loop over iterations (we avoid the case that all points in first or last cluster) //for (int iterationIndex = 0; ; ) { // throw new NotImplementedException(); //} throw new NotImplementedException(); } protected override void Compress4(byte[] block) { throw new NotImplementedException(); } #endregion } } ================================================ FILE: DotSquish/ColourBlock.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DotSquish { internal static class ColourBlock { private static int Unpack565(byte[] packed, int packedOffset, byte[] colour, int colourOffset) { // Build the packed value. var value = (int)packed[packedOffset] | ((int)packed[packedOffset + 1] << 8); // Get the components in the stored range. var red = (byte)((value >> 11) & 0x1F); var green = (byte)((value >> 5) & 0x3F); var blue = (byte)(value & 0x1F); // Scale up to 8 bits colour[colourOffset + 0] = (byte)((red << 3) | (red >> 2)); colour[colourOffset + 1] = (byte)((green << 2) | (green >> 4)); colour[colourOffset + 2] = (byte)((blue << 3) | (blue >> 2)); colour[colourOffset + 3] = 255; return value; } public static byte[] DecompressColour(byte[] block, int blockOffset, bool isDxt1) { // Unpack the endpoints var codes = new byte[16]; var a = Unpack565(block, blockOffset + 0, codes, 0); var b = Unpack565(block, blockOffset + 2, codes, 4); // Generate the midpoints. for (int i = 0; i < 3; ++i) { var c = codes[i]; var d = codes[4 + i]; if (isDxt1 && a <= b) { codes[8 + i] = (byte)((c + d) / 2); codes[12 + i] = 0; } else { codes[8 + i] = (byte)(((2 * c) + d) / 3); codes[12 + i] = (byte)((c + (2 * d)) / 3); } } // Fill in alpha for the intermediate values. codes[8 + 3] = 255; codes[12 + 3] = (byte)((isDxt1 && a <= b) ? 0 : 255); // Unpack the indices var indices = new byte[16]; for (int i = 0; i < 4; i++) { var packed = block[blockOffset + 4 + i]; indices[4 * i + 0] = (byte)(packed & 0x3); indices[4 * i + 1] = (byte)((packed >> 2) & 0x3); indices[4 * i + 2] = (byte)((packed >> 4) & 0x3); indices[4 * i + 3] = (byte)((packed >> 6) & 0x3); } // Store the colours var rgba = new byte[4 * 16]; for (int i = 0; i < 16; ++i) { var offset = 4 * indices[i]; for (int j = 0; j < 4; ++j) rgba[4 * i + j] = codes[offset + j]; } return rgba; } } } ================================================ FILE: DotSquish/ColourFit.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DotSquish { internal abstract class ColourFit { #region Fields protected ColourSet _Colours; private SquishOptions _Flags; #endregion #region Constructor protected ColourFit(ColourSet colours, SquishOptions flags) { this._Colours = colours; this._Flags = flags; } #endregion #region Public public void Compress(ref byte[] block) { if (this._Flags.HasFlag(SquishOptions.DXT1)) { Compress3(block); if (!this._Colours.IsTransparent) Compress4(block); } else Compress4(block); } #endregion #region Protected protected abstract void Compress3(byte[] block); protected abstract void Compress4(byte[] block); #endregion } } ================================================ FILE: DotSquish/ColourSet.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DotSquish { internal class ColourSet { #region Fields private int _Count = 0; private Vector3[] _Points = new Vector3[16]; private float[] _Weights = new float[16]; private int[] _Remap = new int[16]; private bool _IsTransparent = false; #endregion #region Properties public int Count { get { return this._Count; } } public Vector3[] Points { get { return this._Points; } } public float[] Weights { get { return this._Weights; } } public bool IsTransparent { get { return this._IsTransparent; } } #endregion #region Constructor public ColourSet(byte[] rgba, int mask, SquishOptions flags) { // Check the compression mode. var isDxt1 = flags.HasFlag(SquishOptions.DXT1); var weightByAlpha = flags.HasFlag(SquishOptions.WeightColourByAlpha); // Create he minimal set. for (int i = 0; i < 16; ++i) { // Check this pixel is enabled. int bit = 1 << i; if ((mask & bit) == 0) { this._Remap[i] = -1; continue; } // Check for transparent pixels when using DXT1. if (isDxt1 && rgba[4 * i + 3] < 128) { this._Remap[i] = -1; this._IsTransparent = true; } // Loop over previous points for a match. for (int j = 0; ; ++j) { // Allocate a new point. if (j == i) { // Normalise coordinates to [0,1]. var x = rgba[4 * i] / 255f; var y = rgba[4 * i + 1] / 255f; var z = rgba[4 * i + 2] / 255f; // Ensure there is always a non-zero weight even for zero alpha. var w = (rgba[4 * i + 3] + 1) / 256f; // Add the point. this._Points[this._Count] = new Vector3(x, y, z); this._Weights[this._Count] = w; this._Remap[i] = this._Count; // Advance. ++this._Count; break; } // Check for a match. int oldBit = 1 << j; var match = ((mask & oldBit) != 0) && (rgba[4 * i] == rgba[4 * j]) && (rgba[4 * i + 1] == rgba[4 * j + 1]) && (rgba[4 * i + 3] == rgba[4 * j + 2]) && (rgba[4 * j + 3] >= 128 || !isDxt1); if (match) { // Get index of the match. var index = this._Remap[j]; // Ensure there is always a non-zero weight even for zero alpha. var w = (rgba[4 * i + 3] + 1) / 256f; // Map this point and increase the weight. this._Weights[index] += (weightByAlpha ? w : 1f); this._Remap[i] = index; break; } } } // Square root the weights. for (int i = 0; i < this._Count; ++i) this._Weights[i] = (float)Math.Sqrt(this._Weights[i]); } #endregion #region Methods public void RemapIndices(byte[] source, byte[] target, int targetOffset) { for (int i = 0; i < 16; ++i) { var j = this._Remap[i]; if (j == -1) target[i + targetOffset] = 3; else target[i + targetOffset] = source[j]; } } #endregion } } ================================================ FILE: DotSquish/DotSquish.csproj ================================================  {F9681545-4BEA-4FD3-9AB9-A67BD37AB36D} net7.0 bin\$(Configuration)\ true full pdbonly ================================================ FILE: DotSquish/Flags.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DotSquish { [Flags] public enum SquishOptions { /// /// Use DXT1 compression. /// DXT1 = (1 << 0), /// /// Use DXT3 compression. /// DXT3 = (1 << 1), /// /// Use DXT5 compression. /// DXT5 = (1 << 2), /// /// Use a very slow but very high quality colour compressor. /// ColourIterativeClusterFit = (1 << 3), /// /// Use a slow but high quality colour compressor (default). /// ColourClusterFit = (1 << 4), /// /// Use a fast but low quality colour compressor. /// ColourRangeFit = (1 << 5), /// /// Use a perceptual metric for colour error (default). /// ColourMetricPerceptual = (1 << 6), /// /// Use a uniform metric for colour error. /// ColourMetricUniform = (1 << 7), /// /// Weight the colour by alpha during cluster fit (off by default). /// WeightColourByAlpha = (1 << 8), } public static class SquishOptionsExtensions { public static SquishOptions GetMethod(this SquishOptions self) { return (self & (SquishOptions.DXT1 | SquishOptions.DXT3 | SquishOptions.DXT5)); } public static SquishOptions GetFit(this SquishOptions self) { return (self & (SquishOptions.ColourIterativeClusterFit | SquishOptions.ColourClusterFit | SquishOptions.ColourRangeFit)); } public static SquishOptions GetMetric(this SquishOptions self) { return (self & (SquishOptions.ColourMetricPerceptual | SquishOptions.ColourMetricUniform)); } public static SquishOptions GetExtra(this SquishOptions self) { return (self & (SquishOptions.WeightColourByAlpha)); } } } ================================================ FILE: DotSquish/Maths.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DotSquish { internal struct Vector3 { public float X, Y, Z; public Vector3(float value) { this.X = this.Y = this.Z = value; } public Vector3(float x, float y, float z) { this.X = x; this.Y = y; this.Z = z; } public static Vector3 operator +(Vector3 v1, Vector3 v2) { return new Vector3( v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z); } public static Vector3 operator -(Vector3 v) { return new Vector3( - v.X, - v.Y, - v.Z); } public static Vector3 operator -(Vector3 v1, Vector3 v2) { return new Vector3( v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z); } public static Vector3 operator *(Vector3 v1, Vector3 v2) { return new Vector3( v1.X * v2.X, v1.Y * v2.Y, v1.Z * v2.Z); } public static Vector3 operator *(Vector3 v1, float v2) { return new Vector3( v1.X * v2, v1.Y * v2, v1.Z * v2); } public static Vector3 operator *(float v1, Vector3 v2) { return (v2 * v1); } public static Vector3 operator /(Vector3 v1, Vector3 v2) { return new Vector3( v1.X / v2.X, v1.Y / v2.Y, v1.Z / v2.Z); } public static Vector3 operator /(Vector3 v1, float v2) { return new Vector3( v1.X / v2, v1.Y / v2, v1.Z / v2); } public static float Dot(Vector3 v1, Vector3 v2) { return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z; } public static Vector3 Min(Vector3 v1, Vector3 v2) { return new Vector3( (float)Math.Min(v1.X, v2.X), (float)Math.Min(v1.Y, v2.Y), (float)Math.Min(v1.Z, v2.Z)); } public static Vector3 Max(Vector3 v1, Vector3 v2) { return new Vector3( (float)Math.Max(v1.X, v2.X), (float)Math.Max(v1.Y, v2.Y), (float)Math.Max(v1.Z, v2.Z)); } public static Vector3 Max(Vector3 v) { return new Vector3( (float)(v.X > 0f ? Math.Floor(v.X) : Math.Ceiling(v.X)), (float)(v.Y > 0f ? Math.Floor(v.Y) : Math.Ceiling(v.Y)), (float)(v.Z > 0f ? Math.Floor(v.Z) : Math.Ceiling(v.Z))); } } internal struct Vector4 { public float X, Y, Z, W; public Vector4(float value) { this.X = this.Y = this.Z = this.W = value; } public Vector4(float x, float y, float z, float w) { this.X = x; this.Y = y; this.Z = z; this.W = w; } public static Vector4 operator +(Vector4 v1, Vector4 v2) { return new Vector4( v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z, v1.W + v2.W); } public static Vector4 operator -(Vector4 v) { return new Vector4( -v.X, -v.Y, -v.Z, -v.W); } public static Vector4 operator -(Vector4 v1, Vector4 v2) { return new Vector4( v1.X - v2.X, v1.Y - v2.Y, v1.Z - v2.Z, v1.W - v2.W); } public static Vector4 operator *(Vector4 v1, Vector4 v2) { return new Vector4( v1.X * v2.X, v1.Y * v2.Y, v1.Z * v2.Z, v1.W * v2.W); } public static Vector4 operator *(Vector4 v1, float v2) { return new Vector4( v1.X * v2, v1.Y * v2, v1.Z * v2, v1.W * v2); } public static Vector4 operator *(float v1, Vector4 v2) { return (v2 * v1); } public static Vector4 operator /(Vector4 v1, Vector4 v2) { return new Vector4( v1.X / v2.X, v1.Y / v2.Y, v1.Z / v2.Z, v1.W / v2.W); } public static Vector4 operator /(Vector4 v1, float v2) { return new Vector4( v1.X / v2, v1.Y / v2, v1.Z / v2, v1.W / v2); } } internal class Sym3x3 { private float[] _X = new float[6]; public float this[int index] { get { return this._X[index]; } set { this._X[index] = value; } } public Sym3x3() { } public Sym3x3(float s) { for (int i = 0; i < 6; ++i) this._X[i] = s; } public static Sym3x3 ComputeWeightedCovariance(int n, Vector3[] points, float[] weights) { // Compute the centroid. var total = 0f; var centroid = new Vector3(0f); for (int i = 0; i < n; ++i) { total += weights[i]; centroid += weights[i] * points[i]; } centroid /= total; // Accumulate the covariance matrix. var covariance = new Sym3x3(0f); for (int i = 0; i < n; ++i) { var a = points[i] - centroid; var b = weights[i] * a; covariance[0] += a.X * b.X; covariance[1] += a.X * b.Y; covariance[2] += a.X * b.Z; covariance[3] += a.Y * b.Y; covariance[4] += a.Y * b.Z; covariance[5] += a.Z * b.Z; } return covariance; } private static Vector3 GetMultiplicity1Evector(Sym3x3 matrix, float evalue) { // Compute M var m = new Sym3x3(); m[0] = matrix[0] - evalue; m[1] = matrix[1]; m[2] = matrix[2]; m[3] = matrix[3] - evalue; m[4] = matrix[4]; m[5] = matrix[5] - evalue; // Compute U var u = new Sym3x3(); u[0] = (m[3] * m[5]) - (m[4] * m[4]); u[1] = (m[2] * m[4]) - (m[1] * m[5]); u[2] = (m[1] * m[4]) - (m[2] * m[3]); u[3] = (m[0] * m[5]) - (m[2] * m[2]); u[4] = (m[1] * m[2]) - (m[4] * m[0]); u[5] = (m[0] * m[3]) - (m[1] * m[1]); // Find the largest component. var mc = Math.Abs(u[0]); var mi = 0; for (int i = 1; i < 6; ++i) { var c = Math.Abs(u[i]); if (c > mc) { mc = c; mi = i; } } // Pick the column with this component. switch (mi) { case 0: return new Vector3(u[0], u[1], u[2]); case 1: case 3: return new Vector3(u[1], u[3], u[4]); default: return new Vector3(u[2], u[4], u[5]); } } private static Vector3 GetMultiplicity2Evector(Sym3x3 matrix, float evalue) { // Compute M var m = new Sym3x3(); m[0] = matrix[0] - evalue; m[1] = matrix[1]; m[2] = matrix[2]; m[3] = matrix[3] - evalue; m[4] = matrix[4]; m[5] = matrix[5] - evalue; // Find the largest component. var mc = Math.Abs(m[0]); var mi = 0; for (int i = 1; i < 6; ++i) { var c = Math.Abs(m[i]); if (c > mc) { mc = c; mi = i; } } // pick the first eigenvector based on this index switch (mi) { case 0: case 1: return new Vector3(-m[1], m[0], 0.0f); case 2: return new Vector3(m[2], 0.0f, -m[0]); case 3: case 4: return new Vector3(0.0f, -m[4], m[3]); default: return new Vector3(0.0f, -m[5], m[4]); } } public static Vector3 ComputePrincipledComponent(Sym3x3 matrix) { // Compute the cubic coefficients var c0 = (matrix[0] * matrix[3] * matrix[5]) + (matrix[1] * matrix[2] * matrix[4] * 2f) - (matrix[0] * matrix[4] * matrix[4]) - (matrix[3] * matrix[2] * matrix[2]) - (matrix[5] * matrix[1] * matrix[1]); var c1 = (matrix[0] * matrix[3]) + (matrix[0] * matrix[5]) + (matrix[3] * matrix[5]) - (matrix[1] * matrix[1]) - (matrix[2] * matrix[2]) - (matrix[4] * matrix[4]); var c2 = matrix[0] + matrix[3] + matrix[5]; // Compute the quadratic coefficients var a = c1 - ((1f / 3f) * c2 * c2); var b = ((-2f / 27f) * c2 * c2 * c2) + ((1f / 3f) * c1 * c2) - c0; // Compute the root count check; var Q = (.25f * b * b) + ((1f / 27f) * a * a * a); // Test the multiplicity. if (float.Epsilon < Q) return new Vector3(1f); // Only one root, which implies we have a multiple of the identity. else if (Q < -float.Epsilon) { // Three distinct roots var theta = Math.Atan2(Math.Sqrt(Q), -.5f * b); var rho = Math.Sqrt((.25f * b * b) - Q); var rt = Math.Pow(rho, 1f / 3f); var ct = Math.Cos(theta / 3f); var st = Math.Sin(theta / 3f); var l1 = ((1f / 3f) * c2) + (2f * rt * ct); var l2 = ((1f / 3f) * c2) - (rt * (ct + (Math.Sqrt(3f) * st))); var l3 = ((1f / 3f) * c2) - (rt * (ct - (Math.Sqrt(3f) * st))); // Pick the larger. if (Math.Abs(l2) > Math.Abs(l1)) l1 = l2; if (Math.Abs(l3) > Math.Abs(l1)) l1 = l3; // Get the eigenvector return GetMultiplicity1Evector(matrix, (float)l1); } else { // Q very close to 0 // Two roots double rt; if (b < 0.0f) rt = -Math.Pow(-.5f * b, 1f / 3f); else rt = Math.Pow(.5f * b, 1f / 3f); var l1 = ((1f / 3f) * c2) + rt; var l2 = ((1f / 3f) * c2) - (2f * rt); // Get the eigenvector if (Math.Abs(l1) > Math.Abs(l2)) return GetMultiplicity2Evector(matrix, (float)l1); else return GetMultiplicity1Evector(matrix, (float)l2); } } } } ================================================ FILE: DotSquish/Squish.cs ================================================ using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DotSquish { public static class Squish { public static int GetStorageRequirements(int width, int height, SquishOptions flags) { var blockCount = ((width + 3) / 4) * ((height + 3) / 4); var blockSize = flags.HasFlag(SquishOptions.DXT1) ? 8 : 16; return blockCount * blockSize; } #region On buffer public static byte[] CompressBlock(byte[] rgba, SquishOptions flags) { return CompressBlockMasked(rgba, 0xFFFF, flags); } public static byte[] CompressBlockMasked(byte[] rgba, int mask, SquishOptions flags) { throw new NotImplementedException(); } public static byte[] DecompressBlock(byte[] block, int blockOffset, SquishOptions flags) { // Get the block locations var colOff = blockOffset; var alphaOff = blockOffset; if ((flags & (SquishOptions.DXT3 | SquishOptions.DXT5)) != 0) colOff += 8; // Decompress colour. var rgba = ColourBlock.DecompressColour(block, colOff, flags.HasFlag(SquishOptions.DXT1)); // Decompress alpha seperately if necessary. if (flags.HasFlag(SquishOptions.DXT3)) Alpha.DecompressAlphaDxt3(block, alphaOff, rgba, 0); else if (flags.HasFlag(SquishOptions.DXT5)) Alpha.DecompressAlphaDxt5(block, alphaOff, rgba, 0); return rgba; } public static byte[] CompressImage(byte[] rgba, int width, int height, SquishOptions flags) { throw new NotImplementedException(); } public static byte[] DecompressImage(byte[] blocks, int width, int height, SquishOptions flags) { return DecompressImage(blocks, 0, width, height, flags); } public static byte[] DecompressImage(byte[] blocks, int offset, int width, int height, SquishOptions flags) { var argb = new byte[4 * width * height]; var bytesPerBlock = flags.HasFlag(SquishOptions.DXT1) ? 8 : 16; var blockOffset = offset; // Loop over blocks. for (int y = 0; y < height; y += 4) { for (int x = 0; x < width; x += 4) { // Decompress the block. var targetRgba = DecompressBlock(blocks, blockOffset, flags); // Write the decompressed pixels to the correct image locations. var sourcePixelOffset = 0; for (int py = 0; py < 4; ++py) { for (int px = 0; px < 4; ++px) { // Get the target location. var sx = x + px; var sy = y + py; if (sx < width && sy < height) { var targetPixelOffset = 4 * ((width * sy) + sx); // Copy the rgba value argb[targetPixelOffset + 0] = targetRgba[sourcePixelOffset + 2]; argb[targetPixelOffset + 1] = targetRgba[sourcePixelOffset + 1]; argb[targetPixelOffset + 2] = targetRgba[sourcePixelOffset + 0]; argb[targetPixelOffset + 3] = targetRgba[sourcePixelOffset + 3]; } sourcePixelOffset += 4; } } // advance blockOffset += bytesPerBlock; } } return argb; } public static Image DecompressToBitmap(byte[] blocks, int width, int height, SquishOptions flags) { return DecompressToBitmap(blocks, 0, width, height, flags); } public static unsafe Image DecompressToBitmap(byte[] blocks, int offset, int width, int height, SquishOptions flags) { var fullBuffer = new byte[4 * width * height]; var bufferOffset = 0; var bytesPerBlock = flags.HasFlag(SquishOptions.DXT1) ? 8 : 16; var blockOffset = offset; // Loop over blocks. for (int y = 0; y < height; y += 4) { for (int x = 0; x < width; x += 4) { // Decompress the block. var targetRgba = DecompressBlock(blocks, blockOffset, flags); // Write the decompressed pixels to the correct image locations. var sourcePixelOffset = 0; for (int py = 0; py < 4; ++py) { for (int px = 0; px < 4; ++px) { // Get the target location. var sx = x + px; var sy = y + py; if (sx < width && sy < height) { var i = 4 * (sx + (sy * width)); fullBuffer[bufferOffset + i + 0] = targetRgba[sourcePixelOffset + 2]; fullBuffer[bufferOffset + i + 1] = targetRgba[sourcePixelOffset + 1]; fullBuffer[bufferOffset + i + 2] = targetRgba[sourcePixelOffset + 0]; fullBuffer[bufferOffset + i + 3] = targetRgba[sourcePixelOffset + 3]; } sourcePixelOffset += 4; // Skip this pixel as it is outside the image. } } // advance blockOffset += bytesPerBlock; } } Image ret; fixed (byte* p = fullBuffer) { var ptr = (IntPtr)p; var tempImage = new Bitmap(width, height, 4 * width, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ptr); ret = new Bitmap(tempImage); } return ret; } #endregion #region On stream public static void CompressBlock(Stream input, Stream output, SquishOptions flags) { CompressBlockMasked(input, output, 0xFFFF, flags); } public static void CompressBlockMasked(Stream input, Stream output, int mask, SquishOptions flags){ throw new NotImplementedException(); } public static void DecompressBlock(Stream input, Stream output, SquishOptions flags) { throw new NotImplementedException(); } public static void CompressImage(Stream input, Stream output, int width, int height, SquishOptions flags) { throw new NotImplementedException(); } public static void DecompressImage(Stream input, Stream output, int width, int height, SquishOptions flags) { throw new NotImplementedException(); } #endregion } } ================================================ FILE: Godbert/App.config ================================================
================================================ FILE: Godbert/App.xaml ================================================  ================================================ FILE: Godbert/App.xaml.cs ================================================ using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; using Ookii.Dialogs.Wpf; namespace Godbert { /// /// Interaction logic for App.xaml /// public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { if (!RequestGamePath()) { MainWindow = null; Shutdown(1); return; } SaintCoinach.Graphics.Viewer.Interop.HavokInterop.InitializeMTA(); base.OnStartup(e); this.Exit += App_Exit; } private void App_Exit(object sender, ExitEventArgs e) { Settings.Default.Save(); } #region Startup private static bool RequestGamePath() { string path = Godbert.Properties.Settings.Default.GamePath; if (!IsValidGamePath(path)) { string programDir; if (Environment.Is64BitProcess) programDir = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); else programDir = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); path = System.IO.Path.Combine(programDir, "SquareEnix", "FINAL FANTASY XIV - A Realm Reborn"); if (IsValidGamePath(path)) { var msgResult = System.Windows.MessageBox.Show(string.Format("Found game installation at \"{0}\". Is this correct?", path), "Confirm game installation", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.Yes); if (msgResult == MessageBoxResult.Yes) { Godbert.Properties.Settings.Default.GamePath = path; Godbert.Properties.Settings.Default.Save(); return true; } path = null; } } VistaFolderBrowserDialog dlg = null; while (!IsValidGamePath(path)) { var result = (dlg ?? (dlg = new VistaFolderBrowserDialog { Description = "Please select the directory of your FFXIV:ARR game installation (should contain 'boot' and 'game' directories).", ShowNewFolderButton = false, })).ShowDialog(); if (!result.GetValueOrDefault(false)) { var msgResult = System.Windows.MessageBox.Show("Cannot continue without a valid game installation, quit the program?", "That's no good", MessageBoxButton.YesNo, MessageBoxImage.Error, MessageBoxResult.No); if (msgResult == MessageBoxResult.Yes) return false; } path = dlg.SelectedPath; } Godbert.Properties.Settings.Default.GamePath = path; Godbert.Properties.Settings.Default.Save(); return true; } public static bool IsValidGamePath(string path) { if (string.IsNullOrWhiteSpace(path)) return false; if (!Directory.Exists(path)) return false; return File.Exists(Path.Combine(path, "game", "ffxivgame.ver")); } #endregion } } ================================================ FILE: Godbert/Commands/DelegateCommand.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Godbert.Commands { public class DelegateCommand : System.Windows.Input.ICommand { #region Fields private Action _Target; #endregion #region Constructor public DelegateCommand(Action target) { _Target = target; } #endregion #region ICommand Members public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { _Target(); } #endregion #region ICommand Members event EventHandler System.Windows.Input.ICommand.CanExecuteChanged { add { } remove { } } #endregion } public class DelegateCommand : System.Windows.Input.ICommand { #region Fields private Action _Target; #endregion #region Constructor public DelegateCommand(Action target) { _Target = target; } #endregion #region ICommand Members public bool CanExecute(object parameter) { return parameter == null || (parameter is T); } public void Execute(object parameter) { _Target((T)parameter); } #endregion #region ICommand Members event EventHandler System.Windows.Input.ICommand.CanExecuteChanged { add { } remove { } } #endregion } } ================================================ FILE: Godbert/Controls/AttachedImage.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Imaging; namespace Godbert.Controls { public static class AttachedImage { public static readonly DependencyProperty ImageProperty = DependencyProperty.RegisterAttached("Image", typeof(SaintCoinach.Imaging.ImageFile), typeof(AttachedImage), new PropertyMetadata(AttachedImageChanged)); public static readonly DependencyProperty ImageTypeProperty = DependencyProperty.RegisterAttached("ImageType", typeof(string), typeof(AttachedImage), new PropertyMetadata(AttachedImageChanged)); public static readonly DependencyProperty UseImageTypeProperty = DependencyProperty.RegisterAttached("UseImageType", typeof(bool), typeof(AttachedImage), new PropertyMetadata(true, AttachedImageChanged)); public static readonly DependencyProperty IsSizeRestrictedProperty = DependencyProperty.RegisterAttached("IsSizeRestricted", typeof(bool?), typeof(AttachedImage), new PropertyMetadata(null, AttachedImageChanged)); private static readonly Dictionary> _SourceCache = new Dictionary>(); public static bool GetUseImageType(DependencyObject o) { return (bool)o.GetValue(UseImageTypeProperty); } public static void SetUseImageType(DependencyObject o, bool newValue) { o.SetValue(UseImageTypeProperty, newValue); } public static bool? GetIsSizeRestricted(DependencyObject o) { return (bool?)o.GetValue(IsSizeRestrictedProperty); } public static void SetIsSizeRestricted(DependencyObject o, bool? newValue) { o.SetValue(IsSizeRestrictedProperty, newValue); } public static SaintCoinach.Imaging.ImageFile GetImage(DependencyObject o) { return (SaintCoinach.Imaging.ImageFile)o.GetValue(ImageProperty); } public static void SetImage(DependencyObject o, SaintCoinach.Imaging.ImageFile v) { o.SetValue(ImageProperty, v); } public static string GetImageType(DependencyObject o) { return (string)o.GetValue(ImageTypeProperty); } public static void SetImageType(DependencyObject o, string v) { o.SetValue(ImageTypeProperty, v); } private static void AttachedImageChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var img = (Image)o; var srcImage = GetImage(o); var srcImageType = GetImageType(o); var useSrcImageType = GetUseImageType(o); if (!useSrcImageType) srcImageType = null; double w = double.PositiveInfinity, h = double.PositiveInfinity; if (srcImage != null) { var useImage = GetImageOfType(srcImage, srcImageType); w = useImage.Width; h = useImage.Height; img.Source = GetAsImageSource(useImage); } else img.Source = null; var sizeRestrict = GetIsSizeRestricted(o); if (sizeRestrict.HasValue) { if (sizeRestrict.Value == false) w = h = double.PositiveInfinity; img.MaxWidth = w; img.MaxHeight = h; } } private static ImageSource GetAsImageSource(SaintCoinach.Imaging.ImageFile file) { if (file == null) return null; var key = file.Path; WeakReference targetRef; ImageSource target; if (!_SourceCache.TryGetValue(key, out targetRef) || !targetRef.TryGetTarget(out target)) { target = CreateSource(file); if (targetRef == null) _SourceCache.Add(key, new WeakReference(target)); else targetRef.SetTarget(target); } return target; } private static SaintCoinach.Imaging.ImageFile GetImageOfType(SaintCoinach.Imaging.ImageFile original, string type) { if (string.IsNullOrWhiteSpace(type)) return original; if (!original.Path.StartsWith("ui/icon/")) return original; // XXX: Exception instead? var dirPath = original.Path.Substring(0, "ui/icon/000000/".Length); var targetPath = string.Format("{0}{1}/{2}", dirPath, type, original.Path.Split('/').Last()); SaintCoinach.IO.File typeFile; if (original.Pack.Collection.TryGetFile(targetPath, out typeFile)) return (SaintCoinach.Imaging.ImageFile)typeFile; return original; } private static ImageSource CreateSource(SaintCoinach.Imaging.ImageFile file) { var argb = SaintCoinach.Imaging.ImageConverter.GetA8R8G8B8(file); return BitmapSource.Create( file.Width, file.Height, 96, 96, PixelFormats.Bgra32, null, argb, file.Width * 4); } } } ================================================ FILE: Godbert/Controls/ColumnFactory.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using SaintCoinach.Ex.Relational; using SaintCoinach.Ex.Relational.Definition; using SaintCoinach.Ex; using SaintCoinach.Ex.DataReaders; namespace Godbert.Controls { static class ColumnFactory { public static DataGridColumn Create(RelationalColumn column) { var sheetDef = column.Header.SheetDefinition; Type defType = null; if (sheetDef != null) defType = sheetDef.GetValueType(column.Index); var targetType = defType ?? column.Reader.Type; var header = BuildHeader(column); var binding = CreateCellBinding(column); DataGridColumn target = null; if (typeof(SaintCoinach.Imaging.ImageFile).IsAssignableFrom(targetType)) target = new RawDataGridImageColumn(column) { Binding = binding, }; else if (typeof(System.Drawing.Color).IsAssignableFrom(targetType)) target = new RawDataGridColorColumn(column) { Binding = binding }; target = target ?? new RawDataGridTextColumn(column) { Binding = binding }; target.Header = header; target.IsReadOnly = true; target.CanUserSort = true; return target; } private static string BuildHeader(RelationalColumn column) { var sb = new StringBuilder(); sb.Append(column.Index); if (!string.IsNullOrWhiteSpace(column.Name)) sb.AppendFormat(": {0}", column.Name); sb.Append(Environment.NewLine); sb.Append(column.Reader.Type.Name); if (column.ValueType != column.Reader.Name) sb.AppendFormat(" > {0}", column.ValueType); if (Settings.Default.ShowOffsets) { if (column.Reader is PackedBooleanDataReader) sb.AppendFormat(" [{0:X}&{1:X2}]", column.Offset, ((PackedBooleanDataReader)column.Reader).Mask); else sb.AppendFormat(" [{0:X}]", column.Offset); } return sb.ToString(); } private static Binding CreateCellBinding(RelationalColumn column) { return new Binding { Converter = CellConverterInstance, ConverterParameter = column.Index, Mode = BindingMode.OneWay }; } public static bool ForceRaw; public static readonly System.Windows.Data.IValueConverter CellConverterInstance = new CellConverter(); private class CellConverter : System.Windows.Data.IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var row = value as IRow; if (row == null) return null; var i = System.Convert.ToInt32(parameter); if (ForceRaw || RawDataGrid.ColumnSetToRaw[i]) return row.GetRaw(i); return row[i] ?? row.GetRaw(i); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion } } } ================================================ FILE: Godbert/Controls/INavigatable.cs ================================================ using SaintCoinach.Ex; using SaintCoinach.Ex.Relational; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; namespace Godbert.Controls { public interface INavigatable { IRow OnNavigate(object sender, RoutedEventArgs e); } } ================================================ FILE: Godbert/Controls/IRawDataColumn.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SaintCoinach.Ex.Relational; namespace Godbert.Controls { public interface IRawDataColumn { IComparer GetComparer(System.ComponentModel.ListSortDirection direction); RelationalColumn Column { get; } } } ================================================ FILE: Godbert/Controls/RawDataGrid.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.ComponentModel; using System.Windows.Input; using SaintCoinach.Ex.Relational; using Godbert.ViewModels; using SaintCoinach.Xiv; using System.Collections; using System.Windows.Controls.Primitives; using System.Windows.Media; using SaintCoinach.Ex; namespace Godbert.Controls { public class RawDataGrid : DataGrid { #region DependencyProperties public static readonly DependencyProperty SheetProperty = DependencyProperty.Register("Sheet", typeof(IRelationalSheet), typeof(RawDataGrid), new PropertyMetadata(OnSheetChanged)); public static readonly DependencyProperty FilterProperty = DependencyProperty.Register("Filter", typeof(string), typeof(RawDataGrid), new PropertyMetadata(OnFilterChanged)); public IRelationalSheet Sheet { get { return (IRelationalSheet)GetValue(SheetProperty); } set { SetValue(SheetProperty, value); } } public string Filter { get { return (string)GetValue(FilterProperty); } set { SetValue(FilterProperty, value); } } private static void OnSheetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((RawDataGrid)o).OnSheetChanged((IRelationalSheet)e.OldValue, (IRelationalSheet)e.NewValue); } public static bool[] ColumnSetToRaw; protected virtual void OnSheetChanged(IRelationalSheet oldValue, IRelationalSheet newValue) { this.Columns.Clear(); if (newValue != null) { var keyPath = newValue.Header.Variant == 1 ? "Key" : "FullKey"; //prevent multiple enumeration var columns = newValue.Header.Columns.ToList(); if (Settings.Default.SortByOffsets) columns.Sort((x, y) => x.Offset.CompareTo(y.Offset)); ColumnSetToRaw = new bool[columns.Count]; Columns.Add(new RawDataGridKeyColumn(keyPath) { CanUserSort = true }); foreach (var col in columns) Columns.Add(ColumnFactory.Create(col)); var source = new RawDataItemsSource(newValue); if (!string.IsNullOrWhiteSpace(Filter)) source.Filter = Filter; this.ItemsSource = source; } else this.ItemsSource = null; } private static void OnFilterChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((RawDataGrid)o).OnFilterChanged((string)e.OldValue, (string)e.NewValue); } protected virtual void OnFilterChanged(string oldValue, string newValue) { var src = this.ItemsSource as RawDataItemsSource; if (src == null) return; if (string.IsNullOrWhiteSpace(newValue)) src.Filter = null; else src.Filter = newValue; } #endregion protected override void OnSorting(DataGridSortingEventArgs eventArgs) { eventArgs.Handled = true; var src = this.ItemsSource as RawDataItemsSource; var col = eventArgs.Column as IRawDataColumn; if (src == null || col == null) return; var nextDir = (eventArgs.Column.SortDirection != ListSortDirection.Ascending) ? ListSortDirection.Ascending : ListSortDirection.Descending; eventArgs.Column.SortDirection = nextDir; var cmp = col.GetComparer(nextDir); src.Comparer = cmp; } protected override void OnMouseDown(MouseButtonEventArgs e) { base.OnMouseDown(e); if (e.RightButton == MouseButtonState.Pressed) { var cell = GetClickedHeader(e); if (cell == null) return; if (cell.Column is RawDataGridImageColumn || cell.Column is RawDataGridTextColumn || cell.Column is RawDataGridColorColumn) { ColumnFactory.ForceRaw = !ColumnFactory.ForceRaw; Items.Refresh(); e.Handled = true; } } if (e.MiddleButton == MouseButtonState.Pressed) { var cellHeader = GetClickedHeader(e); if (cellHeader != null) { if (cellHeader.Column is RawDataGridImageColumn || cellHeader.Column is RawDataGridTextColumn || cellHeader.Column is RawDataGridColorColumn) { var columnIndex = ((IRawDataColumn) cellHeader.Column).Column.Index; ColumnSetToRaw[columnIndex] = !ColumnSetToRaw[columnIndex]; Items.Refresh(); e.Handled = true; } return; } var cell = GetClickedCell(e); if (cell == null) return; var dataView = WpfHelper.FindParent(cell); if (dataView == null) return; var dataViewModel = dataView.DataContext as DataViewModel; if (dataViewModel == null) return; var row = cell.DataContext as IRow; if (row == null) return; var dataColumn = cell.Column as IRawDataColumn; if (dataColumn == null) return; var bookmark = new BookmarkViewModel() { SheetName = dataViewModel.SelectedSheetName, Key = row.Key, RowDefault = row.ToString(), ColumnName = dataColumn.Column?.Name, ColumnIndex = dataColumn.Column?.Index }; if (dataViewModel.Bookmarks.Contains(bookmark)) { dataViewModel.Bookmarks.Remove(bookmark); return; } dataViewModel.Bookmarks.Add(bookmark); } } private DataGridColumnHeader GetClickedHeader(MouseButtonEventArgs e) { DependencyObject dep = (DependencyObject)e.OriginalSource; // iteratively traverse the visual tree while ((dep != null) && !(dep is DataGridCell) && !(dep is DataGridColumnHeader)) { dep = VisualTreeHelper.GetParent(dep); } return dep as DataGridColumnHeader; } protected override void OnMouseDoubleClick(MouseButtonEventArgs e) { var cell = GetClickedCell(e); if (cell == null) return; var navigatable = cell.Column as INavigatable; if (navigatable == null) return; var mainWindow = WpfHelper.FindParent(cell); if (mainWindow == null) return; var mainViewModel = mainWindow.DataContext as MainViewModel; if (mainViewModel == null) return; var row = navigatable.OnNavigate(cell, e); if (row == null) return; mainViewModel.Data.SelectedSheetName = row.Sheet.Name; SelectRow(row, null); } private static DataGridCell GetClickedCell(MouseButtonEventArgs e) { if (e.OriginalSource is FrameworkElement source) return WpfHelper.FindParent(source); return null; } public void SelectRow(IRow row, int? columnIndex) { this.SelectedItem = row; this.UpdateLayout(); if (this.SelectedItem == null) return; DataGridColumn selectedColumn = null; if (columnIndex != null) selectedColumn = (DataGridColumn)this.Columns.OfType().FirstOrDefault(c => c.Column?.Index == columnIndex); this.ScrollIntoView(this.SelectedItem, selectedColumn); } } } ================================================ FILE: Godbert/Controls/RawDataGridColorColumn.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.ComponentModel; using SaintCoinach.Ex.Relational; using SaintCoinach.Ex; namespace Godbert.Controls { public class RawDataGridColorColumn : DataGridBoundColumn, IRawDataColumn { private RelationalColumn _Column; private ContextMenu _ContextMenu; public RawDataGridColorColumn(RelationalColumn column) { _Column = column; _ContextMenu = new ContextMenu(); var mi = new MenuItem { Header = "Copy", }; mi.Click += OnCopyClick; _ContextMenu.Items.Add(mi); } void OnCopyClick(object sender, RoutedEventArgs e) { var fe = sender as FrameworkElement; if (fe == null) return; var ctx = fe.DataContext; if (!(ctx is System.Drawing.Color)) return; var src = (System.Drawing.Color)ctx; System.Windows.Clipboard.SetDataObject(string.Format("#{0:X8}", src.ToArgb())); } protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) { throw new NotImplementedException(); } protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) { Border obj = cell != null ? cell.Content as Border : null; if (obj == null) { obj = new Border { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, }; obj.SetBinding(Border.BackgroundProperty, new Binding { Converter = ColorConverterInstance }); obj.ContextMenu = _ContextMenu; } var bind = Binding; if (bind == null) BindingOperations.ClearBinding(obj, Border.DataContextProperty); else BindingOperations.SetBinding(obj, Border.DataContextProperty, bind); return obj; } private static readonly ColorConverter ColorConverterInstance = new ColorConverter(); private class ColorConverter : System.Windows.Data.IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (!(value is System.Drawing.Color)) return null; var src = (System.Drawing.Color)value; return new SolidColorBrush(System.Windows.Media.Color.FromArgb(src.A, src.R, src.G, src.B)); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion } #region IRawDataColumn Members public RelationalColumn Column => _Column; public IComparer GetComparer(ListSortDirection direction) { return new Comparer { Column = _Column, Direction = direction, }; } private class Comparer : IComparer { public ListSortDirection Direction; public RelationalColumn Column; #region IComparer Members public int Compare(object x, object y) { if (Direction == ListSortDirection.Descending) return -InnerCompare(x, y); return InnerCompare(x, y); } private int InnerCompare(object x, object y) { var rx = x as IRow; var ry = y as IRow; if (rx == ry) return 0; if (rx == null) return -1; if (ry == null) return 1; var vx = rx[Column.Index]; var vy = ry[Column.Index]; if (vx == vy) return 0; if (!(vx is System.Drawing.Color)) return -1; if (!(vy is System.Drawing.Color)) return 1; var cx = (System.Drawing.Color)vx; var cy = (System.Drawing.Color)vy; return cx.ToArgb().CompareTo(cy.ToArgb()); } #endregion } #endregion } } ================================================ FILE: Godbert/Controls/RawDataGridImageColumn.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.ComponentModel; using SaintCoinach.Ex.Relational; using SaintCoinach.Ex; namespace Godbert.Controls { public class RawDataGridImageColumn : DataGridBoundColumn, IRawDataColumn { private RelationalColumn _Column; private ContextMenu _ContextMenu; public RawDataGridImageColumn(RelationalColumn column) { _Column = column; _ContextMenu = new ContextMenu(); var mi = new MenuItem { Header = "Save image", }; mi.Click += OnSaveImageClick; _ContextMenu.Items.Add(mi); } void OnSaveImageClick(object sender, RoutedEventArgs e) { var fe = sender as FrameworkElement; if (fe == null) return; var ctx = fe.DataContext as IRelationalRow; if (ctx == null) return; var img = ctx[_Column.Index] as SaintCoinach.Imaging.ImageFile; if (img == null) return; var dlg = new Microsoft.Win32.SaveFileDialog { DefaultExt = ".png", Filter = "PNG Files (*.png)|*.png", AddExtension = true, OverwritePrompt = true, FileName = SetExt(img.Path.Split('/').Last(), "png"), }; if (dlg.ShowDialog().GetValueOrDefault(false)) img.GetImage().Save(dlg.FileName); } private static string SetExt(string orig, string ext) { var sb = new StringBuilder(orig); var idx = orig.LastIndexOf('.'); if (idx > 0) sb.Remove(idx, sb.Length - idx); sb.Append('.'); sb.Append(ext); return sb.ToString(); } protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) { throw new NotImplementedException(); } protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) { Image obj = cell != null ? cell.Content as Image : null; if (obj == null) { obj = new Image { Stretch = System.Windows.Media.Stretch.Uniform, ContextMenu = _ContextMenu, }; AttachedImage.SetIsSizeRestricted(obj, true); } var bind = Binding; if (bind == null) BindingOperations.ClearBinding(obj, AttachedImage.ImageProperty); else BindingOperations.SetBinding(obj, AttachedImage.ImageProperty, bind); return obj; } #region IRawDataColumn Members public RelationalColumn Column => _Column; public IComparer GetComparer(ListSortDirection direction) { return new Comparer { Column = _Column, Direction = direction, }; } private class Comparer : IComparer { public ListSortDirection Direction; public RelationalColumn Column; #region IComparer Members public int Compare(object x, object y) { if (Direction == ListSortDirection.Descending) return -InnerCompare(x, y); return InnerCompare(x, y); } private int InnerCompare(object x, object y) { var rx = x as IRow; var ry = y as IRow; if (rx == ry) return 0; if (rx == null) return -1; if (ry == null) return 1; var vx = rx[Column.Index] as SaintCoinach.Imaging.ImageFile; var vy = ry[Column.Index] as SaintCoinach.Imaging.ImageFile; if (vx == vy) return 0; if (vx == null) return -1; if (vy == null) return 1; return vx.Path.CompareTo(vy.Path); } #endregion } #endregion } } ================================================ FILE: Godbert/Controls/RawDataGridKeyColumn.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using SaintCoinach.Ex.Relational; namespace Godbert.Controls { public class RawDataGridKeyColumn : DataGridTextColumn, IRawDataColumn { public RawDataGridKeyColumn(string keyPath) { this.Header = "Key"; this.IsReadOnly = true; this.Binding = new Binding(keyPath) { Mode = BindingMode.OneWay }; } #region IRawDataColumn Members public RelationalColumn Column => null; public IComparer GetComparer(System.ComponentModel.ListSortDirection direction) { return new Comparer { Direction = direction, }; } private class Comparer : IComparer { public ListSortDirection Direction; #region IComparer Members public int Compare(object x, object y) { if (Direction == ListSortDirection.Descending) return -InnerCompare(x, y); return InnerCompare(x, y); } private int InnerCompare(object x, object y) { var rx = x as IRelationalRow; var ry = y as IRelationalRow; if (rx == ry) return 0; if (rx == null) return -1; if (ry == null) return 1; return rx.Key.CompareTo(ry.Key); } #endregion } #endregion } } ================================================ FILE: Godbert/Controls/RawDataGridTextColumn.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.ComponentModel; using SaintCoinach.Ex.Relational; using SaintCoinach.Ex; namespace Godbert.Controls { public class RawDataGridTextColumn : DataGridTextColumn, IRawDataColumn, INavigatable { private RelationalColumn _Column; private ContextMenu _ContextMenu; public RawDataGridTextColumn(RelationalColumn column) { _Column = column; _ContextMenu = new ContextMenu(); var mi = new MenuItem { Header = "Copy", }; mi.Click += OnCopyClick; _ContextMenu.Items.Add(mi); } void OnCopyClick(object sender, RoutedEventArgs e) { var fe = sender as FrameworkElement; if (fe == null) return; var ctx = fe.DataContext as IRelationalRow; if (ctx == null) return; var val = ctx[_Column.Index]; if (val == null) return; System.Windows.Clipboard.SetDataObject(val.ToString()); } public IRow OnNavigate(object sender, RoutedEventArgs e) { var fe = sender as FrameworkElement; if (fe == null) return null; var ctx = fe.DataContext as IRow; if (ctx == null) return null; return ctx[_Column.Index] as IRow; } protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) { var ele = base.GenerateElement(cell, dataItem); ele.ContextMenu = _ContextMenu; return ele; } #region IRawDataColumn Members public RelationalColumn Column => _Column; public IComparer GetComparer(ListSortDirection direction) { return new Comparer { Column = _Column, Direction = direction, }; } private class Comparer : IComparer { private NaturalComparer _NaturalComparer = new NaturalComparer(StringComparer.OrdinalIgnoreCase); public ListSortDirection Direction; public RelationalColumn Column; #region IComparer Members public int Compare(object x, object y) { if (Direction == ListSortDirection.Descending) return -InnerCompare(x, y); return InnerCompare(x, y); } private int InnerCompare(object x, object y) { var rx = x as IRow; var ry = y as IRow; if (rx == ry) return 0; if (rx == null) return -1; if (ry == null) return 1; var vx = ColumnFactory.ForceRaw || RawDataGrid.ColumnSetToRaw[Column.Index] ? rx.GetRaw(Column.Index) : rx[Column.Index]; var vy = ColumnFactory.ForceRaw || RawDataGrid.ColumnSetToRaw[Column.Index] ? ry.GetRaw(Column.Index) : ry[Column.Index]; if (vx == vy) return 0; if (vx == null) return -1; if (vy == null) return 1; var sx = vx.ToString(); var sy = vy.ToString(); return _NaturalComparer.Compare(sx, sy); } #endregion } #endregion } } ================================================ FILE: Godbert/Controls/RawDataItemsSource.cs ================================================ using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; using System.Text; using System.Threading.Tasks; using SaintCoinach.Ex; using SaintCoinach.Ex.Relational; namespace Godbert.Controls { public class RawDataItemsSource : IEnumerable, INotifyCollectionChanged { #region Fields private IRelationalSheet _Sheet; private IComparer _Comparer; private object[] _Items; private string _Filter; private Tuple[] _RowSearchIndex; #endregion #region Properties public IComparer Comparer { get { return _Comparer; } set { _Comparer = value; _Items = null; CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } public string Filter { get { return _Filter; } set { _Filter = value; _Items = null; CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } public IEnumerable Items { get { if (_Items != null) return Items; if (_Sheet.Header.Variant == 1) return GetVariant1Items(); else return GetVariant2Items(); } } private IEnumerable GetVariant1Items() { if (_Comparer == null && _Filter == null) return _Sheet; _Items = FilterAndCompare(_Sheet.Cast()).ToArray(); return _Items; } private IEnumerable GetVariant2Items() { var rows = _Sheet.Cast(); _Items = FilterAndCompare(rows).ToArray(); return _Items; } private IEnumerable FilterAndCompare(IEnumerable results) { if (_Filter != null) { var rows = results.OfType().ToArray(); if (_RowSearchIndex == null) BuildSearchIndex(rows); results = FilterMatchingRows(); } if (_Comparer != null) results = results.OrderBy(o => o, Comparer); return results; } #endregion #region Filter private void BuildSearchIndex(IRow[] rows) { if (rows.Length == 0) return; var start = DateTime.Now; System.Diagnostics.Debug.WriteLine($"Rebuilding search index for {_Sheet.Name}..."); var newIndex = new ConcurrentBag>(); var columns = rows[0].Sheet.Header.Columns.ToArray(); Parallel.ForEach(rows, row => { var index = new StringBuilder(); index.Append(row.Key.ToString()); index.Append("||"); foreach (var value in row.ColumnValues()) { if (value != null) { index.Append(value.ToString()); index.Append("||"); } } newIndex.Add(Tuple.Create(row, index.ToString())); }); _RowSearchIndex = newIndex.OrderBy(r => r.Item1.Key).ToArray(); System.Diagnostics.Debug.WriteLine($"{_Sheet.Name} search index complete. Elapsed {DateTime.Now - start}."); } private IRow[] FilterMatchingRows() { return _RowSearchIndex .Where(r => r.Item2.IndexOf(_Filter, StringComparison.OrdinalIgnoreCase) >= 0) .Select(r => r.Item1) .ToArray(); } #endregion #region Constructor public RawDataItemsSource(IRelationalSheet sheet) { _Sheet = sheet; } #endregion #region INotifyCollectionChanged Members public event NotifyCollectionChangedEventHandler CollectionChanged; #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { return Items.GetEnumerator(); } #endregion } } ================================================ FILE: Godbert/EngineHelper.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Godbert { using SaintCoinach.Graphics.Viewer; public class EngineHelper { public delegate IComponent ComponentFunction(Engine engine); public delegate IComponent[] MultiComponentFunction(Engine engine); #region Fields private List _Instances = new List(); #endregion #region Things public EngineInstance AddToLast(string title, ComponentFunction func) { return AddToLast(title, (e) => new IComponent[] { func(e) }); } public EngineInstance AddToLast(string title, MultiComponentFunction func) { EngineInstance target = null; lock (_Instances) { if (_Instances.Count > 0) target = _Instances[_Instances.Count - 1]; } if (target == null) return OpenInNew(title, func); target.AddComponent(func); target.SetTitle(target.Engine.Form.Text + ", " + title); return target; } public EngineInstance ReplaceInLast(string title, ComponentFunction func) { return ReplaceInLast(title, (e) => new IComponent[] { func(e) }); } public EngineInstance ReplaceInLast(string title, MultiComponentFunction func) { EngineInstance target = null; lock (_Instances) { if (_Instances.Count > 0) target = _Instances[_Instances.Count - 1]; } if (target == null) return OpenInNew(title, func); target.ReplaceComponents(func); target.SetTitle(title); return target; } public EngineInstance OpenInNew(string title, ComponentFunction func) { return OpenInNew(title, (e) => new IComponent[] { func(e) }); } public EngineInstance OpenInNew(string title, MultiComponentFunction func) { var instance = new EngineInstance(title); lock (_Instances) _Instances.Add(instance); instance.Stopped += OnInstanceStopped; instance.AddComponent(func); instance.RunAsync(); return instance; } public EngineInstance GetOrCreate(string title) { EngineInstance target = null; lock (_Instances) { if (_Instances.Count > 0) target = _Instances[_Instances.Count - 1]; } if (target == null) return OpenNew(title); return target; } public EngineInstance OpenNew(string title) { var instance = new EngineInstance(title); lock (_Instances) _Instances.Add(instance); instance.Stopped += OnInstanceStopped; instance.RunAsync(); return instance; } void OnInstanceStopped(object sender, EventArgs e) { lock (_Instances) _Instances.Remove(sender as EngineInstance); } #endregion } } ================================================ FILE: Godbert/EngineInstance.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Godbert { using SaintCoinach.Graphics.Viewer; public class EngineInstance { #region Helper class class ComponentInjector : IUpdateableComponent, IDrawable3DComponent, IContentComponent { internal List AddQueue = new List(); internal EngineHelper.MultiComponentFunction Replacement; internal string SetTitle; private FormEngine _Engine; private ComponentContainer _InnerContainer = new ComponentContainer(); public ComponentInjector(FormEngine engine) { _Engine = engine; } #region IUpdateableComponent Members public bool IsEnabled { get { return true; } } public void Update(EngineTime engineTime) { if (!string.IsNullOrWhiteSpace(SetTitle)) { _Engine.Form.Text = SetTitle; SetTitle = null; } if (Replacement != null) { lock (AddQueue) AddQueue.Clear(); _InnerContainer.Clear(); var l = Replacement(_Engine); foreach (var c in l) { if (c != null) _InnerContainer.Add(c); } Replacement = null; } EngineHelper.MultiComponentFunction[] toAdd; lock (AddQueue) { toAdd = AddQueue.ToArray(); AddQueue.Clear(); } foreach (var f in toAdd) { var l = f(_Engine); foreach (var c in l) { if (c != null) _InnerContainer.Add(c); } } _InnerContainer.Update(engineTime); } #endregion #region IDrawable3DComponent Members public bool IsVisible { get { return true; } } public void Draw(EngineTime time, ref SharpDX.Matrix world, ref SharpDX.Matrix view, ref SharpDX.Matrix projection) { _InnerContainer.Draw(time, ref world, ref view, ref projection); } #endregion #region IContentComponent Members private bool _IsLoaded; public bool IsLoaded { get { return _IsLoaded; } private set { _IsLoaded = value; } } public void LoadContent() { _InnerContainer.LoadContent(); _IsLoaded = true; } public void UnloadContent() { _IsLoaded = false; _InnerContainer.UnloadContent(); } #endregion } #endregion #region Fields private ComponentInjector _Injector; #endregion #region Properties public FormEngine Engine { get; private set; } #endregion #region Event public event EventHandler Stopped; #endregion #region Constructor public EngineInstance(string title) { Engine = new FormEngine(title); Engine.Components.Add(_Injector = new ComponentInjector(Engine)); } #endregion #region Things public void SetTitle(string newTitle) { _Injector.SetTitle = newTitle; } public void AddComponent(EngineHelper.MultiComponentFunction component) { lock (_Injector.AddQueue) _Injector.AddQueue.Add(component); } public void ReplaceComponents(EngineHelper.MultiComponentFunction newComponents) { _Injector.Replacement = newComponents; } public void RunAsync() { var t = new Thread(this.Run); t.IsBackground = true; t.Name = "Renderer"; t.Start(); } private void Run() { try { Engine.Run(); } catch (Exception e) { System.Windows.MessageBox.Show(e.ToString(), "Engine failure", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); System.Diagnostics.Debug.WriteLine(string.Format("Engine failure: {0}", e)); } finally { Engine = null; _Injector = null; GC.Collect(); Stopped?.Invoke(this, EventArgs.Empty); } } #endregion } } ================================================ FILE: Godbert/Godbert.csproj ================================================  {6A5DA7FF-791E-4A43-BF65-B6942917F7D9} {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} WinExe net7.0-windows true 0 1.0.0.%2a true Godbert Godbert Copyright © Rogueadyn 2015 bin\$(Configuration)\ true x86 full pdbonly Godbert.ico ================================================ FILE: Godbert/MainWindow.xaml ================================================  ================================================ FILE: Godbert/MainWindow.xaml.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace Godbert { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var settings = Settings.Default; if (settings.MainWindowWidth > 0) Width = Settings.Default.MainWindowWidth; if (settings.MainWindowHeight > 0) Height = Settings.Default.MainWindowHeight; if (settings.MainWindowLeft > 0) Left = Settings.Default.MainWindowLeft; if (settings.MainWindowTop > 0) Top = Settings.Default.MainWindowTop; } protected override void OnClosing(CancelEventArgs e) { base.OnClosing(e); Settings.Default.MainWindowHeight = Height; Settings.Default.MainWindowWidth = Width; Settings.Default.MainWindowLeft = Left; Settings.Default.MainWindowTop = Top; } } } ================================================ FILE: Godbert/Models/ModelCharaHierarchy.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SaintCoinach.Xiv; namespace Godbert.Models { public class ModelCharaHierarchy : IEnumerable { #region Properties public string MainFormat { get; set; } public string SubFormat { get; set; } public string VariantFormat { get; set; } #endregion #region Constructor public ModelCharaHierarchy(string mainFormat, string subFormat, string variantFormat) { this.MainFormat = mainFormat; this.SubFormat = subFormat; this.VariantFormat = variantFormat; } #endregion #region Fields private SortedDictionary _Items = new SortedDictionary(); #endregion #region Collection public void Add(ModelChara modelChara) { Add(modelChara.ModelKey, modelChara.BaseKey, modelChara.Variant); } public void Add(int main, int sub, int variant) { ModelCharaMain mcm; if (!_Items.TryGetValue(main, out mcm)) _Items.Add(main, mcm = new ModelCharaMain(this, string.Format(MainFormat, main), main)); mcm.Add(sub, variant); } public bool Contains(int main, int sub, int variant) { ModelCharaMain mcm; if (!_Items.TryGetValue(main, out mcm)) return false; return mcm.Contains(sub, variant); } public void Clear() { _Items.Clear(); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { return _Items.Values.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion } } ================================================ FILE: Godbert/Models/ModelCharaMain.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Godbert.Models { public class ModelCharaMain : IEnumerable { #region Properties public int Value { get; set; } public string DisplayName { get; set; } public ModelCharaHierarchy Parent { get; private set; } #endregion #region Constructor public ModelCharaMain(ModelCharaHierarchy parent, string displayName, int value) { this.Parent = parent; this.Value = value; this.DisplayName = displayName; } #endregion #region Fields private SortedDictionary _Items = new SortedDictionary(); #endregion #region Collection public void Add(int sub, int variant) { ModelCharaSub mcs; if (!_Items.TryGetValue(sub, out mcs)) _Items.Add(sub, mcs = new ModelCharaSub(this, string.Format(Parent.SubFormat, sub), sub)); mcs.Add(variant); } public bool Contains(int sub, int variant) { ModelCharaSub mcs; if (!_Items.TryGetValue(sub, out mcs)) return false; return mcs.Contains(variant); } public void Clear() { _Items.Clear(); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { return _Items.Values.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion public override string ToString() { return DisplayName; } } } ================================================ FILE: Godbert/Models/ModelCharaSub.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Godbert.Models { public class ModelCharaSub : IEnumerable { #region Properties public int Value { get; set; } public string DisplayName { get; set; } public ModelCharaMain Parent { get; private set; } #endregion #region Constructor public ModelCharaSub(ModelCharaMain parent, string displayName, int value) { this.Parent = parent; this.Value = value; this.DisplayName = displayName; } #endregion #region Fields private SortedDictionary _Items = new SortedDictionary(); #endregion #region Collection public void Add(int variant) { if (!_Items.ContainsKey(variant)) _Items.Add(variant, new ModelCharaVariant(this, string.Format(Parent.Parent.VariantFormat, variant), variant)); } public bool Contains(int variant) { return _Items.ContainsKey(variant); } public void Clear() { _Items.Clear(); } #endregion #region IEnumerable Members public IEnumerator GetEnumerator() { return _Items.Values.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } #endregion public override string ToString() { return string.Format("{0} / {1}", Parent, DisplayName); } } } ================================================ FILE: Godbert/Models/ModelCharaVariant.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Godbert.Models { public class ModelCharaVariant { #region Properties public int Value { get; set; } public string DisplayName { get; set; } public ModelCharaSub Parent { get; private set; } #endregion #region Constructor public ModelCharaVariant(ModelCharaSub parent, string displayName, int value) { this.Parent = parent; this.Value = value; this.DisplayName = displayName; } #endregion public override string ToString() { return string.Format("{0} / {1}", Parent, DisplayName); } } } ================================================ FILE: Godbert/NaturalComparer.cs ================================================ // Taken from http://www.codeproject.com/Articles/22517/Natural-Sort-Comparer // Modified to allow for different StringComparison using System; using System.Collections.Generic; using System.Text.RegularExpressions; namespace Godbert { public class NaturalComparer : Comparer, IDisposable { private Dictionary table; public NaturalComparer() : this(StringComparer.Ordinal) { } public NaturalComparer(StringComparer partsComparer) { table = new Dictionary(); this.PartsComparer = partsComparer; } public StringComparer PartsComparer { get; set; } public void Dispose() { table.Clear(); table = null; } public override int Compare(string x, string y) { if (x == y) { return 0; } if (x == null) return -1; if (y == null) return 1; string[] x1, y1; if (!table.TryGetValue(x, out x1)) { x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)"); table.Add(x, x1); } if (!table.TryGetValue(y, out y1)) { y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)"); table.Add(y, y1); } for (int i = 0; i < x1.Length && i < y1.Length; i++) { if (x1[i] != y1[i]) { // This always returned, modified to only return if not equal var partCmp = PartCompare(x1[i], y1[i]); if (partCmp != 0) return partCmp; } } if (y1.Length > x1.Length) { return 1; } else if (x1.Length > y1.Length) { return -1; } else { return 0; } } private int PartCompare(string left, string right) { int x, y; if (!int.TryParse(left, out x) || !int.TryParse(right, out y)) return PartsComparer.Compare(left, right); return x.CompareTo(y); } } } ================================================ FILE: Godbert/ObservableBase.cs ================================================ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; namespace Godbert { public class ObservableBase : INotifyPropertyChanged { #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { var h = PropertyChanged; if (h != null) h(this, new PropertyChangedEventArgs(propertyName)); } protected virtual void OnPropertyChanged(Expression> property) { MemberExpression memberExpression; if (property.Body is UnaryExpression) { var unaryExpression = (UnaryExpression)property.Body; memberExpression = (MemberExpression)unaryExpression.Operand; } else { memberExpression = (MemberExpression)property.Body; } OnPropertyChanged(memberExpression.Member.Name); } #endregion } } ================================================ FILE: Godbert/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] //In order to begin building localizable applications, set //CultureYouAreCodingWith in your .csproj file //inside a . For example, if you are using US english //in your source files, set the to en-US. Then uncomment //the NeutralResourceLanguage attribute below. Update the "en-US" in //the line below to match the UICulture setting in the project file. //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located //(used if a resource is not found in the page, // or application resource dictionaries) ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located //(used if a resource is not found in the page, // app, or any theme specific resource dictionaries) )] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] ================================================ FILE: Godbert/Properties/Resources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Godbert.Properties { using System; /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Godbert.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } } } ================================================ FILE: Godbert/Properties/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ================================================ FILE: Godbert/Properties/Settings.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace Godbert.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.3.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); public static Settings Default { get { return defaultInstance; } } [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] public string GamePath { get { return ((string)(this["GamePath"])); } set { this["GamePath"] = value; } } } } ================================================ FILE: Godbert/Properties/Settings.settings ================================================  ================================================ FILE: Godbert/Settings.cs ================================================ using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Godbert { public class Settings { #region IO Boilerplate public static Settings Default { get; } = Load(); private static string FileName => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Godbert", "settings.json"); private static Settings Load() { try { if (File.Exists(FileName)) { var text = File.ReadAllText(FileName); return JsonConvert.DeserializeObject(text) ?? new Settings(); } } catch (Exception) { // Error reading settings. Return default. } return new Settings(); } public void Save() { var path = Path.GetDirectoryName(FileName); if (!Directory.Exists(path)) Directory.CreateDirectory(path); var text = JsonConvert.SerializeObject(this, Formatting.Indented); try { File.WriteAllText(FileName, text); } catch (IOException) { // Error saving settings. Ignore. } } #endregion public double MainWindowLeft; public double MainWindowTop; public double MainWindowWidth; public double MainWindowHeight; public string SelectedSheetName; public string FilterSheetTerm; public string FilterDataTerm; public bool ShowOffsets; public bool SortByOffsets; } } ================================================ FILE: Godbert/ViewModels/BookmarkViewModel.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Godbert.ViewModels { public class BookmarkViewModel { public string SheetName { get; set; } public int Key { get; set; } public string RowDefault { get; set; } public int? ColumnIndex { get; set; } public string ColumnName { get; set; } public override string ToString() { var rowIdentifier = $"{SheetName}#{Key}"; var sb = new StringBuilder(); sb.Append(rowIdentifier); if (ColumnName != null) sb.Append($" [{ColumnIndex} {ColumnName}]"); else if (ColumnIndex != null) sb.Append($" [{ColumnIndex}]"); if (!string.IsNullOrWhiteSpace(RowDefault) && RowDefault != rowIdentifier) { sb.AppendLine(); sb.Append(RowDefault); } return sb.ToString(); } public override bool Equals(object obj) { var b = obj as BookmarkViewModel; if (b == null) return false; return b.SheetName == SheetName && b.Key == Key && b.ColumnIndex == ColumnIndex; } public override int GetHashCode() { return SheetName.GetHashCode() ^ Key.GetHashCode() ^ (ColumnIndex ?? 0).GetHashCode(); } } } ================================================ FILE: Godbert/ViewModels/DataViewModel.cs ================================================ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using System.Windows; using SaintCoinach.Ex.Relational; namespace Godbert.ViewModels { using Commands; public class DataViewModel : ObservableBase { #region Fields private string _SelectedSheetName; private IRelationalSheet _SelectedSheet; private DelegateCommand _ExportCsvCommand; private string _FilterSheetTerm; private IEnumerable _FilteredSheets; private string _FilterDataTerm; private ObservableCollection _Bookmarks = new ObservableCollection(); #endregion #region Properties public ICommand ExportCsvCommand { get { return _ExportCsvCommand ?? (_ExportCsvCommand = new DelegateCommand(OnExportCsv)); } } public SaintCoinach.ARealmReversed Realm { get; private set; } public MainViewModel Parent { get; private set; } public IEnumerable FilteredSheetNames { get { return _FilteredSheets; } } public string SelectedSheetName { get { return _SelectedSheetName; } set { _SelectedSheetName = value; _SelectedSheet = null; OnPropertyChanged(() => SelectedSheetName); OnPropertyChanged(() => SelectedSheet); Settings.Default.SelectedSheetName = value; } } public IRelationalSheet SelectedSheet { get { if (string.IsNullOrWhiteSpace(SelectedSheetName)) return null; if (_SelectedSheet == null) _SelectedSheet = Realm.GameData.GetSheet(SelectedSheetName); return _SelectedSheet; } } public string FilterSheetTerm { get { return _FilterSheetTerm; } set { _FilterSheetTerm = value; if (string.IsNullOrWhiteSpace(value)) _FilteredSheets = Realm.GameData.AvailableSheets; else _FilteredSheets = Realm.GameData.AvailableSheets.Where(s => s.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0).ToArray(); OnPropertyChanged(() => FilterSheetTerm); OnPropertyChanged(() => FilteredSheetNames); Settings.Default.FilterSheetTerm = value; } } public string FilterDataTerm { get { return _FilterDataTerm; } set { _FilterDataTerm = value; OnPropertyChanged(() => FilterDataTerm); Settings.Default.FilterDataTerm = value; } } public ObservableCollection Bookmarks { get { return _Bookmarks; } } public Visibility BookmarksVisibility { get { return _Bookmarks.Count > 0 ? Visibility.Visible : Visibility.Collapsed; } } #endregion #region Constructor public DataViewModel(SaintCoinach.ARealmReversed realm, MainViewModel parent) { this.Realm = realm; this.Parent = parent; _FilteredSheets = Realm.GameData.AvailableSheets; _SelectedSheetName = Settings.Default.SelectedSheetName; _FilterDataTerm = Settings.Default.FilterDataTerm; FilterSheetTerm = Settings.Default.FilterSheetTerm; _Bookmarks.CollectionChanged += _Bookmarks_CollectionChanged; } #endregion #region Export private void OnExportCsv() { if (SelectedSheet == null) return; var dlg = new Microsoft.Win32.SaveFileDialog { DefaultExt = ".csv", Filter = "CSV Files (*.csv)|*.csv", AddExtension = true, OverwritePrompt = true, FileName = FixName(SelectedSheet.Name) + ".csv" }; if (dlg.ShowDialog().GetValueOrDefault(false)) SaveAsCsv(SelectedSheet, dlg.FileName); } private static string FixName(string original) { var idx = original.LastIndexOf('/'); if (idx >= 0) return original.Substring(idx + 1); return original; } static void SaveAsCsv(IRelationalSheet sheet, string path) { using (var s = new StreamWriter(path, false, Encoding.UTF8)) { var indexLine = new StringBuilder("key"); var nameLine = new StringBuilder("#"); var typeLine = new StringBuilder("int32"); var colIndices = new List(); foreach (var col in sheet.Header.Columns) { indexLine.AppendFormat(",{0}", col.Index); nameLine.AppendFormat(",{0}", col.Name); typeLine.AppendFormat(",{0}", col.ValueType); colIndices.Add(col.Index); } s.WriteLine(indexLine); s.WriteLine(nameLine); s.WriteLine(typeLine); foreach (var row in sheet.Cast().OrderBy(_ => _.Key)) { s.Write(row.Key); foreach (var col in colIndices) { var v = row[col]; if (v == null) s.Write(","); else if (v is IDictionary) WriteDict(s, v as IDictionary); else if (IsUnescaped(v)) s.Write(",{0}", v); else s.Write(",\"{0}\"", v.ToString().Replace("\"", "\"\"")); } s.WriteLine(); } } } static void WriteDict(StreamWriter s, IDictionary v) { s.Write(",\""); var isFirst = true; foreach (var kvp in v) { if (isFirst) isFirst = false; else s.Write(","); s.Write("[{0},", kvp.Key); if (kvp.Value != null) s.Write(kvp.Value.ToString().Replace("\"", "\"\"")); s.Write("]"); } s.Write("\""); } static bool IsUnescaped(object self) { return (self is Boolean || self is Byte || self is SByte || self is Int16 || self is Int32 || self is Int64 || self is UInt16 || self is UInt32 || self is UInt64 || self is Single || self is Double); } #endregion private void _Bookmarks_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { OnPropertyChanged(() => BookmarksVisibility); } } } ================================================ FILE: Godbert/ViewModels/DemihumanViewModel.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using SaintCoinach.Graphics; using SaintCoinach.Graphics.Viewer; using SaintCoinach.Graphics.Viewer.Content; using SaintCoinach.Xiv; namespace Godbert.ViewModels { using Commands; public class DemihumanViewModel : ObservableBase { const string ImcPathFormat = "chara/demihuman/d{0:D4}/obj/equipment/e{1:D4}/e{1:D4}.imc"; static readonly string[] ModelPathFormats = new string[] { "chara/demihuman/d{0:D4}/obj/equipment/e{1:D4}/model/d{0:D4}e{1:D4}_met.mdl", "chara/demihuman/d{0:D4}/obj/equipment/e{1:D4}/model/d{0:D4}e{1:D4}_top.mdl", "chara/demihuman/d{0:D4}/obj/equipment/e{1:D4}/model/d{0:D4}e{1:D4}_glv.mdl", "chara/demihuman/d{0:D4}/obj/equipment/e{1:D4}/model/d{0:D4}e{1:D4}_dwn.mdl", "chara/demihuman/d{0:D4}/obj/equipment/e{1:D4}/model/d{0:D4}e{1:D4}_sho.mdl", }; #region Fields private Models.ModelCharaHierarchy _Entries; private object _SelectedEntry; private bool[] _SelectedParts = new bool[] { true, true, true, true, true }; #endregion #region Properties public MainViewModel Parent { get; private set; } public Models.ModelCharaHierarchy Entries { get { return _Entries; } private set { _Entries = value; OnPropertyChanged(() => Entries); } } public object SelectedEntry { get { return _SelectedEntry; } set { _SelectedEntry = value; OnPropertyChanged(() => SelectedEntry); OnPropertyChanged(() => IsValidSelection); } } public bool IsValidSelection { get { return SelectedEntry is Models.ModelCharaVariant; } } public bool ShowPart0 { get { return _SelectedParts[0]; } set { _SelectedParts[0] = value; OnPropertyChanged(() => ShowPart0); } } public bool ShowPart1 { get { return _SelectedParts[1]; } set { _SelectedParts[1] = value; OnPropertyChanged(() => ShowPart0); } } public bool ShowPart2 { get { return _SelectedParts[2]; } set { _SelectedParts[2] = value; OnPropertyChanged(() => ShowPart0); } } public bool ShowPart3 { get { return _SelectedParts[3]; } set { _SelectedParts[3] = value; OnPropertyChanged(() => ShowPart3); } } public bool ShowPart4 { get { return _SelectedParts[4]; } set { _SelectedParts[4] = value; OnPropertyChanged(() => ShowPart4); } } #endregion #region Constructor public DemihumanViewModel(MainViewModel parent) { this.Parent = parent; Entries = new Models.ModelCharaHierarchy("d{0:D4}", "e{0:D4}", "v{0:D4}"); foreach (var mc in parent.Realm.GameData.GetSheet().Where(mc => mc.Type == 2)) { var imcPath = string.Format(ImcPathFormat, mc.ModelKey, mc.BaseKey); if (parent.Realm.Packs.FileExists(imcPath)) Entries.Add(mc); } } #endregion #region Commands private ICommand _AddCommand; private ICommand _ReplaceCommand; private ICommand _NewCommand; public ICommand AddCommand { get { return _AddCommand ?? (_AddCommand = new DelegateCommand(OnAdd)); } } public ICommand ReplaceCommand { get { return _ReplaceCommand ?? (_ReplaceCommand = new DelegateCommand(OnReplace)); } } public ICommand NewCommand { get { return _NewCommand ?? (_NewCommand = new DelegateCommand(OnNew)); } } private void OnAdd() { string title; Tuple[] models; if (TryGetModel(out title, out models)) Parent.EngineHelper.AddToLast(title, (e) => models.Select(m => new SaintCoinach.Graphics.Viewer.Content.ContentModel(e, m.Item2, m.Item1, ModelQuality.High)).ToArray()); } private void OnReplace() { string title; Tuple[] models; if (TryGetModel(out title, out models)) Parent.EngineHelper.ReplaceInLast(title, (e) => models.Select(m => new SaintCoinach.Graphics.Viewer.Content.ContentModel(e, m.Item2, m.Item1, ModelQuality.High)).ToArray()); } private void OnNew() { string title; Tuple[] models; if (TryGetModel(out title, out models)) Parent.EngineHelper.OpenInNew(title, (e) => models.Select(m => new SaintCoinach.Graphics.Viewer.Content.ContentModel(e, m.Item2, m.Item1, ModelQuality.High)).ToArray()); } private bool TryGetModel(out string title, out Tuple[] models) { title = null; models = null; var asVariant = SelectedEntry as Models.ModelCharaVariant; if (asVariant == null) return false; title = asVariant.ToString(); int v = asVariant.Value; int e = asVariant.Parent.Value; var d = asVariant.Parent.Parent.Value; var imcPath = string.Format(ImcPathFormat, d, e); SaintCoinach.IO.File imcFileBase; if (!Parent.Realm.Packs.TryGetFile(imcPath, out imcFileBase)) { System.Windows.MessageBox.Show(string.Format("Unable to find files for {0}.", title), "File not found", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return false; } try { var imcFile = new ImcFile(imcFileBase); var modelsList = new List>(); foreach(var part in imcFile.Parts) { if (!_SelectedParts[part.Bit]) continue; var variant = part.Variants[v]; if(variant.Variant == 0) continue; var mdlPath = string.Format(ModelPathFormats[part.Bit], d, e); SaintCoinach.IO.File mdlBase; if(!Parent.Realm.Packs.TryGetFile(mdlPath, out mdlBase)) continue; var mdl = ((ModelFile)mdlBase).GetModelDefinition(); modelsList.Add(Tuple.Create(mdl, variant)); } if(modelsList.Count == 0) { System.Windows.MessageBox.Show(string.Format("No models found for {0}.", title), "No models", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return false; } models = modelsList.ToArray(); return true; } catch (Exception ex) { System.Windows.MessageBox.Show(string.Format("Unable to load model for {0}:{1}{2}", title, Environment.NewLine, ex), "Failure to load", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return false; } } #endregion #region Brute-force private bool _IsBruteForceAvailable = true; private ICommand _BruteForceCommand; public bool IsBruteForceAvailable { get { return _IsBruteForceAvailable; } private set { _IsBruteForceAvailable = value; OnPropertyChanged(() => IsBruteForceAvailable); } } public ICommand BruteForceCommand { get { return _BruteForceCommand ?? (_BruteForceCommand = new DelegateCommand(OnBruteForce)); } } private void OnBruteForce() { IsBruteForceAvailable = false; var progDlg = new Ookii.Dialogs.Wpf.ProgressDialog(); progDlg.WindowTitle = "Brute-forcing"; progDlg.Text = "This is going to take a while..."; progDlg.DoWork += DoBruteForceWork; progDlg.RunWorkerCompleted += OnBruteForceComplete; progDlg.ShowDialog(System.Windows.Application.Current.MainWindow); progDlg.ProgressBarStyle = Ookii.Dialogs.Wpf.ProgressBarStyle.ProgressBar; progDlg.ShowTimeRemaining = true; } void OnBruteForceComplete(object sender, System.ComponentModel.RunWorkerCompletedEventArgs eventArgs) { if (eventArgs.Cancelled) IsBruteForceAvailable = true; } void DoBruteForceWork(object sender, System.ComponentModel.DoWorkEventArgs eventArgs) { var dlg = (Ookii.Dialogs.Wpf.ProgressDialog)sender; var newEntries = new Models.ModelCharaHierarchy(Entries.MainFormat, Entries.SubFormat, Entries.VariantFormat); for (var d = 0; d < 10000; ++d) { if (dlg.CancellationPending) return; dlg.ReportProgress(d / 100, null, string.Format("Current progress: {0:P}", d / 10000.0)); for (var e = 0; e < 10000; ++e) { //dlg.ReportProgress(((d * 10000) + e) / (10000 * 100)); var imcPath = string.Format(ImcPathFormat, d, e); SaintCoinach.IO.File imcBase; if (!Parent.Realm.Packs.TryGetFile(imcPath, out imcBase)) continue; try { var imc = new SaintCoinach.Graphics.ImcFile(imcBase); for (var v = 1; v < imc.Count; ++v) { /*if (Entries.Contains(d, e, v)) continue;*/ var any = false; foreach (var p in imc.Parts) { if (p.Variants[v].Variant != 0) { any = true; break; } } if (any) newEntries.Add(d, e, v); } } catch (Exception ex) { Console.Error.WriteLine("Failed parsing imc file {0}:{1}{2}", imcPath, Environment.NewLine, ex); } } } Entries = newEntries; } #endregion } } ================================================ FILE: Godbert/ViewModels/EquipmentViewModel.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using SaintCoinach.Graphics; using SaintCoinach.Graphics.Viewer; using SaintCoinach.Xiv; using SaintCoinach.Xiv.Items; namespace Godbert.ViewModels { using Commands; public class EquipmentViewModel : ObservableBase { #region Fields private Equipment[] _AllEquipment; private Equipment[] _FilteredEquipment; private Equipment _SelectedEquipment; private Stain _SelectedStain; private string _FilterTerm; #endregion #region Properties public MainViewModel Parent { get; private set; } public IEnumerable AllEquipment { get { return _AllEquipment; } } public IEnumerable FilteredEquipment { get { return _FilteredEquipment; } } public Equipment SelectedEquipment { get { return _SelectedEquipment; } set { _SelectedEquipment = value; OnPropertyChanged(() => SelectedEquipment); } } public IEnumerable Stains { get; private set; } public Stain SelectedStain { get { return _SelectedStain; } set { _SelectedStain = value; OnPropertyChanged(() => SelectedStain); } } public string FilterTerm { get { return _FilterTerm; } set { _FilterTerm = value; if (string.IsNullOrWhiteSpace(value)) _FilteredEquipment = _AllEquipment; else _FilteredEquipment = _AllEquipment.Where(e => e.Name.ToString().IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0).ToArray(); OnPropertyChanged(() => FilterTerm); OnPropertyChanged(() => FilteredEquipment); } } #endregion #region Constructor public EquipmentViewModel(MainViewModel parent) { this.Parent = parent; // Ignore waist and soul crystals _AllEquipment = Parent.Realm.GameData.GetSheet().OfType().Where(e => !e.EquipSlotCategory.PossibleSlots.Any(s => s.Key == 5 || s.Key == 13)).OrderBy(e => e.Name).ToArray(); Stains = Parent.Realm.GameData.GetSheet(); _FilteredEquipment = _AllEquipment; } #endregion #region Command private ICommand _AddCommand; private ICommand _ReplaceCommand; private ICommand _NewCommand; public ICommand AddCommand { get { return _AddCommand ?? (_AddCommand = new DelegateCommand(OnAdd)); } } public ICommand ReplaceCommand { get { return _ReplaceCommand ?? (_ReplaceCommand = new DelegateCommand(OnReplace)); } } public ICommand NewCommand { get { return _NewCommand ?? (_NewCommand = new DelegateCommand(OnNew)); } } private void OnAdd() { if (TryGetModel(out var model, out var variant)) Parent.EngineHelper.AddToLast(SelectedEquipment.Name, (e) => new SaintCoinach.Graphics.Viewer.Content.ContentModel(e, variant, model, ModelQuality.High)); } private void OnReplace() { if (TryGetModel(out var model, out var variant)) Parent.EngineHelper.ReplaceInLast(SelectedEquipment.Name, (e) => new SaintCoinach.Graphics.Viewer.Content.ContentModel(e, variant, model, ModelQuality.High)); } private void OnNew() { if (TryGetModel(out var model, out var variant)) Parent.EngineHelper.OpenInNew(SelectedEquipment.Name, (e) => new SaintCoinach.Graphics.Viewer.Content.ContentModel(e, variant, model, ModelQuality.High)); } private bool TryGetModel(out ModelDefinition model, out ModelVariantIdentifier variant) { model = null; variant = default(ModelVariantIdentifier); if (SelectedEquipment == null) return false; var charType = SelectedEquipment.GetModelCharacterType(); if (charType == 0) return false; try { model = SelectedEquipment.GetModel(charType, out variant.ImcVariant); if (SelectedEquipment.IsDyeable && SelectedStain != null) variant.StainKey = SelectedStain.Key; var result = (model != null); if (!result) System.Windows.MessageBox.Show(string.Format("Unable to find model for {0} (c{1:D4}).", SelectedEquipment.Name, charType), "Model not found", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return result; } catch (Exception e) { System.Windows.MessageBox.Show(string.Format("Failed to load model for {0} (c{1:D4}):{2}{3}", SelectedEquipment.Name, charType, Environment.NewLine, e), "Read failure", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return false; } } #endregion #region Refresh public void Refresh() { _FilteredEquipment = _FilteredEquipment.ToArray(); OnPropertyChanged(() => FilteredEquipment); } #endregion } } ================================================ FILE: Godbert/ViewModels/FurnitureViewModel.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using SaintCoinach.Graphics; using SaintCoinach.Graphics.Sgb; using SaintCoinach.Graphics.Viewer; using SaintCoinach.Xiv; namespace Godbert.ViewModels { using Commands; public class FurnitureViewModel : ObservableBase { #region Fields private HousingItem[] _AllFurniture; private HousingItem[] _FilteredFurniture; private HousingItem _SelectedFurniture; private Stain _SelectedStain; private string _FilterTerm; private SaintCoinach.Graphics.Viewer.Data.ParametersBase _Parameters = new SaintCoinach.Graphics.Viewer.Data.ParametersBase(); #endregion #region Properties public MainViewModel Parent { get; private set; } public IEnumerable AllFurniture { get { return _AllFurniture; } } public IEnumerable FilteredFurniture { get { return _FilteredFurniture; } } public HousingItem SelectedFurniture { get { return _SelectedFurniture; } set { _SelectedFurniture = value; OnPropertyChanged(() => SelectedFurniture); } } public IEnumerable Stains { get; private set; } public Stain SelectedStain { get { return _SelectedStain; } set { if (value == null || value.Key == 0) _Parameters.Remove(SaintCoinach.Graphics.Viewer.Content.BgColorChangeMaterial.ColorParameterKey); else _Parameters.Set(SaintCoinach.Graphics.Viewer.Content.BgColorChangeMaterial.ColorParameterKey, new SharpDX.Vector4(value.Color.R / 255f, value.Color.G / 255f, value.Color.B / 255f, value.Color.A / 255f)); _SelectedStain = value; OnPropertyChanged(() => SelectedStain); } } public string FilterTerm { get { return _FilterTerm; } set { _FilterTerm = value; if (string.IsNullOrWhiteSpace(value)) _FilteredFurniture = _AllFurniture; else _FilteredFurniture = _AllFurniture.Where(e => e.Item.Name.ToString().IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0).ToArray(); OnPropertyChanged(() => FilterTerm); OnPropertyChanged(() => FilteredFurniture); } } #endregion #region Constructor public FurnitureViewModel(MainViewModel parent) { this.Parent = parent; var indoor = Parent.Realm.GameData.GetSheet(); var outdoor = Parent.Realm.GameData.GetSheet(); _AllFurniture = indoor.Cast().Concat(outdoor.Cast()).Where(_ => _.Item != null && _.Item.Key != 0 && _.Item.Name.ToString().Length > 0).OrderBy(_ => _.Item.Name).ToArray(); Stains = Parent.Realm.GameData.GetSheet(); _FilteredFurniture = _AllFurniture; } #endregion #region Command private ICommand _AddCommand; private ICommand _ReplaceCommand; private ICommand _NewCommand; public ICommand AddCommand { get { return _AddCommand ?? (_AddCommand = new DelegateCommand(OnAdd)); } } public ICommand ReplaceCommand { get { return _ReplaceCommand ?? (_ReplaceCommand = new DelegateCommand(OnReplace)); } } public ICommand NewCommand { get { return _NewCommand ?? (_NewCommand = new DelegateCommand(OnNew)); } } private void OnAdd() { SgbFile scene; if (TryGetModel(out scene)) Parent.EngineHelper.AddToLast(SelectedFurniture.Item.Name, (e) => new SaintCoinach.Graphics.Viewer.Content.ContentSgb(e, scene, (SaintCoinach.Graphics.Viewer.Data.ParametersBase)_Parameters.Clone())); } private void OnReplace() { SgbFile scene; if (TryGetModel(out scene)) Parent.EngineHelper.ReplaceInLast(SelectedFurniture.Item.Name, (e) => new SaintCoinach.Graphics.Viewer.Content.ContentSgb(e, scene, (SaintCoinach.Graphics.Viewer.Data.ParametersBase)_Parameters.Clone())); } private void OnNew() { SgbFile scene; if (TryGetModel(out scene)) Parent.EngineHelper.OpenInNew(SelectedFurniture.Item.Name, (e) => new SaintCoinach.Graphics.Viewer.Content.ContentSgb(e, scene, (SaintCoinach.Graphics.Viewer.Data.ParametersBase)_Parameters.Clone())); } private bool TryGetModel(out SgbFile model) { model = null; if (SelectedFurniture == null) return false; try { model = SelectedFurniture.GetScene(); var result = (model != null); if (!result) System.Windows.MessageBox.Show(string.Format("Unable to find model for {0}.", SelectedFurniture.Item.Name), "Model not found", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return result; } catch (Exception e) { System.Windows.MessageBox.Show(string.Format("Failed to load model for {0}:{2}{3}", SelectedFurniture.Item.Name, Environment.NewLine, e), "Read failure", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return false; } } #endregion #region Refresh public void Refresh() { _FilteredFurniture = _FilteredFurniture.ToArray(); OnPropertyChanged(() => FilteredFurniture); } #endregion } } ================================================ FILE: Godbert/ViewModels/MainViewModel.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; namespace Godbert.ViewModels { using Commands; using SaintCoinach; using SaintCoinach.Xiv; public class MainViewModel : ObservableBase { #region Properties public ARealmReversed Realm { get; private set; } public EngineHelper EngineHelper { get; private set; } public EquipmentViewModel Equipment { get; private set; } public FurnitureViewModel Furniture { get; private set; } public MonstersViewModel Monsters { get; private set; } public TerritoryViewModel Territories { get; private set; } public DemihumanViewModel Demihuman { get; private set; } public DataViewModel Data { get; private set; } public bool IsEnglish { get { return Realm.GameData.ActiveLanguage == SaintCoinach.Ex.Language.English; } } public bool IsJapanese { get { return Realm.GameData.ActiveLanguage == SaintCoinach.Ex.Language.Japanese; } } public bool IsFrench { get { return Realm.GameData.ActiveLanguage == SaintCoinach.Ex.Language.French; } } public bool IsGerman { get { return Realm.GameData.ActiveLanguage == SaintCoinach.Ex.Language.German; } } public bool IsChineseSimplified { get { return Realm.GameData.ActiveLanguage == SaintCoinach.Ex.Language.ChineseSimplified; } } public bool IsKorean { get { return Realm.GameData.ActiveLanguage == SaintCoinach.Ex.Language.Korean; } } public bool SortByOffsets { get { return Settings.Default.SortByOffsets;} } public bool ShowOffsets { get { return Settings.Default.ShowOffsets; } } #endregion #region Events public event EventHandler DataGridChanged; #endregion #region Constructor public MainViewModel() { if (!App.IsValidGamePath(Properties.Settings.Default.GamePath)) return; var languages = new[] { SaintCoinach.Ex.Language.English, SaintCoinach.Ex.Language.ChineseSimplified, SaintCoinach.Ex.Language.Korean }; NotSupportedException lastException = null; foreach (var language in languages) { try { var realm = new ARealmReversed(Properties.Settings.Default.GamePath, language); Initialize(realm); lastException = null; break; } catch (NotSupportedException e) { lastException = e; continue; } } if (lastException != null) { throw new AggregateException(new[] { lastException }); } } public MainViewModel(ARealmReversed realm) { Initialize(realm); } void Initialize(ARealmReversed realm) { realm.Packs.GetPack(new SaintCoinach.IO.PackIdentifier("exd", SaintCoinach.IO.PackIdentifier.DefaultExpansion, 0)).KeepInMemory = true; Realm = realm; EngineHelper = new EngineHelper(); Equipment = new EquipmentViewModel(this); Furniture = new FurnitureViewModel(this); Monsters = new MonstersViewModel(this); Territories = new TerritoryViewModel(this); Demihuman = new DemihumanViewModel(this); Data = new DataViewModel(Realm, this); } #endregion #region Commands private ICommand _LanguageCommand; private ICommand _GameLocationCommand; private ICommand _NewWindowCommand; private ICommand _ShowOffsetsCommand; private ICommand _SortByOffsetsCommand; public ICommand LanguageCommand { get { return _LanguageCommand ?? (_LanguageCommand = new Commands.DelegateCommand(OnLanguage)); } } public ICommand GameLocationCommand { get { return _GameLocationCommand ?? (_GameLocationCommand = new Commands.DelegateCommand(OnGameLocation)); } } public ICommand NewWindowCommand { get { return _NewWindowCommand ?? (_NewWindowCommand = new Commands.DelegateCommand(OnNewWindowCommand)); } } public ICommand ShowOffsetsCommand { get { return _ShowOffsetsCommand ?? (_ShowOffsetsCommand = new Commands.DelegateCommand(OnShowOffsetsCommand)); } } public ICommand SortByOffsetsCommand { get { return _SortByOffsetsCommand ?? (_SortByOffsetsCommand= new Commands.DelegateCommand(OnSortByOffsetsCommand)); } } private void OnLanguage(SaintCoinach.Ex.Language newLanguage) { Realm.GameData.ActiveLanguage = newLanguage; OnPropertyChanged(() => IsEnglish); OnPropertyChanged(() => IsJapanese); OnPropertyChanged(() => IsGerman); OnPropertyChanged(() => IsFrench); OnPropertyChanged(() => IsChineseSimplified); OnPropertyChanged(() => IsKorean); Equipment.Refresh(); Territories.Refresh(); } private void OnGameLocation() { var res = System.Windows.MessageBox.Show("If you continue the application will restart and ask you for the game's installation directory, do you want to continue?", "Change game location", System.Windows.MessageBoxButton.YesNo, System.Windows.MessageBoxImage.Exclamation); if (res == System.Windows.MessageBoxResult.Yes) { Properties.Settings.Default.GamePath = null; Properties.Settings.Default.Save(); var current = System.Diagnostics.Process.GetCurrentProcess(); var startInfo = new System.Diagnostics.ProcessStartInfo(current.MainModule.FileName); startInfo.WorkingDirectory = Directory.GetCurrentDirectory(); System.Diagnostics.Process.Start(startInfo); App.Current.Shutdown(); } } private void OnNewWindowCommand() { Mouse.OverrideCursor = Cursors.Wait; try { var viewModel = new MainViewModel(Realm); var window = new MainWindow() { DataContext = viewModel }; window.Show(); } finally { Mouse.OverrideCursor = null; } } private void OnShowOffsetsCommand() { Settings.Default.ShowOffsets = !Settings.Default.ShowOffsets; OnPropertyChanged(() => ShowOffsets); } private void OnSortByOffsetsCommand() { Settings.Default.SortByOffsets = !Settings.Default.SortByOffsets; OnPropertyChanged(() => SortByOffsets); } #endregion } } ================================================ FILE: Godbert/ViewModels/MapsViewModel.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Input; using SaintCoinach.Graphics; using SaintCoinach.Graphics.Viewer; using SaintCoinach.Xiv; namespace Godbert.ViewModels { using Commands; public class MapsViewModel : ObservableBase { #region Fields private Map[] _Maps; private Map _SelectedMap; #endregion #region Properties public MainViewModel Parent { get; private set; } public IEnumerable Maps { get { return _Maps; } } public Map SelectedMap { get { return _SelectedMap; } set { _SelectedMap = value; OnPropertyChanged(() => SelectedMap); } } #endregion #region Constructor public MapsViewModel(MainViewModel parent) { this.Parent = parent; var allMaps = parent.Realm.GameData.GetSheet(); _Maps = allMaps.Where(m => m.TerritoryType != null && m.TerritoryType.Key > 1 && m.PlaceName != null && m.PlaceName.Key != 0 && m.RegionPlaceName != null && m.RegionPlaceName.Key != 0).ToArray(); } #endregion #region Commands private ICommand _OpenCommand; public ICommand OpenCommand { get { return _OpenCommand ?? (_OpenCommand = new DelegateCommand(OnOpen)); } } private void OnOpen() { if (SelectedMap == null) return; var n = string.Format("({0}) {1}: {2}", SelectedMap.Id, SelectedMap.RegionPlaceName, SelectedMap.PlaceName); try { var t = SelectedMap.GetTerritory(); if (t == null) System.Windows.MessageBox.Show(string.Format("Could not find territory data for {0}.", n), "Territory not found", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); else Parent.EngineHelper.OpenInNew(n, (e) => new SaintCoinach.Graphics.Viewer.Content.ContentTerritory(e, t)); } catch (Exception e) { System.Windows.MessageBox.Show(string.Format("Error reading territory for {0}:{1}{2}", n, Environment.NewLine, e), "Failure to read territory", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } } #endregion #region Refresh public void Refresh() { _Maps = _Maps.ToArray(); OnPropertyChanged(() => Maps); } #endregion } } ================================================ FILE: Godbert/ViewModels/MonstersViewModel.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Forms; using System.Windows.Input; using Ookii.Dialogs.Wpf; using SaintCoinach.Graphics; using SaintCoinach.Graphics.Viewer; using SaintCoinach.Graphics.Viewer.Content; using SaintCoinach.Graphics.Viewer.Interop; using SaintCoinach.IO; using SaintCoinach.Xiv; using SaintCoinach.Xiv.ItemActions; using Directory = System.IO.Directory; using File = SaintCoinach.IO.File; using ProgressBarStyle = Ookii.Dialogs.Wpf.ProgressBarStyle; namespace Godbert.ViewModels { using Commands; public class MonstersViewModel : ObservableBase { const string ImcPathFormat = "chara/monster/m{0:D4}/obj/body/b{1:D4}/b{1:D4}.imc"; const string ModelPathFormat = "chara/monster/m{0:D4}/obj/body/b{1:D4}/model/m{0:D4}b{1:D4}.mdl"; const string SkeletonPathFormat = "chara/monster/m{0:D4}/skeleton/base/b{1:D4}/skl_m{0:D4}b{1:D4}.sklb"; const string PapPathFormat = "chara/monster/m{0:D4}/animation/a0001/bt_common/resident/monster.pap"; #region Fields private Models.ModelCharaHierarchy _Entries; private object _SelectedEntry; private bool _IsExporting = false; public bool IsExporting { get { return _IsExporting; } private set { _IsExporting = value; OnPropertyChanged(() => IsExporting); } } #endregion #region Properties public Models.ModelCharaHierarchy Entries { get { return _Entries; } private set { _Entries = value; OnPropertyChanged(() => Entries); } } public object SelectedEntry { get { return _SelectedEntry; } set { _SelectedEntry = value; OnPropertyChanged(() => SelectedEntry); OnPropertyChanged(() => IsValidSelection); } } public bool IsValidSelection { get { return SelectedEntry is Models.ModelCharaVariant; } } public MainViewModel Parent { get; private set; } #endregion #region Constructor public MonstersViewModel(MainViewModel parent) { this.Parent = parent; var modelCharaSheet = Parent.Realm.GameData.GetSheet(); Entries = new Models.ModelCharaHierarchy("m{0:D4}", "b{0:D4}", "v{0:D4}"); foreach(var mc in modelCharaSheet.Where(mc => mc.Type == 3)) { var imcPath = string.Format(ImcPathFormat, mc.ModelKey, mc.BaseKey); var mdlPath = string.Format(ModelPathFormat, mc.ModelKey, mc.BaseKey); if(!Parent.Realm.Packs.FileExists(imcPath) ||!Parent.Realm.Packs.FileExists(mdlPath)) continue; Entries.Add(mc); } } #endregion #region Command private ICommand _AddCommand; private ICommand _ReplaceCommand; private ICommand _ExportCommand; private ICommand _NewCommand; public ICommand AddCommand { get { return _AddCommand ?? (_AddCommand = new DelegateCommand(OnAdd)); } } public ICommand ReplaceCommand { get { return _ReplaceCommand ?? (_ReplaceCommand = new DelegateCommand(OnReplace)); } } public ICommand ExportCommand { get { return _ExportCommand ?? (_ExportCommand = new DelegateCommand(OnExport)); } } public ICommand NewCommand { get { return _NewCommand ?? (_NewCommand = new DelegateCommand(OnNew)); } } private void OnAdd() { Skeleton skele; ModelDefinition model; ImcVariant variant; int m, b; if (TryGetModel(out skele, out model, out variant, out m, out b)) Parent.EngineHelper.AddToLast(SelectedEntry.ToString(), (e) => CreateModel(e, skele, model, variant, m, b)); } private void OnReplace() { Skeleton skele; ModelDefinition model; ImcVariant variant; int m, b; if (TryGetModel(out skele, out model, out variant, out m, out b)) Parent.EngineHelper.ReplaceInLast(SelectedEntry.ToString(), (e) => CreateModel(e, skele, model, variant, m, b)); } private void OnExport() { Skeleton skele; ModelDefinition model; ImcVariant variant; int m, b; if (!TryGetModel(out skele, out model, out variant, out m, out b)) return; List paps = SearchPaps(model.File.Path, m); VistaFolderBrowserDialog dialog = new VistaFolderBrowserDialog { Description = "Select folder to export to", UseDescriptionForTitle = true }; bool? result = dialog.ShowDialog(); if (result.HasValue && result.Value && !string.IsNullOrEmpty(dialog.SelectedPath)) { Task.Run(() => { string identifier = SelectedEntry.ToString().Replace(" / ", "_"); string folderName = Path.Combine(dialog.SelectedPath, identifier); Directory.CreateDirectory(folderName); string fileName = Path.Combine(folderName, identifier + ".fbx"); // Set IsExporting for feedback IsExporting = true; int exportResult = FbxExport.ExportFbx(fileName, model.GetModel(0).Meshes, skele, paps); FbxExport.ExportMonsterMaterials(Parent.Realm, folderName, model.GetModel(0).Definition.Materials, variant); IsExporting = false; if (exportResult == 0) System.Windows.MessageBox.Show("The export of " + Path.GetFileName(fileName) + " has completed.", "Export Complete", MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK, System.Windows.MessageBoxOptions.DefaultDesktopOnly); else System.Windows.MessageBox.Show("The export of " + Path.GetFileName(fileName) + " has failed.", "Export Failed", MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK, System.Windows.MessageBoxOptions.DefaultDesktopOnly); }); } } private void OnNew() { Skeleton skele; ModelDefinition model; ImcVariant variant; int m, b; if (TryGetModel(out skele, out model, out variant, out m, out b)) Parent.EngineHelper.OpenInNew(SelectedEntry.ToString(), (e) => CreateModel(e, skele, model, variant, m, b)); } static string[] DefaultAnimationNames = { "cbnm_id0", "cbbm_id0" }; private List SearchPaps(string modelPath, int m) { /* The files found in these paths will all be exported * If more paths are found, you can add them here * A good starting point might be attempting to determine how * a0002 animations are decided? */ string[] searchPaths = { "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/mon_sp/m{0:D4}", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/mon_sp/m{0:D4}/hide", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/mon_sp/m{0:D4}/show", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/event", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/minion", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/resident", "chara/monster/m{0:D4}/animation/a{1:D4}/bt_common/specialpop" }; SaintCoinach.IO.Directory d; List allFiles = new List(); // Animations are in the same pack as the model itself Pack p = Parent.Realm.Packs.GetPack(PackIdentifier.Get(modelPath)); foreach (string path in searchPaths) { string currentFullPath = string.Format(path, m, 1); ((IndexSource)p.Source).TryGetDirectory(currentFullPath, out d); if (d == null) continue; IEnumerator dirEnumerator = d.GetEnumerator(); while (dirEnumerator.MoveNext()) allFiles.Add(new PapFile(dirEnumerator.Current)); dirEnumerator.Dispose(); } return allFiles; } private IComponent CreateModel(Engine engine, Skeleton skeleton, ModelDefinition model, ImcVariant variant, int m, int b) { var component = new AnimatedModel(engine, skeleton, variant, model, ModelQuality.High) {}; var papPath = string.Format(PapPathFormat, m, b); SaintCoinach.IO.File papFileBase; if (Parent.Realm.Packs.TryGetFile(papPath, out papFileBase)) { var anim = new AnimationContainer(skeleton, new PapFile(papFileBase)); var hasAnim = false; for(var i = 0; i < DefaultAnimationNames.Length && !hasAnim; ++i) { var n = DefaultAnimationNames[i]; if (anim.AnimationNames.Contains(n)) { component.AnimationPlayer.Animation = anim.Get(n); hasAnim = true; } } if (!hasAnim) component.AnimationPlayer.Animation = anim.Get(0); } return component; } private bool TryGetModel(out Skeleton skeleton, out ModelDefinition model, out ImcVariant variant, out int m, out int b) { model = null; skeleton = null; variant = ImcVariant.Default; m = 0; b = 0; var asVariant = SelectedEntry as Models.ModelCharaVariant; if (asVariant == null) return false; int v = asVariant.Value; b = asVariant.Parent.Value; m = asVariant.Parent.Parent.Value; var imcPath = string.Format(ImcPathFormat, m, b); var mdlPath = string.Format(ModelPathFormat, m, b); var sklPath = string.Format(SkeletonPathFormat, m, 1);// b); SaintCoinach.IO.File imcFileBase; SaintCoinach.IO.File mdlFileBase; if (!Parent.Realm.Packs.TryGetFile(imcPath, out imcFileBase) || !Parent.Realm.Packs.TryGetFile(mdlPath, out mdlFileBase) || !(mdlFileBase is ModelFile)) { System.Windows.MessageBox.Show(string.Format("Unable to find files for {0}.", asVariant), "File not found", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return false; } SaintCoinach.IO.File sklFileBase; if(!Parent.Realm.Packs.TryGetFile(sklPath, out sklFileBase)) { System.Windows.MessageBox.Show(string.Format("Unable to find skeleton for {0}.", asVariant), "File not found", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return false; } skeleton = new Skeleton(new SklbFile(sklFileBase)); try { var imcFile = new ImcFile(imcFileBase); model = ((ModelFile)mdlFileBase).GetModelDefinition(); variant = imcFile.GetVariant(v); return true; } catch (Exception e) { System.Windows.MessageBox.Show(string.Format("Unable to load model for {0}:{1}{2}", asVariant, Environment.NewLine, e), "Failure to load", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return false; } } #endregion #region Brute-force private bool _IsBruteForceAvailable = true; private ICommand _BruteForceCommand; public bool IsBruteForceAvailable { get { return _IsBruteForceAvailable; } private set { _IsBruteForceAvailable = value; OnPropertyChanged(() => IsBruteForceAvailable); } } public ICommand BruteForceCommand { get { return _BruteForceCommand ?? (_BruteForceCommand = new DelegateCommand(OnBruteForce)); } } private void OnBruteForce() { IsBruteForceAvailable = false; var progDlg = new Ookii.Dialogs.Wpf.ProgressDialog(); progDlg.WindowTitle = "Brute-forcing"; progDlg.Text = "This is going to take a while..."; progDlg.DoWork += DoBruteForceWork; progDlg.RunWorkerCompleted += OnBruteForceComplete; progDlg.ShowDialog(System.Windows.Application.Current.MainWindow); progDlg.ProgressBarStyle = Ookii.Dialogs.Wpf.ProgressBarStyle.ProgressBar; progDlg.ShowTimeRemaining = true; } void OnBruteForceComplete(object sender, System.ComponentModel.RunWorkerCompletedEventArgs eventArgs) { if (eventArgs.Cancelled) IsBruteForceAvailable = true; } void DoBruteForceWork(object sender, System.ComponentModel.DoWorkEventArgs eventArgs) { var dlg = (Ookii.Dialogs.Wpf.ProgressDialog)sender; var newEntries = new Models.ModelCharaHierarchy(Entries.MainFormat, Entries.SubFormat, Entries.VariantFormat); for (var m = 0; m < 10000; ++m) { if (dlg.CancellationPending) return; dlg.ReportProgress(m / 100, null, string.Format("Current progress: {0:P}", m / 10000.0)); for (var b = 0; b < 10000; ++b) { var imcPath = string.Format(ImcPathFormat, m, b); SaintCoinach.IO.File imcBase; if (!Parent.Realm.Packs.TryGetFile(imcPath, out imcBase)) continue; try { var imc = new SaintCoinach.Graphics.ImcFile(imcBase); for (var v = 1; v < imc.Count; ++v) { if (Entries.Contains(m, b, v)) continue; var any = false; foreach (var p in imc.Parts) { if (p.Variants[v].Variant != 0) { any = true; break; } } if (any) newEntries.Add(m, b, v); } } catch (Exception ex) { Console.Error.WriteLine("Failed parsing imc file {0}:{1}{2}", imcPath, Environment.NewLine, ex); } } } Entries = newEntries; } #endregion } } ================================================ FILE: Godbert/ViewModels/TerritoryViewModel.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Windows.Input; using SaintCoinach.Graphics; using SaintCoinach.Graphics.Viewer; using SaintCoinach; using SaintCoinach.Xiv; namespace Godbert.ViewModels { using Commands; using SharpDX; using SaintCoinach.Graphics; public class TerritoryViewModel : ObservableBase { class ExportCancelException : Exception { public ExportCancelException(string message) : base(message) { } } #region Fields private TerritoryView[] _AllTerritories; private TerritoryView[] _FilteredTerritories; private TerritoryView _SelectedTerritory; private string _FilterTerm; private Ookii.Dialogs.Wpf.ProgressDialog _Progress; #endregion #region Properties public MainViewModel Parent { get; private set; } public IEnumerable AllTerritories { get { return _AllTerritories; } } public IEnumerable FilteredTerritories { get { return _FilteredTerritories; } } public TerritoryView SelectedTerritory { get { return _SelectedTerritory; } set { _SelectedTerritory = value; OnPropertyChanged(() => SelectedTerritory); } } public string FilterTerm { get { return _FilterTerm; } set { _FilterTerm = value; if (string.IsNullOrWhiteSpace(value)) _FilteredTerritories = _AllTerritories; else _FilteredTerritories = _AllTerritories.Where(e => e.Name.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0).ToArray(); OnPropertyChanged(() => FilterTerm); OnPropertyChanged(() => FilteredTerritories); } } #endregion #region Constructor public TerritoryViewModel(MainViewModel parent) { this.Parent = parent; var allTerritoryTypes = parent.Realm.GameData.GetSheet(); _AllTerritories = allTerritoryTypes .Where(t => !string.IsNullOrEmpty(t.Bg.ToString())) .Select(t => new TerritoryView(t)) .OrderBy(m => m.PlaceNames) .ThenBy(m => m.TerritoryType.Key) .ToArray(); _FilteredTerritories = _AllTerritories; } #endregion #region Commands private ICommand _OpenCommand; private ICommand _ExportCommand; public ICommand OpenCommand { get { return _OpenCommand ?? (_OpenCommand = new DelegateCommand(OnOpen)); } } public ICommand ExportCommand { get { return _ExportCommand ?? (_ExportCommand = new DelegateCommand(OnExport)); } } private void OnOpen() { if (SelectedTerritory == null) return; try { var t = new Territory(SelectedTerritory.TerritoryType); if (t == null) System.Windows.MessageBox.Show(string.Format("Could not find territory data for {0}.", SelectedTerritory.Name), "Territory not found", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); else Parent.EngineHelper.OpenInNew(SelectedTerritory.Name, (e) => new SaintCoinach.Graphics.Viewer.Content.ContentTerritory(e, t)); } catch (Exception e) { System.Windows.MessageBox.Show(string.Format("Error reading territory for {0}:{1}{2}", SelectedTerritory.Name, Environment.NewLine, e), "Failure to read territory", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } } private void OnExport() { if (SelectedTerritory == null) return; try { Territory territory = new Territory(SelectedTerritory.TerritoryType); if (territory == null) { System.Windows.MessageBox.Show($"Could not find territory data for {SelectedTerritory.Name}", "Territory not found", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); return; } _Progress = new Ookii.Dialogs.Wpf.ProgressDialog() { WindowTitle = "Exporting territory " + territory.Name }; _Progress.Show(); _Progress.DoWork += (object sender, System.ComponentModel.DoWorkEventArgs eventArgs) => { _Export(territory, (Ookii.Dialogs.Wpf.ProgressDialog)sender); }; } catch (Exception e) { System.Windows.MessageBox.Show(string.Format("Error reading territory for {0}:{1}{2}", SelectedTerritory.Name, Environment.NewLine, e), "Failure to read territory", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error); } _Progress = null; } private void _Export(Territory territory, Ookii.Dialogs.Wpf.ProgressDialog progress) { string teriName = territory.Name; try { string currentTitle = ""; var _ExportDirectory = $"./{territory.Name}/"; Dictionary objCount = new Dictionary(); if (!System.IO.Directory.Exists(Environment.CurrentDirectory + $"{_ExportDirectory}")) { System.IO.Directory.CreateDirectory(Environment.CurrentDirectory + $"{_ExportDirectory}"); } var teriFileName = $"./{_ExportDirectory}/{territory.Name}.obj"; var fileName = teriFileName; var lightsFileName = $"./{_ExportDirectory}/{territory.Name}-lights.txt"; var _ExportFileName = fileName; { var f = System.IO.File.Create(fileName); f.Close(); } System.IO.File.AppendAllText(fileName, $"o {territory.Name}\n"); System.IO.File.WriteAllText(lightsFileName, ""); int lights = 0; List lightStrs = new List() { "import bpy" }; List vertStr = new List(); Dictionary exportedPaths = new Dictionary(); UInt64 vs = 1, vt = 1, vn = 1, i = 0; Matrix IdentityMatrix = Matrix.Identity; void ExportMaterials(Material m, string path) { vertStr.Add($"mtllib {path}.mtl"); bool found = false; if (exportedPaths.TryGetValue(path, out found)) { return; } exportedPaths.Add(path, true); System.IO.File.Delete($"{_ExportDirectory}/{path}.mtl"); System.IO.File.AppendAllText($"{_ExportDirectory}/{path}.mtl", $"newmtl {path}\n"); foreach (var img in m.TexturesFiles) { var mtlName = img.Path.Replace('/', '_'); if (exportedPaths.TryGetValue(path + mtlName, out found)) { continue; } //SaintCoinach.Imaging.ImageConverter.Convert(img).Save($"{_ExportDirectory}/{mtlName}.png"); if (mtlName.Contains("_dummy_")) continue; var ddsBytes = SaintCoinach.Imaging.ImageConverter.GetDDS(img); var fileExt = ddsBytes != null ? ".dds" : ".png"; if (fileExt == ".dds") System.IO.File.WriteAllBytes($"{_ExportDirectory}/{mtlName}.dds", ddsBytes); else SaintCoinach.Imaging.ImageConverter.Convert(img).Save($"{_ExportDirectory}/{mtlName}.png"); if (mtlName.Contains("_n.tex")) { System.IO.File.AppendAllText($"./{_ExportDirectory}/{path}.mtl", $"bump {mtlName}{fileExt}\n"); } else if (mtlName.Contains("_s.tex")) { System.IO.File.AppendAllText($"./{_ExportDirectory}/{path}.mtl", $"map_Ks {mtlName}{fileExt}\n"); } else if (!mtlName.Contains("_a.tex")) { System.IO.File.AppendAllText($"./{_ExportDirectory}/{path}.mtl", $"map_Kd {mtlName}{fileExt}\n"); } else { System.IO.File.AppendAllText($"./{_ExportDirectory}/{path}.mtl", $"map_Ka {mtlName}{fileExt}\n"); } exportedPaths.Add(path + mtlName, true); } } Matrix CreateMatrix(SaintCoinach.Graphics.Vector3 translation, SaintCoinach.Graphics.Vector3 rotation, SaintCoinach.Graphics.Vector3 scale) { return (Matrix.Scaling(scale.ToDx()) * Matrix.RotationX(rotation.X) * Matrix.RotationY(rotation.Y) * Matrix.RotationZ(rotation.Z) * Matrix.Translation(translation.ToDx())); } void ExportMesh(ref Mesh mesh, ref Matrix lgbTransform, ref string materialName, ref string modelFilePath, ref Matrix rootGimTransform, ref Matrix currGimTransform, ref Matrix modelTransform) { i++; if (progress.CancellationPending) throw new ExportCancelException("User canceled export"); var k = 0; UInt64 tempVs = 0, tempVn = 0, tempVt = 0; foreach (var v in mesh.Vertices) { var x = v.Position.Value.X; var y = v.Position.Value.Y; var z = v.Position.Value.Z; var w = v.Position.Value.W; var transform = (modelTransform * rootGimTransform * currGimTransform) * lgbTransform; var t = Matrix.Translation(x, y, z) * transform; x = t.TranslationVector.X; y = t.TranslationVector.Y; z = t.TranslationVector.Z; // .Replace(',','.') cause decimal separator locale memes if (v.Color != null) vertStr.Add($"v {x} {y} {z} {v.Color.Value.X} {v.Color.Value.Y} {v.Color.Value.Z} {v.Color.Value.W}".Replace(',','.')); else vertStr.Add($"v {x} {y} {z}".Replace(',','.')); tempVs++; vertStr.Add($"vn {v.Normal.Value.X} {v.Normal.Value.Y} {v.Normal.Value.Z}".Replace(',', '.')); tempVn++; if (v.UV != null) { vertStr.Add($"vt {v.UV.Value.X} {v.UV.Value.Y * -1.0}".Replace(',', '.')); tempVt++; } } vertStr.Add($"g {modelFilePath}_{i.ToString()}_{k.ToString()}"); vertStr.Add($"usemtl {materialName}"); for (UInt64 j = 0; j + 3 < (UInt64)mesh.Indices.Length + 1; j += 3) { vertStr.Add( $"f " + $"{mesh.Indices[j] + vs}/{mesh.Indices[j] + vt}/{mesh.Indices[j] + vn} " + $"{mesh.Indices[j + 1] + vs}/{mesh.Indices[j + 1] + vt}/{mesh.Indices[j + 1] + vn} " + $"{mesh.Indices[j + 2] + vs}/{mesh.Indices[j + 2] + vt}/{mesh.Indices[j + 2] + vn}"); } if (i % 1000 == 0) { System.IO.File.AppendAllLines(_ExportFileName, vertStr); vertStr.Clear(); } vs += tempVs; vn += tempVn; vt += tempVt; } Dictionary exportedSgbFiles = new Dictionary(); void ExportSgbModels(SaintCoinach.Graphics.Sgb.SgbFile sgbFile, ref Matrix lgbTransform, ref Matrix rootGimTransform, ref Matrix currGimTransform) { foreach (var sgbGroup in sgbFile.Data.OfType()) { bool newGroup = true; foreach (var sgb1CEntry in sgbGroup.Entries.OfType()) { if (sgb1CEntry.Gimmick != null) { ExportSgbModels(sgb1CEntry.Gimmick, ref lgbTransform, ref IdentityMatrix, ref IdentityMatrix); foreach (var subGimGroup in sgb1CEntry.Gimmick.Data.OfType()) { foreach (var subGimEntry in subGimGroup.Entries.OfType()) { var subGimTransform = CreateMatrix(subGimEntry.Header.Translation, subGimEntry.Header.Rotation, subGimEntry.Header.Scale); ExportSgbModels(subGimEntry.Gimmick, ref lgbTransform, ref IdentityMatrix, ref subGimTransform); } } } } foreach (var mdl in sgbGroup.Entries.OfType()) { Model hq = null; var filePath = mdl.ModelFilePath; var modelTransform = CreateMatrix(mdl.Header.Translation, mdl.Header.Rotation, mdl.Header.Scale); progress.ReportProgress(0, currentTitle, filePath); try { hq = mdl.Model.Model.GetModel(ModelQuality.High); } catch (Exception e) { System.Diagnostics.Debug.WriteLine($"Unable to load model for {mdl.Name} path: {filePath}. Exception: {e.Message}"); continue; } if (newGroup) { //vertStr.Add($"o {sgbFile.File.Path}_{sgbGroup.Name}_{i}"); newGroup = false; } for (var j = 0; j < hq.Meshes.Length; ++j) { var mesh = hq.Meshes[j]; var mtl = mesh.Material.Get(); var path = mtl.File.Path.Replace('/', '_').Replace(".mtrl", ".tex"); ExportMaterials(mtl, path); ExportMesh(ref mesh, ref lgbTransform, ref path, ref filePath, ref rootGimTransform, ref currGimTransform, ref modelTransform); } } foreach (var light in sgbGroup.Entries.OfType()) { var pos = light.Header.Translation; var transform = (Matrix.Translation(pos.X, pos.Y, pos.Z) * (rootGimTransform * currGimTransform) * lgbTransform).TranslationVector; pos.X = transform.X; pos.Y = transform.Y; pos.Z = transform.Z; lightStrs.Add($"#LIGHT_{lights++}_{light.Name}_{light.Header.UnknownId}"); lightStrs.Add($"#pos {pos.X} {pos.Y} {pos.Z}"); lightStrs.Add($"#UNKNOWNFLAGS 0x{light.Header.UnknownFlag1:X8} 0x{light.Header.UnknownFlag2:X8} 0x{light.Header.UnknownFlag3:X8} 0x{light.Header.UnknownFlag4:X8}"); lightStrs.Add($"#UNKNOWN {light.Header.Rotation.X} {light.Header.Rotation.Y} {light.Header.Rotation.Z}"); lightStrs.Add($"#UNKNOWN2 {light.Header.Scale.X} {light.Header.Scale.Y} {light.Header.Scale.Z}"); lightStrs.Add($"#unk {light.Header.Entry1.X} {light.Header.Entry1.Y}"); lightStrs.Add($"#unk2 {light.Header.Entry2.X} {light.Header.Entry2.Y}"); lightStrs.Add($"#unk3 {light.Header.Entry3.X} {light.Header.Entry3.Y}"); lightStrs.Add($"#unk4 {light.Header.Entry4.X} {light.Header.Entry4.Y}"); lightStrs.Add(""); } } } progress.ReportProgress(0, currentTitle = "Terrain", ""); if (territory.Terrain != null) { foreach (var part in territory.Terrain.Parts) { var hq = part.Model.GetModel(ModelQuality.High); var filePath = hq.Definition.File.Path; var lgbTransform = CreateMatrix(part.Translation, part.Rotation, part.Scale); progress.ReportProgress(0, currentTitle, part.Model.File.Path); for (var j = 0; j < hq.Meshes.Length; ++j) { var mesh = hq.Meshes[j]; var mtl = mesh.Material.Get(); var path = mtl.File.Path.Replace('/', '_').Replace(".mtrl", ".tex"); ExportMaterials(mtl, path); ExportMesh(ref mesh, ref lgbTransform, ref path, ref filePath, ref IdentityMatrix, ref IdentityMatrix, ref IdentityMatrix); } } } System.IO.File.AppendAllLines(_ExportFileName, vertStr); vertStr.Clear(); vs = 1; vn = 1; vt = 1; i = 0; foreach (var lgb in territory.LgbFiles) { foreach (var lgbGroup in lgb.Groups) { bool newGroup = true; foreach (var part in lgbGroup.Entries) { if (part == null) continue; if (newGroup && (part.Type == SaintCoinach.Graphics.Lgb.LgbEntryType.Model || part.Type == SaintCoinach.Graphics.Lgb.LgbEntryType.Gimmick || part.Type == SaintCoinach.Graphics.Lgb.LgbEntryType.Light)) { progress.WindowTitle = $"Exporting {territory.Name} ({lgbGroup.Name})"; progress.ReportProgress(0, currentTitle = $"Exporting {territory.Name} Group {lgbGroup.Name}", $"Group {lgbGroup.Name}"); newGroup = false; System.IO.File.AppendAllLines(_ExportFileName, vertStr); vertStr.Clear(); //vertStr.Add($"o {lgbGroup.Name}"); vs = 1; vn = 1; vt = 1; i = 0; _ExportFileName = $"./{_ExportDirectory}/{teriName}-{lgbGroup.Name}.obj"; lightsFileName = $"./{_ExportDirectory}/{teriName}-{lgbGroup.Name}-lights.txt"; var f = System.IO.File.Create(_ExportFileName); f.Close(); f = System.IO.File.Create(lightsFileName); f.Close(); } switch (part.Type) { case SaintCoinach.Graphics.Lgb.LgbEntryType.Model: var asMdl = part as SaintCoinach.Graphics.Lgb.LgbModelEntry; progress.ReportProgress(0, currentTitle = "Exporting LgbModel", asMdl.ModelFilePath); if (asMdl.Model == null) continue; var hq = asMdl.Model.Model.GetModel(ModelQuality.High); var lgbTransform = CreateMatrix(asMdl.Header.Translation, asMdl.Header.Rotation, asMdl.Header.Scale); var filePath = asMdl.ModelFilePath; for (var j = 0; j < hq.Meshes.Length; ++j) { var mesh = hq.Meshes[j]; var mtl = mesh.Material.Get(); var path = mtl.File.Path.Replace('/', '_').Replace(".mtrl", ".tex"); ExportMaterials(mtl, path); ExportMesh(ref mesh, ref lgbTransform, ref path, ref filePath, ref IdentityMatrix, ref IdentityMatrix, ref IdentityMatrix); } break; case SaintCoinach.Graphics.Lgb.LgbEntryType.Gimmick: var asGim = part as SaintCoinach.Graphics.Lgb.LgbGimmickEntry; if (asGim.Gimmick == null) continue; progress.ReportProgress(0, currentTitle = $"Exporting Gimmick {asGim.Name} {asGim.Header.GimmickId}", ""); lgbTransform = CreateMatrix(asGim.Header.Translation, asGim.Header.Rotation, asGim.Header.Scale); ExportSgbModels(asGim.Gimmick, ref lgbTransform, ref IdentityMatrix, ref IdentityMatrix); foreach (var rootGimGroup in asGim.Gimmick.Data.OfType()) { foreach (var rootGimEntry in rootGimGroup.Entries.OfType()) { if (rootGimEntry.Gimmick != null) { var rootGimTransform = CreateMatrix(rootGimEntry.Header.Translation, rootGimEntry.Header.Rotation, rootGimEntry.Header.Scale); ExportSgbModels(rootGimEntry.Gimmick, ref lgbTransform, ref rootGimTransform, ref IdentityMatrix); foreach (var subGimGroup in rootGimEntry.Gimmick.Data.OfType()) { foreach (var subGimEntry in subGimGroup.Entries.OfType()) { var subGimTransform = CreateMatrix(subGimEntry.Header.Translation, subGimEntry.Header.Rotation, subGimEntry.Header.Scale); ExportSgbModels(subGimEntry.Gimmick, ref lgbTransform, ref rootGimTransform, ref subGimTransform); } } } } } break; case SaintCoinach.Graphics.Lgb.LgbEntryType.EventObject: var asEobj = part as SaintCoinach.Graphics.Lgb.LgbEventObjectEntry; if (asEobj.Gimmick == null) continue; progress.ReportProgress(0, currentTitle = $"Exporting EObj {asEobj.Name} {asEobj.Header.EventObjectId} {asEobj.Header.GimmickId}", ""); lgbTransform = CreateMatrix(asEobj.Header.Translation, asEobj.Header.Rotation, asEobj.Header.Scale); ExportSgbModels(asEobj.Gimmick, ref lgbTransform, ref IdentityMatrix, ref IdentityMatrix); foreach (var rootGimGroup in asEobj.Gimmick.Data.OfType()) { foreach (var rootGimEntry in rootGimGroup.Entries.OfType()) { if (rootGimEntry.Gimmick != null) { var rootGimTransform = CreateMatrix(rootGimEntry.Header.Translation, rootGimEntry.Header.Rotation, rootGimEntry.Header.Scale); ExportSgbModels(rootGimEntry.Gimmick, ref lgbTransform, ref rootGimTransform, ref IdentityMatrix); foreach (var subGimGroup in rootGimEntry.Gimmick.Data.OfType()) { foreach (var subGimEntry in subGimGroup.Entries.OfType()) { var subGimTransform = CreateMatrix(subGimEntry.Header.Translation, subGimEntry.Header.Rotation, subGimEntry.Header.Scale); ExportSgbModels(subGimEntry.Gimmick, ref lgbTransform, ref rootGimTransform, ref subGimTransform); } } } } } break; case SaintCoinach.Graphics.Lgb.LgbEntryType.Light: var asLight = part as SaintCoinach.Graphics.Lgb.LgbLightEntry; lightStrs.Add($"#LIGHT_{lights++}_{asLight.Name}_{asLight.Header.UnknownId}"); lightStrs.Add($"#pos {asLight.Header.Translation.X} {asLight.Header.Translation.Y} {asLight.Header.Translation.Z}"); lightStrs.Add($"#UNKNOWNFLAGS 0x{asLight.Header.UnknownFlag1:X8} 0x{asLight.Header.UnknownFlag2:X8} 0x{asLight.Header.UnknownFlag3:X8} 0x{asLight.Header.UnknownFlag4:X8}"); lightStrs.Add($"#UNKNOWN {asLight.Header.Rotation.X} {asLight.Header.Rotation.Y} {asLight.Header.Rotation.Z}"); lightStrs.Add($"#UNKNOWN2 {asLight.Header.Scale.X} {asLight.Header.Scale.Y} {asLight.Header.Scale.Z}"); lightStrs.Add($"#unk {asLight.Header.Entry1.X} {asLight.Header.Entry1.Y}"); lightStrs.Add($"#unk2 {asLight.Header.Entry2.X} {asLight.Header.Entry2.Y}"); lightStrs.Add($"#unk3 {asLight.Header.Entry3.X} {asLight.Header.Entry3.Y}"); lightStrs.Add($"#unk4 {asLight.Header.Entry4.X} {asLight.Header.Entry4.Y}"); lightStrs.Add(""); break; } } System.IO.File.AppendAllLines(lightsFileName, lightStrs); lightStrs.Clear(); } } System.IO.File.AppendAllLines(_ExportFileName, vertStr); vertStr.Clear(); System.IO.File.AppendAllLines(lightsFileName, lightStrs); lightStrs.Clear(); System.Windows.Forms.MessageBox.Show("Finished exporting " + territory.Name, "", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information); } catch (ExportCancelException e) { System.Windows.Forms.MessageBox.Show(e.Message, $"Canceled {teriName} export"); } catch (Exception e) { System.Diagnostics.Debug.WriteLine(e.StackTrace); System.Windows.Forms.MessageBox.Show(e.StackTrace, $"Unable to export {teriName}"); } } #endregion #region Refresh public void Refresh() { _FilteredTerritories = _FilteredTerritories.ToArray(); OnPropertyChanged(() => FilteredTerritories); } #endregion } public class TerritoryView { public TerritoryView(TerritoryType territory) { TerritoryType = territory; var places = new List(); places.Add(territory.RegionPlaceName.Name.ToString()); places.Add(territory.ZonePlaceName.Name.ToString()); places.Add(territory.PlaceName.Name.ToString()); PlaceNames = string.Join(" > ", places.Where(p => !string.IsNullOrEmpty(p)).Distinct()); Name = string.Format("({0}) {1}", territory.Name.ToString(), PlaceNames); } public TerritoryType TerritoryType { get; private set; } public string Name { get; private set; } public string PlaceNames { get; private set; } } } ================================================ FILE: Godbert/Views/DataView.xaml ================================================  ================================================ FILE: Godbert/Views/DataView.xaml.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Godbert.ViewModels; namespace Godbert.Views { /// /// Interaction logic for DataView.xaml /// public partial class DataView : UserControl { public DataView() { InitializeComponent(); DataContextChanged += DataView_DataContextChanged; } private void DataView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) { var viewModel = (DataViewModel)DataContext; viewModel.Parent.DataGridChanged += MainViewModel_DataGridChanged; } private void MainViewModel_DataGridChanged(object sender, EventArgs e) { // Hacky contents refresh. var sheet = _DataGrid.Sheet; _DataGrid.Sheet = null; _DataGrid.Sheet = sheet; } public void RowSizeChanged(object sender, SizeChangedEventArgs e) { var row = sender as DataGridRow; if (row == null) return; // Ensure row heights do not fluctuate as virtualized columns are // loaded/unloaded. if (row.MinHeight < e.NewSize.Height) row.MinHeight = e.NewSize.Height; } private void _BookmarksList_SelectionChanged(object sender, SelectionChangedEventArgs e) { var bookmark = e.AddedItems.OfType().FirstOrDefault(); if (bookmark == null) return; var mainWindow = WpfHelper.FindParent(this); if (mainWindow == null) return; var mainViewModel = mainWindow.DataContext as MainViewModel; if (mainViewModel == null) return; mainViewModel.Data.SelectedSheetName = bookmark.SheetName; var row = mainViewModel.Data.SelectedSheet[bookmark.Key]; _DataGrid.SelectRow(row, bookmark.ColumnIndex); // Remove selection. _BookmarksList.SelectedIndex = -1; } private void BookmarkListItem_MouseDown(object sender, MouseButtonEventArgs e) { if (e.MiddleButton == MouseButtonState.Pressed) { var source = e.OriginalSource as FrameworkElement; if (source == null) return; var listItem = WpfHelper.FindParent(source); var bookmark = listItem?.DataContext as BookmarkViewModel; if (bookmark == null) return; var dataViewModel = (DataViewModel)DataContext; dataViewModel.Bookmarks.Remove(bookmark); } } } } ================================================ FILE: Godbert/Views/DemihumansView.xaml ================================================