├── images ├── image1.png └── image2.png ├── NBTEditor ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── icon.png │ │ ├── icon 7.png │ │ └── Contents.json │ └── AccentColor.colorset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── NBTEditor.entitlements ├── NBTEditorRelease.entitlements ├── ContentView.swift ├── NBTEditorApp.swift ├── NBTDocument.swift └── NBTView.swift ├── NBTEditor.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcuserdata │ └── tuhaotian.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── README.md └── .gitignore /images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thtTNT/NBTEditor/HEAD/images/image1.png -------------------------------------------------------------------------------- /images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thtTNT/NBTEditor/HEAD/images/image2.png -------------------------------------------------------------------------------- /NBTEditor/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /NBTEditor/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /NBTEditor/Assets.xcassets/AppIcon.appiconset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thtTNT/NBTEditor/HEAD/NBTEditor/Assets.xcassets/AppIcon.appiconset/icon.png -------------------------------------------------------------------------------- /NBTEditor/Assets.xcassets/AppIcon.appiconset/icon 7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thtTNT/NBTEditor/HEAD/NBTEditor/Assets.xcassets/AppIcon.appiconset/icon 7.png -------------------------------------------------------------------------------- /NBTEditor.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NBTEditor/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /NBTEditor.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /NBTEditor/NBTEditor.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /NBTEditor/NBTEditorRelease.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /NBTEditor/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // NBTEditor 4 | // 5 | // Created by 屠昊天 on 8/6/2024. 6 | // 7 | 8 | import SwiftUI 9 | import UniformTypeIdentifiers 10 | import MinecraftNBT 11 | 12 | 13 | struct ContentView: View { 14 | 15 | @Binding var document : NBTDocument 16 | 17 | var body: some View { 18 | NBTView(data: $document.nbt) 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /NBTEditor.xcodeproj/xcuserdata/tuhaotian.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | NBTEditor.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /NBTEditor/NBTEditorApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NBTEditorApp.swift 3 | // NBTEditor 4 | // 5 | // Created by 屠昊天 on 8/6/2024. 6 | // 7 | 8 | import SwiftUI 9 | import MinecraftNBT 10 | 11 | class AppDelegate : NSObject, NSApplicationDelegate { 12 | 13 | } 14 | 15 | @main 16 | struct NBTEditorApp: App { 17 | 18 | @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate; 19 | 20 | 21 | var body: some Scene { 22 | DocumentGroup(newDocument: NBTDocument()) {configuration in 23 | ContentView(document: configuration.$document) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /NBTEditor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "68b72d5e07500e0953fdf41f00bdd8e619a04f07ae98f9b13b805a377f5f16ad", 3 | "pins" : [ 4 | { 5 | "identity" : "gzipswift", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/1024jp/GzipSwift.git", 8 | "state" : { 9 | "revision" : "7a7f17761c76a932662ab77028a4329f67d645a4", 10 | "version" : "5.2.0" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-collections", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/apple/swift-collections.git", 17 | "state" : { 18 | "revision" : "ee97538f5b81ae89698fd95938896dec5217b148", 19 | "version" : "1.1.1" 20 | } 21 | }, 22 | { 23 | "identity" : "swift-nbt", 24 | "kind" : "remoteSourceControl", 25 | "location" : "https://github.com/ezfe/swift-nbt", 26 | "state" : { 27 | "branch" : "main", 28 | "revision" : "b7457ff8426066010ef748a5077fd50e7fa19459" 29 | } 30 | } 31 | ], 32 | "version" : 3 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NBTEditor 2 | 3 | This is a MacOS application that allows users to read and edit NBT files in Minecraft. 4 | If you find this project useful give me a Star. 5 | **This project is still in development and all bug reports and feature requests are welcome.** 6 | 7 | The goal of this project is: 8 | 9 | - Complete 10 | 11 | - Lightweight 12 | 13 | - Friendly UI 14 | 15 | ![](images/image1.png) 16 | 17 | ![](images/image2.png) 18 | 19 | ## Download 20 | 21 | This pre-released version can be found on the release page. 22 | 23 | ## To-Do 24 | 25 | - [x] Read NBT 26 | 27 | - [x] Edit Value 28 | 29 | - [x] Rename 30 | 31 | - [x] Delete Value 32 | 33 | - [ ] Add Value 34 | 35 | - [ ] Better UI 36 | 37 | ## Contribute 38 | 39 | The contribution to this project is welcomed. If you have some suggestions or bug reports, please create an issue. 40 | 41 | If you are a developer, feel free to create a PR with the issue with the mark `to-do` instead of `processing` 42 | 43 | ## Acknowledgement 44 | 45 | Thanks for the contributor of the following library: 46 | 47 | Swift-NBT [GitHub - ezfe/swift-nbt](https://github.com/ezfe/swift-nbt) 48 | -------------------------------------------------------------------------------- /NBTEditor/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x", 6 | "size" : "16x16" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "scale" : "2x", 11 | "size" : "16x16" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "scale" : "1x", 16 | "size" : "32x32" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "scale" : "2x", 21 | "size" : "32x32" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "scale" : "1x", 26 | "size" : "128x128" 27 | }, 28 | { 29 | "filename" : "icon.png", 30 | "idiom" : "mac", 31 | "scale" : "2x", 32 | "size" : "128x128" 33 | }, 34 | { 35 | "filename" : "icon 7.png", 36 | "idiom" : "mac", 37 | "scale" : "1x", 38 | "size" : "256x256" 39 | }, 40 | { 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "idiom" : "mac", 47 | "scale" : "1x", 48 | "size" : "512x512" 49 | }, 50 | { 51 | "idiom" : "mac", 52 | "scale" : "2x", 53 | "size" : "512x512" 54 | } 55 | ], 56 | "info" : { 57 | "author" : "xcode", 58 | "version" : 1 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /NBTEditor/NBTDocument.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NBTDocument.swift 3 | // NBTEditor 4 | // 5 | // Created by 屠昊天 on 17/6/2024. 6 | // 7 | 8 | import SwiftUI 9 | import UniformTypeIdentifiers 10 | import MinecraftNBT 11 | 12 | func isDataGzip(data : Data) -> Bool{ 13 | guard data.count >= 2 else { 14 | return false; 15 | } 16 | 17 | return data[0] == 0x1F && data[1] == 0x8B; 18 | } 19 | 20 | struct NBTDocument : FileDocument { 21 | static var readableContentTypes: [UTType] = [UTType.data] 22 | var nbt : NBTStructure 23 | 24 | init(){ 25 | self.nbt = NBTStructure() 26 | } 27 | 28 | init(configuration: ReadConfiguration) throws { 29 | guard let binaryData = configuration.file.regularFileContents else { 30 | throw NSError() 31 | } 32 | 33 | let structure: NBTStructure? 34 | 35 | if isDataGzip(data: binaryData) { 36 | structure = NBTStructure(compressed: binaryData) 37 | } else { 38 | structure = NBTStructure(decompressed: binaryData) 39 | } 40 | 41 | guard structure != nil else { 42 | throw NSError() 43 | } 44 | 45 | self.nbt = structure! 46 | } 47 | 48 | func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { 49 | return FileWrapper(regularFileWithContents: self.nbt.data) 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/swift,xcode,macos 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,xcode,macos 3 | 4 | ### macOS ### 5 | # General 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # Icon must end with two \r 11 | Icon 12 | 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### macOS Patch ### 34 | # iCloud generated files 35 | *.icloud 36 | 37 | ### Swift ### 38 | # Xcode 39 | # 40 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 41 | 42 | ## User settings 43 | xcuserdata/ 44 | 45 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 46 | *.xcscmblueprint 47 | *.xccheckout 48 | 49 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 50 | build/ 51 | DerivedData/ 52 | *.moved-aside 53 | *.pbxuser 54 | !default.pbxuser 55 | *.mode1v3 56 | !default.mode1v3 57 | *.mode2v3 58 | !default.mode2v3 59 | *.perspectivev3 60 | !default.perspectivev3 61 | 62 | ## Obj-C/Swift specific 63 | *.hmap 64 | 65 | ## App packaging 66 | *.ipa 67 | *.dSYM.zip 68 | *.dSYM 69 | 70 | ## Playgrounds 71 | timeline.xctimeline 72 | playground.xcworkspace 73 | 74 | # Swift Package Manager 75 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 76 | # Packages/ 77 | # Package.pins 78 | # Package.resolved 79 | # *.xcodeproj 80 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 81 | # hence it is not needed unless you have added a package configuration file to your project 82 | # .swiftpm 83 | 84 | .build/ 85 | 86 | # CocoaPods 87 | # We recommend against adding the Pods directory to your .gitignore. However 88 | # you should judge for yourself, the pros and cons are mentioned at: 89 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 90 | # Pods/ 91 | # Add this line if you want to avoid checking in source code from the Xcode workspace 92 | # *.xcworkspace 93 | 94 | # Carthage 95 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 96 | # Carthage/Checkouts 97 | 98 | Carthage/Build/ 99 | 100 | # Accio dependency management 101 | Dependencies/ 102 | .accio/ 103 | 104 | # fastlane 105 | # It is recommended to not store the screenshots in the git repo. 106 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 107 | # For more information about the recommended setup visit: 108 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 109 | 110 | fastlane/report.xml 111 | fastlane/Preview.html 112 | fastlane/screenshots/**/*.png 113 | fastlane/test_output 114 | 115 | # Code Injection 116 | # After new code Injection tools there's a generated folder /iOSInjectionProject 117 | # https://github.com/johnno1962/injectionforxcode 118 | 119 | iOSInjectionProject/ 120 | 121 | ### Xcode ### 122 | 123 | ## Xcode 8 and earlier 124 | 125 | ### Xcode Patch ### 126 | *.xcodeproj/* 127 | !*.xcodeproj/project.pbxproj 128 | !*.xcodeproj/xcshareddata/ 129 | !*.xcodeproj/project.xcworkspace/ 130 | !*.xcworkspace/contents.xcworkspacedata 131 | /*.gcno 132 | **/xcshareddata/WorkspaceSettings.xcsettings 133 | 134 | # End of https://www.toptal.com/developers/gitignore/api/swift,xcode,macos 135 | -------------------------------------------------------------------------------- /NBTEditor/NBTView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NBTView.swift 3 | // NBTEditor 4 | // 5 | // Created by 屠昊天 on 8/6/2024. 6 | // 7 | 8 | import SwiftUI 9 | import MinecraftNBT 10 | 11 | struct NBTView : View { 12 | 13 | @Binding var data: NBTStructure 14 | 15 | @State var selectKey : [String] = [] 16 | 17 | @State var editSheetVisible = false 18 | @State var renameSheetVisible = false 19 | 20 | var body: some View { 21 | let tag = data.tag 22 | List { 23 | ForEach(tag.contents.keys, id: \.self){ name in 24 | generateTree(key: [name], tag: tag[name]!) 25 | } 26 | }.sheet(isPresented: $editSheetVisible){ 27 | EditValueSheetView( 28 | nbt: $data, 29 | key: $selectKey, 30 | visible: $editSheetVisible 31 | ) 32 | }.sheet(isPresented: $renameSheetVisible){ 33 | RenameSheetView( 34 | nbt: $data, 35 | key: $selectKey, 36 | visible: $renameSheetVisible 37 | ) 38 | } 39 | } 40 | 41 | func getMenuItems(key: [String], tag : any NBTTag) -> some View{ 42 | let parent = try! self.data.read(Array(key[0.. AnyView{ 63 | let name = key.last! 64 | var letterView : LetterView 65 | switch tag { 66 | case is NBTByte: 67 | letterView = LetterView(letter: "B", color: Color(red: 158 / 255, green: 39 / 255, blue: 50 / 255)) 68 | case is NBTShort: 69 | letterView = LetterView(letter: "S", color: Color(red: 156 / 255,green: 31 / 255, blue: 179 / 255)) 70 | case is NBTInt: 71 | letterView = LetterView(letter: "I", color: Color(red: 50 / 255, green : 42 / 255, blue: 173 / 255)) 72 | case is NBTLong: 73 | letterView = LetterView(letter: "L", color: Color(red: 54 / 255, green: 123 / 255, blue: 138 / 255)) 74 | case is NBTDouble: 75 | letterView = LetterView(letter: "D", color: Color(red: 87 / 255, green: 175 / 255, blue: 83 / 255)) 76 | case is NBTString: 77 | letterView = LetterView(letter: "S", color: Color(red: 62 / 255, green: 62 / 255, blue: 62 / 255)) 78 | default: 79 | letterView = LetterView(letter: "U", color: Color.pink) 80 | } 81 | return AnyView( 82 | HStack { 83 | letterView 84 | Text(name + ":").bold() 85 | Text(tag.description) 86 | } 87 | .contextMenu{ 88 | getMenuItems(key: key, tag: tag) 89 | } 90 | 91 | ) 92 | } 93 | 94 | func generateTree(key: [String], tag: any NBTTag) -> AnyView{ 95 | let name = key.last! 96 | switch tag{ 97 | case is NBTByte: 98 | return generatePrimitiveElementLine(key: key, tag: tag) 99 | case is NBTShort: 100 | return generatePrimitiveElementLine(key: key, tag: tag) 101 | case is NBTInt: 102 | return generatePrimitiveElementLine(key: key, tag: tag) 103 | case is NBTLong: 104 | return generatePrimitiveElementLine(key: key, tag: tag) 105 | case is NBTFloat: 106 | return generatePrimitiveElementLine(key: key, tag: tag) 107 | case is NBTDouble: 108 | return generatePrimitiveElementLine(key: key, tag: tag) 109 | case is NBTString: 110 | return generatePrimitiveElementLine(key: key, tag: tag) 111 | case is NBTList: 112 | let list = tag as! NBTList 113 | return AnyView(DisclosureGroup( 114 | content: { 115 | if list.elements.count != 0 { 116 | ForEach(0..