├── .gitignore ├── LICENSE.md ├── README.md ├── fileio ├── rdt.go └── rdt_scd.go ├── go.mod ├── go.sum ├── main.go ├── screenshots └── ScriptViewer.png └── ui ├── app.go ├── fileloader.go └── shortcuts.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .vscode/ 18 | fyne-cross 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Samuel Yuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resident Evil 2 Script Viewer 2 | 3 |
4 | ScriptViewer 5 |
6 | 7 | ## About 8 | 9 | You can view the script files in the original Resident Evil 2 / Biohazard 2 as pseudocode next to the original bytecode. Every function is placed in a separate file to make it easier to switch between functions. 10 | 11 | The script data is stored as part of the room description file (.RDT). When you open any RDT file, the script files will be extracted from the RDT and the list of files is shown on the left. 12 | 13 | ## Scripting Engine 14 | 15 | This script viewer will make it easier for anyone to understand the scripting logic used by the original Resident Evil 2 game. 16 | 17 | The script in the RDT file is originally stored as one block of hex values, but the ScriptViewer parses the data by splitting it into script commands, and combining script commands into functions using the EvtEnd opcode (0x01) as the separator. 18 | 19 | * "init.scd" is the script that executes when the player initially enters a room and only executes once. This script is used for static events. 20 | 21 | * "sub0.scd" and "sub1.scd" are scripts that will start running while the player is in the room as part of the main game loop. The script engine creates two events and runs each event separately on its own ScriptThread until the function exits, i.e. sub0.scd will run on ScriptThread0 and sub1.scd will run on ScriptThread1. These scripts can either spawn other events, which starts a separate ScriptThread, or call other functions on the same ScriptThread. The sub scripts are used for dynamic events. 22 | 23 | The left panel shows the original bytecode in hexadecimal, which is the same data that can be found if you open the RDT file in an hex editor and search for the sequence of bytes. 24 | 25 | The right panel shows the corresponding pseudocode that contains a function name and its parameters. The first hex value in each row is the opcode and the subsequent hex values after the opcode are the function parameters. The opcode parameters are determined in advance by the scripting engine, and the parameter types can be 8 bit, 16 bit, or 32 bit values. 26 | 27 | -------------------------------------------------------------------------------- /fileio/rdt.go: -------------------------------------------------------------------------------- 1 | package fileio 2 | 3 | // .rdt - Room data 4 | 5 | import ( 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | "log" 10 | "os" 11 | ) 12 | 13 | type RDTHeader struct { 14 | NumSprites uint8 15 | NumCameras uint8 16 | NumModels uint8 17 | NumItems uint8 18 | NumDoors uint8 19 | NumRooms uint8 20 | NumReverb uint8 // related to sound 21 | SpriteMax uint8 // max number of .pri sprites used by one of the room's cameras 22 | } 23 | 24 | type RDTOffsets struct { 25 | OffsetRoomSound uint32 // offset to room .snd sound table data 26 | OffsetRoomVABHeader uint32 // .vh file 27 | OffsetRoomVABData uint32 // .vb file 28 | OffsetEnemyVABHeader uint32 // .vh file 29 | OffsetEnemyVABData uint32 // .vb file 30 | OffsetOTA uint32 31 | OffsetCollisionData uint32 // .sca file 32 | OffsetCameraPosition uint32 // .rid file 33 | OffsetCameraSwitches uint32 // .rvd file 34 | OffsetLights uint32 // .lit file 35 | OffsetItems uint32 36 | OffsetFloorSound uint32 // .flr file 37 | OffsetBlocks uint32 // .blk file 38 | OffsetLang1 uint32 // .msg file 39 | OffsetLang2 uint32 // .msg file 40 | OffsetScrollTexture uint32 // .tim file 41 | OffsetInitScript uint32 // .scd file 42 | OffsetExecuteScript uint32 // .scd file 43 | OffsetSpriteAnimations uint32 // .esp file 44 | OffsetSpriteAnimationsOffset uint32 // .esp file 45 | OffsetSpriteImage uint32 // .tim file 46 | OffsetModelImage uint32 // .tim file 47 | OffsetRBJ uint32 // .rbj file 48 | } 49 | 50 | type RDTOutput struct { 51 | InitScriptData *SCDOutput 52 | RoomScriptData *SCDOutput 53 | } 54 | 55 | func LoadRDTFile(filename string) (*RDTOutput, error) { 56 | rdtFile, _ := os.Open(filename) 57 | defer rdtFile.Close() 58 | 59 | if rdtFile == nil { 60 | log.Fatal("RDT file doesn't exist. Filename:", filename) 61 | return nil, fmt.Errorf("RDT file doesn't exist") 62 | } 63 | 64 | fi, err := rdtFile.Stat() 65 | if err != nil { 66 | log.Fatal(err) 67 | return nil, err 68 | } 69 | 70 | fileLength := fi.Size() 71 | return LoadRDT(rdtFile, fileLength) 72 | } 73 | 74 | func LoadRDT(r io.ReaderAt, fileLength int64) (*RDTOutput, error) { 75 | reader := io.NewSectionReader(r, int64(0), fileLength) 76 | 77 | rdtHeader := RDTHeader{} 78 | if err := binary.Read(reader, binary.LittleEndian, &rdtHeader); err != nil { 79 | return nil, err 80 | } 81 | 82 | offsets := RDTOffsets{} 83 | if err := binary.Read(reader, binary.LittleEndian, &offsets); err != nil { 84 | return nil, err 85 | } 86 | 87 | // Script data 88 | // Run once when the level loads 89 | offset := int64(offsets.OffsetInitScript) 90 | initSCDReader := io.NewSectionReader(r, offset, fileLength-offset) 91 | initSCDOutput, err := LoadRDT_SCDStream(initSCDReader, fileLength) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | // Run during the game 97 | offset = int64(offsets.OffsetExecuteScript) 98 | roomSCDReader := io.NewSectionReader(r, offset, fileLength-offset) 99 | roomSCDOutput, err := LoadRDT_SCDStream(roomSCDReader, fileLength) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | output := &RDTOutput{ 105 | InitScriptData: initSCDOutput, 106 | RoomScriptData: roomSCDOutput, 107 | } 108 | return output, nil 109 | } 110 | -------------------------------------------------------------------------------- /fileio/rdt_scd.go: -------------------------------------------------------------------------------- 1 | package fileio 2 | 3 | // .scd - Script data 4 | 5 | import ( 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | "log" 10 | ) 11 | 12 | const ( 13 | OP_NO_OP = 0 14 | OP_EVT_END = 1 15 | OP_EVT_NEXT = 2 16 | OP_EVT_CHAIN = 3 17 | OP_EVT_EXEC = 4 18 | OP_EVT_KILL = 5 19 | OP_IF_START = 6 20 | OP_ELSE_START = 7 21 | OP_END_IF = 8 22 | OP_SLEEP = 9 23 | OP_SLEEPING = 10 24 | OP_WSLEEP = 11 25 | OP_WSLEEPING = 12 26 | OP_FOR = 13 27 | OP_FOR_END = 14 28 | OP_WHILE_START = 15 29 | OP_WHILE_END = 16 30 | OP_DO_START = 17 31 | OP_DO_END = 18 32 | OP_SWITCH = 19 33 | OP_CASE = 20 34 | OP_DEFAULT = 21 35 | OP_END_SWITCH = 22 36 | OP_GOTO = 23 37 | OP_GOSUB = 24 38 | OP_GOSUB_RETURN = 25 39 | OP_BREAK = 26 40 | OP_WORK_COPY = 29 41 | OP_NO_OP2 = 32 42 | OP_CHECK = 33 43 | OP_SET_BIT = 34 44 | OP_COMPARE = 35 45 | OP_SAVE = 36 46 | OP_COPY = 37 47 | OP_CALC = 38 48 | OP_CALC2 = 39 49 | OP_SCE_RND = 40 50 | OP_CUT_CHG = 41 51 | OP_CUT_OLD = 42 52 | OP_MESSAGE_ON = 43 53 | OP_AOT_SET = 44 54 | OP_OBJ_MODEL_SET = 45 55 | OP_WORK_SET = 46 56 | OP_SPEED_SET = 47 57 | OP_ADD_SPEED = 48 58 | OP_ADD_ASPEED = 49 59 | OP_POS_SET = 50 60 | OP_DIR_SET = 51 61 | OP_MEMBER_SET = 52 62 | OP_MEMBER_SET2 = 53 63 | OP_SE_ON = 54 64 | OP_SCA_ID_SET = 55 65 | OP_DIR_CK = 57 66 | OP_SCE_ESPR_ON = 58 67 | OP_DOOR_AOT_SET = 59 68 | OP_CUT_AUTO = 60 69 | OP_MEMBER_COPY = 61 70 | OP_MEMBER_CMP = 62 71 | OP_PLC_MOTION = 63 72 | OP_PLC_DEST = 64 73 | OP_PLC_NECK = 65 74 | OP_PLC_RET = 66 75 | OP_PLC_FLAG = 67 76 | OP_SCE_EM_SET = 68 77 | OP_AOT_RESET = 70 78 | OP_AOT_ON = 71 79 | OP_SUPER_SET = 72 80 | OP_CUT_REPLACE = 75 81 | OP_SCE_ESPR_KILL = 76 82 | OP_DOOR_MODEL_SET = 77 83 | OP_ITEM_AOT_SET = 78 84 | OP_SCE_TRG_CK = 80 85 | OP_SCE_BGM_CONTROL = 81 86 | OP_SCE_ESPR_CONTROL = 82 87 | OP_SCE_FADE_SET = 83 88 | OP_SCE_ESPR3D_ON = 84 89 | OP_SCE_BGMTBL_SET = 87 90 | OP_PLC_ROT = 88 91 | OP_XA_ON = 89 92 | OP_WEAPON_CHG = 90 93 | OP_PLC_CNT = 91 94 | OP_SCE_SHAKE_ON = 92 95 | OP_MIZU_DIV_SET = 93 96 | OP_KEEP_ITEM_CK = 94 97 | OP_XA_VOL = 95 98 | OP_KAGE_SET = 96 99 | OP_CUT_BE_SET = 97 100 | OP_SCE_ITEM_LOST = 98 101 | OP_PLC_GUN_EFF = 99 102 | OP_SCE_ESPR_ON2 = 100 103 | OP_SCE_ESPR_KILL2 = 101 104 | OP_PLC_STOP = 102 105 | OP_AOT_SET_4P = 103 106 | OP_DOOR_AOT_SET_4P = 104 107 | OP_ITEM_AOT_SET_4P = 105 108 | OP_LIGHT_POS_SET = 106 109 | OP_LIGHT_KIDO_SET = 107 110 | OP_RBJ_RESET = 108 111 | OP_SCE_SCR_MOVE = 109 112 | OP_PARTS_SET = 110 113 | OP_MOVIE_ON = 111 114 | OP_SCE_PARTS_BOMB = 122 115 | OP_SCE_PARTS_DOWN = 123 116 | ) 117 | 118 | var ( 119 | InstructionSize = map[byte]int{ 120 | OP_NO_OP: 1, 121 | OP_EVT_END: 1, 122 | OP_EVT_NEXT: 1, 123 | OP_EVT_CHAIN: 4, 124 | OP_EVT_EXEC: 4, 125 | OP_EVT_KILL: 2, 126 | OP_IF_START: 4, 127 | OP_ELSE_START: 4, 128 | OP_END_IF: 1, 129 | OP_SLEEP: 4, 130 | OP_SLEEPING: 3, 131 | OP_WSLEEP: 1, 132 | OP_WSLEEPING: 1, 133 | OP_FOR: 6, 134 | OP_FOR_END: 2, 135 | OP_WHILE_START: 4, 136 | OP_WHILE_END: 2, 137 | OP_DO_START: 4, 138 | OP_DO_END: 2, 139 | OP_SWITCH: 4, 140 | OP_CASE: 6, 141 | OP_DEFAULT: 2, 142 | OP_END_SWITCH: 2, 143 | OP_GOTO: 6, 144 | OP_GOSUB: 2, 145 | OP_GOSUB_RETURN: 2, 146 | OP_BREAK: 2, 147 | OP_WORK_COPY: 4, 148 | OP_NO_OP2: 1, 149 | OP_CHECK: 4, 150 | OP_SET_BIT: 4, 151 | OP_COMPARE: 6, 152 | OP_SAVE: 4, 153 | OP_COPY: 3, 154 | OP_CALC: 6, 155 | OP_CALC2: 4, 156 | OP_SCE_RND: 1, 157 | OP_CUT_CHG: 2, 158 | OP_CUT_OLD: 1, 159 | OP_MESSAGE_ON: 6, 160 | OP_AOT_SET: 20, 161 | OP_OBJ_MODEL_SET: 38, 162 | OP_WORK_SET: 3, 163 | OP_SPEED_SET: 4, 164 | OP_ADD_SPEED: 1, 165 | OP_ADD_ASPEED: 1, 166 | OP_POS_SET: 8, 167 | OP_DIR_SET: 8, 168 | OP_MEMBER_SET: 4, 169 | OP_MEMBER_SET2: 3, 170 | OP_SE_ON: 12, 171 | OP_SCA_ID_SET: 4, 172 | OP_DIR_CK: 8, 173 | OP_SCE_ESPR_ON: 16, 174 | OP_DOOR_AOT_SET: 32, 175 | OP_CUT_AUTO: 2, 176 | OP_MEMBER_COPY: 3, 177 | OP_MEMBER_CMP: 6, 178 | OP_PLC_MOTION: 4, 179 | OP_PLC_DEST: 8, 180 | OP_PLC_NECK: 10, 181 | OP_PLC_RET: 1, 182 | OP_PLC_FLAG: 4, 183 | OP_SCE_EM_SET: 22, 184 | OP_AOT_RESET: 10, 185 | OP_AOT_ON: 2, 186 | OP_SUPER_SET: 16, 187 | OP_CUT_REPLACE: 3, 188 | OP_SCE_ESPR_KILL: 5, 189 | OP_DOOR_MODEL_SET: 22, 190 | OP_ITEM_AOT_SET: 22, 191 | OP_SCE_TRG_CK: 4, 192 | OP_SCE_BGM_CONTROL: 6, 193 | OP_SCE_ESPR_CONTROL: 6, 194 | OP_SCE_FADE_SET: 6, 195 | OP_SCE_ESPR3D_ON: 22, 196 | OP_SCE_BGMTBL_SET: 8, 197 | OP_PLC_ROT: 4, 198 | OP_XA_ON: 4, 199 | OP_WEAPON_CHG: 2, 200 | OP_PLC_CNT: 2, 201 | OP_SCE_SHAKE_ON: 3, 202 | OP_MIZU_DIV_SET: 2, 203 | OP_KEEP_ITEM_CK: 2, 204 | OP_XA_VOL: 2, 205 | OP_KAGE_SET: 14, 206 | OP_CUT_BE_SET: 4, 207 | OP_SCE_ITEM_LOST: 2, 208 | OP_PLC_GUN_EFF: 1, 209 | OP_SCE_ESPR_ON2: 16, 210 | OP_SCE_ESPR_KILL2: 2, 211 | OP_PLC_STOP: 1, 212 | OP_AOT_SET_4P: 28, 213 | OP_DOOR_AOT_SET_4P: 40, 214 | OP_ITEM_AOT_SET_4P: 30, 215 | OP_LIGHT_POS_SET: 6, 216 | OP_LIGHT_KIDO_SET: 4, 217 | OP_RBJ_RESET: 1, 218 | OP_SCE_SCR_MOVE: 4, 219 | OP_PARTS_SET: 6, 220 | OP_MOVIE_ON: 2, 221 | OP_SCE_PARTS_BOMB: 16, 222 | OP_SCE_PARTS_DOWN: 16, 223 | } 224 | FunctionName = map[byte]string{ 225 | OP_NO_OP: "NoOp", 226 | OP_EVT_END: "EvtEnd", 227 | OP_EVT_NEXT: "EvtNext", 228 | OP_EVT_CHAIN: "EvtChain", 229 | OP_EVT_EXEC: "EvtExec", 230 | OP_EVT_KILL: "EvtKill", 231 | OP_IF_START: "IfStart", 232 | OP_ELSE_START: "ElseStart", 233 | OP_END_IF: "EndIf", 234 | OP_SLEEP: "Sleep", 235 | OP_SLEEPING: "Sleeping", 236 | OP_WSLEEP: "Wsleep", 237 | OP_WSLEEPING: "Wsleeping", 238 | OP_FOR: "ForStart", 239 | OP_FOR_END: "ForEnd", 240 | OP_WHILE_START: "WhileStart", 241 | OP_WHILE_END: "WhileEnd", 242 | OP_DO_START: "DoStart", 243 | OP_DO_END: "DoEnd", 244 | OP_SWITCH: "Switch", 245 | OP_CASE: "Case", 246 | OP_DEFAULT: "Default", 247 | OP_END_SWITCH: "EndSwitch", 248 | OP_GOTO: "Goto", 249 | OP_GOSUB: "Gosub", 250 | OP_GOSUB_RETURN: "GosubReturn", 251 | OP_BREAK: "Break", 252 | OP_WORK_COPY: "WorkCopy", 253 | OP_NO_OP2: "NoOp2", 254 | OP_CHECK: "CheckBit", 255 | OP_SET_BIT: "SetBit", 256 | OP_COMPARE: "Compare", 257 | OP_SAVE: "Save", 258 | OP_COPY: "Copy", 259 | OP_CALC: "Calc", 260 | OP_CALC2: "Calc2", 261 | OP_SCE_RND: "SceRnd", 262 | OP_CUT_CHG: "CutChg", 263 | OP_CUT_OLD: "CutOld", 264 | OP_MESSAGE_ON: "MessageOn", 265 | OP_AOT_SET: "AotSet", 266 | OP_OBJ_MODEL_SET: "ObjModelSet", 267 | OP_WORK_SET: "WorkSet", 268 | OP_SPEED_SET: "SpeedSet", 269 | OP_ADD_SPEED: "AddSpeed", 270 | OP_ADD_ASPEED: "AddAspeed", 271 | OP_POS_SET: "PosSet", 272 | OP_DIR_SET: "DirSet", 273 | OP_MEMBER_SET: "MemberSet", 274 | OP_MEMBER_SET2: "MemberSet2", 275 | OP_SE_ON: "SeOn", 276 | OP_SCA_ID_SET: "ScaIdSet", 277 | OP_DIR_CK: "DirCk", 278 | OP_SCE_ESPR_ON: "SceEsprOn", 279 | OP_DOOR_AOT_SET: "DoorAotSet", 280 | OP_CUT_AUTO: "CutAuto", 281 | OP_MEMBER_COPY: "MemberCopy", 282 | OP_MEMBER_CMP: "MemberCmp", 283 | OP_PLC_MOTION: "PlcMotion", 284 | OP_PLC_DEST: "PlcDest", 285 | OP_PLC_NECK: "PlcNeck", 286 | OP_PLC_RET: "PlcRet", 287 | OP_PLC_FLAG: "PlcFlag", 288 | OP_SCE_EM_SET: "SceEmSet", 289 | OP_AOT_RESET: "AotReset", 290 | OP_AOT_ON: "AotOn", 291 | OP_SUPER_SET: "SuperSet", 292 | OP_CUT_REPLACE: "CutReplace", 293 | OP_SCE_ESPR_KILL: "SceEsprKill", 294 | OP_DOOR_MODEL_SET: "DoorModelSet", 295 | OP_ITEM_AOT_SET: "ItemAotSet", 296 | OP_SCE_TRG_CK: "SceTrgCk", 297 | OP_SCE_BGM_CONTROL: "SceBgmControl", 298 | OP_SCE_ESPR_CONTROL: "SceEsprControl", 299 | OP_SCE_FADE_SET: "SceFadeSet", 300 | OP_SCE_ESPR3D_ON: "SceEspr3dOn", 301 | OP_SCE_BGMTBL_SET: "SceBgmTblSet", 302 | OP_PLC_ROT: "PlcRot", 303 | OP_XA_ON: "XaOn", 304 | OP_WEAPON_CHG: "WeaponChg", 305 | OP_PLC_CNT: "PlcCnt", 306 | OP_SCE_SHAKE_ON: "SceShakeOn", 307 | OP_MIZU_DIV_SET: "MizuDivSet", 308 | OP_KEEP_ITEM_CK: "KeepItemCk", 309 | OP_XA_VOL: "XaVol", 310 | OP_KAGE_SET: "KageSet", 311 | OP_CUT_BE_SET: "CutBeSet", 312 | OP_SCE_ITEM_LOST: "SceItemLost", 313 | OP_PLC_GUN_EFF: "PlcGunEff", 314 | OP_SCE_ESPR_ON2: "SceEsprOn2", 315 | OP_SCE_ESPR_KILL2: "SceEsprKill2", 316 | OP_PLC_STOP: "PlcStop", 317 | OP_AOT_SET_4P: "AotSet4P", 318 | OP_DOOR_AOT_SET_4P: "DoorAotSet4P", 319 | OP_ITEM_AOT_SET_4P: "ItemAotSet4P", 320 | OP_LIGHT_POS_SET: "LightPosSet", 321 | OP_LIGHT_KIDO_SET: "LightKidoSet", 322 | OP_RBJ_RESET: "RbjReset", 323 | OP_SCE_SCR_MOVE: "SceScrMove", 324 | OP_PARTS_SET: "PartsSet", 325 | OP_MOVIE_ON: "MovieOn", 326 | OP_SCE_PARTS_BOMB: "ScePartsBomb", 327 | OP_SCE_PARTS_DOWN: "ScePartsDown", 328 | } 329 | ) 330 | 331 | type ScriptInstrGoSub struct { 332 | Opcode uint8 // 0x18 333 | Event uint8 334 | } 335 | 336 | type ScriptInstrCheckBitTest struct { 337 | Opcode uint8 // 0x21 338 | BitArray uint8 // Index of array of bits to use 339 | BitNumber uint8 // Bit number to check 340 | Value uint8 // Value to compare (0 or 1) 341 | } 342 | 343 | type ScriptInstrSetBit struct { 344 | Opcode uint8 // 0x22 345 | BitArray uint8 // Index of array of bits to use 346 | BitNumber uint8 // Bit number to check 347 | Operation uint8 // 0x0: clear, 0x1: set, 0x2-0x6: invalid, 0x7: flip bit 348 | } 349 | 350 | type ScriptInstrCutChg struct { 351 | Opcode uint8 // 0x29 352 | CameraId uint8 353 | } 354 | 355 | type ScriptInstrAotSet struct { 356 | Opcode uint8 // 0x2c 357 | Aot uint8 358 | Id uint8 359 | Type uint8 360 | Floor uint8 361 | Super uint8 362 | X, Z int16 363 | Width, Depth int16 364 | Data [6]uint8 365 | } 366 | 367 | type ScriptInstrObjModelSet struct { 368 | Opcode uint8 // 0x2d 369 | ObjectIndex uint8 370 | ObjectId uint8 371 | Counter uint8 372 | Wait uint8 373 | Num uint8 374 | Floor uint8 375 | Flag0 uint8 376 | Type uint16 377 | Flag1 uint16 378 | Attribute int16 379 | Position [3]int16 380 | Direction [3]int16 381 | Offset [3]int16 382 | Dimensions [3]uint16 383 | } 384 | 385 | type ScriptInstrPosSet struct { 386 | Opcode uint8 // 0x32 387 | Dummy uint8 388 | X int16 389 | Y int16 390 | Z int16 391 | } 392 | 393 | type ScriptInstrSceEsprOn struct { 394 | Opcode uint8 // 0x3a 395 | Dummy uint8 396 | Id uint8 397 | Type uint8 398 | Work uint16 399 | Unknown1 int16 400 | X, Y, Z int16 401 | DirY uint16 402 | } 403 | 404 | type ScriptInstrDoorAotSet struct { 405 | Opcode uint8 // 0x3b 406 | Aot uint8 // Index of item in array of room objects list 407 | Id uint8 408 | Type uint8 409 | Floor uint8 410 | Super uint8 411 | X, Z int16 // Location of door 412 | Width, Depth int16 // Size of door 413 | NextX, NextY, NextZ, NextDir int16 // Position and direction of player after door entered 414 | Stage, Room, Camera uint8 // Stage, room, camera after door entered 415 | NextFloor uint8 416 | TextureType uint8 417 | DoorType uint8 418 | KnockType uint8 419 | KeyId uint8 420 | KeyType uint8 421 | Free uint8 422 | } 423 | 424 | type ScriptInstrPlcNeck struct { 425 | Opcode uint8 // 0x41 426 | Operation uint8 427 | NeckX int16 428 | NeckY int16 429 | NeckZ int16 430 | Unknown [2]int8 431 | } 432 | 433 | type ScriptInstrSceEmSet struct { 434 | Opcode uint8 // 0x44 435 | Dummy uint8 436 | Aot uint8 437 | Id uint8 438 | Type uint8 439 | Status uint8 440 | Floor uint8 441 | SoundFlag uint8 442 | ModelType uint8 443 | EmSetFlag int8 444 | X, Y, Z int16 445 | DirY uint16 446 | Motion uint16 447 | CtrFlag uint16 448 | } 449 | 450 | type ScriptInstrAotReset struct { 451 | Opcode uint8 // 0x46 452 | Aot uint8 453 | Id uint8 454 | Type uint8 455 | Data [6]uint8 456 | } 457 | 458 | type ScriptInstrItemAotSet struct { 459 | Opcode uint8 // 0x4e 460 | Aot uint8 461 | Id uint8 462 | Type uint8 463 | Floor uint8 464 | Super uint8 465 | X, Z int16 466 | Width, Depth int16 467 | ItemId uint16 468 | Amount uint16 469 | ItemPickedIndex uint16 // flag to check if item is picked up 470 | Md1ModelId uint8 471 | Act uint8 472 | } 473 | 474 | type ScriptInstrSceBgmControl struct { 475 | Opcode uint8 // 0x51 476 | Id uint8 // 0: Main, 1: sub0, 2: sub1 477 | Operation uint8 // 0: nop, 1: start, 2: stop, 3: restart, 4: pause, 5: fadeout 478 | Type uint8 // 0: MAIN_VOL, 1: PROG0_VOL, 2: PROG1_VOL, 3: PROG2_VOL 479 | LeftVolume uint8 480 | RightVolume uint8 481 | } 482 | 483 | type ScriptInstrAotSet4p struct { 484 | Opcode uint8 // 0x67 485 | Aot uint8 486 | Id uint8 487 | Type uint8 488 | Floor uint8 489 | Super uint8 490 | X1, Z1 int16 491 | X2, Z2 int16 492 | X3, Z3 int16 493 | X4, Z4 int16 494 | Data [6]uint8 495 | } 496 | 497 | type SCDOutput struct { 498 | ScriptData ScriptFunction 499 | } 500 | 501 | type ScriptFunction struct { 502 | Instructions map[int][]byte // key is program counter, value is command 503 | StartProgramCounter []int // set per function 504 | } 505 | 506 | func LoadRDT_SCDStream(fileReader io.ReaderAt, fileLength int64) (*SCDOutput, error) { 507 | streamReader := io.NewSectionReader(fileReader, int64(0), fileLength) 508 | firstOffset := uint16(0) 509 | if err := binary.Read(streamReader, binary.LittleEndian, &firstOffset); err != nil { 510 | return nil, err 511 | } 512 | 513 | functionOffsets := make([]uint16, 0) 514 | functionOffsets = append(functionOffsets, firstOffset) 515 | for i := 2; i < int(firstOffset); i += 2 { 516 | nextOffset := uint16(0) 517 | if err := binary.Read(streamReader, binary.LittleEndian, &nextOffset); err != nil { 518 | return nil, err 519 | } 520 | functionOffsets = append(functionOffsets, nextOffset) 521 | } 522 | 523 | programCounter := 0 524 | scriptData := ScriptFunction{} 525 | scriptData.Instructions = make(map[int][]byte) 526 | scriptData.StartProgramCounter = make([]int, 0) 527 | for functionNum := 0; functionNum < len(functionOffsets); functionNum++ { 528 | scriptData.StartProgramCounter = append(scriptData.StartProgramCounter, programCounter) 529 | 530 | var functionLength int64 531 | if functionNum != len(functionOffsets)-1 { 532 | functionLength = int64(functionOffsets[functionNum+1]) - int64(functionOffsets[functionNum]) 533 | } else { 534 | functionLength = fileLength - int64(functionOffsets[functionNum]) 535 | } 536 | 537 | streamReader = io.NewSectionReader(fileReader, int64(functionOffsets[functionNum]), functionLength) 538 | for lineNum := 0; lineNum < int(functionLength); lineNum++ { 539 | opcode := byte(0) 540 | if err := binary.Read(streamReader, binary.LittleEndian, &opcode); err != nil { 541 | return nil, err 542 | } 543 | 544 | byteSize, exists := InstructionSize[opcode] 545 | if !exists { 546 | fmt.Println("Unknown opcode:", opcode) 547 | } 548 | 549 | scriptData.Instructions[programCounter] = generateScriptLine(streamReader, byteSize, opcode) 550 | 551 | // Sleep contains sleep and sleeping commands 552 | if opcode == OP_SLEEP { 553 | scriptData.Instructions[programCounter+1] = scriptData.Instructions[programCounter][1:] 554 | } 555 | 556 | programCounter += byteSize 557 | 558 | // return 559 | if opcode == OP_EVT_END { 560 | break 561 | } 562 | } 563 | } 564 | 565 | output := &SCDOutput{ 566 | ScriptData: scriptData, 567 | } 568 | return output, nil 569 | } 570 | 571 | func generateScriptLine(streamReader *io.SectionReader, totalByteSize int, opcode byte) []byte { 572 | scriptLine := make([]byte, 0) 573 | scriptLine = append(scriptLine, opcode) 574 | 575 | if totalByteSize == 1 { 576 | return scriptLine 577 | } 578 | 579 | parameters, err := readRemainingBytes(streamReader, totalByteSize-1) 580 | if err != nil { 581 | log.Fatal("Error reading script for opcode %v\n", opcode) 582 | } 583 | scriptLine = append(scriptLine, parameters...) 584 | return scriptLine 585 | } 586 | 587 | func readRemainingBytes(streamReader *io.SectionReader, byteSize int) ([]byte, error) { 588 | parameters := make([]byte, byteSize) 589 | if err := binary.Read(streamReader, binary.LittleEndian, ¶meters); err != nil { 590 | return nil, err 591 | } 592 | return parameters, nil 593 | } 594 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/OpenBiohazard2/Bio2ScriptViewer 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.23.2 6 | 7 | require fyne.io/fyne/v2 v2.6.0 8 | 9 | require ( 10 | fyne.io/systray v1.11.0 // indirect 11 | github.com/BurntSushi/toml v1.5.0 // indirect 12 | github.com/davecgh/go-spew v1.1.1 // indirect 13 | github.com/fredbi/uri v1.1.0 // indirect 14 | github.com/fsnotify/fsnotify v1.9.0 // indirect 15 | github.com/fyne-io/gl-js v0.1.0 // indirect 16 | github.com/fyne-io/glfw-js v0.2.0 // indirect 17 | github.com/fyne-io/image v0.1.1 // indirect 18 | github.com/fyne-io/oksvg v0.1.0 // indirect 19 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect 20 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect 21 | github.com/go-text/render v0.2.0 // indirect 22 | github.com/go-text/typesetting v0.3.0 // indirect 23 | github.com/godbus/dbus/v5 v5.1.0 // indirect 24 | github.com/hack-pad/go-indexeddb v0.3.2 // indirect 25 | github.com/hack-pad/safejs v0.1.1 // indirect 26 | github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 // indirect 27 | github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect 28 | github.com/kr/text v0.2.0 // indirect 29 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect 30 | github.com/nicksnyder/go-i18n/v2 v2.6.0 // indirect 31 | github.com/pmezard/go-difflib v1.0.0 // indirect 32 | github.com/rymdport/portal v0.4.1 // indirect 33 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect 34 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect 35 | github.com/stretchr/testify v1.10.0 // indirect 36 | github.com/yuin/goldmark v1.7.11 // indirect 37 | golang.org/x/image v0.26.0 // indirect 38 | golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 // indirect 39 | golang.org/x/net v0.39.0 // indirect 40 | golang.org/x/sys v0.32.0 // indirect 41 | golang.org/x/text v0.24.0 // indirect 42 | gopkg.in/yaml.v3 v3.0.1 // indirect 43 | ) 44 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | fyne.io/fyne/v2 v2.5.5 h1:IhS8Vf1EtSHS94/i41D9Rh4s1rG1habkGN/oISA0kTU= 2 | fyne.io/fyne/v2 v2.5.5/go.mod h1:0GOXKqyvNwk3DLmsFu9v0oYM0ZcD1ysGnlHCerKoAmo= 3 | fyne.io/fyne/v2 v2.6.0 h1:Rywo9yKYN4qvNuvkRuLF+zxhJYWbIFM+m4N4KV4p1pQ= 4 | fyne.io/fyne/v2 v2.6.0/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU= 5 | fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg= 6 | fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= 7 | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= 8 | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= 13 | github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= 14 | github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8= 15 | github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4= 16 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 17 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 18 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 19 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 20 | github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM= 21 | github.com/fyne-io/gl-js v0.1.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= 22 | github.com/fyne-io/glfw-js v0.2.0 h1:8GUZtN2aCoTPNqgRDxK5+kn9OURINhBEBc7M4O1KrmM= 23 | github.com/fyne-io/glfw-js v0.2.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= 24 | github.com/fyne-io/image v0.1.0 h1:Vm2TQJ2PWGHCf3jYi1/XroaNNMu+GfI/O2QpSbZd4XQ= 25 | github.com/fyne-io/image v0.1.0/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= 26 | github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= 27 | github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= 28 | github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw= 29 | github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= 30 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= 31 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= 32 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8= 33 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc= 34 | github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= 35 | github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= 36 | github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4= 37 | github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY= 38 | github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= 39 | github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= 40 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 41 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 42 | github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= 43 | github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= 44 | github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= 45 | github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= 46 | github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8= 47 | github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= 48 | github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc= 49 | github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= 50 | github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 h1:vFdvrlsVU+p/KFBWTq0lTG4fvWvG88sawGlCzM+RUEU= 51 | github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= 52 | github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M= 53 | github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= 54 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 55 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 56 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 57 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 58 | github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk= 59 | github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ= 60 | github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ= 61 | github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE= 62 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 63 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 64 | github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= 65 | github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= 66 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 67 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 68 | github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA= 69 | github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= 70 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= 71 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= 72 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= 73 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= 74 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 75 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 76 | github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= 77 | github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 78 | github.com/yuin/goldmark v1.7.11 h1:ZCxLyDMtz0nT2HFfsYG8WZ47Trip2+JyLysKcMYE5bo= 79 | github.com/yuin/goldmark v1.7.11/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= 80 | golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= 81 | golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= 82 | golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= 83 | golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= 84 | golang.org/x/mobile v0.0.0-20250305212854-3a7bc9f8a4de h1:WuckfUoaRGJfaQTPZvlmcaQwg4Xj9oS2cvvh3dUqpDo= 85 | golang.org/x/mobile v0.0.0-20250305212854-3a7bc9f8a4de/go.mod h1:/IZuixag1ELW37+FftdmIt59/3esqpAWM/QqWtf7HUI= 86 | golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7 h1:8MGTx39304caZ/OMsjPfuxUoDGI2tRas92F5x97tIYc= 87 | golang.org/x/mobile v0.0.0-20250408133729-978277e7eaf7/go.mod h1:ftACcHgQ7vaOnQbHOHvXt9Y6bEPHrs5Ovk67ClwrPJA= 88 | golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= 89 | golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 90 | golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= 91 | golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= 92 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 93 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 94 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 95 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 96 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 97 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 98 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 99 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 100 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 101 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 102 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 103 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 104 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 105 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/OpenBiohazard2/Bio2ScriptViewer/ui" 5 | ) 6 | 7 | func main() { 8 | ui.RunApp() 9 | } 10 | -------------------------------------------------------------------------------- /screenshots/ScriptViewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenBiohazard2/Bio2ScriptViewer/0592d9b5b7d1bf39c3b2d354b093afe06a4e8efd/screenshots/ScriptViewer.png -------------------------------------------------------------------------------- /ui/app.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "runtime" 5 | 6 | "fyne.io/fyne/v2" 7 | "fyne.io/fyne/v2/app" 8 | "fyne.io/fyne/v2/container" 9 | "fyne.io/fyne/v2/dialog" 10 | "fyne.io/fyne/v2/driver/desktop" 11 | "fyne.io/fyne/v2/layout" 12 | "fyne.io/fyne/v2/theme" 13 | "fyne.io/fyne/v2/widget" 14 | ) 15 | 16 | // App represents the whole application with all its windows, widgets and functions 17 | type App struct { 18 | app fyne.App 19 | mainWin fyne.Window 20 | 21 | mainModKey desktop.Modifier 22 | 23 | split *container.Split 24 | rawScriptData *widget.Entry 25 | convertedScriptCode *widget.Entry 26 | 27 | fileListBar *widget.List 28 | statusBar *fyne.Container 29 | 30 | fullscreenWin fyne.Window 31 | } 32 | 33 | func (a *App) init() { 34 | // theme 35 | switch a.app.Preferences().StringWithFallback("Theme", "Dark") { 36 | case "Light": 37 | a.app.Settings().SetTheme(theme.LightTheme()) 38 | case "Dark": 39 | a.app.Settings().SetTheme(theme.DarkTheme()) 40 | } 41 | 42 | // show/hide statusbar 43 | if a.app.Preferences().BoolWithFallback("statusBarVisible", true) == false { 44 | a.statusBar.Hide() 45 | } 46 | } 47 | 48 | func (a *App) loadStatusBar() *fyne.Container { 49 | a.statusBar = container.NewVBox( 50 | widget.NewSeparator(), 51 | container.NewHBox( 52 | layout.NewSpacer(), 53 | )) 54 | return a.statusBar 55 | } 56 | 57 | func (a *App) loadFileList(filenames []string, scriptFiles map[string][][]byte) *widget.List { 58 | data := filenames 59 | 60 | icon := widget.NewIcon(nil) 61 | label := widget.NewLabel("Select An Item From The List") 62 | 63 | list := widget.NewList( 64 | func() int { 65 | return len(data) 66 | }, 67 | func() fyne.CanvasObject { 68 | return container.NewHBox(widget.NewIcon(theme.DocumentIcon()), widget.NewLabel("Template Object")) 69 | }, 70 | func(id widget.ListItemID, item fyne.CanvasObject) { 71 | item.(*fyne.Container).Objects[1].(*widget.Label).SetText(data[id]) 72 | }, 73 | ) 74 | list.OnSelected = func(id widget.ListItemID) { 75 | label.SetText(data[id]) 76 | icon.SetResource(theme.DocumentIcon()) 77 | 78 | if scriptFiles != nil { 79 | a.rawScriptData.SetText(convertRawScriptInstructionsToString(scriptFiles[filenames[id]])) 80 | a.convertedScriptCode.SetText(convertScriptInstructionsToCode(scriptFiles[filenames[id]])) 81 | } 82 | } 83 | list.OnUnselected = func(id widget.ListItemID) { 84 | label.SetText("Select An Item From The List") 85 | icon.SetResource(nil) 86 | } 87 | // Select first item at the top 88 | list.Select(0) 89 | 90 | a.fileListBar = list 91 | return a.fileListBar 92 | } 93 | 94 | func (a *App) loadMainUI() fyne.CanvasObject { 95 | a.mainWin.SetMaster() 96 | // set main mod key to super on darwin hosts, else set it to ctrl 97 | if runtime.GOOS == "darwin" { 98 | a.mainModKey = desktop.SuperModifier 99 | } else { 100 | a.mainModKey = desktop.ControlModifier 101 | } 102 | 103 | // main menu 104 | mainMenu := fyne.NewMainMenu( 105 | fyne.NewMenu("File", 106 | fyne.NewMenuItem("Open", a.openFileDialog), 107 | ), 108 | fyne.NewMenu("Help", 109 | fyne.NewMenuItem("About", func() { 110 | dialog.ShowCustom("About", "Ok", container.NewVBox( 111 | widget.NewLabel("Original Resident Evil 2 / Biohazard 2 Script Viewer."), 112 | ), a.mainWin) 113 | }), 114 | ), 115 | ) 116 | a.mainWin.SetMainMenu(mainMenu) 117 | 118 | a.loadKeyboardShortcuts() 119 | 120 | a.rawScriptData = widget.NewMultiLineEntry() 121 | a.rawScriptData.Wrapping = fyne.TextWrapWord 122 | a.rawScriptData.SetText("") 123 | 124 | a.convertedScriptCode = widget.NewMultiLineEntry() 125 | a.convertedScriptCode.Wrapping = fyne.TextWrapWord 126 | a.convertedScriptCode.SetText("") 127 | 128 | a.split = container.NewHSplit( 129 | a.rawScriptData, 130 | a.convertedScriptCode, 131 | ) 132 | a.split.SetOffset(0.50) 133 | layout := container.NewBorder(nil, a.loadStatusBar(), a.loadFileList([]string{}, nil), nil, a.split) 134 | return layout 135 | } 136 | 137 | func RunApp() { 138 | curApp := app.NewWithID("bio2-scd-viewer") 139 | mainWindow := curApp.NewWindow("Biohazard 2 Script Viewer") 140 | userInterface := &App{app: curApp, mainWin: mainWindow} 141 | userInterface.init() 142 | mainWindow.SetContent(userInterface.loadMainUI()) 143 | mainWindow.Resize(fyne.NewSize(1200, 750)) 144 | mainWindow.ShowAndRun() 145 | } 146 | -------------------------------------------------------------------------------- /ui/fileloader.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | "os" 9 | "sort" 10 | 11 | "fyne.io/fyne/v2" 12 | "fyne.io/fyne/v2/container" 13 | "fyne.io/fyne/v2/dialog" 14 | "fyne.io/fyne/v2/storage" 15 | 16 | "github.com/OpenBiohazard2/Bio2ScriptViewer/fileio" 17 | ) 18 | 19 | func (a *App) openFileDialog() { 20 | dialog := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) { 21 | if err != nil { 22 | dialog.ShowError(err, a.mainWin) 23 | return 24 | } 25 | if err == nil && reader == nil { 26 | return 27 | } 28 | 29 | file, err := os.Open(reader.URI().String()[7:]) 30 | if err != nil { 31 | dialog.ShowError(err, a.mainWin) 32 | return 33 | } 34 | 35 | err = a.open(file, true) 36 | if err != nil { 37 | dialog.ShowError(err, a.mainWin) 38 | return 39 | } 40 | defer reader.Close() 41 | }, a.mainWin) 42 | dialog.SetFilter(storage.NewExtensionFileFilter([]string{".rdt"})) 43 | dialog.Show() 44 | } 45 | 46 | func (a *App) open(file *os.File, folder bool) error { 47 | defer file.Close() 48 | 49 | fi, err := file.Stat() 50 | if err != nil { 51 | return err 52 | } 53 | fileLength := fi.Size() 54 | 55 | streamReader := io.NewSectionReader(file, int64(0), fileLength) 56 | rdtOutput, err := fileio.LoadRDT(streamReader, fileLength) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | scriptFiles := splitScriptDataIntoFiles(rdtOutput.RoomScriptData) 62 | // Add script from init 63 | scriptFiles["init.scd"] = convertInitialScriptIntoFile(rdtOutput.InitScriptData) 64 | 65 | filenames := make([]string, 0) 66 | for filename, _ := range scriptFiles { 67 | filenames = append(filenames, filename) 68 | } 69 | sort.Strings(filenames) 70 | 71 | layout := container.NewBorder(nil, a.loadStatusBar(), a.loadFileList(filenames, scriptFiles), nil, a.split) 72 | a.mainWin.SetContent(layout) 73 | 74 | return nil 75 | } 76 | 77 | func convertInitialScriptIntoFile(scriptFile *fileio.SCDOutput) [][]byte { 78 | programCounters := sortProgramCounters(scriptFile.ScriptData.Instructions) 79 | 80 | fileLines := make([][]byte, 0) 81 | for _, programCounter := range programCounters { 82 | fileLines = append(fileLines, scriptFile.ScriptData.Instructions[programCounter]) 83 | } 84 | return fileLines 85 | } 86 | 87 | func splitScriptDataIntoFiles(scriptFile *fileio.SCDOutput) map[string][][]byte { 88 | programCounters := sortProgramCounters(scriptFile.ScriptData.Instructions) 89 | 90 | startCounterExists := make(map[int]bool) 91 | for _, start := range scriptFile.ScriptData.StartProgramCounter { 92 | startCounterExists[start] = true 93 | } 94 | 95 | scriptFiles := make(map[string][][]byte) 96 | fileLines := make([][]byte, 0) 97 | fileIndex := 0 98 | for _, programCounter := range programCounters { 99 | _, ok := startCounterExists[programCounter] 100 | if ok && programCounter > 0 { 101 | scriptFiles[fmt.Sprintf("sub%d.scd", fileIndex)] = fileLines 102 | fileIndex++ 103 | fileLines = make([][]byte, 0) 104 | } 105 | 106 | fileLines = append(fileLines, scriptFile.ScriptData.Instructions[programCounter]) 107 | } 108 | 109 | // Add last script function 110 | if len(fileLines) > 0 { 111 | scriptFiles[fmt.Sprintf("sub%d.scd", fileIndex)] = fileLines 112 | } 113 | 114 | return scriptFiles 115 | } 116 | 117 | func convertRawScriptInstructionsToString(instructions [][]byte) string { 118 | rawDataString := "" 119 | for _, lineBytes := range instructions { 120 | lineString := "" 121 | // print out hex values 122 | for i := 0; i < len(lineBytes); i++ { 123 | lineString += fmt.Sprintf("%02x ", lineBytes[i]) 124 | } 125 | rawDataString += lineString + "\n" 126 | } 127 | 128 | return rawDataString 129 | } 130 | 131 | func convertScriptInstructionsToCode(instructions [][]byte) string { 132 | rawDataString := "" 133 | for _, lineBytes := range instructions { 134 | lineString := fmt.Sprintf("%s", getFunctionNameFromOpcode(lineBytes[0])) 135 | lineString += showParameters(lineBytes) 136 | rawDataString += lineString + "\n" 137 | } 138 | 139 | return rawDataString 140 | } 141 | 142 | func sortProgramCounters(instructions map[int][]byte) []int { 143 | // sort script commands in order 144 | programCounters := make([]int, 0) 145 | for counter, _ := range instructions { 146 | programCounters = append(programCounters, counter) 147 | } 148 | sort.Ints(programCounters) 149 | 150 | return programCounters 151 | } 152 | 153 | func getFunctionNameFromOpcode(opcode byte) string { 154 | return fileio.FunctionName[opcode] 155 | } 156 | 157 | func showParameters(lineBytes []byte) string { 158 | opcode := lineBytes[0] 159 | parameterString := "(" 160 | switch opcode { 161 | case fileio.OP_GOSUB: // 0x18 162 | byteArr := bytes.NewBuffer(lineBytes) 163 | instruction := fileio.ScriptInstrGoSub{} 164 | binary.Read(byteArr, binary.LittleEndian, &instruction) 165 | parameterString += fmt.Sprintf("Event=%d", instruction.Event) 166 | case fileio.OP_CHECK: // 0x21 167 | byteArr := bytes.NewBuffer(lineBytes) 168 | instruction := fileio.ScriptInstrCheckBitTest{} 169 | binary.Read(byteArr, binary.LittleEndian, &instruction) 170 | parameterString += fmt.Sprintf("BitArray=%d, ", instruction.BitArray) 171 | parameterString += fmt.Sprintf("BitNumber=%d, ", instruction.BitNumber) 172 | parameterString += fmt.Sprintf("Value=%d", instruction.Value) 173 | case fileio.OP_SET_BIT: // 0x22 174 | byteArr := bytes.NewBuffer(lineBytes) 175 | instruction := fileio.ScriptInstrSetBit{} 176 | binary.Read(byteArr, binary.LittleEndian, &instruction) 177 | parameterString += fmt.Sprintf("BitArray=%d, ", instruction.BitArray) 178 | parameterString += fmt.Sprintf("BitNumber=%d, ", instruction.BitNumber) 179 | parameterString += fmt.Sprintf("Operation=%d", instruction.Operation) 180 | case fileio.OP_CUT_CHG: // 0x29 181 | byteArr := bytes.NewBuffer(lineBytes) 182 | instruction := fileio.ScriptInstrCutChg{} 183 | binary.Read(byteArr, binary.LittleEndian, &instruction) 184 | parameterString += fmt.Sprintf("CameraId=%d", instruction.CameraId) 185 | case fileio.OP_AOT_SET: // 0x2c 186 | byteArr := bytes.NewBuffer(lineBytes) 187 | instruction := fileio.ScriptInstrAotSet{} 188 | binary.Read(byteArr, binary.LittleEndian, &instruction) 189 | parameterString += fmt.Sprintf("Aot=%d, ", instruction.Aot) 190 | parameterString += fmt.Sprintf("Id=%d, ", instruction.Id) 191 | parameterString += fmt.Sprintf("Type=%d, ", instruction.Type) 192 | parameterString += fmt.Sprintf("Floor=%d, ", instruction.Floor) 193 | parameterString += fmt.Sprintf("Super=%d, ", instruction.Super) 194 | parameterString += fmt.Sprintf("X=%d, Z=%d, ", instruction.X, instruction.Z) 195 | parameterString += fmt.Sprintf("Width=%d, Depth=%d, ", instruction.Width, instruction.Depth) 196 | parameterString += fmt.Sprintf("Data=[%d,%d,%d,%d,%d,%d]", instruction.Data[0], instruction.Data[1], instruction.Data[2], 197 | instruction.Data[3], instruction.Data[4], instruction.Data[5]) 198 | case fileio.OP_OBJ_MODEL_SET: // 0x2d 199 | byteArr := bytes.NewBuffer(lineBytes) 200 | instruction := fileio.ScriptInstrObjModelSet{} 201 | binary.Read(byteArr, binary.LittleEndian, &instruction) 202 | parameterString += fmt.Sprintf("ObjectIndex=%d, ", instruction.ObjectIndex) 203 | parameterString += fmt.Sprintf("ObjectId=%d, ", instruction.ObjectId) 204 | parameterString += fmt.Sprintf("Counter=%d, ", instruction.Counter) 205 | parameterString += fmt.Sprintf("Wait=%d, ", instruction.Wait) 206 | parameterString += fmt.Sprintf("Num=%d, ", instruction.Num) 207 | parameterString += fmt.Sprintf("Floor=%d, ", instruction.Floor) 208 | parameterString += fmt.Sprintf("Flag0=%d, ", instruction.Flag0) 209 | parameterString += fmt.Sprintf("Type=%d, ", instruction.Type) 210 | parameterString += fmt.Sprintf("Flag1=%d, ", instruction.Flag1) 211 | parameterString += fmt.Sprintf("Attribute=%d, ", instruction.Attribute) 212 | parameterString += fmt.Sprintf("Position=[%d, %d, %d], ", instruction.Position[0], instruction.Position[1], instruction.Position[2]) 213 | parameterString += fmt.Sprintf("Direction=[%d, %d, %d], ", instruction.Direction[0], instruction.Direction[1], instruction.Direction[2]) 214 | parameterString += fmt.Sprintf("Offset=[%d, %d, %d], ", instruction.Offset[0], instruction.Offset[1], instruction.Offset[2]) 215 | parameterString += fmt.Sprintf("Dimensions=[%d, %d, %d]", instruction.Dimensions[0], instruction.Dimensions[1], instruction.Dimensions[2]) 216 | case fileio.OP_POS_SET: // 0x32 217 | byteArr := bytes.NewBuffer(lineBytes) 218 | instruction := fileio.ScriptInstrPosSet{} 219 | binary.Read(byteArr, binary.LittleEndian, &instruction) 220 | parameterString += fmt.Sprintf("Dummy=%d, ", instruction.Dummy) 221 | parameterString += fmt.Sprintf("X=%d, Y=%d, Z=%d", instruction.X, instruction.Y, instruction.Z) 222 | case fileio.OP_SCE_ESPR_ON: // 0x3a 223 | byteArr := bytes.NewBuffer(lineBytes) 224 | instruction := fileio.ScriptInstrSceEsprOn{} 225 | binary.Read(byteArr, binary.LittleEndian, &instruction) 226 | parameterString += fmt.Sprintf("Dummy=%d, ", instruction.Dummy) 227 | parameterString += fmt.Sprintf("Id=%d, ", instruction.Id) 228 | parameterString += fmt.Sprintf("Type=%d, ", instruction.Type) 229 | parameterString += fmt.Sprintf("Work=%d, ", instruction.Work) 230 | parameterString += fmt.Sprintf("Unknown1=%d, ", instruction.Unknown1) 231 | parameterString += fmt.Sprintf("X=%d, Y=%d, Z=%d, ", instruction.X, instruction.Y, instruction.Z) 232 | parameterString += fmt.Sprintf("DirY=%d", instruction.DirY) 233 | case fileio.OP_DOOR_AOT_SET: // 0x3b 234 | byteArr := bytes.NewBuffer(lineBytes) 235 | door := fileio.ScriptInstrDoorAotSet{} 236 | binary.Read(byteArr, binary.LittleEndian, &door) 237 | parameterString += fmt.Sprintf("Aot=%d, ", door.Aot) 238 | parameterString += fmt.Sprintf("Id=%d, ", door.Id) 239 | parameterString += fmt.Sprintf("Type=%d, ", door.Type) 240 | parameterString += fmt.Sprintf("Floor=%d, ", door.Floor) 241 | parameterString += fmt.Sprintf("Super=%d, ", door.Super) 242 | parameterString += fmt.Sprintf("X=%d, Z=%d, ", door.X, door.Z) 243 | parameterString += fmt.Sprintf("Width=%d, Depth=%d, ", door.Width, door.Depth) 244 | parameterString += fmt.Sprintf("NextX=%d, NextY=%d, ", door.NextX, door.NextY) 245 | parameterString += fmt.Sprintf("NextZ=%d, NextDir=%d, ", door.NextZ, door.NextDir) 246 | parameterString += fmt.Sprintf("Stage=%d, Room=%d, Camera=%d, ", door.Stage, door.Room, door.Camera) 247 | parameterString += fmt.Sprintf("NextFloor=%d, ", door.NextFloor) 248 | parameterString += fmt.Sprintf("TextureType=%d, ", door.TextureType) 249 | parameterString += fmt.Sprintf("DoorType=%d, ", door.DoorType) 250 | parameterString += fmt.Sprintf("KnockType=%d, ", door.KnockType) 251 | parameterString += fmt.Sprintf("KeyId=%d, ", door.KeyId) 252 | parameterString += fmt.Sprintf("KeyType=%d, ", door.KeyType) 253 | parameterString += fmt.Sprintf("Free=%d", door.Free) 254 | case fileio.OP_PLC_NECK: // 0x41 255 | byteArr := bytes.NewBuffer(lineBytes) 256 | instruction := fileio.ScriptInstrPlcNeck{} 257 | binary.Read(byteArr, binary.LittleEndian, &instruction) 258 | parameterString += fmt.Sprintf("Operation=%d, ", instruction.Operation) 259 | parameterString += fmt.Sprintf("NeckX=%d, ", instruction.NeckX) 260 | parameterString += fmt.Sprintf("NeckY=%d, ", instruction.NeckY) 261 | parameterString += fmt.Sprintf("NeckZ=%d, ", instruction.NeckZ) 262 | parameterString += fmt.Sprintf("Unknown=[%d, %d]", instruction.Unknown[0], instruction.Unknown[1]) 263 | case fileio.OP_SCE_EM_SET: // 0x44 264 | byteArr := bytes.NewBuffer(lineBytes) 265 | instruction := fileio.ScriptInstrSceEmSet{} 266 | binary.Read(byteArr, binary.LittleEndian, &instruction) 267 | parameterString += fmt.Sprintf("Dummy=%d, ", instruction.Dummy) 268 | parameterString += fmt.Sprintf("Aot=%d, ", instruction.Aot) 269 | parameterString += fmt.Sprintf("Id=%d, ", instruction.Id) 270 | parameterString += fmt.Sprintf("Type=%d, ", instruction.Type) 271 | parameterString += fmt.Sprintf("Status=%d, ", instruction.Status) 272 | parameterString += fmt.Sprintf("Floor=%d, ", instruction.Floor) 273 | parameterString += fmt.Sprintf("SoundFlag=%d, ", instruction.SoundFlag) 274 | parameterString += fmt.Sprintf("ModelType=%d, ", instruction.ModelType) 275 | parameterString += fmt.Sprintf("EmSetFlag=%d, ", instruction.EmSetFlag) 276 | parameterString += fmt.Sprintf("X=%d, Y=%d, Z=%d, ", instruction.X, instruction.Y, instruction.Z) 277 | parameterString += fmt.Sprintf("DirY=%d, ", instruction.DirY) 278 | parameterString += fmt.Sprintf("Motion=%d, ", instruction.Motion) 279 | parameterString += fmt.Sprintf("CtrFlag=%d", instruction.CtrFlag) 280 | case fileio.OP_AOT_RESET: // 0x46 281 | byteArr := bytes.NewBuffer(lineBytes) 282 | instruction := fileio.ScriptInstrAotReset{} 283 | binary.Read(byteArr, binary.LittleEndian, &instruction) 284 | parameterString += fmt.Sprintf("Aot=%d, ", instruction.Aot) 285 | parameterString += fmt.Sprintf("Id=%d, ", instruction.Id) 286 | parameterString += fmt.Sprintf("Type=%d, ", instruction.Type) 287 | parameterString += fmt.Sprintf("Data=[%d,%d,%d,%d,%d,%d]", instruction.Data[0], instruction.Data[1], instruction.Data[2], 288 | instruction.Data[3], instruction.Data[4], instruction.Data[5]) 289 | case fileio.OP_ITEM_AOT_SET: // 0x4e 290 | byteArr := bytes.NewBuffer(lineBytes) 291 | instruction := fileio.ScriptInstrItemAotSet{} 292 | binary.Read(byteArr, binary.LittleEndian, &instruction) 293 | parameterString += fmt.Sprintf("Aot=%d, ", instruction.Aot) 294 | parameterString += fmt.Sprintf("Id=%d, ", instruction.Id) 295 | parameterString += fmt.Sprintf("Type=%d, ", instruction.Type) 296 | parameterString += fmt.Sprintf("Floor=%d, ", instruction.Floor) 297 | parameterString += fmt.Sprintf("Super=%d, ", instruction.Super) 298 | parameterString += fmt.Sprintf("X=%d, Z=%d, ", instruction.X, instruction.Z) 299 | parameterString += fmt.Sprintf("Width=%d, Depth=%d, ", instruction.Width, instruction.Depth) 300 | parameterString += fmt.Sprintf("ItemId=%d, ", instruction.ItemId) 301 | parameterString += fmt.Sprintf("Amount=%d, ", instruction.Amount) 302 | parameterString += fmt.Sprintf("ItemPickedIndex=%d, ", instruction.ItemPickedIndex) 303 | parameterString += fmt.Sprintf("Md1ModelId=%d, ", instruction.Md1ModelId) 304 | parameterString += fmt.Sprintf("Act=%d", instruction.Act) 305 | case fileio.OP_SCE_BGM_CONTROL: // 0x51 306 | byteArr := bytes.NewBuffer(lineBytes) 307 | instruction := fileio.ScriptInstrSceBgmControl{} 308 | binary.Read(byteArr, binary.LittleEndian, &instruction) 309 | parameterString += fmt.Sprintf("Id=%d, ", instruction.Id) 310 | parameterString += fmt.Sprintf("Operation=%d, ", instruction.Operation) 311 | parameterString += fmt.Sprintf("Type=%d, ", instruction.Type) 312 | parameterString += fmt.Sprintf("LeftVolume=%d, ", instruction.LeftVolume) 313 | parameterString += fmt.Sprintf("RightVolume=%d", instruction.RightVolume) 314 | case fileio.OP_AOT_SET_4P: // 0x67 315 | byteArr := bytes.NewBuffer(lineBytes) 316 | instruction := fileio.ScriptInstrAotSet4p{} 317 | binary.Read(byteArr, binary.LittleEndian, &instruction) 318 | parameterString += fmt.Sprintf("Aot=%d, ", instruction.Aot) 319 | parameterString += fmt.Sprintf("Id=%d, ", instruction.Id) 320 | parameterString += fmt.Sprintf("Type=%d, ", instruction.Type) 321 | parameterString += fmt.Sprintf("Floor=%d, ", instruction.Floor) 322 | parameterString += fmt.Sprintf("Super=%d, ", instruction.Super) 323 | parameterString += fmt.Sprintf("X1=%d, Z1=%d, ", instruction.X1, instruction.Z1) 324 | parameterString += fmt.Sprintf("X2=%d, Z2=%d, ", instruction.X2, instruction.Z2) 325 | parameterString += fmt.Sprintf("X3=%d, Z3=%d, ", instruction.X3, instruction.Z3) 326 | parameterString += fmt.Sprintf("X4=%d, Z4=%d, ", instruction.X4, instruction.Z4) 327 | parameterString += fmt.Sprintf("Data=[%d,%d,%d,%d,%d,%d]", instruction.Data[0], instruction.Data[1], instruction.Data[2], 328 | instruction.Data[3], instruction.Data[4], instruction.Data[5]) 329 | default: 330 | // Log each byte as its own parameter 331 | for i := 1; i < len(lineBytes); i++ { 332 | parameterString += fmt.Sprintf("%d, ", lineBytes[i]) 333 | } 334 | } 335 | parameterString += ");" 336 | return parameterString 337 | } 338 | -------------------------------------------------------------------------------- /ui/shortcuts.go: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import ( 4 | "fyne.io/fyne/v2" 5 | "fyne.io/fyne/v2/driver/desktop" 6 | ) 7 | 8 | func (a *App) loadKeyboardShortcuts() { 9 | // keyboard shortcuts 10 | // ctrl+o to open file 11 | a.mainWin.Canvas().AddShortcut(&desktop.CustomShortcut{ 12 | KeyName: fyne.KeyO, 13 | Modifier: a.mainModKey, 14 | }, func(shortcut fyne.Shortcut) { a.openFileDialog() }) 15 | 16 | // ctrl+q to quit application 17 | a.mainWin.Canvas().AddShortcut(&desktop.CustomShortcut{ 18 | KeyName: fyne.KeyQ, 19 | Modifier: a.mainModKey, 20 | }, func(shortcut fyne.Shortcut) { a.app.Quit() }) 21 | 22 | a.mainWin.Canvas().SetOnTypedKey(func(key *fyne.KeyEvent) { 23 | switch key.Name { 24 | // close dialogs with esc key 25 | case fyne.KeyEscape: 26 | if len(a.mainWin.Canvas().Overlays().List()) > 0 { 27 | a.mainWin.Canvas().Overlays().Top().Hide() 28 | } 29 | } 30 | }) 31 | } 32 | --------------------------------------------------------------------------------