├── .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 |

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 |
--------------------------------------------------------------------------------