├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── common.rs ├── emulator ├── c_ctype.rs ├── c_fenv.rs ├── c_stdio.rs ├── c_stdlib.rs ├── c_string.rs ├── c_time.rs ├── flex_lm.rs ├── heap.rs ├── helpers.rs ├── interface_lib.rs ├── mac_files.rs ├── mac_fp.rs ├── mac_gestalt.rs ├── mac_low_mem.rs ├── mac_memory.rs ├── mac_os_utils.rs ├── mac_quickdraw.rs ├── mac_resources.rs ├── mac_text_utils.rs ├── mod.rs └── std_c_lib.rs ├── filesystem.rs ├── linker.rs ├── mac_roman.rs ├── macbinary.rs ├── main.rs ├── pef ├── data.rs └── mod.rs └── resources.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.18" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.55" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.1.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 36 | 37 | [[package]] 38 | name = "bimap" 39 | version = "0.6.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" 42 | 43 | [[package]] 44 | name = "binread" 45 | version = "2.2.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "16598dfc8e6578e9b597d9910ba2e73618385dc9f4b1d43dd92c349d6be6418f" 48 | dependencies = [ 49 | "binread_derive", 50 | "rustversion", 51 | ] 52 | 53 | [[package]] 54 | name = "binread_derive" 55 | version = "2.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "1d9672209df1714ee804b1f4d4f68c8eb2a90b1f7a07acf472f88ce198ef1fed" 58 | dependencies = [ 59 | "either", 60 | "proc-macro2", 61 | "quote", 62 | "syn", 63 | ] 64 | 65 | [[package]] 66 | name = "bitflags" 67 | version = "1.3.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 70 | 71 | [[package]] 72 | name = "bitvec" 73 | version = "1.0.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" 76 | dependencies = [ 77 | "funty", 78 | "radium", 79 | "tap", 80 | "wyz", 81 | ] 82 | 83 | [[package]] 84 | name = "cc" 85 | version = "1.0.73" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 88 | 89 | [[package]] 90 | name = "cfg-if" 91 | version = "1.0.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 94 | 95 | [[package]] 96 | name = "chrono" 97 | version = "0.4.19" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 100 | dependencies = [ 101 | "libc", 102 | "num-integer", 103 | "num-traits", 104 | "time", 105 | "winapi", 106 | ] 107 | 108 | [[package]] 109 | name = "cmake" 110 | version = "0.1.49" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" 113 | dependencies = [ 114 | "cc", 115 | ] 116 | 117 | [[package]] 118 | name = "crc" 119 | version = "3.0.1" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" 122 | dependencies = [ 123 | "crc-catalog", 124 | ] 125 | 126 | [[package]] 127 | name = "crc-catalog" 128 | version = "2.2.0" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" 131 | 132 | [[package]] 133 | name = "either" 134 | version = "1.6.1" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 137 | 138 | [[package]] 139 | name = "env_logger" 140 | version = "0.9.0" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 143 | dependencies = [ 144 | "atty", 145 | "humantime", 146 | "log", 147 | "regex", 148 | "termcolor", 149 | ] 150 | 151 | [[package]] 152 | name = "funty" 153 | version = "2.0.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 156 | 157 | [[package]] 158 | name = "hermit-abi" 159 | version = "0.1.19" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 162 | dependencies = [ 163 | "libc", 164 | ] 165 | 166 | [[package]] 167 | name = "humantime" 168 | version = "2.1.0" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 171 | 172 | [[package]] 173 | name = "lazy_static" 174 | version = "1.4.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 177 | 178 | [[package]] 179 | name = "libc" 180 | version = "0.2.119" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" 183 | 184 | [[package]] 185 | name = "log" 186 | version = "0.4.14" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 189 | dependencies = [ 190 | "cfg-if", 191 | ] 192 | 193 | [[package]] 194 | name = "memchr" 195 | version = "2.4.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 198 | 199 | [[package]] 200 | name = "mpw-emu" 201 | version = "0.1.0" 202 | dependencies = [ 203 | "anyhow", 204 | "bimap", 205 | "binread", 206 | "bitvec", 207 | "chrono", 208 | "crc", 209 | "env_logger", 210 | "lazy_static", 211 | "log", 212 | "num", 213 | "unicorn-engine", 214 | "xattr", 215 | ] 216 | 217 | [[package]] 218 | name = "num" 219 | version = "0.4.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" 222 | dependencies = [ 223 | "num-bigint", 224 | "num-complex", 225 | "num-integer", 226 | "num-iter", 227 | "num-rational", 228 | "num-traits", 229 | ] 230 | 231 | [[package]] 232 | name = "num-bigint" 233 | version = "0.4.3" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" 236 | dependencies = [ 237 | "autocfg", 238 | "num-integer", 239 | "num-traits", 240 | ] 241 | 242 | [[package]] 243 | name = "num-complex" 244 | version = "0.4.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" 247 | dependencies = [ 248 | "num-traits", 249 | ] 250 | 251 | [[package]] 252 | name = "num-integer" 253 | version = "0.1.44" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 256 | dependencies = [ 257 | "autocfg", 258 | "num-traits", 259 | ] 260 | 261 | [[package]] 262 | name = "num-iter" 263 | version = "0.1.42" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" 266 | dependencies = [ 267 | "autocfg", 268 | "num-integer", 269 | "num-traits", 270 | ] 271 | 272 | [[package]] 273 | name = "num-rational" 274 | version = "0.4.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" 277 | dependencies = [ 278 | "autocfg", 279 | "num-bigint", 280 | "num-integer", 281 | "num-traits", 282 | ] 283 | 284 | [[package]] 285 | name = "num-traits" 286 | version = "0.2.14" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 289 | dependencies = [ 290 | "autocfg", 291 | ] 292 | 293 | [[package]] 294 | name = "pkg-config" 295 | version = "0.3.24" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" 298 | 299 | [[package]] 300 | name = "proc-macro2" 301 | version = "1.0.36" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" 304 | dependencies = [ 305 | "unicode-xid", 306 | ] 307 | 308 | [[package]] 309 | name = "quote" 310 | version = "1.0.15" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" 313 | dependencies = [ 314 | "proc-macro2", 315 | ] 316 | 317 | [[package]] 318 | name = "radium" 319 | version = "0.7.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 322 | 323 | [[package]] 324 | name = "regex" 325 | version = "1.5.4" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 328 | dependencies = [ 329 | "aho-corasick", 330 | "memchr", 331 | "regex-syntax", 332 | ] 333 | 334 | [[package]] 335 | name = "regex-syntax" 336 | version = "0.6.25" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 339 | 340 | [[package]] 341 | name = "rustversion" 342 | version = "1.0.6" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" 345 | 346 | [[package]] 347 | name = "syn" 348 | version = "1.0.86" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" 351 | dependencies = [ 352 | "proc-macro2", 353 | "quote", 354 | "unicode-xid", 355 | ] 356 | 357 | [[package]] 358 | name = "tap" 359 | version = "1.0.1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 362 | 363 | [[package]] 364 | name = "termcolor" 365 | version = "1.1.2" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 368 | dependencies = [ 369 | "winapi-util", 370 | ] 371 | 372 | [[package]] 373 | name = "time" 374 | version = "0.1.44" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 377 | dependencies = [ 378 | "libc", 379 | "wasi", 380 | "winapi", 381 | ] 382 | 383 | [[package]] 384 | name = "unicode-xid" 385 | version = "0.2.2" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 388 | 389 | [[package]] 390 | name = "unicorn-engine" 391 | version = "2.0.1" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "f3b881bfd9837ff4f62e81a1e64b40a584604375ae0a73d0d5f09b7a72350b96" 394 | dependencies = [ 395 | "bitflags", 396 | "cc", 397 | "cmake", 398 | "libc", 399 | "pkg-config", 400 | ] 401 | 402 | [[package]] 403 | name = "wasi" 404 | version = "0.10.0+wasi-snapshot-preview1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 407 | 408 | [[package]] 409 | name = "winapi" 410 | version = "0.3.9" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 413 | dependencies = [ 414 | "winapi-i686-pc-windows-gnu", 415 | "winapi-x86_64-pc-windows-gnu", 416 | ] 417 | 418 | [[package]] 419 | name = "winapi-i686-pc-windows-gnu" 420 | version = "0.4.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 423 | 424 | [[package]] 425 | name = "winapi-util" 426 | version = "0.1.5" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 429 | dependencies = [ 430 | "winapi", 431 | ] 432 | 433 | [[package]] 434 | name = "winapi-x86_64-pc-windows-gnu" 435 | version = "0.4.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 438 | 439 | [[package]] 440 | name = "wyz" 441 | version = "0.5.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" 444 | dependencies = [ 445 | "tap", 446 | ] 447 | 448 | [[package]] 449 | name = "xattr" 450 | version = "0.2.2" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" 453 | dependencies = [ 454 | "libc", 455 | ] 456 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpw-emu" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1.0.55" 10 | bimap = "0.6.2" 11 | binread = "2.2.0" 12 | bitvec = "1" 13 | chrono = "0.4.19" 14 | crc = "3.0.1" 15 | env_logger = "0.9.0" 16 | lazy_static = "1.4.0" 17 | log = "0.4.14" 18 | num = "0.4.0" 19 | unicorn-engine = "2.0.1" 20 | xattr = "0.2.2" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mpw-emu 2 | 3 | An extremely ridiculous weekend project that tries to do user-mode emulation of PowerPC executables for classic Mac OS, to run the CodeWarrior C++ compiler without faffing about with SheepShaver or QEMU. 4 | 5 | ## Features 6 | 7 | - Speaks MacBinary so you can interact with Mac files on Windows 8 | - Implements enough nonsense to compile object files using MWCPPC from CodeWarrior Pro 1 *and* decompile resources using DeRez! 9 | - Probably won't destroy your file system 10 | - It's written in Rust! 🦀 11 | 12 | ## TODO 13 | 14 | - Implement the missing relocations for the PEF linker 15 | - Implement more of the C standard library 16 | - Figure out a way to make printf better (maybe fork one of the existing Rust implementations) 17 | - Implement more of the Macintosh Toolbox(tm) 18 | - Add MacBinary writing so you can save files on Windows 19 | - Maybe support AppleDouble as well? 20 | - Do something more elegant for CR-LF conversion 21 | - Test whether `#include`ing files works 22 | - Get more MPW executables working 23 | - Investigate why some of them aren't PEF files (are these XCOFF?) 24 | -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt, time::SystemTime}; 2 | use binread::BinRead; 3 | use chrono::{prelude::*, Duration}; 4 | 5 | pub fn lf_to_cr(buffer: &mut [u8]) { 6 | for ch in buffer { 7 | if *ch == b'\n' { 8 | *ch = b'\r'; 9 | } 10 | } 11 | } 12 | 13 | fn get_mac_epoch() -> DateTime { 14 | Local.ymd(1904, 1, 1).and_hms(0, 0, 0) 15 | } 16 | 17 | pub fn parse_mac_time(time: u32) -> DateTime { 18 | get_mac_epoch() + Duration::seconds(time.into()) 19 | } 20 | 21 | pub fn get_mac_time(dt: DateTime) -> u32 { 22 | (dt - get_mac_epoch()).num_seconds() as u32 23 | } 24 | 25 | pub fn system_time_to_mac_time(st: SystemTime) -> u32 { 26 | if let Ok(diff) = st.duration_since(SystemTime::UNIX_EPOCH) { 27 | (diff.as_secs() + 2082844800) as u32 28 | } else { 29 | 0 30 | } 31 | } 32 | 33 | #[derive(BinRead, Clone, Copy, Hash, PartialEq, Eq)] 34 | pub struct FourCC(pub u32); 35 | impl fmt::Debug for FourCC { 36 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 | write!( 38 | f, "{:08x} ({}{}{}{})", 39 | self.0, 40 | ((self.0 & 0xFF000000) >> 24) as u8 as char, 41 | ((self.0 & 0x00FF0000) >> 16) as u8 as char, 42 | ((self.0 & 0x0000FF00) >> 8) as u8 as char, 43 | (self.0 & 0x000000FF) as u8 as char, 44 | ) 45 | } 46 | } 47 | 48 | pub const fn four_cc(what: [u8; 4]) -> FourCC { 49 | FourCC(((what[0] as u32) << 24) | ((what[1] as u32) << 16) | ((what[2] as u32) << 8) | (what[3] as u32)) 50 | } 51 | 52 | #[derive(Clone, Copy, PartialEq, Eq)] 53 | #[repr(i16)] 54 | #[allow(dead_code)] 55 | pub enum OSErr { 56 | NoError = 0, 57 | NoSuchVolume = -35, 58 | IOError = -36, 59 | BadName = -37, 60 | Eof = -39, 61 | Position = -40, 62 | FileNotFound = -43, 63 | FileLocked = -45, 64 | FileBusy = -47, 65 | DuplicateFilename = -48, 66 | Param = -50, 67 | RefNum = -51, 68 | NotEnoughMemory = -108, 69 | NilHandle = -109, 70 | DirNotFound = -120, 71 | ResNotFound = -192, 72 | ResFileNotFound = -193, 73 | AddResFailed = -194, 74 | MapRead = -199, 75 | GestaltUndefSelector = -5551 76 | } 77 | 78 | impl OSErr { 79 | pub fn to_u32(self) -> u32 { 80 | self as i16 as i32 as u32 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/emulator/c_ctype.rs: -------------------------------------------------------------------------------- 1 | use crate::mac_roman; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, UcResult, helpers::{ArgReader, UnicornExtras}}; 4 | 5 | const CTYPE_DATA: [u8; 256] = [ 6 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x28, 0x28, 0x28, 0x28, 0x28, 0x20, 0x20, 7 | 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 8 | 0x48, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 9 | 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 10 | 0x10, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 11 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x10, 0x10, 0x10, 0x10, 0x10, 12 | 0x10, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 13 | 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x10, 0x10, 0x10, 0x10, 0x20, 14 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 15 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 18 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 20 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 22 | ]; 23 | 24 | fn tolower(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 25 | let ch: u8 = reader.read1(uc)?; 26 | Ok(Some(mac_roman::to_lower(ch).into())) 27 | } 28 | 29 | fn toupper(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 30 | let ch: u8 = reader.read1(uc)?; 31 | Ok(Some(mac_roman::to_upper(ch).into())) 32 | } 33 | 34 | pub(super) fn install_shims(uc: &mut EmuUC, state: &mut EmuState) -> UcResult<()> { 35 | state.install_shim_function("tolower", tolower); 36 | state.install_shim_function("toupper", toupper); 37 | 38 | if let Some(p_ctype) = state.get_shim_addr(uc, "__p_CType")? { 39 | debug!(target: "c_ctype", "__p_CType ptr is at: {p_ctype:08X}"); 40 | 41 | // The linker gives us 1024 bytes of free space in each symbol, 42 | // so let's take advantage of this... 43 | let ctype = p_ctype + 4; 44 | uc.write_u32(p_ctype, ctype)?; 45 | uc.mem_write(ctype.into(), &CTYPE_DATA)?; 46 | } 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /src/emulator/c_fenv.rs: -------------------------------------------------------------------------------- 1 | use unicorn_engine::RegisterPPC; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, helpers::ArgReader}; 4 | 5 | fn feclearexcept(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 6 | let excepts: u32 = reader.read1(uc)?; 7 | let mut reg = uc.reg_read(RegisterPPC::FPSCR)?; 8 | reg &= !(excepts as u64); 9 | uc.reg_write(RegisterPPC::FPSCR, reg)?; 10 | Ok(Some(0)) 11 | } 12 | 13 | fn fetestexcept(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 14 | let excepts: u32 = reader.read1(uc)?; 15 | let mut reg = uc.reg_read(RegisterPPC::FPSCR)?; 16 | reg &= excepts as u64; 17 | Ok(Some(reg as u32)) 18 | } 19 | 20 | pub(super) fn install_shims(state: &mut EmuState) { 21 | state.install_shim_function("feclearexcept", feclearexcept); 22 | state.install_shim_function("fetestexcept", fetestexcept); 23 | } 24 | -------------------------------------------------------------------------------- /src/emulator/c_stdio.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, io::{Write, Read}, rc::Rc, cell::RefCell}; 2 | 3 | use crate::{mac_roman, filesystem::MacFile}; 4 | 5 | use super::{EmuState, EmuUC, FuncResult, UcResult, helpers::{ArgReader, UnicornExtras}}; 6 | 7 | pub(super) struct FileHandle { 8 | file: Rc>, 9 | position: usize 10 | } 11 | 12 | pub(super) enum CFile { 13 | StdIn, 14 | StdOut, 15 | StdErr, 16 | File(FileHandle) 17 | } 18 | 19 | impl CFile { 20 | pub(super) fn is_terminal(&self) -> bool { 21 | match self { 22 | CFile::StdIn | CFile::StdOut | CFile::StdErr => true, 23 | _ => false 24 | } 25 | } 26 | 27 | pub(super) fn generic_read(&mut self, buffer: &mut [u8]) -> u32 { 28 | let read_result = match self { 29 | CFile::StdIn => std::io::stdin().read(buffer), 30 | CFile::StdOut | CFile::StdErr => return 0, 31 | CFile::File(handle) => { 32 | let file = handle.file.borrow(); 33 | let current_pos = handle.position; 34 | let new_pos = (handle.position + buffer.len()).min(file.data_fork.len()); 35 | buffer[0..new_pos - current_pos].copy_from_slice(&file.data_fork[current_pos..new_pos]); 36 | handle.position = new_pos; 37 | return (new_pos - current_pos) as u32; 38 | } 39 | }; 40 | 41 | match read_result { 42 | Ok(amount) => amount as u32, 43 | Err(e) => { 44 | error!(target: "stdio", "failed to read from file: {e:?}"); 45 | 0 46 | } 47 | } 48 | } 49 | 50 | pub(super) fn generic_write(&mut self, buffer: &[u8]) -> u32 { 51 | let write_result = match self { 52 | CFile::StdIn => return 0, 53 | CFile::StdOut => std::io::stdout().write(buffer), 54 | CFile::StdErr => std::io::stderr().write(buffer), 55 | CFile::File(handle) => { 56 | let mut file = handle.file.borrow_mut(); 57 | let current_pos = handle.position; 58 | let new_pos = handle.position + buffer.len(); 59 | if new_pos > file.data_fork.len() { 60 | file.data_fork.resize(new_pos, 0); 61 | } 62 | file.data_fork[current_pos..new_pos].copy_from_slice(buffer); 63 | handle.position = new_pos; 64 | return buffer.len() as u32; 65 | } 66 | }; 67 | 68 | match write_result { 69 | Ok(amount) => amount as u32, 70 | Err(e) => { 71 | error!(target: "stdio", "failed to write to file: {e:?}"); 72 | 0 73 | } 74 | } 75 | } 76 | 77 | pub(super) fn tell(&mut self) -> u32 { 78 | match self { 79 | CFile::File(handle) => { 80 | handle.position as u32 81 | }, 82 | _ => { 83 | warn!(target: "stdio", "running ftell() on stdin, stdout or stderr"); 84 | 0xFFFFFFFF 85 | } 86 | } 87 | } 88 | } 89 | 90 | #[allow(unused_variables)] 91 | #[allow(unused_assignments)] 92 | fn internal_printf(uc: &EmuUC, format: &[u8], arg_reader: &mut ArgReader) -> UcResult> { 93 | let mut output = Vec::new(); 94 | let mut iter = format.iter(); 95 | 96 | loop { 97 | let mut ch = match iter.next() { 98 | Some(c) => *c, None => break 99 | }; 100 | 101 | if ch != b'%' { 102 | output.push(ch); 103 | continue; 104 | } 105 | 106 | let mut alternate = false; 107 | let mut zero_pad = false; 108 | let mut negative = false; 109 | let mut blank = false; 110 | let mut plus = false; 111 | 112 | ch = *iter.next().unwrap_or(&0); 113 | if ch == b'%' { 114 | output.push(ch); 115 | continue; 116 | } 117 | 118 | // part 1: flags 119 | loop { 120 | match ch { 121 | b'#' => alternate = true, 122 | b'0' => zero_pad = true, 123 | b'-' => negative = true, 124 | b' ' => blank = true, 125 | b'+' => plus = true, 126 | _ => break 127 | } 128 | ch = *iter.next().unwrap_or(&0); 129 | } 130 | 131 | // part 2: minimum width 132 | let mut min_width = None; 133 | if ch == b'*' { 134 | min_width = Some(arg_reader.read1(uc)?); 135 | ch = *iter.next().unwrap_or(&0); 136 | } else { 137 | while (b'1'..b'9').contains(&ch) || (ch == b'0' && min_width.is_some()) { 138 | let digit = (ch - b'0') as i32; 139 | min_width = Some(min_width.unwrap_or(0) * 10 + digit); 140 | 141 | ch = *iter.next().unwrap_or(&0); 142 | } 143 | } 144 | 145 | // part 3: precision 146 | let mut precision = None; 147 | if ch == b'.' { 148 | precision = Some(0); 149 | ch = *iter.next().unwrap_or(&0); 150 | 151 | if ch == b'*' { 152 | precision = Some(arg_reader.read1(uc)?); 153 | ch = *iter.next().unwrap_or(&0); 154 | } else { 155 | while (b'0'..b'9').contains(&ch) { 156 | let digit = (ch - b'0') as i32; 157 | precision = Some(precision.unwrap() * 10 + digit); 158 | ch = *iter.next().unwrap_or(&0); 159 | } 160 | } 161 | } 162 | 163 | // part 4: modifiers 164 | let mut modifier = None; 165 | if ch == b'h' || ch == b'l' || ch == b'j' || ch == b't' || ch == b'z' { 166 | modifier = Some(ch); 167 | ch = *iter.next().unwrap_or(&0); 168 | } 169 | 170 | // double h or l? 171 | let mut double_modifier = false; 172 | if (ch == b'h' || ch == b'l') && modifier == Some(ch) { 173 | double_modifier = true; 174 | ch = *iter.next().unwrap_or(&0); 175 | } 176 | 177 | // finally produce the actual thing 178 | let what = match ch { 179 | b's' => { 180 | let addr: u32 = arg_reader.read1(uc)?; 181 | let mut inner_string = if addr == 0 { 182 | b"(null)".to_vec() 183 | } else { 184 | uc.read_c_string(addr)?.into_bytes() 185 | }; 186 | if let Some(prec) = precision { 187 | inner_string.truncate(prec as usize); 188 | } 189 | inner_string 190 | } 191 | b'd' => { 192 | let num: i32 = arg_reader.read1(uc)?; 193 | format!("{}", num).into_bytes() 194 | } 195 | b'X' => { 196 | let num: u32 = arg_reader.read1(uc)?; 197 | format!("{:01$X}", num, precision.unwrap_or(0) as usize).into_bytes() 198 | } 199 | _ => { 200 | error!(target: "stdio", "Unimplemented format character: {}", ch as char); 201 | format!("?{}", ch as char).into_bytes() 202 | } 203 | }; 204 | 205 | let what_len = what.len() as isize; 206 | let min_width = min_width.unwrap_or(0) as isize; 207 | let padding = min_width.max(what_len) - what_len; 208 | if !negative { 209 | for _ in 0..padding { 210 | output.push(if zero_pad { b'0' } else { b' ' }); 211 | } 212 | } 213 | output.extend(what); 214 | if negative { 215 | for _ in 0..padding { 216 | output.push(b' '); 217 | } 218 | } 219 | } 220 | 221 | Ok(output) 222 | } 223 | 224 | fn setvbuf(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 225 | let (file, buf_ptr, mode, size): (u32, u32, i32, u32) = reader.read4(uc)?; 226 | trace!(target: "stdio", "setvbuf(file={file:08X}, buf={buf_ptr:08x}, mode={mode}, size={size}"); 227 | Ok(Some(0)) // pretend we did something. (we didn't) 228 | } 229 | 230 | fn fclose(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 231 | let file: u32 = reader.read1(uc)?; 232 | 233 | if state.stdio_files.contains_key(&file) { 234 | state.stdio_files.remove(&file); 235 | state.heap.dispose_ptr(uc, file)?; 236 | Ok(Some(0)) 237 | } else { 238 | warn!(target: "stdio", "fclose() on invalid file {file:08X}"); 239 | // TODO: this should be EOF, check what it is in MSL 240 | Ok(Some(0xFFFFFFFF)) 241 | } 242 | } 243 | 244 | fn fflush(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 245 | let file: u32 = reader.read1(uc)?; 246 | 247 | if state.stdio_files.contains_key(&file) { 248 | // pretend we did something 249 | Ok(Some(0)) 250 | } else { 251 | warn!(target: "stdio", "fflush() on invalid file {file:08X}"); 252 | // TODO: this should be EOF, check what it is in MSL 253 | Ok(Some(0xFFFFFFFF)) 254 | } 255 | } 256 | 257 | fn fopen(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 258 | let (name, mode): (CString, CString) = reader.read2(uc)?; 259 | 260 | trace!(target: "stdio", "fopen({name:?}, {mode:?})"); 261 | 262 | let path = match state.filesystem.resolve_path(0, 0, name.as_bytes()) { 263 | Ok(p) => p, 264 | Err(e) => { 265 | error!(target: "stdio", "fopen failed to resolve path {name:?}: {e:?}"); 266 | return Ok(Some(0)); 267 | } 268 | }; 269 | 270 | let file = match state.filesystem.get_file(&path) { 271 | Ok(f) => f, 272 | Err(e) => { 273 | error!(target: "stdio", "fopen failed to get file {name:?}: {e:?}"); 274 | return Ok(Some(0)); 275 | } 276 | }; 277 | 278 | let ptr = state.heap.new_ptr(uc, 0x18)?; 279 | // set _cnt to a negative value so getc() and putc() will always call a hooked function 280 | uc.write_u32(ptr, 0xFFFFFFFE)?; 281 | state.stdio_files.insert(ptr, CFile::File(FileHandle { file, position: 0 })); 282 | 283 | Ok(Some(ptr)) 284 | } 285 | 286 | fn fprintf(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 287 | let (file, format): (u32, CString) = reader.read2(uc)?; 288 | trace!(target: "stdio", "fprintf({file:08X}, {format:?}, ...)"); 289 | let output = internal_printf(uc, format.as_bytes(), reader)?; 290 | 291 | match state.stdio_files.get_mut(&file) { 292 | Some(f) => { 293 | if f.is_terminal() { 294 | Ok(Some(f.generic_write(&mac_roman::decode_buffer(&output, true)))) 295 | } else { 296 | Ok(Some(f.generic_write(&output))) 297 | } 298 | } 299 | None => { 300 | warn!(target: "stdio", "fprintf() is writing to invalid file {file:08X}"); 301 | // set errno later? 302 | Ok(Some(0)) 303 | } 304 | } 305 | } 306 | 307 | fn printf(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 308 | let format: CString = reader.read1(uc)?; 309 | trace!(target: "stdio", "printf({format:?}, ...)"); 310 | let output = internal_printf(uc, format.as_bytes(), reader)?; 311 | Ok(Some(CFile::StdOut.generic_write(&mac_roman::decode_buffer(&output, true)))) 312 | } 313 | 314 | fn sprintf(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 315 | let (buffer, format): (u32, CString) = reader.read2(uc)?; 316 | trace!(target: "stdio", "sprintf({buffer:08X}, {format:?}, ...)"); 317 | let output = internal_printf(uc, format.as_bytes(), reader)?; 318 | 319 | uc.write_c_string(buffer, &output)?; 320 | Ok(Some(output.len() as u32)) 321 | } 322 | 323 | fn vfprintf(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 324 | let (file, format, va_list): (u32, CString, u32) = reader.read3(uc)?; 325 | trace!(target: "stdio", "vfprintf({file:08X}, {format:?}, va_list={va_list:08X})"); 326 | let mut va_reader = ArgReader::new_with_va_list(va_list); 327 | let output = internal_printf(uc, format.as_bytes(), &mut va_reader)?; 328 | 329 | match state.stdio_files.get_mut(&file) { 330 | Some(f) => { 331 | if f.is_terminal() { 332 | Ok(Some(f.generic_write(&mac_roman::decode_buffer(&output, true)))) 333 | } else { 334 | Ok(Some(f.generic_write(&output))) 335 | } 336 | } 337 | None => { 338 | warn!(target: "stdio", "vfprintf() is writing to invalid file {file:08X}"); 339 | // set errno later? 340 | Ok(Some(0)) 341 | } 342 | } 343 | } 344 | 345 | fn fgets(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 346 | let (ptr, max_size, file): (u32, u32, u32) = reader.read3(uc)?; 347 | 348 | match state.stdio_files.get_mut(&file) { 349 | Some(f) => { 350 | let mut buffer = Vec::new(); 351 | let mut byte = [0u8]; 352 | 353 | while buffer.len() < (max_size - 1) as usize { 354 | if f.generic_read(&mut byte) == 0 { 355 | break; 356 | } 357 | buffer.push(byte[0]); 358 | if byte[0] == b'\r' || byte[0] == b'\n' { 359 | break; 360 | } 361 | } 362 | 363 | if buffer.len() == 0 { 364 | trace!(target: "stdio", "fgets() reached end"); 365 | Ok(Some(0)) 366 | } else { 367 | // do we need to handle MacRoman here? probably 368 | uc.write_c_string(ptr, &buffer)?; 369 | 370 | let s = CString::new(buffer).unwrap(); 371 | trace!(target: "stdio", "fgets() returned: [{s:?}]"); 372 | 373 | Ok(Some(ptr)) 374 | } 375 | } 376 | None => { 377 | warn!(target: "stdio", "fgets() is getting from invalid file {file:08X}"); 378 | // set errno later? 379 | Ok(Some(0)) 380 | } 381 | } 382 | } 383 | 384 | fn fputs(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 385 | let (s, file): (CString, u32) = reader.read2(uc)?; 386 | let output = s.into_bytes(); 387 | 388 | match state.stdio_files.get_mut(&file) { 389 | Some(f) => { 390 | if f.is_terminal() { 391 | Ok(Some(f.generic_write(&mac_roman::decode_buffer(&output, true)))) 392 | } else { 393 | Ok(Some(f.generic_write(&output))) 394 | } 395 | } 396 | None => { 397 | warn!(target: "stdio", "fputs() is writing to invalid file {file:08X}"); 398 | // set errno later? 399 | Ok(Some(0)) 400 | } 401 | } 402 | } 403 | 404 | fn fwrite(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 405 | let (ptr, size, count, file): (u32, u32, u32, u32) = reader.read4(uc)?; 406 | let output = uc.mem_read_as_vec(ptr.into(), (size * count) as usize)?; 407 | 408 | match state.stdio_files.get_mut(&file) { 409 | Some(f) => { 410 | if f.is_terminal() { 411 | Ok(Some(f.generic_write(&mac_roman::decode_buffer(&output, true)) / size)) 412 | } else { 413 | Ok(Some(f.generic_write(&output) / size)) 414 | } 415 | } 416 | None => { 417 | warn!(target: "stdio", "fwrite() is writing to invalid file {file:08X}"); 418 | // set errno later? 419 | Ok(Some(0)) 420 | } 421 | } 422 | } 423 | 424 | fn ftell(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 425 | let file: u32 = reader.read1(uc)?; 426 | 427 | match state.stdio_files.get_mut(&file) { 428 | Some(f) => { 429 | Ok(Some(f.tell())) 430 | } 431 | None => { 432 | warn!(target: "stdio", "ftell() is telling from invalid file {file:08X}"); 433 | // set errno later? 434 | Ok(Some(0xFFFFFFFF)) 435 | } 436 | } 437 | } 438 | 439 | fn putchar(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 440 | let ch: u8 = reader.read1(uc)?; 441 | print!("{}", mac_roman::decode_char(ch, true)); 442 | Ok(Some(ch.into())) 443 | } 444 | 445 | fn filbuf(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 446 | let file_ptr: u32 = reader.read1(uc)?; 447 | 448 | // set _cnt to a negative value so getc() and putc() will always call a hooked function 449 | uc.write_u32(file_ptr, 0xFFFFFFFE)?; 450 | 451 | // get a singular byte 452 | let mut byte = [0u8]; 453 | if let Some(file) = state.stdio_files.get_mut(&file_ptr) { 454 | if file.generic_read(&mut byte) == 1 { 455 | Ok(Some(byte[0] as u32)) 456 | } else { 457 | Ok(Some(0xFFFFFFFF)) 458 | } 459 | } else { 460 | Ok(Some(0)) 461 | } 462 | } 463 | 464 | fn flsbuf(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 465 | let (ch, file_ptr): (u8, u32) = reader.read2(uc)?; 466 | 467 | // set _cnt to a negative value so getc() and putc() will always call a hooked function 468 | uc.write_u32(file_ptr, 0xFFFFFFFE)?; 469 | 470 | // write a singular byte 471 | let byte = [ch]; 472 | if let Some(file) = state.stdio_files.get_mut(&file_ptr) { 473 | if file.generic_write(&byte) == 1 { 474 | return Ok(Some(byte[0] as u32)); 475 | } 476 | } 477 | 478 | Ok(Some(0xFFFFFFFF)) 479 | } 480 | 481 | pub(super) fn install_shims(uc: &mut EmuUC, state: &mut EmuState) -> UcResult<()> { 482 | if let Some(iob) = state.get_shim_addr(uc, "_iob")? { 483 | state.stdio_files.insert(iob, CFile::StdIn); 484 | state.stdio_files.insert(iob + 0x18, CFile::StdOut); 485 | state.stdio_files.insert(iob + 0x30, CFile::StdErr); 486 | 487 | // for stuff using write(), etc 488 | state.stdio_files.insert(0, CFile::StdIn); 489 | state.stdio_files.insert(1, CFile::StdOut); 490 | state.stdio_files.insert(2, CFile::StdErr); 491 | } 492 | 493 | // remove 494 | // rename 495 | // tmpnam 496 | // tmpfile 497 | // setbuf 498 | state.install_shim_function("setvbuf", setvbuf); 499 | state.install_shim_function("fclose", fclose); 500 | state.install_shim_function("fflush", fflush); 501 | state.install_shim_function("fopen", fopen); 502 | // freopen 503 | state.install_shim_function("fprintf", fprintf); 504 | // fscanf 505 | state.install_shim_function("printf", printf); 506 | // scanf 507 | state.install_shim_function("sprintf", sprintf); 508 | // sscanf 509 | state.install_shim_function("vfprintf", vfprintf); 510 | // vprintf 511 | // vsprintf 512 | // fgetc 513 | state.install_shim_function("fgets", fgets); 514 | // fputc 515 | state.install_shim_function("fputs", fputs); 516 | // gets 517 | // puts 518 | // ungetc 519 | // fread 520 | state.install_shim_function("fwrite", fwrite); 521 | // fgetpos 522 | state.install_shim_function("ftell", ftell); 523 | // fsetpos 524 | // fseek 525 | // rewind 526 | // clearerr 527 | // perror 528 | // getc 529 | // putc 530 | // getchar 531 | state.install_shim_function("putchar", putchar); 532 | // feof 533 | // ferror 534 | 535 | state.install_shim_function("_filbuf", filbuf); 536 | state.install_shim_function("_flsbuf", flsbuf); 537 | 538 | Ok(()) 539 | } 540 | -------------------------------------------------------------------------------- /src/emulator/c_stdlib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use unicorn_engine::RegisterPPC; 4 | 5 | use super::{EmuState, EmuUC, FuncResult, UcResult, helpers::{ArgReader, UnicornExtras}}; 6 | 7 | const QSORT_CODE: &[u32] = &[ 8 | // Offset 0 9 | 0x7C0802A6, // mflr r0 10 | 0x90010008, // stw r0,8(SP) 11 | 0x9421FFC0, // stwu SP,-64(SP) 12 | 0x7C882378, // mr r8,r4 13 | 0x7CA42B78, // mr r4,r5 14 | 0x7CC73378, // mr r7,r6 15 | 0x38C8FFFF, // subi r6,r8,1 16 | 0x38A00000, // li r5,0 17 | 0x48000015, // bl ._qsort (+0x14) 18 | 0x80010048, // lwz r0,72(SP) 19 | 0x38210040, // addi SP,SP,64 20 | 0x7C0803A6, // mtlr r0 21 | 0x4E800020, // blr 22 | // Offset 0x34 23 | 0x7C0802A6, // mflr r0 24 | 0xBEE1FFDC, // stmw r23,-36(SP) 25 | 0x90010008, // stw r0,8(SP) 26 | 0x9421FFA0, // stwu SP,-96(SP) 27 | 0x7C7B1B78, // mr r27,r3 28 | 0x7C9C2378, // mr r28,r4 29 | 0x7CBD2B78, // mr r29,r5 30 | 0x7CDE3378, // mr r30,r6 31 | 0x7CFF3B78, // mr r31,r7 32 | 0x2C1D0000, // cmpwi r29,0 33 | 0x3B1DFFFF, // subi r24,r29,1 34 | 0x418000E8, // blt *+232 35 | 0x7C1DF000, // cmpw r29,r30 36 | 0x408000E0, // bge *+224 37 | 0x7FB7EB78, // mr r23,r29 38 | 0x7F5EE1D6, // mullw r26,r30,r28 39 | 0x48000060, // b *+96 40 | 0x7F37E1D6, // mullw r25,r23,r28 41 | 0x7FECFB78, // mr r12,r31 42 | 0x7C7BCA14, // add r3,r27,r25 43 | 0x7C9BD214, // add r4,r27,r26 44 | 0x480000D5, // bl .__ptr_glue (+0xD4) 45 | 0x80410014, // lwz r2,20(SP) 46 | 0x2C030000, // cmpwi r3,0 47 | 0x4181003C, // bgt *+60 48 | 0x3B180001, // addi r24,r24,1 49 | 0x7C18E1D6, // mullw r0,r24,r28 50 | 0x7CDB0214, // add r6,r27,r0 51 | 0x7CBBCA14, // add r5,r27,r25 52 | 0x38600000, // li r3,0 53 | 0x7F8903A6, // mtctr r28 54 | 0x281C0000, // cmplwi r28,$0000 55 | 0x4081001C, // ble *+28 56 | 0x7C8618AE, // lbzx r4,r6,r3 57 | 0x7C0518AE, // lbzx r0,r5,r3 58 | 0x7C0619AE, // stbx r0,r6,r3 59 | 0x7C8519AE, // stbx r4,r5,r3 60 | 0x38630001, // addi r3,r3,1 61 | 0x4200FFEC, // bdnz *-20 62 | 0x3AF70001, // addi r23,r23,1 63 | 0x7C17F040, // cmplw r23,r30 64 | 0x4180FFA0, // blt *-96 65 | 0x3B180001, // addi r24,r24,1 66 | 0x7C78E1D6, // mullw r3,r24,r28 67 | 0x7C1EE1D6, // mullw r0,r30,r28 68 | 0x7CDB1A14, // add r6,r27,r3 69 | 0x7CBB0214, // add r5,r27,r0 70 | 0x38600000, // li r3,0 71 | 0x7F8903A6, // mtctr r28 72 | 0x281C0000, // cmplwi r28,$0000 73 | 0x4081001C, // ble *+28 74 | 0x7C8618AE, // lbzx r4,r6,r3 75 | 0x7C0518AE, // lbzx r0,r5,r3 76 | 0x7C0619AE, // stbx r0,r6,r3 77 | 0x7C8519AE, // stbx r4,r5,r3 78 | 0x38630001, // addi r3,r3,1 79 | 0x4200FFEC, // bdnz *-20 80 | 0x7F63DB78, // mr r3,r27 81 | 0x7F84E378, // mr r4,r28 82 | 0x7FA5EB78, // mr r5,r29 83 | 0x7FE7FB78, // mr r7,r31 84 | 0x38D8FFFF, // subi r6,r24,1 85 | 0x4BFFFF09, // bl ._qsort (-0xF8) 86 | 0x7F63DB78, // mr r3,r27 87 | 0x7F84E378, // mr r4,r28 88 | 0x7FC6F378, // mr r6,r30 89 | 0x7FE7FB78, // mr r7,r31 90 | 0x38B80001, // addi r5,r24,1 91 | 0x4BFFFEF1, // bl ._qsort (-0x110) 92 | 0x80010068, // lwz r0,104(SP) 93 | 0x38210060, // addi SP,SP,96 94 | 0x7C0803A6, // mtlr r0 95 | 0xBAE1FFDC, // lmw r23,-36(SP) 96 | 0x4E800020, // blr 97 | // Offset 0x15C 98 | 0x800C0000, // lwz r0,0(r12) 99 | 0x90410014, // stw r2,20(SP) 100 | 0x7C0903A6, // mtctr r0 101 | 0x804C0004, // lwz r2,0(r12) 102 | 0x4E800420, // bctr 103 | ]; 104 | 105 | fn atoi(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 106 | let s: CString = reader.read1(uc)?; 107 | if let Ok(s) = s.into_string() { 108 | if let Ok(num) = s.parse::() { 109 | return Ok(Some(num as u32)); 110 | } 111 | } 112 | Ok(Some(0)) 113 | } 114 | 115 | fn is_digit_for_base(ch: u8, base: u32) -> bool { 116 | if base > 10 { 117 | let max_upper = b'A' + (base as u8) - 11; 118 | let max_lower = b'a' + (base as u8) - 11; 119 | if ch >= b'0' && ch <= b'9' { return true; } 120 | if ch >= b'A' && ch <= max_upper { return true; } 121 | if ch >= b'a' && ch <= max_lower { return true; } 122 | } else { 123 | let max_digit = b'0' + (base as u8) - 1; 124 | if ch >= b'0' && ch <= max_digit { return true; } 125 | } 126 | 127 | false 128 | } 129 | 130 | fn digit_to_num(ch: u8) -> u8 { 131 | match ch { 132 | b'0' ..= b'9' => (ch - b'0'), 133 | b'A' ..= b'Z' => (ch - b'A' + 11), 134 | b'a' ..= b'z' => (ch - b'a' + 11), 135 | _ => 0 136 | } 137 | } 138 | 139 | fn strtol(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 140 | let (str_ptr, end_ptr, mut base): (u32, u32, u32) = reader.read3(uc)?; 141 | let s = uc.read_c_string(str_ptr)?; 142 | let s = s.as_bytes(); 143 | let mut i = 0; 144 | let mut num = 0i64; 145 | let mut negative = false; 146 | 147 | while i < s.len() && s[i].is_ascii_whitespace() { 148 | i += 1; 149 | } 150 | 151 | if i < s.len() { 152 | if s[i] == b'-' { 153 | negative = true; 154 | i += 1; 155 | } else if s[i] == b'+' { 156 | i += 1; 157 | } 158 | } 159 | 160 | if (base == 0 || base == 16) && (s[i..].starts_with(b"0x") || s[i..].starts_with(b"0X")) { 161 | base = 16; 162 | i += 2; 163 | } else if (base == 0) && (s[i..].starts_with(b"0")) { 164 | base = 8; 165 | } 166 | 167 | while i < s.len() && is_digit_for_base(s[i], base) { 168 | num = num.wrapping_mul(base.into()).wrapping_add(digit_to_num(s[i]).into()); 169 | } 170 | 171 | if negative { 172 | num = -num; 173 | } 174 | 175 | if end_ptr != 0 { 176 | uc.write_u32(end_ptr, str_ptr + i as u32)?; 177 | } 178 | 179 | Ok(Some(num as u32)) 180 | } 181 | 182 | fn calloc(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 183 | let (count, size): (u32, u32) = reader.read2(uc)?; 184 | let ptr = state.heap.new_ptr(uc, count * size)?; 185 | Ok(Some(ptr)) 186 | } 187 | 188 | fn free(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 189 | let ptr: u32 = reader.read1(uc)?; 190 | if ptr != 0 { 191 | state.heap.dispose_ptr(uc, ptr)?; 192 | } 193 | Ok(None) 194 | } 195 | 196 | fn malloc(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 197 | let size: u32 = reader.read1(uc)?; 198 | let ptr = state.heap.new_ptr(uc, size)?; 199 | Ok(Some(ptr)) 200 | } 201 | 202 | fn realloc(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 203 | let (ptr, new_size): (u32, u32) = reader.read2(uc)?; 204 | if ptr != 0 { 205 | if state.heap.set_ptr_size(uc, ptr, new_size)? { 206 | // resized successfully 207 | Ok(Some(ptr)) 208 | } else { 209 | // no dice, we need to allocate a new buffer 210 | let new_ptr = state.heap.new_ptr(uc, new_size)?; 211 | if new_ptr != 0 { 212 | let old_size = state.heap.get_ptr_size(uc, ptr)?; 213 | let to_copy = old_size.min(new_size); 214 | for i in 0..to_copy { 215 | uc.write_u8(new_ptr + i, uc.read_u8(ptr + i)?)?; 216 | } 217 | state.heap.dispose_ptr(uc, ptr)?; 218 | Ok(Some(new_ptr)) 219 | } else { 220 | // failed 221 | Ok(Some(0)) 222 | } 223 | } 224 | } else { 225 | let ptr = state.heap.new_ptr(uc, new_size)?; 226 | Ok(Some(ptr)) 227 | } 228 | } 229 | 230 | fn atexit(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 231 | let _func: u32 = reader.read1(uc)?; 232 | // Not implemented, but we pretend it is to satisfy the program 233 | Ok(Some(0)) 234 | } 235 | 236 | fn exit(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 237 | let status: i32 = reader.read1(uc)?; 238 | 239 | info!(target: "stdlib", "exit({status})"); 240 | state.exit_status = Some(status); 241 | uc.emu_stop()?; 242 | 243 | Ok(None) 244 | } 245 | 246 | fn getenv(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 247 | let name: CString = reader.read1(uc)?; 248 | if let Ok(name) = name.into_string() { 249 | if let Some(ptr) = state.env_var_map.get(&name) { 250 | return Ok(Some(*ptr)); 251 | } 252 | } 253 | 254 | Ok(Some(0)) 255 | } 256 | 257 | fn abs(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 258 | let value: u32 = reader.read1(uc)?; 259 | Ok(Some((value as i32).unsigned_abs())) 260 | } 261 | 262 | fn signal(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 263 | let (sig, func): (i32, u32) = reader.read2(uc)?; 264 | 265 | trace!(target: "stdlib", "signal({sig}, {func:08X})"); 266 | 267 | // MSL defines SIG_DFL as 0, SIG_IGN as 1 and SIG_ERR as -1 268 | 269 | Ok(Some(0)) 270 | } 271 | 272 | fn setjmp(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 273 | let env: u32 = reader.read1(uc)?; 274 | 275 | trace!(target: "stdlib", "setjmp(env={env:08X})"); 276 | 277 | // hateful, absolutely hateful 278 | uc.write_u32(env, uc.reg_read(RegisterPPC::LR)? as u32)?; 279 | uc.write_u32(env + 4, uc.reg_read(RegisterPPC::CR)? as u32)?; 280 | uc.write_u32(env + 8, uc.reg_read(RegisterPPC::R1)? as u32)?; 281 | uc.write_u32(env + 12, uc.reg_read(RegisterPPC::R2)? as u32)?; 282 | 283 | for i in 0..19 { 284 | uc.write_u32(env + 20 + (i as u32) * 4, uc.reg_read(RegisterPPC::R13 as i32 + i)? as u32)?; 285 | } 286 | for i in 0..18 { 287 | uc.write_u64(env + 96 + (i as u32) * 8, uc.reg_read(RegisterPPC::FPR14 as i32 + i)?)?; 288 | } 289 | 290 | uc.write_u64(env + 240, uc.reg_read(RegisterPPC::FPSCR)?)?; 291 | 292 | Ok(Some(0)) 293 | } 294 | 295 | fn longjmp(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 296 | let (env, val): (u32, i32) = reader.read2(uc)?; 297 | 298 | trace!(target: "stdlib", "longjmp(env={env:08X}, val={val})"); 299 | 300 | uc.reg_write(RegisterPPC::LR, uc.read_u32(env)? as u64)?; // LR 301 | uc.reg_write(RegisterPPC::CR, uc.read_u32(env + 4)? as u64)?; // CR 302 | uc.reg_write(RegisterPPC::R1, uc.read_u32(env + 8)? as u64)?; 303 | uc.reg_write(RegisterPPC::R2, uc.read_u32(env + 12)? as u64)?; 304 | 305 | for i in 0..19 { 306 | uc.reg_write(RegisterPPC::R13 as i32 + i, uc.read_u32(env + 20 + (i as u32) * 4)? as u64)?; 307 | } 308 | for i in 0i32..18 { 309 | uc.reg_write(RegisterPPC::FPR14 as i32 + i, uc.read_u64(env + 96 + (i as u32) * 8)?)?; 310 | } 311 | 312 | uc.reg_write(RegisterPPC::FPSCR, uc.read_u64(env + 240)?)?; 313 | 314 | Ok(Some(if val == 0 { 1 } else { val as u32 })) 315 | } 316 | 317 | pub(super) fn setup_environment(uc: &mut EmuUC, state: &mut EmuState, args: &[String], env_vars: &[(String, String)]) -> UcResult<()> { 318 | // Environment Variables 319 | for (name, value) in env_vars { 320 | let ptr = state.heap.new_ptr(uc, value.as_bytes().len() as u32 + 1)?; 321 | uc.write_c_string(ptr, value.as_bytes())?; 322 | 323 | state.env_var_map.insert(name.clone(), ptr); 324 | } 325 | 326 | // Arguments 327 | if let Some(int_env) = state.get_shim_addr(uc, "_IntEnv")? { 328 | debug!(target: "stdlib", "_IntEnv ptr is at: {int_env:08X}"); 329 | 330 | let argv = state.heap.new_ptr(uc, (args.len() * 4) as u32)?; 331 | uc.write_u32(int_env + 2, args.len() as u32)?; 332 | uc.write_u32(int_env + 6, argv)?; 333 | 334 | // _IntEnv also contains EnvP but I'm not sure what the format is for that yet 335 | 336 | for (i, arg) in args.iter().enumerate() { 337 | let arg_ptr = state.heap.new_ptr(uc, arg.as_bytes().len() as u32 + 1)?; 338 | 339 | uc.write_u32(argv + (i as u32) * 4, arg_ptr)?; 340 | uc.write_c_string(arg_ptr, arg.as_bytes())?; 341 | } 342 | } 343 | 344 | // Special setjmp 345 | if let Some(target_for_exit) = state.get_shim_addr(uc, "__target_for_exit")? { 346 | // we're ok to ignore this for now 347 | // we just need the extra space allocated at this location 348 | debug!(target: "stdlib", "__target_for_exit ptr is at {target_for_exit:08X}"); 349 | } 350 | 351 | Ok(()) 352 | } 353 | 354 | pub(super) fn install_shims(uc: &mut EmuUC, state: &mut EmuState) -> UcResult<()> { 355 | // atof 356 | state.install_shim_function("atoi", atoi); 357 | state.install_shim_function("atol", atoi); 358 | // strtod 359 | state.install_shim_function("strtol", strtol); 360 | // strtoul 361 | // strtoll (?) 362 | // strtoull (?) 363 | // rand 364 | // srand 365 | state.install_shim_function("calloc", calloc); 366 | state.install_shim_function("free", free); 367 | state.install_shim_function("malloc", malloc); 368 | state.install_shim_function("realloc", realloc); 369 | // abort 370 | state.install_shim_function("atexit", atexit); 371 | state.install_shim_function("exit", exit); 372 | state.install_shim_function("getenv", getenv); 373 | // system 374 | // bsearch 375 | state.install_shim_function("abs", abs); 376 | // labs 377 | // div 378 | // ldiv 379 | // mblen 380 | // mbtowc 381 | // wctomb 382 | // mbstowcs 383 | // wcstombs 384 | 385 | // This isn't actually in stdlib.h, but I didn't feel like creating 386 | // c_signal.rs just for one stub. Fight me. 387 | state.install_shim_function("signal", signal); 388 | 389 | // ... Same for setjmp.h. 390 | state.install_shim_function("__setjmp", setjmp); 391 | state.install_shim_function("longjmp", longjmp); 392 | 393 | // qsort() needs special handling. 394 | // We implement it in PowerPC so it can invoke a callback function 395 | if let Some(qsort) = state.get_shim_addr(uc, "qsort")? { 396 | let qsort_code = state.heap.new_ptr(uc, (QSORT_CODE.len() * 4) as u32)?; 397 | uc.write_u32(qsort, qsort_code)?; 398 | for (i, insn) in QSORT_CODE.iter().enumerate() { 399 | uc.write_u32(qsort_code + 4 * (i as u32), *insn)?; 400 | } 401 | } 402 | 403 | Ok(()) 404 | } 405 | -------------------------------------------------------------------------------- /src/emulator/c_string.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}}; 4 | 5 | fn memset(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 6 | let (ptr, byte, len): (u32, u8, u32) = reader.read3(uc)?; 7 | for i in 0..len { 8 | uc.write_u8(ptr + i, byte)?; 9 | } 10 | Ok(Some(ptr)) 11 | } 12 | 13 | fn memcmp(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 14 | let (s1, s2, len): (u32, u32, u32) = reader.read3(uc)?; 15 | for i in 0..len { 16 | let b1 = uc.read_u8(s1 + i)? as i32; 17 | let b2 = uc.read_u8(s2 + i)? as i32; 18 | if b1 != b2 { 19 | return Ok(Some((b1 - b2) as u32)); 20 | } 21 | } 22 | Ok(Some(0)) 23 | } 24 | 25 | fn memcpy(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 26 | let (dst, src, len): (u32, u32, u32) = reader.read3(uc)?; 27 | for i in 0..len { 28 | uc.write_u8(dst + i, uc.read_u8(src + i)?)?; 29 | } 30 | Ok(Some(dst)) 31 | } 32 | 33 | fn memmove(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 34 | let (dst, src, len): (u32, u32, u32) = reader.read3(uc)?; 35 | if (src + len) <= dst || src >= (dst + len) { 36 | for i in 0..len { 37 | uc.write_u8(dst + i, uc.read_u8(src + i)?)?; 38 | } 39 | } else { 40 | // backwards 41 | for i in 0..len { 42 | let offset = len - 1 - i; 43 | uc.write_u8(dst + offset, uc.read_u8(src + offset)?)?; 44 | } 45 | } 46 | Ok(Some(dst)) 47 | } 48 | 49 | fn strlen(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 50 | let addr: u32 = reader.read1(uc)?; 51 | let mut len = 0; 52 | while uc.read_u8(addr + len)? != 0 { 53 | len += 1; 54 | } 55 | Ok(Some(len)) 56 | } 57 | 58 | fn strcpy(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 59 | let (dst, src): (u32, u32) = reader.read2(uc)?; 60 | for i in 0..u32::MAX { 61 | let byte = uc.read_u8(src + i)?; 62 | uc.write_u8(dst + i, byte)?; 63 | if byte == 0 { break; } 64 | } 65 | Ok(Some(dst)) 66 | } 67 | 68 | fn strncpy(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 69 | let (dst, src, len): (u32, u32, u32) = reader.read3(uc)?; 70 | let mut reached_null = false; 71 | for i in 0..len { 72 | let byte = if reached_null { 0 } else { uc.read_u8(src + i)? }; 73 | uc.write_u8(dst + i, byte)?; 74 | reached_null = byte == 0; 75 | } 76 | Ok(Some(dst)) 77 | } 78 | 79 | fn strcat(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 80 | let (dst, src): (u32, u32) = reader.read2(uc)?; 81 | // find the length of dest 82 | let mut len = 0; 83 | while uc.read_u8(dst + len)? != 0 { 84 | len += 1; 85 | } 86 | // now copy src over 87 | for i in 0..u32::MAX { 88 | let byte = uc.read_u8(src + i)?; 89 | uc.write_u8(dst + len + i, byte)?; 90 | if byte == 0 { break; } 91 | } 92 | Ok(Some(dst)) 93 | } 94 | 95 | fn strcmp(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 96 | let (s1, s2): (u32, u32) = reader.read2(uc)?; 97 | for i in 0..u32::MAX { 98 | let b1 = uc.read_u8(s1 + i)?; 99 | let b2 = uc.read_u8(s2 + i)?; 100 | if b1 > b2 { 101 | return Ok(Some(1)); 102 | } else if b1 < b2 { 103 | return Ok(Some(0xFFFFFFFF)); 104 | } else if b1 == 0 { 105 | return Ok(Some(0)); 106 | } 107 | } 108 | unreachable!() 109 | } 110 | 111 | fn strncmp(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 112 | let (s1, s2, n): (u32, u32, u32) = reader.read3(uc)?; 113 | for i in 0..n { 114 | let b1 = uc.read_u8(s1 + i)?; 115 | let b2 = uc.read_u8(s2 + i)?; 116 | if b1 > b2 { 117 | return Ok(Some(1)); 118 | } else if b1 < b2 { 119 | return Ok(Some(0xFFFFFFFF)); 120 | } else if b1 == 0 { 121 | break; 122 | } 123 | } 124 | Ok(Some(0)) 125 | } 126 | 127 | fn strchr(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 128 | let (addr, needle): (u32, u8) = reader.read2(uc)?; 129 | for i in 0..u32::MAX { 130 | let ch = uc.read_u8(addr + i)?; 131 | if ch == needle { 132 | return Ok(Some(addr + i)); 133 | } else if ch == 0 { 134 | return Ok(Some(0)); 135 | } 136 | } 137 | unreachable!() 138 | } 139 | 140 | fn strrchr(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 141 | let (addr, needle): (u32, u8) = reader.read2(uc)?; 142 | let mut best = 0; 143 | for i in 0..u32::MAX { 144 | let ch = uc.read_u8(addr + i)?; 145 | if ch == needle { 146 | best = addr + i; 147 | } else if ch == 0 { 148 | break; 149 | } 150 | } 151 | Ok(Some(best)) 152 | } 153 | 154 | fn strspn(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 155 | let (ptr, sep): (u32, CString) = reader.read2(uc)?; 156 | if ptr == 0 { return Ok(Some(0)); } 157 | 158 | for i in 0..u32::MAX { 159 | let ch = uc.read_u8(ptr + i)?; 160 | if ch == 0 || !sep.as_bytes().contains(&ch) { 161 | return Ok(Some(i)); 162 | } 163 | } 164 | 165 | unreachable!() 166 | } 167 | 168 | fn strtok(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 169 | let (mut ptr, sep): (u32, CString) = reader.read2(uc)?; 170 | if ptr == 0 { 171 | ptr = state.strtok_state; 172 | if ptr == 0 { 173 | // no more tokens remain 174 | return Ok(Some(0)); 175 | } 176 | } 177 | 178 | // find first separator 179 | for i in 0..u32::MAX { 180 | let ch = uc.read_u8(ptr + i)?; 181 | if sep.as_bytes().contains(&ch) { 182 | // delimiter found 183 | state.strtok_state = ptr + i + 1; 184 | uc.write_u8(ptr + i, 0)?; 185 | return Ok(Some(ptr)); 186 | } else if ch == 0 { 187 | // we reached the end 188 | state.strtok_state = 0; 189 | if i == 0 { 190 | // token was empty 191 | return Ok(Some(0)); 192 | } else { 193 | return Ok(Some(ptr)); 194 | } 195 | } 196 | } 197 | 198 | unreachable!() 199 | } 200 | 201 | pub(super) fn install_shims(state: &mut EmuState) { 202 | state.install_shim_function("memset", memset); 203 | // memchr 204 | state.install_shim_function("memcmp", memcmp); 205 | state.install_shim_function("memcpy", memcpy); 206 | state.install_shim_function("memmove", memmove); 207 | 208 | state.install_shim_function("strlen", strlen); 209 | state.install_shim_function("strcpy", strcpy); 210 | state.install_shim_function("strncpy", strncpy); 211 | state.install_shim_function("strcat", strcat); 212 | // strncat 213 | state.install_shim_function("strcmp", strcmp); 214 | state.install_shim_function("strncmp", strncmp); 215 | // strcoll 216 | // strxfrm 217 | state.install_shim_function("strchr", strchr); 218 | state.install_shim_function("strrchr", strrchr); 219 | // strpbrk 220 | state.install_shim_function("strspn", strspn); 221 | // strcspn 222 | state.install_shim_function("strtok", strtok); 223 | // strstr 224 | // strerror 225 | // strcasecmp 226 | // strncasecmp 227 | // strdup 228 | } 229 | -------------------------------------------------------------------------------- /src/emulator/c_time.rs: -------------------------------------------------------------------------------- 1 | use chrono::Utc; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}}; 4 | 5 | fn time(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 6 | let time_ptr: u32 = reader.read1(uc)?; 7 | let now = Utc::now().timestamp() as u32; 8 | if time_ptr != 0 { 9 | uc.write_u32(time_ptr, now)?; 10 | } 11 | Ok(Some(now)) 12 | } 13 | 14 | pub(super) fn install_shims(state: &mut EmuState) { 15 | state.install_shim_function("time", time); 16 | } 17 | -------------------------------------------------------------------------------- /src/emulator/flex_lm.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use crate::emulator::{EmuState, EmuUC, FuncResult}; 3 | use crate::emulator::helpers::{ArgReader, UnicornExtras}; 4 | 5 | pub(super) struct Checkout { 6 | feature: CString, 7 | version: CString 8 | } 9 | 10 | fn flex_init(_uc: &mut EmuUC, _state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 11 | debug!(target: "FlexLM", "Flex_Init()"); 12 | Ok(None) 13 | } 14 | 15 | fn lp_checkout(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 16 | let (code, policy, feature, version, num_lic, path, lp): 17 | (u32, u32, CString, CString, u32, CString, u32) = reader.read7(uc)?; 18 | 19 | debug!(target: "FlexLM", "lp_checkout(code={code:08X}, policy={policy:X}, feature={feature:?}, version={version:?}, num_lic={num_lic}, path={path:?}, lp={lp:08X})"); 20 | uc.write_u32(lp, state.next_checkout)?; 21 | state.checkouts.insert(state.next_checkout, Checkout { feature, version }); 22 | state.next_checkout += 0x100; 23 | 24 | Ok(Some(0)) 25 | } 26 | 27 | fn lp_checkin(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 28 | let lp: u32 = reader.read1(uc)?; 29 | 30 | debug!(target: "FlexLM", "lp_checkin(lp={lp:08X})"); 31 | if let Some(checkout) = state.checkouts.remove(&lp) { 32 | let feature = checkout.feature; 33 | let version = checkout.version; 34 | debug!(target: "FlexLM", "feature={feature:?}, version={version:?}"); 35 | } 36 | 37 | Ok(None) 38 | } 39 | 40 | pub(super) fn install_shims(state: &mut EmuState) { 41 | state.install_shim_function("Flex_Init", flex_init); 42 | state.install_shim_function("lp_checkout", lp_checkout); 43 | state.install_shim_function("lp_checkin", lp_checkin); 44 | } 45 | -------------------------------------------------------------------------------- /src/emulator/heap.rs: -------------------------------------------------------------------------------- 1 | use super::{EmuUC, UcResult, helpers::UnicornExtras}; 2 | 3 | use bitvec::prelude::*; 4 | use unicorn_engine::unicorn_const::Permission; 5 | 6 | const FREE_FLAG: u32 = 0x80000000; 7 | const SIZE_OF_HEADER: u32 = 0x10; 8 | 9 | const HDR_USER_SIZE: u32 = 0; 10 | const HDR_BLOCK_SIZE: u32 = 4; 11 | const HDR_PREV: u32 = 8; 12 | const HDR_NEXT: u32 = 0xC; 13 | 14 | pub struct Heap { 15 | used_handles: BitVec, 16 | region_start: u32, 17 | region_size: u32, 18 | handles_start: u32, 19 | handle_count: u32, 20 | arena_start: u32, 21 | arena_size: u32, 22 | first_block: u32, 23 | last_block: u32 24 | } 25 | 26 | impl Heap { 27 | pub fn new(region_start: u32, region_size: u32, handle_count: u32) -> Heap { 28 | let handles_size = handle_count * 4; 29 | 30 | Heap { 31 | used_handles: BitVec::new(), 32 | region_start, 33 | region_size, 34 | handles_start: region_start, 35 | handle_count, 36 | arena_start: region_start + handles_size, 37 | arena_size: region_size - handles_size, 38 | first_block: 0, 39 | last_block: 0 40 | } 41 | } 42 | 43 | pub(super) fn init(&mut self, uc: &mut EmuUC) -> UcResult<()> { 44 | self.used_handles.clear(); 45 | self.used_handles.resize(self.handle_count as usize, false); 46 | 47 | uc.mem_map(self.region_start as u64, self.region_size as usize, Permission::ALL)?; 48 | 49 | // create one large block, covering the entire arena 50 | let block = self.arena_start; 51 | uc.write_u32(block + HDR_USER_SIZE, FREE_FLAG)?; 52 | uc.write_u32(block + HDR_BLOCK_SIZE, self.arena_size)?; 53 | uc.write_u32(block + HDR_PREV, 0)?; 54 | uc.write_u32(block + HDR_NEXT, 0)?; 55 | 56 | self.first_block = block; 57 | self.last_block = block; 58 | 59 | Ok(()) 60 | } 61 | 62 | fn get_handle_index_if_valid(&self, uc: &EmuUC, handle: u32) -> Option { 63 | if handle >= self.handles_start && handle < (self.handles_start + self.handle_count * 4) { 64 | if (handle & 3) == 0 { 65 | let handle_index = (handle - self.handles_start) as usize / 4; 66 | if self.used_handles[handle_index] { 67 | return Some(handle_index); 68 | } 69 | } 70 | } 71 | 72 | let lr = uc.reg_read(unicorn_engine::RegisterPPC::LR).unwrap(); 73 | error!(target: "heap", "Invalid handle {handle:08X}! LR={lr:08X}"); 74 | None 75 | } 76 | 77 | pub(super) fn new_handle(&mut self, uc: &mut EmuUC, size: u32) -> UcResult { 78 | let handle_index = match self.used_handles.first_zero() { 79 | Some(i) => i, 80 | None => { 81 | error!(target: "heap", "out of memory handles!"); 82 | return Ok(0); 83 | } 84 | }; 85 | let handle = self.handles_start + 4 * (handle_index as u32); 86 | 87 | let backing_ptr = self.new_ptr(uc, size)?; 88 | if backing_ptr == 0 { 89 | return Ok(0); 90 | } 91 | 92 | self.used_handles.set(handle_index, true); 93 | 94 | uc.write_u32(handle, backing_ptr)?; 95 | Ok(handle) 96 | } 97 | 98 | pub(super) fn dispose_handle(&mut self, uc: &mut EmuUC, handle: u32) -> UcResult<()> { 99 | if handle == 0 { return Ok(()); } 100 | 101 | let handle_index = self.get_handle_index_if_valid(uc, handle).expect("disposing invalid handle"); 102 | let backing_ptr = uc.read_u32(handle)?; 103 | self.dispose_ptr(uc, backing_ptr)?; 104 | uc.write_u32(handle, 0)?; 105 | self.used_handles.set(handle_index, false); 106 | 107 | Ok(()) 108 | } 109 | 110 | pub(super) fn get_handle_size(&self, uc: &EmuUC, handle: u32) -> UcResult> { 111 | if let Some(_handle_index) = self.get_handle_index_if_valid(uc, handle) { 112 | let backing_ptr = uc.read_u32(handle)?; 113 | Ok(Some(self.get_ptr_size(uc, backing_ptr)?)) 114 | } else { 115 | Ok(None) 116 | } 117 | } 118 | 119 | pub(super) fn set_handle_size(&mut self, uc: &mut EmuUC, handle: u32, new_size: u32) -> UcResult { 120 | let _handle_index = self.get_handle_index_if_valid(uc, handle).expect("setting size of invalid handle"); 121 | let backing_ptr = uc.read_u32(handle)?; 122 | 123 | if self.set_ptr_size(uc, backing_ptr, new_size)? { 124 | // easy mode 125 | Ok(true) 126 | } else { 127 | // hard mode, allocate a new buffer 128 | let new_backing_ptr = self.new_ptr(uc, new_size)?; 129 | if new_backing_ptr != 0 { 130 | let old_size = self.get_ptr_size(uc, backing_ptr)?; 131 | let amount_to_copy = old_size.min(new_size); 132 | 133 | // copy data over 134 | let buffer = uc.mem_read_as_vec(backing_ptr.into(), amount_to_copy as usize)?; 135 | uc.mem_write(new_backing_ptr.into(), &buffer)?; 136 | 137 | if amount_to_copy < new_size { 138 | for i in amount_to_copy..new_size { 139 | uc.write_u8(new_backing_ptr + i, 0)?; 140 | } 141 | } 142 | 143 | self.dispose_ptr(uc, backing_ptr)?; 144 | uc.write_u32(handle, new_backing_ptr)?; 145 | Ok(true) 146 | } else { 147 | error!(target: "heap", "Could not resize handle to {new_size} bytes!"); 148 | Ok(false) 149 | } 150 | } 151 | } 152 | 153 | pub(super) fn new_ptr(&mut self, uc: &mut EmuUC, size: u32) -> UcResult { 154 | let aligned_size = (size + 0xF) & !0xF; 155 | 156 | if let Some(block) = self.find_free_block(uc, aligned_size)? { 157 | let ptr = block + SIZE_OF_HEADER; 158 | uc.write_u32(block + HDR_USER_SIZE, size)?; 159 | self.shrink_used_block_by_splitting(uc, block)?; 160 | 161 | for i in 0..size { 162 | uc.write_u8(ptr + i, 0)?; 163 | } 164 | 165 | Ok(ptr) 166 | } else { 167 | error!(target: "heap", "Failed to allocate {size} bytes!"); 168 | self.dump(uc)?; 169 | Ok(0) 170 | } 171 | } 172 | 173 | pub(super) fn dump(&self, uc: &EmuUC) -> UcResult<()> { 174 | let mut block = self.first_block; 175 | let mut index = 0; 176 | 177 | while block != 0 { 178 | let user_size = uc.read_u32(block + HDR_USER_SIZE)?; 179 | let block_size = uc.read_u32(block + HDR_BLOCK_SIZE)?; 180 | let next = uc.read_u32(block + HDR_NEXT)?; 181 | let end = block + block_size; 182 | if (user_size & FREE_FLAG) == FREE_FLAG { 183 | trace!(target: "heap", "#{index:4} {block:8X}-{end:8X} ---FREE---"); 184 | } else { 185 | trace!(target: "heap", "#{index:4} {block:8X}-{end:8X} U:{user_size:8X}"); 186 | if block_size <= 0x50 { 187 | let data = uc.mem_read_as_vec(block as u64, block_size as usize)?; 188 | trace!(target: "heap", "{data:?}"); 189 | } 190 | } 191 | index += 1; 192 | block = next; 193 | } 194 | 195 | Ok(()) 196 | } 197 | 198 | pub(super) fn dispose_ptr(&mut self, uc: &mut EmuUC, ptr: u32) -> UcResult<()> { 199 | let block = ptr - SIZE_OF_HEADER; 200 | let prev = uc.read_u32(block + HDR_PREV)?; 201 | let next = uc.read_u32(block + HDR_NEXT)?; 202 | 203 | uc.write_u32(block + HDR_USER_SIZE, FREE_FLAG)?; 204 | 205 | if next != 0 && self.is_block_free(uc, next)? { 206 | self.merge_blocks(uc, block, next)?; 207 | } 208 | if prev != 0 && self.is_block_free(uc, prev)? { 209 | self.merge_blocks(uc, prev, block)?; 210 | } 211 | 212 | Ok(()) 213 | } 214 | 215 | pub(super) fn get_ptr_size(&self, uc: &EmuUC, ptr: u32) -> UcResult { 216 | let block = ptr - SIZE_OF_HEADER; 217 | let size = uc.read_u32(block + HDR_USER_SIZE)?; 218 | Ok(size) 219 | } 220 | 221 | pub(super) fn set_ptr_size(&mut self, uc: &mut EmuUC, ptr: u32, new_size: u32) -> UcResult { 222 | let block = ptr - SIZE_OF_HEADER; 223 | let current_size = uc.read_u32(block + HDR_USER_SIZE)?; 224 | 225 | // The simplest option 226 | if new_size == current_size { return Ok(true); } 227 | 228 | // Occupy all room up to the next used block 229 | let next = uc.read_u32(block + HDR_NEXT)?; 230 | if next != 0 && self.is_block_free(uc, next)? { 231 | self.merge_blocks(uc, block, next)?; 232 | } 233 | 234 | // Can we fit the desired size in? 235 | let max_size = uc.read_u32(block + HDR_BLOCK_SIZE)?; 236 | let success = if new_size < (max_size - SIZE_OF_HEADER) { 237 | uc.write_u32(block + HDR_USER_SIZE, new_size)?; 238 | 239 | if new_size > current_size { 240 | for i in current_size..new_size { 241 | uc.write_u8(ptr + i, 0)?; 242 | } 243 | } 244 | 245 | true 246 | } else { 247 | false 248 | }; 249 | 250 | // Give space back 251 | self.shrink_used_block_by_splitting(uc, block)?; 252 | 253 | Ok(success) 254 | } 255 | 256 | fn is_block_free(&self, uc: &EmuUC, block: u32) -> UcResult { 257 | let user_size = uc.read_u32(block + HDR_USER_SIZE)?; 258 | Ok((user_size & FREE_FLAG) == FREE_FLAG) 259 | } 260 | 261 | fn find_free_block(&self, uc: &EmuUC, min_size: u32) -> UcResult> { 262 | let mut block = self.last_block; 263 | 264 | while block != 0 { 265 | let block_size = uc.read_u32(block + HDR_BLOCK_SIZE)?; 266 | 267 | if self.is_block_free(uc, block)? && block_size >= (SIZE_OF_HEADER + min_size) { 268 | return Ok(Some(block)); 269 | } 270 | 271 | block = uc.read_u32(block + HDR_PREV)?; 272 | } 273 | 274 | Ok(None) 275 | } 276 | 277 | fn shrink_used_block_by_splitting(&mut self, uc: &mut EmuUC, block: u32) -> UcResult<()> { 278 | assert_ne!(block, 0); 279 | 280 | let user_size = uc.read_u32(block + HDR_USER_SIZE)?; 281 | let block_size = uc.read_u32(block + HDR_BLOCK_SIZE)?; 282 | 283 | let min_block_size = SIZE_OF_HEADER + ((user_size + 0xF) & !0xF); 284 | let free_space = block_size - min_block_size; 285 | if free_space < (SIZE_OF_HEADER + 0x10) { 286 | // too small to bother splitting! 287 | return Ok(()); 288 | } 289 | 290 | let next = uc.read_u32(block + HDR_NEXT)?; 291 | 292 | // Update used block 293 | uc.write_u32(block + HDR_BLOCK_SIZE, min_block_size)?; 294 | 295 | // Mark new block as free 296 | let second_block = block + min_block_size; 297 | uc.write_u32(second_block + HDR_USER_SIZE, FREE_FLAG)?; 298 | uc.write_u32(second_block + HDR_BLOCK_SIZE, free_space)?; 299 | 300 | // Set up linkages 301 | uc.write_u32(block + HDR_NEXT, second_block)?; 302 | uc.write_u32(second_block + HDR_PREV, block)?; 303 | uc.write_u32(second_block + HDR_NEXT, next)?; 304 | 305 | if next == 0 { 306 | self.last_block = second_block; 307 | } else { 308 | uc.write_u32(next + HDR_PREV, second_block)?; 309 | } 310 | 311 | Ok(()) 312 | } 313 | 314 | fn merge_blocks(&mut self, uc: &mut EmuUC, a: u32, b: u32) -> UcResult<()> { 315 | assert_ne!(a, 0); 316 | assert_ne!(b, 0); 317 | 318 | let a_block_size = uc.read_u32(a + HDR_BLOCK_SIZE)?; 319 | let b_block_size = uc.read_u32(b + HDR_BLOCK_SIZE)?; 320 | assert_eq!(a + a_block_size, b); 321 | 322 | let combined_block_size = a_block_size + b_block_size; 323 | uc.write_u32(a + HDR_BLOCK_SIZE, combined_block_size)?; 324 | 325 | let next = uc.read_u32(b + HDR_NEXT)?; 326 | if next == 0 { 327 | self.last_block = a; 328 | } else { 329 | uc.write_u32(next + HDR_PREV, a)?; 330 | } 331 | uc.write_u32(a + HDR_NEXT, next)?; 332 | 333 | Ok(()) 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/emulator/helpers.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use unicorn_engine::{Unicorn, RegisterPPC}; 4 | 5 | use crate::common::FourCC; 6 | 7 | use super::{EmuUC, UcResult}; 8 | 9 | pub(super) struct ArgReader { 10 | use_pascal_strings: bool, 11 | gpr_id: i32, 12 | va_list_position: u32 13 | } 14 | 15 | impl ArgReader { 16 | pub fn new() -> Self { 17 | ArgReader { 18 | use_pascal_strings: false, 19 | gpr_id: RegisterPPC::R3.into(), 20 | va_list_position: 0 21 | } 22 | } 23 | 24 | pub fn new_with_va_list(va_list: u32) -> Self { 25 | ArgReader { 26 | use_pascal_strings: false, 27 | gpr_id: -1, 28 | va_list_position: va_list 29 | } 30 | } 31 | 32 | pub fn pstr(&mut self) -> &mut Self { 33 | self.use_pascal_strings = true; 34 | self 35 | } 36 | 37 | pub(super) fn read1(&mut self, uc: &EmuUC) -> UcResult { 38 | T::get_from_reader(self, uc, self.use_pascal_strings) 39 | } 40 | pub(super) fn read2(&mut self, uc: &EmuUC) -> UcResult<(T1, T2)> { 41 | let a = T1::get_from_reader(self, uc, self.use_pascal_strings)?; 42 | let b = T2::get_from_reader(self, uc, self.use_pascal_strings)?; 43 | Ok((a, b)) 44 | } 45 | pub(super) fn read3(&mut self, uc: &EmuUC) -> UcResult<(T1, T2, T3)> { 46 | let a = T1::get_from_reader(self, uc, self.use_pascal_strings)?; 47 | let b = T2::get_from_reader(self, uc, self.use_pascal_strings)?; 48 | let c = T3::get_from_reader(self, uc, self.use_pascal_strings)?; 49 | Ok((a, b, c)) 50 | } 51 | pub(super) fn read4(&mut self, uc: &EmuUC) -> UcResult<(T1, T2, T3, T4)> { 52 | let a = T1::get_from_reader(self, uc, self.use_pascal_strings)?; 53 | let b = T2::get_from_reader(self, uc, self.use_pascal_strings)?; 54 | let c = T3::get_from_reader(self, uc, self.use_pascal_strings)?; 55 | let d = T4::get_from_reader(self, uc, self.use_pascal_strings)?; 56 | Ok((a, b, c, d)) 57 | } 58 | pub(super) fn read5(&mut self, uc: &EmuUC) -> UcResult<(T1, T2, T3, T4, T5)> { 59 | let a = T1::get_from_reader(self, uc, self.use_pascal_strings)?; 60 | let b = T2::get_from_reader(self, uc, self.use_pascal_strings)?; 61 | let c = T3::get_from_reader(self, uc, self.use_pascal_strings)?; 62 | let d = T4::get_from_reader(self, uc, self.use_pascal_strings)?; 63 | let e = T5::get_from_reader(self, uc, self.use_pascal_strings)?; 64 | Ok((a, b, c, d, e)) 65 | } 66 | #[allow(dead_code)] 67 | pub(super) fn read6(&mut self, uc: &EmuUC) -> UcResult<(T1, T2, T3, T4, T5, T6)> { 68 | let a = T1::get_from_reader(self, uc, self.use_pascal_strings)?; 69 | let b = T2::get_from_reader(self, uc, self.use_pascal_strings)?; 70 | let c = T3::get_from_reader(self, uc, self.use_pascal_strings)?; 71 | let d = T4::get_from_reader(self, uc, self.use_pascal_strings)?; 72 | let e = T5::get_from_reader(self, uc, self.use_pascal_strings)?; 73 | let f = T6::get_from_reader(self, uc, self.use_pascal_strings)?; 74 | Ok((a, b, c, d, e, f)) 75 | } 76 | pub(super) fn read7(&mut self, uc: &EmuUC) -> UcResult<(T1, T2, T3, T4, T5, T6, T7)> { 77 | let a = T1::get_from_reader(self, uc, self.use_pascal_strings)?; 78 | let b = T2::get_from_reader(self, uc, self.use_pascal_strings)?; 79 | let c = T3::get_from_reader(self, uc, self.use_pascal_strings)?; 80 | let d = T4::get_from_reader(self, uc, self.use_pascal_strings)?; 81 | let e = T5::get_from_reader(self, uc, self.use_pascal_strings)?; 82 | let f = T6::get_from_reader(self, uc, self.use_pascal_strings)?; 83 | let g = T7::get_from_reader(self, uc, self.use_pascal_strings)?; 84 | Ok((a, b, c, d, e, f, g)) 85 | } 86 | pub(super) fn read8(&mut self, uc: &EmuUC) -> UcResult<(T1, T2, T3, T4, T5, T6, T7, T8)> { 87 | let a = T1::get_from_reader(self, uc, self.use_pascal_strings)?; 88 | let b = T2::get_from_reader(self, uc, self.use_pascal_strings)?; 89 | let c = T3::get_from_reader(self, uc, self.use_pascal_strings)?; 90 | let d = T4::get_from_reader(self, uc, self.use_pascal_strings)?; 91 | let e = T5::get_from_reader(self, uc, self.use_pascal_strings)?; 92 | let f = T6::get_from_reader(self, uc, self.use_pascal_strings)?; 93 | let g = T7::get_from_reader(self, uc, self.use_pascal_strings)?; 94 | let h = T8::get_from_reader(self, uc, self.use_pascal_strings)?; 95 | Ok((a, b, c, d, e, f, g, h)) 96 | } 97 | 98 | pub fn read_gpr(&mut self, uc: &EmuUC) -> UcResult { 99 | if self.gpr_id < 0 { 100 | let value = uc.read_u32(self.va_list_position)?; 101 | self.va_list_position += 4; 102 | Ok(value) 103 | } else { 104 | let value = uc.reg_read(self.gpr_id)? as u32; 105 | self.gpr_id += 1; 106 | Ok(value) 107 | } 108 | } 109 | } 110 | 111 | pub(super) trait ReadableArg { 112 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, pstr_flag: bool) -> UcResult where Self: Sized; 113 | } 114 | 115 | impl ReadableArg for bool { 116 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 117 | Ok(reader.read_gpr(uc)? != 0) 118 | } 119 | } 120 | impl ReadableArg for u8 { 121 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 122 | Ok((reader.read_gpr(uc)? & 0xFF) as u8) 123 | } 124 | } 125 | impl ReadableArg for u16 { 126 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 127 | Ok((reader.read_gpr(uc)? & 0xFFFF) as u16) 128 | } 129 | } 130 | impl ReadableArg for u32 { 131 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 132 | reader.read_gpr(uc) 133 | } 134 | } 135 | impl ReadableArg for u64 { 136 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 137 | // TODO: check this 138 | let hi = reader.read_gpr(uc)? as u64; 139 | let lo = reader.read_gpr(uc)? as u64; 140 | Ok((hi << 32) | lo) 141 | } 142 | } 143 | impl ReadableArg for i8 { 144 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 145 | Ok((reader.read_gpr(uc)? & 0xFF) as u8 as i8) 146 | } 147 | } 148 | impl ReadableArg for i16 { 149 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 150 | Ok((reader.read_gpr(uc)? & 0xFFFF) as u16 as i16) 151 | } 152 | } 153 | impl ReadableArg for i32 { 154 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 155 | Ok(reader.read_gpr(uc)? as i32) 156 | } 157 | } 158 | impl ReadableArg for i64 { 159 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 160 | // TODO: check this 161 | let hi = reader.read_gpr(uc)? as i64; 162 | let lo = reader.read_gpr(uc)? as i64; 163 | Ok((hi << 32) | lo) 164 | } 165 | } 166 | impl ReadableArg for FourCC { 167 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, _pstr_flag: bool) -> UcResult { 168 | Ok(FourCC(reader.read_gpr(uc)?)) 169 | } 170 | } 171 | impl ReadableArg for CString { 172 | fn get_from_reader(reader: &mut ArgReader, uc: &EmuUC, pstr_flag: bool) -> UcResult { 173 | let addr = reader.read_gpr(uc)?; 174 | if pstr_flag { 175 | uc.read_pascal_string(addr) 176 | } else { 177 | uc.read_c_string(addr) 178 | } 179 | } 180 | } 181 | 182 | pub trait UnicornExtras { 183 | fn read_u8(&self, addr: u32) -> UcResult; 184 | fn read_u16(&self, addr: u32) -> UcResult; 185 | fn read_u32(&self, addr: u32) -> UcResult; 186 | fn read_u64(&self, addr: u32) -> UcResult; 187 | fn read_i8(&self, addr: u32) -> UcResult; 188 | fn read_i16(&self, addr: u32) -> UcResult; 189 | fn read_i32(&self, addr: u32) -> UcResult; 190 | fn read_i64(&self, addr: u32) -> UcResult; 191 | fn read_c_string(&self, addr: u32) -> UcResult; 192 | fn read_pascal_string(&self, addr: u32) -> UcResult; 193 | 194 | fn write_u8(&mut self, addr: u32, value: u8) -> UcResult<()>; 195 | fn write_u16(&mut self, addr: u32, value: u16) -> UcResult<()>; 196 | fn write_u32(&mut self, addr: u32, value: u32) -> UcResult<()>; 197 | fn write_u64(&mut self, addr: u32, value: u64) -> UcResult<()>; 198 | fn write_i8(&mut self, addr: u32, value: i8) -> UcResult<()>; 199 | fn write_i16(&mut self, addr: u32, value: i16) -> UcResult<()>; 200 | fn write_i32(&mut self, addr: u32, value: i32) -> UcResult<()>; 201 | fn write_i64(&mut self, addr: u32, value: i64) -> UcResult<()>; 202 | fn write_c_string(&mut self, addr: u32, s: &[u8]) -> UcResult<()>; 203 | fn write_pascal_string(&mut self, addr: u32, s: &[u8]) -> UcResult<()>; 204 | } 205 | impl UnicornExtras for Unicorn<'_, D> { 206 | fn read_u8(&self, addr: u32) -> UcResult { 207 | let mut bytes = [0u8]; 208 | self.mem_read(addr as u64, &mut bytes)?; 209 | Ok(bytes[0]) 210 | } 211 | 212 | fn read_u16(&self, addr: u32) -> UcResult { 213 | let mut bytes = [0u8; 2]; 214 | self.mem_read(addr as u64, &mut bytes)?; 215 | Ok(u16::from_be_bytes(bytes)) 216 | } 217 | 218 | fn read_u32(&self, addr: u32) -> UcResult { 219 | let mut bytes = [0u8; 4]; 220 | self.mem_read(addr as u64, &mut bytes)?; 221 | Ok(u32::from_be_bytes(bytes)) 222 | } 223 | 224 | fn read_u64(&self, addr: u32) -> UcResult { 225 | let mut bytes = [0u8; 8]; 226 | self.mem_read(addr as u64, &mut bytes)?; 227 | Ok(u64::from_be_bytes(bytes)) 228 | } 229 | 230 | fn read_i8(&self, addr: u32) -> UcResult { 231 | let mut bytes = [0u8]; 232 | self.mem_read(addr as u64, &mut bytes)?; 233 | Ok(bytes[0] as i8) 234 | } 235 | 236 | fn read_i16(&self, addr: u32) -> UcResult { 237 | let mut bytes = [0u8; 2]; 238 | self.mem_read(addr as u64, &mut bytes)?; 239 | Ok(i16::from_be_bytes(bytes)) 240 | } 241 | 242 | fn read_i32(&self, addr: u32) -> UcResult { 243 | let mut bytes = [0u8; 4]; 244 | self.mem_read(addr as u64, &mut bytes)?; 245 | Ok(i32::from_be_bytes(bytes)) 246 | } 247 | 248 | fn read_i64(&self, addr: u32) -> UcResult { 249 | let mut bytes = [0u8; 8]; 250 | self.mem_read(addr as u64, &mut bytes)?; 251 | Ok(i64::from_be_bytes(bytes)) 252 | } 253 | 254 | fn read_c_string(&self, mut addr: u32) -> UcResult { 255 | let mut result = Vec::new(); 256 | 257 | loop { 258 | let b = self.read_u8(addr)?; 259 | if b == 0 { 260 | break; 261 | } else { 262 | result.push(b); 263 | addr += 1; 264 | } 265 | } 266 | 267 | Ok(CString::new(result).unwrap()) 268 | } 269 | 270 | fn read_pascal_string(&self, addr: u32) -> UcResult { 271 | let len = self.read_u8(addr)?; 272 | let mut result = Vec::new(); 273 | 274 | for i in 0..len { 275 | result.push(self.read_u8(addr + 1 + (i as u32))?); 276 | } 277 | 278 | Ok(CString::new(result).unwrap()) 279 | } 280 | 281 | fn write_u8(&mut self, addr: u32, value: u8) -> UcResult<()> { 282 | self.mem_write(addr as u64, &[value]) 283 | } 284 | fn write_u16(&mut self, addr: u32, value: u16) -> UcResult<()> { 285 | self.mem_write(addr as u64, &value.to_be_bytes()) 286 | } 287 | fn write_u32(&mut self, addr: u32, value: u32) -> UcResult<()> { 288 | self.mem_write(addr as u64, &value.to_be_bytes()) 289 | } 290 | fn write_u64(&mut self, addr: u32, value: u64) -> UcResult<()> { 291 | self.mem_write(addr as u64, &value.to_be_bytes()) 292 | } 293 | fn write_i8(&mut self, addr: u32, value: i8) -> UcResult<()> { 294 | self.mem_write(addr as u64, &value.to_be_bytes()) 295 | } 296 | fn write_i16(&mut self, addr: u32, value: i16) -> UcResult<()> { 297 | self.mem_write(addr as u64, &value.to_be_bytes()) 298 | } 299 | fn write_i32(&mut self, addr: u32, value: i32) -> UcResult<()> { 300 | self.mem_write(addr as u64, &value.to_be_bytes()) 301 | } 302 | fn write_i64(&mut self, addr: u32, value: i64) -> UcResult<()> { 303 | self.mem_write(addr as u64, &value.to_be_bytes()) 304 | } 305 | 306 | fn write_c_string(&mut self, addr: u32, s: &[u8]) -> UcResult<()> { 307 | self.mem_write(addr as u64, s)?; 308 | if !s.ends_with(b"\0") { 309 | self.mem_write((addr as usize + s.len()) as u64, b"\0")?; 310 | } 311 | Ok(()) 312 | } 313 | 314 | fn write_pascal_string(&mut self, addr: u32, s: &[u8]) -> UcResult<()> { 315 | self.write_u8(addr, s.len() as u8)?; 316 | self.mem_write((addr + 1) as u64, s) 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/emulator/interface_lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use crate::common::{four_cc, FourCC, OSErr}; 3 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}}; 4 | 5 | const STDCLIB_ID: u32 = 100; 6 | 7 | fn get_shared_library(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 8 | let (lib_name, arch_type, load_flags, conn_id, main_addr, err_message): 9 | (CString, FourCC, u32, u32, u32, u32) = reader.pstr().read6(uc)?; 10 | 11 | debug!(target: "InterfaceLib", 12 | "GetSharedLibrary(libName={lib_name:?}, archType={arch_type:?}, loadFlags={load_flags}, connID={conn_id:08X}, mainAddr={main_addr:08X}, errMessage={err_message:08X})"); 13 | 14 | if arch_type == four_cc(*b"pwpc") && lib_name.as_bytes() == b"StdCLib" { 15 | uc.write_u32(conn_id, STDCLIB_ID)?; 16 | Ok(Some(0)) 17 | } else { 18 | // not sure what the error code should be 19 | Ok(Some(OSErr::BadName.to_u32())) 20 | } 21 | } 22 | 23 | fn find_symbol(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 24 | let (conn_id, sym_name, sym_addr, sym_class): 25 | (u32, CString, u32, u32) = reader.pstr().read4(uc)?; 26 | 27 | debug!(target: "InterfaceLib", 28 | "FindSymbol(connID={conn_id:08X}, symName={sym_name:?}, symAddr={sym_addr:08X}, symClass={sym_class:08X})"); 29 | 30 | if conn_id == STDCLIB_ID { 31 | // should probably do something with symClass...? 32 | let stub = state.find_stub(uc, "StdCLib", sym_name.to_str().unwrap())?; 33 | uc.write_u32(sym_addr, stub)?; 34 | debug!(target: "InterfaceLib", "returned stub: {stub:08X}"); 35 | Ok(Some(0)) 36 | } else { 37 | // not sure what the error code should be 38 | Ok(Some(OSErr::BadName.to_u32())) 39 | } 40 | } 41 | 42 | pub(super) fn install_shims(state: &mut EmuState) { 43 | state.install_shim_function("GetSharedLibrary", get_shared_library); 44 | state.install_shim_function("FindSymbol", find_symbol); 45 | } 46 | -------------------------------------------------------------------------------- /src/emulator/mac_files.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, rc::Rc, cell::RefCell}; 2 | 3 | // const ATTRIB_LOCKED: u8 = 1; 4 | // const ATTRIB_RESOURCE_FORK_OPEN: u8 = 4; 5 | // const ATTRIB_DATA_FORK_OPEN: u8 = 8; 6 | const ATTRIB_DIRECTORY: u8 = 0x10; 7 | // const ATTRIB_ANY_FORK_OPEN: u8 = 0x80; 8 | 9 | use crate::{common::{OSErr, FourCC, system_time_to_mac_time}, filesystem::{MacFile, Fork}, mac_roman}; 10 | 11 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}}; 12 | 13 | pub(super) struct FileHandle { 14 | file: Rc>, 15 | position: usize, 16 | fork: Fork 17 | } 18 | 19 | fn nice_error(error: anyhow::Error) -> u32 { 20 | if let Some(io) = error.downcast_ref::() { 21 | if io.kind() == std::io::ErrorKind::NotFound { 22 | return OSErr::FileNotFound.to_u32(); 23 | } 24 | } 25 | 26 | OSErr::IOError.to_u32() 27 | } 28 | 29 | fn pb_open_rf_sync(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 30 | let pb: u32 = reader.read1(uc)?; 31 | 32 | let name = uc.read_pascal_string(uc.read_u32(pb + 0x12)?)?; 33 | let volume = uc.read_i16(pb + 0x16)?; // ioVRefNum 34 | let permission = uc.read_i8(pb + 0x1B)?; 35 | 36 | // this is nasty 37 | info!(target: "files", "PBOpenRFSync(vol={volume}, name={name:?}, permission={permission})"); 38 | 39 | let path = match state.filesystem.resolve_path(volume, 0, name.as_bytes()) { 40 | Ok(p) => p, 41 | Err(e) => { 42 | error!(target: "files", "PBOpenRFSync failed to resolve path: {e:?}"); 43 | return Ok(Some(OSErr::BadName.to_u32())) 44 | } 45 | }; 46 | 47 | let file = match state.filesystem.get_file(&path) { 48 | Ok(f) => f, 49 | Err(e) => { 50 | error!(target: "files", "PBOpenRFSync failed to get file: {e:?}"); 51 | return Ok(Some(OSErr::IOError.to_u32())) 52 | } 53 | }; 54 | 55 | let handle = state.next_file_handle; 56 | state.next_file_handle += 1; 57 | state.file_handles.insert(handle, FileHandle { 58 | file, 59 | fork: Fork::Resource, 60 | position: 0 61 | }); 62 | 63 | info!(target: "files", "... returned handle {handle}"); 64 | uc.write_u16(pb + 0x18, handle)?; 65 | Ok(Some(0)) 66 | } 67 | 68 | fn pb_write_sync(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 69 | let pb: u32 = reader.read1(uc)?; 70 | 71 | let ref_num = uc.read_u16(pb + 0x18)?; 72 | let buffer_ptr = uc.read_u32(pb + 0x20)?; 73 | let req_count = uc.read_u32(pb + 0x24)?; 74 | let pos_mode = uc.read_i16(pb + 0x2C)?; 75 | let pos_offset = uc.read_i32(pb + 0x2E)?; 76 | let mut result = OSErr::NoError; 77 | 78 | // this is nasty 79 | info!(target: "files", "PBWriteSync(ref={ref_num}, buffer={buffer_ptr:08X}, req_count={req_count:X}, pos_mode={pos_mode}, pos_offset={pos_offset:X})"); 80 | 81 | if let Some(handle) = state.file_handles.get_mut(&ref_num) { 82 | let mut file = handle.file.borrow_mut(); 83 | file.set_dirty(); 84 | 85 | // work out the new position 86 | let buffer = file.get_fork_mut(handle.fork); 87 | let write_start = match pos_mode & 3 { 88 | 1 => pos_offset as isize, 89 | 2 => (buffer.len() as isize + pos_offset as isize), 90 | 3 => (handle.position as isize + pos_offset as isize), 91 | _ => handle.position as isize 92 | }; 93 | if write_start >= 0 { 94 | let write_start = write_start as usize; 95 | let write_end = write_start + req_count as usize; 96 | if buffer.len() < write_end { 97 | buffer.resize(write_end, 0); 98 | } 99 | 100 | uc.mem_read(buffer_ptr.into(), &mut buffer[write_start..write_end])?; 101 | handle.position = write_end; 102 | 103 | uc.write_u32(pb + 0x28, req_count)?; // we always write the full amount 104 | uc.write_u32(pb + 0x2E, write_end as u32)?; 105 | } else { 106 | result = OSErr::Position; 107 | } 108 | } else { 109 | result = OSErr::RefNum; 110 | } 111 | 112 | uc.write_i16(pb + 0x10, result as i16)?; 113 | Ok(Some(0)) 114 | } 115 | 116 | fn fs_close(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 117 | let ref_num: u16 = reader.read1(uc)?; 118 | 119 | trace!(target: "files", "FSClose({ref_num})"); 120 | 121 | match state.file_handles.remove(&ref_num) { 122 | Some(handle) => { 123 | let mut file = handle.file.borrow_mut(); 124 | trace!(target: "files", " ... {:?}", file.path); 125 | if let Err(err) = file.save_if_dirty() { 126 | error!(target: "files", "Error while saving modified file: {err:?}"); 127 | } 128 | Ok(Some(0)) 129 | } 130 | None => Ok(Some(OSErr::RefNum.to_u32())) // file not opened 131 | } 132 | } 133 | 134 | fn fs_read(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 135 | let (ref_num, count_ptr, buf_ptr): (u16, u32, u32) = reader.read3(uc)?; 136 | 137 | trace!(target: "files", "FSRead(ref={ref_num}, count_ptr={count_ptr:08X}, buf_ptr={buf_ptr:08X})"); 138 | 139 | let handle = match state.file_handles.get_mut(&ref_num) { 140 | Some(h) => h, 141 | None => return Ok(Some(OSErr::RefNum.to_u32())) 142 | }; 143 | 144 | let file = handle.file.borrow(); 145 | let buffer = file.get_fork(handle.fork); 146 | 147 | let count = uc.read_i32(count_ptr)?; 148 | if count < 0 { 149 | return Ok(Some(OSErr::Param.to_u32())); 150 | } 151 | let current_pos = handle.position; 152 | handle.position += count as usize; 153 | if handle.position > buffer.len() { 154 | handle.position = buffer.len(); 155 | } 156 | 157 | let actual_count = handle.position - current_pos; 158 | uc.mem_write(buf_ptr.into(), &buffer[current_pos..handle.position])?; 159 | uc.write_u32(count_ptr, actual_count as u32)?; 160 | 161 | trace!(target: "files", "..requested {count:X} bytes at position {current_pos:X}, got {actual_count:X}, new position is {:X}", handle.position); 162 | 163 | if actual_count < count as usize { 164 | Ok(Some(OSErr::Eof.to_u32())) 165 | } else { 166 | Ok(Some(0)) 167 | } 168 | } 169 | 170 | fn fs_write(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 171 | let (ref_num, count_ptr, buf_ptr): (u16, u32, u32) = reader.read3(uc)?; 172 | 173 | trace!(target: "files", "FSWrite(ref={ref_num}, count_ptr={count_ptr:08X}, buf_ptr={buf_ptr:08X})"); 174 | 175 | let handle = match state.file_handles.get_mut(&ref_num) { 176 | Some(h) => h, 177 | None => return Ok(Some(OSErr::RefNum.to_u32())) 178 | }; 179 | 180 | let mut file = handle.file.borrow_mut(); 181 | file.set_dirty(); 182 | 183 | let buffer = file.get_fork_mut(handle.fork); 184 | 185 | let count = uc.read_i32(count_ptr)?; 186 | if count < 0 { 187 | return Ok(Some(OSErr::Param.to_u32())); 188 | } 189 | let current_pos = handle.position; 190 | handle.position += count as usize; 191 | if handle.position > buffer.len() { 192 | buffer.resize(handle.position, 0); 193 | } 194 | 195 | uc.mem_read(buf_ptr.into(), &mut buffer[current_pos..handle.position])?; 196 | Ok(Some(0)) 197 | } 198 | 199 | fn get_v_info(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 200 | let (drive_number, name_ptr, ref_num_ptr, free_bytes_ptr): (i16, u32, u32, u32) = reader.read4(uc)?; 201 | 202 | trace!(target: "files", "GetVInfo(drive={drive_number})"); 203 | 204 | match state.filesystem.get_volume_info_by_drive_number(drive_number) { 205 | Some((name, volume_ref)) => { 206 | // we have data 207 | uc.write_pascal_string(name_ptr, &mac_roman::encode_string(&name, false))?; 208 | uc.write_i16(ref_num_ptr, volume_ref)?; 209 | uc.write_u32(free_bytes_ptr, 0x100000)?; 210 | Ok(Some(0)) 211 | } 212 | None => { 213 | // no idea 214 | if drive_number == 0 { 215 | Ok(Some(OSErr::Param.to_u32())) // no default volume 216 | } else { 217 | Ok(Some(OSErr::NoSuchVolume.to_u32())) 218 | } 219 | } 220 | } 221 | } 222 | 223 | fn get_eof(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 224 | let (ref_num, eof_ptr): (u16, u32) = reader.read2(uc)?; 225 | 226 | if let Some(handle) = state.file_handles.get(&ref_num) { 227 | let eof = handle.file.borrow().len(handle.fork); 228 | uc.write_u32(eof_ptr, eof as u32)?; 229 | Ok(Some(0)) 230 | } else { 231 | Ok(Some(OSErr::RefNum.to_u32())) 232 | } 233 | } 234 | 235 | fn set_eof(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 236 | let (ref_num, eof): (u16, u32) = reader.read2(uc)?; 237 | 238 | if let Some(handle) = state.file_handles.get_mut(&ref_num) { 239 | handle.file.borrow_mut().get_fork_mut(handle.fork).resize(eof as usize, 0); 240 | Ok(Some(0)) 241 | } else { 242 | Ok(Some(OSErr::RefNum.to_u32())) 243 | } 244 | } 245 | 246 | fn get_f_pos(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 247 | let (ref_num, pos_ptr): (u16, u32) = reader.read2(uc)?; 248 | 249 | if let Some(handle) = state.file_handles.get(&ref_num) { 250 | uc.write_u32(pos_ptr, handle.position as u32)?; 251 | Ok(Some(0)) 252 | } else { 253 | Ok(Some(OSErr::RefNum.to_u32())) 254 | } 255 | } 256 | 257 | fn set_f_pos(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 258 | let (ref_num, pos_mode, pos_offset): (u16, i16, i32) = reader.read3(uc)?; 259 | 260 | if let Some(handle) = state.file_handles.get_mut(&ref_num) { 261 | let eof = handle.file.borrow().len(handle.fork) as isize; 262 | 263 | let current_pos = handle.position as isize; 264 | let new_pos = match pos_mode { 265 | 1 => pos_offset as isize, 266 | 2 => eof + pos_offset as isize, 267 | 3 => current_pos + pos_offset as isize, 268 | _ => current_pos 269 | }; 270 | 271 | if new_pos < 0 { 272 | Ok(Some(OSErr::Position.to_u32())) 273 | } else if new_pos > eof { 274 | Ok(Some(OSErr::Eof.to_u32())) 275 | } else { 276 | handle.position = new_pos as usize; 277 | Ok(Some(0)) 278 | } 279 | } else { 280 | Ok(Some(OSErr::RefNum.to_u32())) 281 | } 282 | } 283 | 284 | fn pb_get_cat_info_sync(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 285 | let pb: u32 = reader.read1(uc)?; 286 | 287 | let name_ptr = uc.read_u32(pb + 0x12)?; 288 | let volume_ref = uc.read_i16(pb + 0x16)?; 289 | let io_f_dir_index = uc.read_i16(pb + 0x1C)?; 290 | let dir_id = uc.read_i32(pb + 0x30)?; 291 | 292 | trace!(target: "files", "PBGetCatInfoSync(volume={volume_ref}, name_ptr={name_ptr:08X}, dir_id={dir_id}, io_f_dir_index={io_f_dir_index})"); 293 | 294 | if io_f_dir_index > 0 { 295 | // this does a lookup by index in the dir 296 | panic!("not implemented lol"); 297 | } else { 298 | // gets info about a dir 299 | let path = if io_f_dir_index == 0 { 300 | let name = uc.read_pascal_string(name_ptr)?; 301 | trace!(target: "files", "PBGetCatInfoSync(volume={volume_ref}, dir_id={dir_id}, name={name:?})"); 302 | state.filesystem.resolve_path(volume_ref, dir_id, name.as_bytes()).unwrap() 303 | } else { 304 | trace!(target: "files", "PBGetCatInfoSync(volume={volume_ref}, dir_id={dir_id})"); 305 | state.filesystem.resolve_path(volume_ref, dir_id, b"").unwrap() 306 | }; 307 | 308 | if path.exists() { 309 | let info = state.filesystem.spec(&path).unwrap(); 310 | if name_ptr != 0 { 311 | uc.write_pascal_string(name_ptr, &info.node_name)?; 312 | } 313 | uc.write_i16(pb + 0x16, info.volume_ref)?; 314 | uc.write_u8(pb + 0x1E, if path.is_dir() { ATTRIB_DIRECTORY } else { 0 })?; 315 | uc.write_i32(pb + 0x30, info.node_id)?; 316 | uc.write_i32(pb + 0x64, info.parent_id)?; 317 | Ok(Some(0)) 318 | } else { 319 | Ok(Some(OSErr::FileNotFound.to_u32())) 320 | } 321 | } 322 | } 323 | 324 | fn pb_h_open_sync(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 325 | let pb: u32 = reader.read1(uc)?; 326 | 327 | // this is HParamBlockRec (HFileParam) 328 | let name = uc.read_pascal_string(uc.read_u32(pb + 0x12)?)?; 329 | let volume_ref = uc.read_i16(pb + 0x16)?; 330 | let dir_id = uc.read_i32(pb + 0x30)?; 331 | trace!(target: "files", "PBHOpenSync(vol={volume_ref}, dir={dir_id}, name={name:?})"); 332 | 333 | let path = match state.filesystem.resolve_path(volume_ref, dir_id, name.as_bytes()) { 334 | Ok(p) => p, 335 | Err(e) => { 336 | error!(target: "files", "PBHOpenSync failed to resolve path: {e:?}"); 337 | return Ok(Some(OSErr::BadName.to_u32())); 338 | } 339 | }; 340 | 341 | let file = match state.filesystem.get_file(&path) { 342 | Ok(f) => f, 343 | Err(e) => { 344 | error!(target: "files", "PBHOpenSync failed to get file: {e:?}"); 345 | return Ok(Some(OSErr::IOError.to_u32())) 346 | } 347 | }; 348 | 349 | let handle = state.next_file_handle; 350 | state.next_file_handle += 1; 351 | state.file_handles.insert(handle, FileHandle { 352 | file, 353 | fork: Fork::Data, 354 | position: 0 355 | }); 356 | 357 | info!(target: "files", "... returned handle {handle}"); 358 | uc.write_u16(pb + 0x18, handle)?; 359 | 360 | Ok(Some(0)) 361 | } 362 | 363 | fn pb_h_get_f_info_sync(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 364 | // https://web.archive.org/web/20011122050640/http://developer.apple.com/techpubs/mac/Files/Files-240.html#HEADING240-0 365 | let pb: u32 = reader.read1(uc)?; 366 | 367 | // this is HParamBlockRec (HFileParam) 368 | let io_f_dir_index = uc.read_i16(pb + 0x1C)?; 369 | if io_f_dir_index > 0 { 370 | // this does a lookup by index in the dir 371 | panic!("not implemented lol"); 372 | } else { 373 | // looks up info about a file or dir, by name 374 | let name = uc.read_pascal_string(uc.read_u32(pb + 0x12)?)?; 375 | let volume_ref = uc.read_i16(pb + 0x16)?; 376 | let dir_id = uc.read_i32(pb + 0x30)?; 377 | trace!(target: "files", "PBHGetFInfoSync is looking up dir {dir_id}, name {name:?}"); 378 | 379 | let path = match state.filesystem.resolve_path(volume_ref, dir_id, name.as_bytes()) { 380 | Ok(p) => p, 381 | Err(e) => { 382 | error!(target: "files", "PBHGetFInfoSync failed to resolve path: {e:?}"); 383 | return Ok(Some(nice_error(e))); 384 | } 385 | }; 386 | 387 | let metadata = match std::fs::metadata(&path) { 388 | Ok(m) => m, 389 | Err(e) => { 390 | error!(target: "files", "PBHGetFInfoSync failed to get metadata: {e:?}"); 391 | return Ok(Some(OSErr::IOError.to_u32())); 392 | } 393 | }; 394 | 395 | let file = match state.filesystem.get_file(&path) { 396 | Ok(f) => f, 397 | Err(e) => { 398 | error!(target: "files", "PBHGetFInfoSync failed to get file: {e:?}"); 399 | return Ok(Some(nice_error(e))) 400 | } 401 | }; 402 | let file = file.borrow(); 403 | 404 | // I should probably write more of this data... 405 | uc.write_u8(pb + 0x1E, if metadata.is_dir() { ATTRIB_DIRECTORY } else { 0 })?; 406 | uc.write_u32(pb + 0x20, file.file_info.file_type.0)?; 407 | uc.write_u32(pb + 0x24, file.file_info.file_creator.0)?; 408 | uc.write_u16(pb + 0x28, file.file_info.finder_flags)?; 409 | uc.write_i16(pb + 0x2A, file.file_info.location.0)?; 410 | uc.write_i16(pb + 0x2C, file.file_info.location.1)?; 411 | uc.write_u16(pb + 0x2E, file.file_info.reserved_field)?; 412 | 413 | if let Ok(time) = metadata.created() { 414 | uc.write_u32(pb + 0x48, system_time_to_mac_time(time))?; 415 | } 416 | if let Ok(time) = metadata.modified() { 417 | uc.write_u32(pb + 0x4C, system_time_to_mac_time(time))?; 418 | } 419 | 420 | Ok(Some(0)) 421 | } 422 | } 423 | 424 | fn pb_h_set_f_info_sync(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 425 | let pb: u32 = reader.read1(uc)?; 426 | 427 | // this is HParamBlockRec (HFileParam) 428 | let name = uc.read_pascal_string(uc.read_u32(pb + 0x12)?)?; 429 | let volume_ref = uc.read_i16(pb + 0x16)?; 430 | let dir_id = uc.read_i32(pb + 0x30)?; 431 | trace!(target: "files", "PBHSetFInfoSync is updating dir {dir_id}, name {name:?}"); 432 | 433 | let path = match state.filesystem.resolve_path(volume_ref, dir_id, name.as_bytes()) { 434 | Ok(p) => p, 435 | Err(e) => { 436 | error!(target: "files", "PBHSetFInfoSync failed to resolve path: {e:?}"); 437 | return Ok(Some(nice_error(e))); 438 | } 439 | }; 440 | 441 | let file = match state.filesystem.get_file(&path) { 442 | Ok(f) => f, 443 | Err(e) => { 444 | error!(target: "files", "PBHSetFInfoSync failed to get file: {e:?}"); 445 | return Ok(Some(nice_error(e))) 446 | } 447 | }; 448 | 449 | // we don't support setting the metadata times here 450 | // (use the 'filetime' crate?) 451 | let mut file_ref = file.borrow_mut(); 452 | file_ref.file_info.file_type.0 = uc.read_u32(pb + 0x20)?; 453 | file_ref.file_info.file_creator.0 = uc.read_u32(pb + 0x24)?; 454 | file_ref.file_info.finder_flags = uc.read_u16(pb + 0x28)?; 455 | file_ref.file_info.location.0 = uc.read_i16(pb + 0x2A)?; 456 | file_ref.file_info.location.1 = uc.read_i16(pb + 0x2C)?; 457 | file_ref.file_info.reserved_field = uc.read_u16(pb + 0x2E)?; 458 | 459 | file_ref.set_dirty(); 460 | match file_ref.save_if_dirty() { 461 | Ok(()) => Ok(Some(0)), 462 | Err(e) => { 463 | error!(target: "files", "PBHSetFInfoSync failed to save file: {e:?}"); 464 | Ok(Some(nice_error(e))) 465 | } 466 | } 467 | } 468 | 469 | fn h_open(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 470 | let (volume, dir_id, name, permission, ref_num_ptr): (i16, i32, CString, i8, u32) = reader.pstr().read5(uc)?; 471 | info!(target: "files", "HOpen(vol={volume}, dir={dir_id}, name={name:?}, permission={permission})"); 472 | 473 | let path = match state.filesystem.resolve_path(volume, dir_id, name.as_bytes()) { 474 | Ok(p) => p, 475 | Err(e) => { 476 | error!(target: "files", "HOpen failed to resolve path: {e:?}"); 477 | return Ok(Some(OSErr::BadName.to_u32())) 478 | } 479 | }; 480 | 481 | let file = match state.filesystem.get_file(&path) { 482 | Ok(f) => f, 483 | Err(e) => { 484 | error!(target: "files", "HOpen failed to get file: {e:?}"); 485 | return Ok(Some(OSErr::IOError.to_u32())) 486 | } 487 | }; 488 | 489 | let handle = state.next_file_handle; 490 | state.next_file_handle += 1; 491 | state.file_handles.insert(handle, FileHandle { 492 | file, 493 | fork: Fork::Data, 494 | position: 0 495 | }); 496 | 497 | info!(target: "files", "... returned handle {handle}"); 498 | uc.write_u16(ref_num_ptr, handle)?; 499 | Ok(Some(0)) 500 | } 501 | 502 | fn h_open_rf(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 503 | let (volume, dir_id, name, permission, ref_num_ptr): (i16, i32, CString, i8, u32) = reader.pstr().read5(uc)?; 504 | info!(target: "files", "HOpenRF(vol={volume}, dir={dir_id}, name={name:?}, permission={permission})"); 505 | 506 | let path = match state.filesystem.resolve_path(volume, dir_id, name.as_bytes()) { 507 | Ok(p) => p, 508 | Err(e) => { 509 | error!(target: "files", "HOpenRF failed to resolve path: {e:?}"); 510 | return Ok(Some(OSErr::BadName.to_u32())) 511 | } 512 | }; 513 | 514 | let file = match state.filesystem.get_file(&path) { 515 | Ok(f) => f, 516 | Err(e) => { 517 | error!(target: "files", "HOpenRF failed to get file: {e:?}"); 518 | return Ok(Some(OSErr::IOError.to_u32())) 519 | } 520 | }; 521 | 522 | let handle = state.next_file_handle; 523 | state.next_file_handle += 1; 524 | state.file_handles.insert(handle, FileHandle { 525 | file, 526 | fork: Fork::Resource, 527 | position: 0 528 | }); 529 | 530 | info!(target: "files", "... returned handle {handle}"); 531 | uc.write_u16(ref_num_ptr, handle)?; 532 | Ok(Some(0)) 533 | } 534 | 535 | fn h_create(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 536 | let (volume_ref, dir_id, name, creator, file_type): (i16, i32, CString, FourCC, FourCC) = reader.pstr().read5(uc)?; 537 | info!(target: "files", "HCreate(vol={volume_ref}, dir={dir_id}, name={name:?}, creator={creator:?}, type={file_type:?})"); 538 | 539 | let path = match state.filesystem.resolve_path(volume_ref, dir_id, name.as_bytes()) { 540 | Ok(p) => p, 541 | Err(e) => { 542 | error!(target: "files", "HCreate failed to resolve path: {e:?}"); 543 | return Ok(Some(OSErr::BadName.to_u32())); 544 | } 545 | }; 546 | 547 | if path.exists() { 548 | error!(target: "files", "Path already exists for HCreate: {path:?}"); 549 | return Ok(Some(OSErr::DuplicateFilename.to_u32())); 550 | } 551 | 552 | match state.filesystem.create_file(&path, creator, file_type) { 553 | Ok(()) => Ok(Some(0)), 554 | Err(e) => { 555 | error!(target: "files", "HCreate failed to create file: {e:?}"); 556 | return Ok(Some(OSErr::IOError.to_u32())); 557 | } 558 | } 559 | } 560 | 561 | fn h_delete(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 562 | let (volume_ref, dir_id, name): (i16, i32, CString) = reader.pstr().read3(uc)?; 563 | info!(target: "files", "HDelete(vol={volume_ref}, dir={dir_id}, name={name:?})"); 564 | 565 | let path = match state.filesystem.resolve_path(volume_ref, dir_id, name.as_bytes()) { 566 | Ok(p) => p, 567 | Err(e) => { 568 | error!(target: "files", "HDelete failed to resolve path: {e:?}"); 569 | return Ok(Some(OSErr::BadName.to_u32())); 570 | } 571 | }; 572 | 573 | // temporary safety check 574 | let ok_to_delete = match path.extension() { 575 | Some(e) => e.to_string_lossy() == "o", 576 | None => false 577 | }; 578 | 579 | if !ok_to_delete { 580 | error!(target: "files", "HDelete is protecting against a delete of: {path:?}"); 581 | return Ok(Some(OSErr::FileLocked.to_u32())); 582 | } 583 | 584 | // is this file open at all? 585 | for handle in state.file_handles.values() { 586 | if handle.file.borrow().path == path { 587 | error!(target: "files", "HDelete cannot delete open file: {path:?}"); 588 | return Ok(Some(OSErr::FileBusy.to_u32())); 589 | } 590 | } 591 | 592 | match state.filesystem.delete_file(&path) { 593 | Ok(()) => Ok(Some(0)), 594 | Err(e) => { 595 | if let Some(e) = e.downcast_ref::() { 596 | if e.kind() == std::io::ErrorKind::NotFound { 597 | // MWCPPC calls this on files that don't exist, so let's be quiet 598 | info!(target: "files", "HDelete failed to delete non-existent file: {e:?}"); 599 | return Ok(Some(OSErr::FileNotFound.to_u32())); 600 | } 601 | } 602 | error!(target: "files", "HDelete failed to delete file: {e:?}"); 603 | Ok(Some(OSErr::IOError.to_u32())) 604 | } 605 | } 606 | } 607 | 608 | fn h_get_f_info(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 609 | let (volume, dir_id, name, info_ptr): (i16, i32, CString, u32) = reader.pstr().read4(uc)?; 610 | info!(target: "files", "HGetFInfo(vol={volume}, dir={dir_id}, name={name:?})"); 611 | 612 | let path = match state.filesystem.resolve_path(volume, dir_id, name.as_bytes()) { 613 | Ok(p) => p, 614 | Err(e) => { 615 | error!(target: "files", "HOpen failed to resolve path: {e:?}"); 616 | return Ok(Some(OSErr::BadName.to_u32())) 617 | } 618 | }; 619 | 620 | let file = match state.filesystem.get_file(&path) { 621 | Ok(f) => f, 622 | Err(e) => { 623 | error!(target: "files", "HOpen failed to get file: {e:?}"); 624 | return Ok(Some(nice_error(e))) 625 | } 626 | }; 627 | 628 | let f = file.borrow(); 629 | uc.write_u32(info_ptr, f.file_info.file_type.0)?; 630 | uc.write_u32(info_ptr + 4, f.file_info.file_creator.0)?; 631 | uc.write_u16(info_ptr + 8, f.file_info.finder_flags)?; 632 | uc.write_i16(info_ptr + 10, f.file_info.location.0)?; 633 | uc.write_i16(info_ptr + 12, f.file_info.location.1)?; 634 | uc.write_u16(info_ptr + 14, 0)?; // fdFldr - do I need this? maybe. 635 | Ok(Some(0)) 636 | } 637 | 638 | fn fs_make_fs_spec(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 639 | let (volume, dir_id, name, spec_ptr): (i16, i32, CString, u32) = reader.pstr().read4(uc)?; 640 | info!(target: "files", "FSMakeFSSpec(vol={volume}, dir={dir_id}, name={name:?}, spec={spec_ptr:08X})"); 641 | 642 | let name = match name.to_str() { 643 | Ok(n) => n, 644 | Err(_) => return Ok(Some(OSErr::BadName.to_u32())) 645 | }; 646 | 647 | let path = state.filesystem.resolve_path(volume, dir_id, name.as_bytes()).unwrap(); 648 | let info = state.filesystem.spec(&path).unwrap(); 649 | 650 | uc.write_i16(spec_ptr, info.volume_ref)?; 651 | uc.write_i32(spec_ptr + 2, info.parent_id)?; 652 | uc.write_pascal_string(spec_ptr + 6, &info.node_name)?; 653 | 654 | if path.exists() { 655 | Ok(Some(0)) 656 | } else { 657 | Ok(Some(OSErr::FileNotFound.to_u32())) 658 | } 659 | } 660 | 661 | fn fsp_open_df(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 662 | let (spec_ptr, permission, ref_num_ptr): (u32, i8, u32) = reader.read3(uc)?; 663 | let volume = uc.read_i16(spec_ptr)?; 664 | let dir_id = uc.read_i32(spec_ptr + 2)?; 665 | let name = uc.read_pascal_string(spec_ptr + 6)?; 666 | 667 | info!(target: "files", "FSpOpenDF(vol={volume}, dir={dir_id}, name={name:?}, permission={permission})"); 668 | 669 | let path = match state.filesystem.resolve_path(volume, dir_id, name.as_bytes()) { 670 | Ok(p) => p, 671 | Err(e) => { 672 | error!(target: "files", "FSpOpenDF failed to resolve path: {e:?}"); 673 | return Ok(Some(OSErr::BadName.to_u32())) 674 | } 675 | }; 676 | 677 | let file = match state.filesystem.get_file(&path) { 678 | Ok(f) => f, 679 | Err(e) => { 680 | error!(target: "files", "FSpOpenDF failed to get file: {e:?}"); 681 | return Ok(Some(nice_error(e))) 682 | } 683 | }; 684 | 685 | let handle = state.next_file_handle; 686 | state.next_file_handle += 1; 687 | state.file_handles.insert(handle, FileHandle { 688 | file, 689 | fork: Fork::Data, 690 | position: 0 691 | }); 692 | 693 | info!(target: "files", "... returned handle {handle}"); 694 | uc.write_u16(ref_num_ptr, handle)?; 695 | Ok(Some(0)) 696 | } 697 | 698 | fn fsp_create(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 699 | let (spec_ptr, creator, file_type, script_tag): (u32, FourCC, FourCC, i16) = reader.read4(uc)?; 700 | let volume = uc.read_i16(spec_ptr)?; 701 | let dir_id = uc.read_i32(spec_ptr + 2)?; 702 | let name = uc.read_pascal_string(spec_ptr + 6)?; 703 | 704 | info!(target: "files", "FSpCreate(vol={volume}, dir={dir_id}, name={name:?}, creator={creator:?}, file_type={file_type:?}, script_tag={script_tag})"); 705 | 706 | let path = match state.filesystem.resolve_path(volume, dir_id, name.as_bytes()) { 707 | Ok(p) => p, 708 | Err(e) => { 709 | error!(target: "files", "FSpCreate failed to resolve path: {e:?}"); 710 | return Ok(Some(OSErr::BadName.to_u32())) 711 | } 712 | }; 713 | 714 | if path.exists() { 715 | error!(target: "files", "Path already exists for FSpCreate: {path:?}"); 716 | return Ok(Some(OSErr::DuplicateFilename.to_u32())); 717 | } 718 | 719 | match state.filesystem.create_file(&path, creator, file_type) { 720 | Ok(()) => Ok(Some(0)), 721 | Err(e) => { 722 | error!(target: "files", "FSpCreate failed to create file: {e:?}"); 723 | return Ok(Some(nice_error(e))) 724 | } 725 | } 726 | } 727 | 728 | fn fsp_delete(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 729 | let spec_ptr: u32 = reader.read1(uc)?; 730 | let volume = uc.read_i16(spec_ptr)?; 731 | let dir_id = uc.read_i32(spec_ptr + 2)?; 732 | let name = uc.read_pascal_string(spec_ptr + 6)?; 733 | 734 | info!(target: "files", "FSpDelete(vol={volume}, dir={dir_id}, name={name:?})"); 735 | 736 | let path = match state.filesystem.resolve_path(volume, dir_id, name.as_bytes()) { 737 | Ok(p) => p, 738 | Err(e) => { 739 | error!(target: "files", "FSpDelete failed to resolve path: {e:?}"); 740 | return Ok(Some(OSErr::BadName.to_u32())) 741 | } 742 | }; 743 | 744 | // temporary safety check 745 | let ok_to_delete = match path.extension() { 746 | Some(e) => e.to_string_lossy() == "o", 747 | None => false 748 | }; 749 | 750 | if !ok_to_delete { 751 | error!(target: "files", "FSpDelete is protecting against a delete of: {path:?}"); 752 | return Ok(Some(OSErr::FileLocked.to_u32())); 753 | } 754 | 755 | // is this file open at all? 756 | for handle in state.file_handles.values() { 757 | if handle.file.borrow().path == path { 758 | error!(target: "files", "FSpDelete cannot delete open file: {path:?}"); 759 | return Ok(Some(OSErr::FileBusy.to_u32())); 760 | } 761 | } 762 | 763 | match state.filesystem.delete_file(&path) { 764 | Ok(()) => Ok(Some(0)), 765 | Err(e) => { 766 | error!(target: "files", "FSpDelete failed to delete file: {e:?}"); 767 | return Ok(Some(nice_error(e))) 768 | } 769 | } 770 | } 771 | 772 | fn fsp_get_f_info(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 773 | let (spec_ptr, info_ptr): (u32, u32) = reader.read2(uc)?; 774 | let volume = uc.read_i16(spec_ptr)?; 775 | let dir_id = uc.read_i32(spec_ptr + 2)?; 776 | let name = uc.read_pascal_string(spec_ptr + 6)?; 777 | 778 | info!(target: "files", "FSpGetFInfo(vol={volume}, dir={dir_id}, name={name:?})"); 779 | 780 | let path = match state.filesystem.resolve_path(volume, dir_id, name.as_bytes()) { 781 | Ok(p) => p, 782 | Err(e) => { 783 | error!(target: "files", "FSpGetFInfo failed to resolve path: {e:?}"); 784 | return Ok(Some(OSErr::BadName.to_u32())) 785 | } 786 | }; 787 | 788 | let file = match state.filesystem.get_file(&path) { 789 | Ok(f) => f, 790 | Err(e) => { 791 | error!(target: "files", "FSpGetFInfo failed to get file: {e:?}"); 792 | return Ok(Some(nice_error(e))) 793 | } 794 | }; 795 | 796 | let f = file.borrow(); 797 | uc.write_u32(info_ptr, f.file_info.file_type.0)?; 798 | uc.write_u32(info_ptr + 4, f.file_info.file_creator.0)?; 799 | uc.write_u16(info_ptr + 8, f.file_info.finder_flags)?; 800 | uc.write_i16(info_ptr + 10, f.file_info.location.0)?; 801 | uc.write_i16(info_ptr + 12, f.file_info.location.1)?; 802 | uc.write_u16(info_ptr + 14, 0)?; // fdFldr - do I need this? maybe. 803 | Ok(Some(0)) 804 | } 805 | 806 | fn mpw_make_resolved_path(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 807 | let (volume, dir_id, path, resolve_leaf_name, buffer_ptr, is_folder_ptr, had_alias_ptr, leaf_is_alias_ptr): (i16, i32, CString, bool, u32, u32, u32, u32) = reader.pstr().read8(uc)?; 808 | trace!(target: "files", "MakeResolvedPath(vol={volume}, dir={dir_id}, path={path:?}, resolve_leaf_name={resolve_leaf_name}, buffer={buffer_ptr:08X}, ...)"); 809 | 810 | let fs_path = state.filesystem.resolve_path(volume, dir_id, path.as_bytes()).unwrap(); 811 | trace!(target: "files", "..resolved to {fs_path:?}"); 812 | if fs_path.exists() { 813 | trace!(target: "files", "..exists!"); 814 | // is this correct? not sure. 815 | uc.write_pascal_string(buffer_ptr, path.as_bytes())?; 816 | uc.write_u8(is_folder_ptr, if fs_path.is_dir() { 1 } else { 0 })?; 817 | uc.write_u8(had_alias_ptr, 0)?; 818 | uc.write_u8(leaf_is_alias_ptr, 0)?; 819 | Ok(Some(0)) 820 | } else { 821 | Ok(Some(OSErr::FileNotFound.to_u32())) 822 | } 823 | } 824 | 825 | fn mpw_make_resolved_fs_spec(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 826 | let (volume, dir_id, name, spec_ptr, is_folder_ptr, had_alias_ptr, leaf_is_alias_ptr): (i16, i32, CString, u32, u32, u32, u32) = reader.pstr().read7(uc)?; 827 | trace!(target: "files", "MakeResolvedFSSpec(vol={volume}, dir={dir_id}, name={name:?}, spec={spec_ptr:08X}, ...)"); 828 | 829 | let path = state.filesystem.resolve_path(volume, dir_id, name.as_bytes()).unwrap(); 830 | 831 | // Write a spec 832 | let info = state.filesystem.spec(&path).unwrap(); 833 | uc.write_i16(spec_ptr, info.volume_ref)?; 834 | uc.write_i32(spec_ptr + 2, info.parent_id)?; 835 | uc.write_pascal_string(spec_ptr + 6, &info.node_name)?; 836 | uc.write_u8(is_folder_ptr, if path.is_dir() { 1 } else { 0 })?; 837 | uc.write_u8(had_alias_ptr, 0)?; 838 | uc.write_u8(leaf_is_alias_ptr, 0)?; 839 | Ok(Some(0)) 840 | } 841 | 842 | fn resolve_alias_file(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 843 | let (spec_ptr, resolve_alias_chains, target_is_folder_ptr, was_aliased_ptr): (u32, bool, u32, u32) = reader.read4(uc)?; 844 | let volume_ref = uc.read_i16(spec_ptr)?; 845 | let dir_id = uc.read_i32(spec_ptr + 2)?; 846 | let name = uc.read_pascal_string(spec_ptr + 6)?; 847 | trace!(target: "files", "ResolveAliasFile(spec={spec_ptr:08X}, resolve_alias_chains={resolve_alias_chains} - vol={volume_ref}, dir_id={dir_id}, name={name:?})"); 848 | 849 | let path = match state.filesystem.resolve_path(volume_ref, dir_id, name.as_bytes()) { 850 | Ok(p) => p, 851 | Err(e) => { 852 | error!(target: "files", "ResolveAliasFile failed to resolve path: {e:?}"); 853 | return Ok(Some(OSErr::DirNotFound.to_u32())); 854 | } 855 | }; 856 | 857 | if path.exists() { 858 | // Yep 859 | let info = state.filesystem.spec(&path).unwrap(); 860 | uc.write_i16(spec_ptr, info.volume_ref)?; 861 | uc.write_i32(spec_ptr + 2, info.parent_id)?; 862 | uc.write_pascal_string(spec_ptr + 6, &info.node_name)?; 863 | 864 | uc.write_u8(target_is_folder_ptr, if path.is_dir() { 1 } else { 0 })?; 865 | uc.write_u8(was_aliased_ptr, 0)?; 866 | Ok(Some(0)) 867 | } else { 868 | Ok(Some(OSErr::FileNotFound.to_u32())) 869 | } 870 | } 871 | 872 | pub(super) fn install_shims(state: &mut EmuState) { 873 | state.install_shim_function("PBOpenRFSync", pb_open_rf_sync); 874 | // PBCloseSync 875 | // PBReadSync 876 | state.install_shim_function("PBWriteSync", pb_write_sync); 877 | state.install_shim_function("FSClose", fs_close); 878 | state.install_shim_function("FSRead", fs_read); 879 | state.install_shim_function("FSWrite", fs_write); 880 | state.install_shim_function("GetVInfo", get_v_info); 881 | state.install_shim_function("GetEOF", get_eof); 882 | state.install_shim_function("SetEOF", set_eof); 883 | state.install_shim_function("GetFPos", get_f_pos); 884 | state.install_shim_function("SetFPos", set_f_pos); 885 | state.install_shim_function("PBGetCatInfoSync", pb_get_cat_info_sync); 886 | state.install_shim_function("PBHOpenSync", pb_h_open_sync); 887 | state.install_shim_function("PBHGetFInfoSync", pb_h_get_f_info_sync); 888 | state.install_shim_function("PBHSetFInfoSync", pb_h_set_f_info_sync); 889 | state.install_shim_function("HOpen", h_open); 890 | state.install_shim_function("HOpenDF", h_open); 891 | state.install_shim_function("HOpenRF", h_open_rf); 892 | state.install_shim_function("HCreate", h_create); 893 | state.install_shim_function("HDelete", h_delete); 894 | state.install_shim_function("HGetFInfo", h_get_f_info); 895 | state.install_shim_function("FSMakeFSSpec", fs_make_fs_spec); 896 | state.install_shim_function("FSpOpenDF", fsp_open_df); 897 | state.install_shim_function("FSpCreate", fsp_create); 898 | state.install_shim_function("FSpDelete", fsp_delete); 899 | state.install_shim_function("FSpGetFInfo", fsp_get_f_info); 900 | 901 | // not actually in Files.h but we'll let it slide. 902 | // Aliases.h 903 | state.install_shim_function("ResolveAliasFile", resolve_alias_file); 904 | 905 | // these are from MPW itself 906 | state.install_shim_function("MakeResolvedPath", mpw_make_resolved_path); 907 | state.install_shim_function("MakeResolvedFSSpec", mpw_make_resolved_fs_spec); 908 | } 909 | -------------------------------------------------------------------------------- /src/emulator/mac_fp.rs: -------------------------------------------------------------------------------- 1 | use unicorn_engine::RegisterPPC; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}}; 4 | 5 | fn dec2num(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 6 | let ptr: u32 = reader.read1(uc)?; 7 | let sgn = uc.read_i8(ptr)?; 8 | let exp = uc.read_i16(ptr + 2)?; 9 | let text = uc.read_pascal_string(ptr + 4)?; 10 | trace!(target: "fp", "dec2num(sgn={sgn}, exp={exp}, text={text:?})"); 11 | 12 | // let's rely on Rust to do it 13 | let sgn = if sgn == 1 { "-" } else { "" }; 14 | let text = text.to_str().unwrap(); 15 | let num_str = format!("{sgn}{text}E{exp}"); 16 | match num_str.parse::() { 17 | Ok(n) => { 18 | // we should set overflow/underflow flags in the PPC FPSCR here 19 | // but not sure how to do that atm 20 | uc.reg_write(RegisterPPC::FPR1, n.to_bits())?; 21 | } 22 | Err(e) => { 23 | error!(target: "fp", "dec2num failed to parse: {num_str} - {e:?}"); 24 | } 25 | } 26 | Ok(Some(0)) 27 | } 28 | 29 | pub(super) fn install_shims(state: &mut EmuState) { 30 | state.install_shim_function("dec2num", dec2num); 31 | } 32 | -------------------------------------------------------------------------------- /src/emulator/mac_gestalt.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{FourCC, OSErr, four_cc}; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}}; 4 | 5 | fn build_response(selector: FourCC) -> Option { 6 | let response = if selector == four_cc(*b"alis") { 7 | // alias manager present, without remote appletalk 8 | 1 9 | } else if selector == four_cc(*b"os ") { 10 | // no special OS features 11 | 0 12 | } else if selector == four_cc(*b"fold") { 13 | // FindFolder not available, whatever it is 14 | 0 15 | } else { 16 | return None; 17 | }; 18 | Some(response) 19 | } 20 | 21 | fn gestalt(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 22 | let (selector, response_ptr): (FourCC, u32) = reader.read2(uc)?; 23 | 24 | if let Some(response) = build_response(selector) { 25 | uc.write_u32(response_ptr, response)?; 26 | Ok(Some(0)) 27 | } else { 28 | Ok(Some(OSErr::GestaltUndefSelector.to_u32())) 29 | } 30 | } 31 | 32 | pub(super) fn install_shims(state: &mut EmuState) { 33 | state.install_shim_function("Gestalt", gestalt); 34 | } 35 | -------------------------------------------------------------------------------- /src/emulator/mac_low_mem.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, helpers::ArgReader}; 4 | 5 | fn lm_get_ticks(_uc: &mut EmuUC, state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 6 | let duration = (state.start_time.elapsed().as_millis() * 60) / 1000; 7 | Ok(Some(duration as u32)) 8 | } 9 | 10 | fn lm_get_time(_uc: &mut EmuUC, _state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 11 | // Assuming that this is the same as GetDateTime... hopefully? 12 | let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); 13 | let mac_time = now.as_secs() + 2082844800; 14 | Ok(Some((mac_time & 0xFFFFFFFF) as u32)) 15 | } 16 | 17 | fn lm_get_boot_drive(_uc: &mut EmuUC, _state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 18 | Ok(Some(0)) 19 | } 20 | 21 | fn lm_get_mem_err(_uc: &mut EmuUC, state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 22 | Ok(Some(state.mem_error.to_u32())) 23 | } 24 | 25 | pub(super) fn install_shims(state: &mut EmuState) { 26 | state.install_shim_function("LMGetTicks", lm_get_ticks); 27 | state.install_shim_function("LMGetTime", lm_get_time); 28 | state.install_shim_function("LMGetBootDrive", lm_get_boot_drive); 29 | state.install_shim_function("LMGetMemErr", lm_get_mem_err); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/emulator/mac_memory.rs: -------------------------------------------------------------------------------- 1 | use crate::common::OSErr; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}}; 4 | 5 | fn stub_return_void(_uc: &mut EmuUC, _state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 6 | Ok(None) 7 | } 8 | 9 | fn mem_error(_uc: &mut EmuUC, state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 10 | Ok(Some(state.mem_error.to_u32())) 11 | } 12 | 13 | fn temp_new_handle(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 14 | let size: u32 = reader.read1(uc)?; 15 | trace!("ignoring TempNewHandle({size})"); 16 | state.mem_error = OSErr::NotEnoughMemory; 17 | Ok(Some(0)) 18 | } 19 | 20 | fn new_handle(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 21 | let size: u32 = reader.read1(uc)?; 22 | let handle = state.heap.new_handle(uc, size)?; 23 | state.mem_error = if handle == 0 { OSErr::NotEnoughMemory } else { OSErr::NoError }; 24 | Ok(Some(handle)) 25 | } 26 | 27 | fn new_ptr(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 28 | let size: u32 = reader.read1(uc)?; 29 | let ptr = state.heap.new_ptr(uc, size)?; 30 | state.mem_error = if ptr == 0 { OSErr::NotEnoughMemory } else { OSErr::NoError }; 31 | Ok(Some(ptr)) 32 | } 33 | 34 | fn dispose_ptr(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 35 | let ptr: u32 = reader.read1(uc)?; 36 | state.mem_error = OSErr::NoError; 37 | state.heap.dispose_ptr(uc, ptr)?; 38 | Ok(None) 39 | } 40 | 41 | fn get_ptr_size(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 42 | let ptr: u32 = reader.read1(uc)?; 43 | state.mem_error = OSErr::NoError; 44 | Ok(Some(state.heap.get_ptr_size(uc, ptr)?)) 45 | } 46 | 47 | fn set_ptr_size(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 48 | let (ptr, new_size): (u32, u32) = reader.read2(uc)?; 49 | if state.heap.set_ptr_size(uc, ptr, new_size)? { 50 | state.mem_error = OSErr::NoError; 51 | } else { 52 | state.mem_error = OSErr::NotEnoughMemory; 53 | } 54 | Ok(None) 55 | } 56 | 57 | fn dispose_handle(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 58 | let handle: u32 = reader.read1(uc)?; 59 | state.mem_error = OSErr::NoError; 60 | state.heap.dispose_handle(uc, handle)?; 61 | Ok(None) 62 | } 63 | 64 | fn get_handle_size(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 65 | let handle: u32 = reader.read1(uc)?; 66 | if let Some(size) = state.heap.get_handle_size(uc, handle)? { 67 | state.mem_error = OSErr::NoError; 68 | Ok(Some(size)) 69 | } else { 70 | state.mem_error = OSErr::NilHandle; 71 | Ok(Some(0)) 72 | } 73 | } 74 | 75 | fn set_handle_size(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 76 | let (handle, new_size): (u32, u32) = reader.read2(uc)?; 77 | if state.heap.set_handle_size(uc, handle, new_size)? { 78 | state.mem_error = OSErr::NoError; 79 | } else { 80 | state.mem_error = OSErr::NotEnoughMemory; 81 | } 82 | Ok(None) 83 | } 84 | 85 | fn block_move_data(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 86 | let (src, dest, len): (u32, u32, u32) = reader.read3(uc)?; 87 | 88 | if src < dest { 89 | for i in 0..len { 90 | uc.write_u8(dest + i, uc.read_u8(src + i)?)?; 91 | } 92 | } else if src > dest { 93 | for i in 0..len { 94 | let inv_i = len - 1 - i; 95 | uc.write_u8(dest + inv_i, uc.read_u8(src + inv_i)?)?; 96 | } 97 | } 98 | 99 | Ok(None) 100 | } 101 | 102 | fn h_get_state(_uc: &mut EmuUC, _state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 103 | // We don't implement this 104 | Ok(Some(0)) 105 | } 106 | 107 | fn block_move(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 108 | let (src_ptr, dest_ptr, size): (u32, u32, u32) = reader.read3(uc)?; 109 | 110 | for i in 0..size { 111 | uc.write_u8(dest_ptr + i, uc.read_u8(src_ptr + i)?)?; 112 | } 113 | 114 | Ok(Some(0)) 115 | } 116 | 117 | fn ptr_and_hand(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 118 | let (ptr, handle, size): (u32, u32, u32) = reader.read3(uc)?; 119 | 120 | let current_size = state.heap.get_handle_size(uc, handle)?.unwrap(); 121 | if state.heap.set_handle_size(uc, handle, current_size + size)? { 122 | let dest = uc.read_u32(handle)? + current_size; 123 | for i in 0..size { 124 | uc.write_u8(dest + i, uc.read_u8(ptr + i)?)?; 125 | } 126 | Ok(Some(0)) 127 | } else { 128 | Ok(Some(OSErr::NotEnoughMemory.to_u32())) 129 | } 130 | } 131 | 132 | fn hand_and_hand(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 133 | let (hand1, hand2): (u32, u32) = reader.read2(uc)?; 134 | 135 | let src_size = state.heap.get_handle_size(uc, hand1)?.unwrap(); 136 | let dest_size = state.heap.get_handle_size(uc, hand2)?.unwrap(); 137 | if state.heap.set_handle_size(uc, hand2, dest_size + src_size)? { 138 | let src = uc.read_u32(hand1)?; 139 | let dest = uc.read_u32(hand2)? + dest_size; 140 | for i in 0..src_size { 141 | uc.write_u8(dest + i, uc.read_u8(src + i)?)?; 142 | } 143 | Ok(Some(0)) 144 | } else { 145 | Ok(Some(OSErr::NotEnoughMemory.to_u32())) 146 | } 147 | } 148 | 149 | pub(super) fn install_shims(state: &mut EmuState) { 150 | state.install_shim_function("MemError", mem_error); 151 | state.install_shim_function("NewHandle", new_handle); 152 | state.install_shim_function("NewHandleClear", new_handle); 153 | state.install_shim_function("NewPtr", new_ptr); 154 | state.install_shim_function("NewPtrClear", new_ptr); 155 | state.install_shim_function("HLock", stub_return_void); 156 | state.install_shim_function("HUnlock", stub_return_void); 157 | state.install_shim_function("HLockHi", stub_return_void); 158 | state.install_shim_function("MoveHHi", stub_return_void); 159 | state.install_shim_function("DisposePtr", dispose_ptr); 160 | state.install_shim_function("GetPtrSize", get_ptr_size); 161 | state.install_shim_function("SetPtrSize", set_ptr_size); 162 | state.install_shim_function("DisposeHandle", dispose_handle); 163 | state.install_shim_function("GetHandleSize", get_handle_size); 164 | state.install_shim_function("SetHandleSize", set_handle_size); 165 | state.install_shim_function("BlockMoveData", block_move_data); 166 | state.install_shim_function("HGetState", h_get_state); 167 | state.install_shim_function("HSetState", stub_return_void); 168 | state.install_shim_function("BlockMove", block_move); 169 | state.install_shim_function("PtrAndHand", ptr_and_hand); 170 | state.install_shim_function("HandAndHand", hand_and_hand); 171 | 172 | state.install_shim_function("TempNewHandle", temp_new_handle); 173 | } 174 | -------------------------------------------------------------------------------- /src/emulator/mac_os_utils.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | 3 | use crate::common::get_mac_time; 4 | 5 | use super::{EmuState, EmuUC, FuncResult, helpers::ArgReader}; 6 | 7 | fn get_date_time(_uc: &mut EmuUC, _state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 8 | Ok(Some(get_mac_time(Local::now()))) 9 | } 10 | 11 | fn tick_count(_uc: &mut EmuUC, state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 12 | let duration = (state.start_time.elapsed().as_millis() * 60) / 1000; 13 | Ok(Some(duration as u32)) 14 | } 15 | 16 | fn trap_available(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 17 | let trap: u16 = reader.read1(uc)?; 18 | if trap == 0xA1AD { 19 | // Gestalt is available 20 | Ok(Some(1)) 21 | } else { 22 | // nothing else is available, according to us 23 | warn!(target: "os_utils", "Unknown trap for TrapAvailable: 0x{trap:04X} (reporting unavailable)"); 24 | Ok(Some(0)) 25 | } 26 | } 27 | 28 | pub(super) fn install_shims(state: &mut EmuState) { 29 | state.install_shim_function("GetDateTime", get_date_time); 30 | 31 | // this is actually in Events.h but shhh 32 | state.install_shim_function("TickCount", tick_count); 33 | 34 | // not sure where this lies...? 35 | state.install_shim_function("TrapAvailable", trap_available); 36 | } 37 | -------------------------------------------------------------------------------- /src/emulator/mac_quickdraw.rs: -------------------------------------------------------------------------------- 1 | use super::{EmuState, EmuUC, FuncResult, helpers::ArgReader}; 2 | 3 | fn get_cursor(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 4 | let id: i16 = reader.read1(uc)?; 5 | 6 | // DumpPEF wants to dereference a cursor handle, so we'd better give it something 7 | if state.dummy_cursor_handle.is_none() { 8 | state.dummy_cursor_handle = Some(state.heap.new_handle(uc, 0x20)?); 9 | }; 10 | 11 | info!(target: "quickdraw", "GetCursor({id}) - unimplemented"); 12 | Ok(Some(state.dummy_cursor_handle.unwrap())) 13 | } 14 | 15 | fn init_graf(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 16 | let global_ptr: u32 = reader.read1(uc)?; 17 | info!(target: "quickdraw", "InitGraf(globalPtr = {global_ptr:08X}) - unimplemented"); 18 | Ok(None) 19 | } 20 | 21 | fn set_cursor(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 22 | let crsr: u32 = reader.read1(uc)?; 23 | info!(target: "quickdraw", "SetCursor(crsr = {crsr:08X}) - unimplemented"); 24 | Ok(None) 25 | } 26 | 27 | pub(super) fn install_shims(state: &mut EmuState) { 28 | state.install_shim_function("GetCursor", get_cursor); 29 | state.install_shim_function("InitGraf", init_graf); 30 | state.install_shim_function("SetCursor", set_cursor); 31 | } 32 | -------------------------------------------------------------------------------- /src/emulator/mac_resources.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use crate::{common::{FourCC, OSErr, four_cc}, resources}; 4 | 5 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}, UcResult}; 6 | 7 | fn update_res_file_internal(uc: &mut EmuUC, state: &mut EmuState, ref_num: u16) -> UcResult { 8 | let resources = state.resource_files.get_mut(&ref_num).unwrap(); 9 | 10 | // restore all loaded resources 11 | for (cache_key, &handle) in state.loaded_resources.iter() { 12 | if cache_key.0 == ref_num { 13 | let res = resources.get(cache_key.1, cache_key.2).unwrap(); 14 | let mut res = res.borrow_mut(); 15 | 16 | let ptr = uc.read_u32(handle)?; 17 | let size = state.heap.get_handle_size(uc, handle)?.unwrap(); 18 | res.data.resize(size as usize, 0); 19 | uc.mem_read(ptr.into(), &mut res.data)?; 20 | } 21 | } 22 | 23 | resources.save_to_file(); 24 | let mut file = resources.file.borrow_mut(); 25 | match file.save_if_dirty() { 26 | Ok(()) => Ok(true), 27 | Err(e) => { 28 | error!(target: "resources", "failed to save modified resources file {:?}: {:?}", file.path, e); 29 | Ok(false) 30 | } 31 | } 32 | } 33 | 34 | fn close_res_file(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 35 | let ref_num: u16 = reader.read1(uc)?; 36 | 37 | trace!(target: "resources", "CloseResFile({ref_num})"); 38 | 39 | if state.resource_files.contains_key(&ref_num) { 40 | state.res_error = OSErr::NoError; 41 | 42 | // stage 1: update 43 | if !update_res_file_internal(uc, state, ref_num)? { 44 | state.res_error = OSErr::IOError; 45 | return Ok(None); 46 | } 47 | 48 | // stage 2: get rid of all loaded resources from this fork 49 | for (cache_key, &handle) in state.loaded_resources.iter() { 50 | if cache_key.0 == ref_num { 51 | state.heap.dispose_handle(uc, handle)?; 52 | } 53 | } 54 | state.loaded_resources.retain(|cache_key, _| cache_key.0 != ref_num); 55 | 56 | // stage 3: get rid of the resources 57 | state.resource_files.remove(&ref_num); 58 | 59 | if state.active_resource_file == ref_num { 60 | // find another one to put in 61 | state.active_resource_file = *state.resource_files.keys().max().unwrap(); 62 | trace!(target: "resources", "Active resource file has been set to {}", state.active_resource_file); 63 | } 64 | } else { 65 | state.res_error = OSErr::ResFileNotFound; 66 | } 67 | 68 | Ok(None) 69 | } 70 | 71 | fn res_error(_uc: &mut EmuUC, state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 72 | Ok(Some(state.res_error as u32)) 73 | } 74 | 75 | fn cur_res_file(_uc: &mut EmuUC, state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 76 | Ok(Some(state.active_resource_file as u32)) 77 | } 78 | 79 | fn use_res_file(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 80 | let new_file: u16 = reader.read1(uc)?; 81 | 82 | if state.resource_files.contains_key(&new_file) { 83 | let old_file = state.active_resource_file; 84 | trace!(target: "resources", "changing resource files from {old_file} to {new_file}"); 85 | state.active_resource_file = new_file; 86 | state.res_error = OSErr::NoError; 87 | } else { 88 | state.res_error = OSErr::ResFileNotFound; 89 | } 90 | 91 | Ok(None) 92 | } 93 | 94 | fn set_res_load(_uc: &mut EmuUC, _state: &mut EmuState, _reader: &mut ArgReader) -> FuncResult { 95 | // do nothing here... 96 | Ok(Some(0)) 97 | } 98 | 99 | fn get_resource(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 100 | let (ty, id): (FourCC, i16) = reader.read2(uc)?; 101 | let cache_key = (state.active_resource_file, ty, id); 102 | 103 | trace!(target: "resources", "GetResource({ty:?}, {id}) [active file is {}]", state.active_resource_file); 104 | 105 | state.res_error = OSErr::NoError; 106 | 107 | if let Some(&handle) = state.loaded_resources.get_by_left(&cache_key) { 108 | // easy mode 109 | return Ok(Some(handle)); 110 | } 111 | 112 | let resources = state.resource_files.get(&state.active_resource_file).unwrap(); 113 | if let Some(res) = resources.get(ty, id) { 114 | let res = res.borrow(); 115 | let handle = state.heap.new_handle(uc, res.data.len() as u32)?; 116 | if handle != 0 { 117 | let ptr = uc.read_u32(handle)?; 118 | uc.mem_write(ptr.into(), &res.data)?; 119 | state.loaded_resources.insert(cache_key, handle); 120 | return Ok(Some(handle)); 121 | } 122 | } 123 | 124 | state.res_error = OSErr::ResNotFound; 125 | Ok(Some(0)) 126 | } 127 | 128 | fn release_resource(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 129 | let handle: u32 = reader.read1(uc)?; 130 | match state.loaded_resources.get_by_right(&handle) { 131 | Some(_) => { 132 | state.res_error = OSErr::NoError; 133 | 134 | // lose it 135 | state.heap.dispose_handle(uc, handle)?; 136 | state.loaded_resources.remove_by_right(&handle); 137 | 138 | // might need to keep track of ResChanged on the resource...? 139 | // "Be aware that ReleaseResource won't release a resource whose resChanged attribute has been set, but ResError still returns the result code noErr." 140 | } 141 | None => { 142 | state.res_error = OSErr::ResNotFound; 143 | } 144 | } 145 | 146 | Ok(None) 147 | } 148 | 149 | fn detach_resource(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 150 | let handle: u32 = reader.read1(uc)?; 151 | match state.loaded_resources.get_by_right(&handle) { 152 | Some(_) => { 153 | state.res_error = OSErr::NoError; 154 | state.loaded_resources.remove_by_right(&handle); 155 | } 156 | None => { 157 | state.res_error = OSErr::ResNotFound; 158 | } 159 | } 160 | 161 | Ok(None) 162 | } 163 | 164 | fn add_resource(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 165 | let (data_handle, type_id, res_id, name_ptr): (u32, FourCC, i16, u32) = reader.read4(uc)?; 166 | 167 | trace!(target: "resources", "AddResource(data={data_handle:08X}, type={type_id:?}, id={res_id}, name_ptr={name_ptr:08X})"); 168 | state.res_error = OSErr::AddResFailed; 169 | 170 | if data_handle == 0 { 171 | error!(target: "resources", "AddResource called with nil handle"); 172 | return Ok(None); 173 | } 174 | if state.loaded_resources.contains_right(&data_handle) { 175 | error!(target: "resources", "AddResource called with a handle that's already attached to a resource"); 176 | return Ok(None); 177 | } 178 | 179 | let name = if name_ptr == 0 { 180 | None 181 | } else { 182 | Some(uc.read_pascal_string(name_ptr)?.into_bytes()) 183 | }; 184 | 185 | let resources = state.resource_files.get_mut(&state.active_resource_file).unwrap(); 186 | match resources.add(type_id, res_id, name) { 187 | Some(res) => { 188 | let mut res = res.borrow_mut(); 189 | 190 | let data_ptr = uc.read_u32(data_handle)?; 191 | let data_size = state.heap.get_handle_size(uc, data_handle)?.unwrap(); 192 | res.data.resize(data_size as usize, 0); 193 | uc.mem_read(data_ptr.into(), &mut res.data)?; 194 | 195 | // we've taken ownership of the handle for this 196 | let cache_key = (state.active_resource_file, type_id, res_id); 197 | state.loaded_resources.insert(cache_key, data_handle); 198 | } 199 | None => { 200 | error!(target: "resources", "AddResource trying to add a duplicate resource ({type_id:?}, {res_id})"); 201 | return Ok(None); 202 | } 203 | } 204 | 205 | state.res_error = OSErr::NoError; 206 | Ok(None) 207 | } 208 | 209 | fn remove_resource(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 210 | let handle: u32 = reader.read1(uc)?; 211 | 212 | match state.loaded_resources.get_by_right(&handle) { 213 | Some(&cache_key) => { 214 | // technically, we may be leaking a handle here, but that's probably fine for our needs 215 | state.res_error = OSErr::NoError; 216 | state.loaded_resources.remove_by_right(&handle); 217 | 218 | let rf = state.resource_files.get_mut(&cache_key.0).unwrap(); 219 | rf.remove(cache_key.1, cache_key.2); 220 | } 221 | None => { 222 | state.res_error = OSErr::ResNotFound; 223 | } 224 | } 225 | 226 | Ok(None) 227 | } 228 | 229 | fn update_res_file(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 230 | let ref_num: u16 = reader.read1(uc)?; 231 | 232 | if state.resource_files.contains_key(&ref_num) { 233 | if update_res_file_internal(uc, state, ref_num)? { 234 | state.res_error = OSErr::NoError; 235 | } else { 236 | state.res_error = OSErr::IOError; 237 | } 238 | } else { 239 | state.res_error = OSErr::ResNotFound; 240 | } 241 | 242 | Ok(None) 243 | } 244 | 245 | fn h_create_res_file(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 246 | let (volume_ref, dir_id, name): (i16, i32, CString) = reader.pstr().read3(uc)?; 247 | info!(target: "resources", "HCreateResFile(vol={volume_ref}, dir={dir_id}, name={name:?})"); 248 | 249 | let path = match state.filesystem.resolve_path(volume_ref, dir_id, name.as_bytes()) { 250 | Ok(p) => p, 251 | Err(e) => { 252 | error!(target: "resources", "HCreateResFile failed to resolve path: {e:?}"); 253 | state.res_error = OSErr::BadName; 254 | return Ok(None); 255 | } 256 | }; 257 | 258 | if !path.exists() { 259 | // make an empty file 260 | match state.filesystem.create_file(&path, four_cc(*b"????"), four_cc(*b"????")) { 261 | Ok(()) => {} 262 | Err(e) => { 263 | error!(target: "resources", "HCreateResFile failed to create file: {e:?}"); 264 | state.res_error = OSErr::IOError; 265 | return Ok(None); 266 | } 267 | } 268 | } 269 | 270 | match state.filesystem.get_file(&path) { 271 | Ok(f) => { 272 | let mut file = f.borrow_mut(); 273 | if file.resource_fork.is_empty() { 274 | trace!(target: "resources", "creating empty resource map"); 275 | 276 | // create an empty resource map 277 | file.resource_fork.resize(286, 0); 278 | 279 | // main header 280 | file.resource_fork[2] = 1; // set data offset to 0x100 281 | file.resource_fork[6] = 1; // set map offset to 0x100 282 | file.resource_fork[15] = 30; // set map size to 30 bytes 283 | 284 | // copy of header 285 | file.resource_fork[256 + 2] = 1; // set data offset to 0x100 286 | file.resource_fork[256 + 6] = 1; // set map offset to 0x100 287 | file.resource_fork[256 + 15] = 30; // set map size to 30 bytes 288 | file.resource_fork[256 + 25] = 28; // type list offset 289 | file.resource_fork[256 + 27] = 30; // name list offset 290 | file.resource_fork[256 + 28] = 0xFF; // number of types is -1 291 | file.resource_fork[256 + 29] = 0xFF; // number of types is -1 292 | 293 | file.set_dirty(); 294 | } else { 295 | trace!(target: "resources", "resource map already existed"); 296 | } 297 | 298 | state.res_error = OSErr::NoError; 299 | }, 300 | Err(e) => { 301 | error!(target: "resources", "HCreateResFile failed to get file: {e:?}"); 302 | state.res_error = OSErr::IOError; 303 | } 304 | } 305 | 306 | Ok(None) 307 | } 308 | 309 | fn f_sp_open_res_file(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 310 | let (spec_ptr, permission): (u32, i8) = reader.read2(uc)?; 311 | let volume = uc.read_i16(spec_ptr)?; 312 | let dir_id = uc.read_i32(spec_ptr + 2)?; 313 | let name = uc.read_pascal_string(spec_ptr + 6)?; 314 | 315 | info!(target: "resources", "FSpOpenResFile(vol={volume}, dir={dir_id}, name={name:?}, permission={permission})"); 316 | 317 | let path = match state.filesystem.resolve_path(volume, dir_id, name.as_bytes()) { 318 | Ok(p) => p, 319 | Err(e) => { 320 | error!(target: "resources", "FSpOpenResFile failed to resolve path: {e:?}"); 321 | state.res_error = OSErr::BadName; 322 | return Ok(Some(0xFFFFFFFF)); 323 | } 324 | }; 325 | 326 | let file = match state.filesystem.get_file(&path) { 327 | Ok(f) => f, 328 | Err(e) => { 329 | error!(target: "resources", "FSpOpenResFile failed to get file: {e:?}"); 330 | state.res_error = OSErr::FileNotFound; 331 | return Ok(Some(0xFFFFFFFF)); 332 | } 333 | }; 334 | 335 | // Oh boy this is fun... 336 | let resources = match resources::parse_resources(file) { 337 | Ok(r) => r, 338 | Err(e) => { 339 | error!(target: "resources", "FSpOpenResFile failed to parse resource fork: {e:?}"); 340 | state.res_error = OSErr::MapRead; 341 | return Ok(Some(0xFFFFFFFF)); 342 | } 343 | }; 344 | 345 | let rf_id = state.next_resource_file; 346 | state.next_resource_file += 1; 347 | state.resource_files.insert(rf_id, resources); 348 | state.active_resource_file = rf_id; 349 | state.res_error = OSErr::NoError; 350 | 351 | info!(target: "resources", "... returned handle {rf_id}, made active"); 352 | Ok(Some(rf_id as u32)) 353 | } 354 | 355 | pub(super) fn install_shims(state: &mut EmuState) { 356 | state.install_shim_function("CloseResFile", close_res_file); 357 | state.install_shim_function("ResError", res_error); 358 | state.install_shim_function("CurResFile", cur_res_file); 359 | // short HomeResFile(Handle theResource) 360 | // void CreateResFile(ConstStr255Param fileName) 361 | // short OpenResFile(ConstStr255Param fileName) 362 | state.install_shim_function("UseResFile", use_res_file); 363 | // short CountTypes() 364 | // short Count1Types() 365 | // void GetIndType(ResType *theType, short index) 366 | // void Get1IndType(ResType *theType, short index) 367 | state.install_shim_function("SetResLoad", set_res_load); 368 | // short CountResources(ResType theType) 369 | // short Count1Resources(ResType theType) 370 | // Handle GetIndResource(ResType theType, short index) 371 | // Handle Get1IndResource(ResType theType, short index) 372 | state.install_shim_function("GetResource", get_resource); 373 | state.install_shim_function("Get1Resource", get_resource); 374 | // Handle GetNamedResource(ResType theType, ConstStr255Param name) 375 | // Handle Get1NamedResource(ResType theType, ConstStr255Param name) 376 | // void LoadResource(Handle theResource) 377 | state.install_shim_function("ReleaseResource", release_resource); 378 | state.install_shim_function("DetachResource", detach_resource); 379 | // short UniqueID(ResType theType) 380 | // short Unique1ID(ResType theType) 381 | // short GetResAttrs(Handle theResource) 382 | // void GetResInfo(Handle theResource, short *theID, ResType *theType, Str255 name) 383 | // void SetResInfo(Handle theResource, short theID, ConstStr255Param name) 384 | state.install_shim_function("AddResource", add_resource); 385 | // long GetResourceSizeOnDisk(Handle theResource) 386 | // long GetMaxResourceSize(Handle theResource) 387 | // long RsrcMapEntry(Handle theResource) 388 | // void SetResAttrs(Handle theResource, short attrs) 389 | // void ChangedResource(Handle theResource) 390 | state.install_shim_function("RemoveResource", remove_resource); 391 | state.install_shim_function("UpdateResFile", update_res_file); 392 | // void WriteResource(Handle theResource) 393 | state.install_shim_function("HCreateResFile", h_create_res_file); 394 | state.install_shim_function("FSpOpenResFile", f_sp_open_res_file); 395 | } 396 | -------------------------------------------------------------------------------- /src/emulator/mac_text_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::common::{four_cc, parse_mac_time}; 2 | 3 | use super::{EmuState, EmuUC, FuncResult, helpers::{ArgReader, UnicornExtras}}; 4 | 5 | fn generic_get_ind_string(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader, pascal: bool) -> FuncResult { 6 | let (ptr, table_id, mut str_id): (u32, i16, i16) = reader.read3(uc)?; 7 | 8 | trace!(target: "text_utils", "GetIndString(table={table_id}, str={str_id})"); 9 | 10 | if let Some(res) = state.resource_files.get(&state.active_resource_file).unwrap().get(four_cc(*b"STR#"), table_id) { 11 | let res = res.borrow(); 12 | 13 | let mut offset = 2; 14 | while offset < res.data.len() && str_id > 1 { 15 | str_id -= 1; 16 | offset += res.data[offset] as usize; 17 | offset += 1; 18 | } 19 | 20 | if offset < res.data.len() { 21 | let len = res.data[offset] as usize; 22 | let s = &res.data[offset + 1 .. offset + len + 1]; 23 | if pascal { 24 | uc.write_pascal_string(ptr, s)?; 25 | } else { 26 | uc.write_c_string(ptr, s)?; 27 | } 28 | return Ok(None); 29 | } 30 | 31 | warn!(target: "text_utils", "GetIndString failed to find string {str_id} in STR# table {table_id}"); 32 | } else { 33 | warn!(target: "text_utils", "GetIndString failed to find STR# table {table_id}"); 34 | } 35 | 36 | // write empty string as penance 37 | if pascal { 38 | uc.write_pascal_string(ptr, b"")?; 39 | } else { 40 | uc.write_c_string(ptr, b"")?; 41 | } 42 | Ok(None) 43 | } 44 | 45 | fn pascal_get_ind_string(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 46 | generic_get_ind_string(uc, state, reader, true) 47 | } 48 | 49 | fn c_get_ind_string(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 50 | generic_get_ind_string(uc, state, reader, false) 51 | } 52 | 53 | fn numtostring(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 54 | let (num, ptr): (i32, u32) = reader.read2(uc)?; 55 | let s = format!("{}", num); 56 | uc.write_c_string(ptr, s.as_bytes())?; 57 | Ok(None) 58 | } 59 | 60 | fn iudatestring(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 61 | let (date, long_flag, ptr): (u32, u32, u32) = reader.read3(uc)?; 62 | let date = parse_mac_time(date); 63 | let s = match long_flag { 64 | 2 => { 65 | // longDate: Friday, January 31, 1992 66 | date.format("%A, %B %d, %Y") 67 | } 68 | 1 => { 69 | // abbrevDate: Fri, Jan 31, 1992 70 | date.format("%a, %m %d, %Y") 71 | } 72 | _ => { 73 | // shortDate: 1/31/92 74 | date.format("%m/%d/%y") 75 | } 76 | }.to_string(); 77 | uc.write_c_string(ptr, s.as_bytes())?; 78 | Ok(None) 79 | } 80 | 81 | fn iutimestring(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 82 | let (date, want_seconds, ptr): (u32, bool, u32) = reader.read3(uc)?; 83 | let date = parse_mac_time(date); 84 | let s = if want_seconds { 85 | date.format("%H:%M:%S") 86 | } else { 87 | date.format("%H:%M") 88 | }.to_string(); 89 | uc.write_c_string(ptr, s.as_bytes())?; 90 | Ok(None) 91 | } 92 | 93 | fn c2pstr(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 94 | let ptr: u32 = reader.read1(uc)?; 95 | let mut i = 0; 96 | let mut c = uc.read_u8(ptr)?; 97 | while c != 0 { 98 | let next_c = uc.read_u8(ptr + i + 1)?; 99 | uc.write_u8(ptr + i + 1, c)?; 100 | c = next_c; 101 | i += 1; 102 | } 103 | uc.write_u8(ptr, i as u8)?; 104 | Ok(Some(ptr)) 105 | } 106 | 107 | fn p2cstr(uc: &mut EmuUC, _state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 108 | let ptr: u32 = reader.read1(uc)?; 109 | let len = uc.read_u8(ptr)? as u32; 110 | for i in 0..len { 111 | uc.write_u8(ptr + i, uc.read_u8(ptr + i + 1)?)?; 112 | } 113 | uc.write_u8(ptr + len, 0)?; 114 | Ok(Some(ptr)) 115 | } 116 | 117 | pub(super) fn install_shims(state: &mut EmuState) { 118 | state.install_shim_function("GetIndString", pascal_get_ind_string); 119 | state.install_shim_function("getindstring", c_get_ind_string); 120 | state.install_shim_function("numtostring", numtostring); 121 | state.install_shim_function("iudatestring", iudatestring); 122 | state.install_shim_function("iutimestring", iutimestring); 123 | state.install_shim_function("c2pstr", c2pstr); 124 | state.install_shim_function("p2cstr", p2cstr); 125 | } 126 | -------------------------------------------------------------------------------- /src/emulator/mod.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::collections::HashMap; 3 | use std::rc::Rc; 4 | use std::time::Instant; 5 | 6 | use anyhow::Result; 7 | use bimap::BiHashMap; 8 | use unicorn_engine::{Unicorn, RegisterPPC}; 9 | use unicorn_engine::unicorn_const::{Arch, HookType, Mode, Permission}; 10 | 11 | use crate::common::{FourCC, OSErr}; 12 | use crate::{linker, filesystem, pef}; 13 | use crate::emulator::helpers::UnicornExtras; 14 | use crate::resources::Resources; 15 | 16 | mod c_ctype; 17 | mod c_fenv; 18 | mod c_stdio; 19 | mod c_stdlib; 20 | mod c_string; 21 | mod c_time; 22 | mod flex_lm; 23 | mod heap; 24 | mod helpers; 25 | mod interface_lib; 26 | mod mac_files; 27 | mod mac_fp; 28 | mod mac_gestalt; 29 | mod mac_low_mem; 30 | mod mac_memory; 31 | mod mac_os_utils; 32 | mod mac_quickdraw; 33 | mod mac_resources; 34 | mod mac_text_utils; 35 | mod std_c_lib; 36 | 37 | type UcResult = Result; 38 | 39 | type LibraryShim = fn(&mut EmuUC, &mut EmuState, &mut helpers::ArgReader) -> UcResult>; 40 | 41 | struct ShimSymbol { 42 | shim_address: u32, 43 | class: pef::SymbolClass, 44 | library_name: String, 45 | name: String, 46 | func: Option 47 | } 48 | 49 | struct EmuState { 50 | start_time: Instant, 51 | hle_functions: HashMap, 52 | dyn_stubs: HashMap, 53 | dyn_functions: Vec, 54 | missing_dyn_functions: Vec<(String, String)>, 55 | sc_thunk_addr: u32, 56 | imports: Vec, 57 | dummy_cursor_handle: Option, 58 | resource_files: HashMap, 59 | active_resource_file: u16, 60 | next_resource_file: u16, 61 | loaded_resources: BiHashMap<(u16, FourCC, i16), u32>, 62 | env_var_map: HashMap, 63 | strtok_state: u32, 64 | stdio_files: HashMap, 65 | file_handles: HashMap, 66 | next_file_handle: u16, 67 | next_checkout: u32, 68 | checkouts: HashMap, 69 | exit_status: Option, 70 | heap: heap::Heap, 71 | filesystem: filesystem::FileSystem, 72 | mem_error: OSErr, 73 | res_error: OSErr 74 | } 75 | 76 | impl EmuState { 77 | fn new(exe: &linker::Executable, resources: Resources) -> Self { 78 | let mut state = EmuState { 79 | start_time: Instant::now(), 80 | hle_functions: HashMap::new(), 81 | dyn_stubs: HashMap::new(), 82 | dyn_functions: Vec::new(), 83 | missing_dyn_functions: Vec::new(), 84 | sc_thunk_addr: exe.sc_thunk_addr, 85 | imports: Vec::new(), 86 | dummy_cursor_handle: None, 87 | resource_files: HashMap::new(), 88 | active_resource_file: 3, 89 | next_resource_file: 4, 90 | loaded_resources: BiHashMap::new(), 91 | env_var_map: HashMap::new(), 92 | strtok_state: 0, 93 | stdio_files: HashMap::new(), 94 | file_handles: HashMap::new(), 95 | next_file_handle: 4, 96 | next_checkout: 0x10000000, 97 | checkouts: HashMap::new(), 98 | exit_status: None, 99 | heap: heap::Heap::new(0x30000000, 1024 * 1024 * 32, 512), 100 | filesystem: filesystem::FileSystem::new(), 101 | mem_error: OSErr::NoError, 102 | res_error: OSErr::NoError 103 | }; 104 | 105 | state.resource_files.insert(state.active_resource_file, resources); 106 | 107 | for (import, shim_address) in exe.imports.iter().zip(&exe.shim_addrs) { 108 | if import.class == pef::SymbolClass::Data { 109 | trace!(target: "emulator", "(!) Data import: {}", import.name); 110 | } 111 | 112 | state.imports.push(ShimSymbol { 113 | shim_address: *shim_address, 114 | class: import.class, 115 | library_name: exe.libraries[import.library].clone(), 116 | name: import.name.clone(), 117 | func: None 118 | }); 119 | } 120 | 121 | state 122 | } 123 | 124 | fn get_shim_addr(&mut self, uc: &mut EmuUC, name: &str) -> UcResult> { 125 | for import in &self.imports { 126 | if import.name == name { 127 | return Ok(Some(import.shim_address)); 128 | } 129 | } 130 | 131 | // just allocate some space 132 | let addr = self.heap.new_ptr(uc, 0x1000)?; 133 | self.dyn_stubs.insert(String::from(name), addr); 134 | Ok(Some(addr)) 135 | } 136 | 137 | fn install_shim_function(&mut self, name: &str, func: LibraryShim) { 138 | for import in &mut self.imports { 139 | if import.name == name { 140 | import.func = Some(func); 141 | } 142 | } 143 | 144 | self.hle_functions.insert(String::from(name), func); 145 | } 146 | 147 | fn find_stub(&mut self, uc: &mut EmuUC, lib_name: &str, func_name: &str) -> UcResult { 148 | if let Some(stub) = self.dyn_stubs.get(func_name) { 149 | return Ok(*stub); 150 | } 151 | 152 | let stub = self.heap.new_ptr(uc, 12)?; 153 | uc.write_u32(stub.into(), self.sc_thunk_addr)?; 154 | self.dyn_stubs.insert(String::from(func_name), stub); 155 | 156 | if let Some(func) = self.hle_functions.get(func_name) { 157 | let id = self.dyn_functions.len() as u32; 158 | uc.write_u32((stub + 4).into(), id)?; 159 | uc.write_u32((stub + 8).into(), 101)?; 160 | 161 | self.dyn_functions.push(*func); 162 | } else { 163 | warn!("Executable dynamically imports missing function from {lib_name}: {func_name}"); 164 | let id = self.missing_dyn_functions.len() as u32; 165 | uc.write_u32((stub + 4).into(), id)?; 166 | uc.write_u32((stub + 8).into(), 404)?; 167 | 168 | self.missing_dyn_functions.push((String::from(lib_name), String::from(func_name))); 169 | } 170 | 171 | Ok(stub) 172 | } 173 | } 174 | 175 | type EmuUC<'a> = Unicorn<'a, Rc>>; 176 | 177 | #[allow(dead_code)] 178 | fn code_hook(_uc: &mut EmuUC, _addr: u64, _size: u32) { 179 | } 180 | 181 | fn intr_hook(uc: &mut EmuUC, _number: u32) { 182 | let tvect = uc.reg_read(RegisterPPC::R12).unwrap(); 183 | let rtoc = uc.reg_read(RegisterPPC::R2).unwrap(); 184 | let lr = uc.reg_read(RegisterPPC::LR).unwrap(); 185 | let pc = uc.pc_read().unwrap(); 186 | let code = uc.read_u32((tvect + 8) as u32).unwrap(); 187 | 188 | let state = Rc::clone(uc.get_data()); 189 | let mut state = state.borrow_mut(); 190 | 191 | if state.exit_status.is_some() { 192 | // we have exited, go away 193 | // (unicorn keeps running code afterwards) 194 | uc.emu_stop().unwrap(); 195 | return; 196 | } 197 | 198 | match code { 199 | 100 => match state.imports[rtoc as usize].func { 200 | Some(func) => { 201 | let mut arg_reader = helpers::ArgReader::new(); 202 | match func(uc, &mut state, &mut arg_reader) { 203 | Ok(Some(result)) => uc.reg_write(RegisterPPC::R3, result.into()).unwrap(), 204 | Ok(None) => {}, 205 | Err(e) => { 206 | error!(target: "emulator", "Error {e:?} while executing {} (lr={lr:08x})", state.imports[rtoc as usize].name); 207 | } 208 | } 209 | } 210 | None => { 211 | error!( 212 | target: "emulator", 213 | "Unimplemented call to {}::{} @{lr:08X}", 214 | state.imports[rtoc as usize].library_name, 215 | state.imports[rtoc as usize].name 216 | ); 217 | } 218 | } 219 | 101 => { 220 | let func = state.dyn_functions[rtoc as usize]; 221 | let mut arg_reader = helpers::ArgReader::new(); 222 | match func(uc, &mut state, &mut arg_reader) { 223 | Ok(Some(result)) => uc.reg_write(RegisterPPC::R3, result.into()).unwrap(), 224 | Ok(None) => {}, 225 | Err(e) => { 226 | error!(target: "emulator", "Error {e:?} while executing {} (lr={lr:08x})", state.imports[rtoc as usize].name); 227 | } 228 | } 229 | } 230 | 404 => { 231 | error!( 232 | target: "emulator", 233 | "Unimplemented dynamic call to {}::{} @{lr:08X}", 234 | state.missing_dyn_functions[rtoc as usize].0, 235 | state.missing_dyn_functions[rtoc as usize].1 236 | ); 237 | } 238 | _ => error!( 239 | target: "emulator", 240 | "Unknown code in hooked transition vector: {code} (at {tvect:08X})" 241 | ) 242 | } 243 | 244 | // NOTE: next unicorn will not need this i think? 245 | uc.set_pc(pc + 4).unwrap(); 246 | } 247 | 248 | 249 | type FuncResult = UcResult>; 250 | 251 | fn dump_context(uc: &EmuUC) { 252 | println!(" PC: {:08x} / LR: {:08x}", uc.pc_read().unwrap(), uc.reg_read(RegisterPPC::LR).unwrap()); 253 | println!(" R00: {:08x} / R08: {:08x} / R16: {:08x} / R24: {:08x}", uc.reg_read(RegisterPPC::R0).unwrap(), uc.reg_read(RegisterPPC::R8).unwrap(), uc.reg_read(RegisterPPC::R16).unwrap(), uc.reg_read(RegisterPPC::R24).unwrap()); 254 | println!(" R01: {:08x} / R09: {:08x} / R17: {:08x} / R25: {:08x}", uc.reg_read(RegisterPPC::R1).unwrap(), uc.reg_read(RegisterPPC::R9).unwrap(), uc.reg_read(RegisterPPC::R17).unwrap(), uc.reg_read(RegisterPPC::R25).unwrap()); 255 | println!(" R02: {:08x} / R10: {:08x} / R18: {:08x} / R26: {:08x}", uc.reg_read(RegisterPPC::R2).unwrap(), uc.reg_read(RegisterPPC::R10).unwrap(), uc.reg_read(RegisterPPC::R18).unwrap(), uc.reg_read(RegisterPPC::R26).unwrap()); 256 | println!(" R03: {:08x} / R11: {:08x} / R19: {:08x} / R27: {:08x}", uc.reg_read(RegisterPPC::R3).unwrap(), uc.reg_read(RegisterPPC::R11).unwrap(), uc.reg_read(RegisterPPC::R19).unwrap(), uc.reg_read(RegisterPPC::R27).unwrap()); 257 | println!(" R04: {:08x} / R12: {:08x} / R20: {:08x} / R28: {:08x}", uc.reg_read(RegisterPPC::R4).unwrap(), uc.reg_read(RegisterPPC::R12).unwrap(), uc.reg_read(RegisterPPC::R20).unwrap(), uc.reg_read(RegisterPPC::R28).unwrap()); 258 | println!(" R05: {:08x} / R13: {:08x} / R21: {:08x} / R29: {:08x}", uc.reg_read(RegisterPPC::R5).unwrap(), uc.reg_read(RegisterPPC::R13).unwrap(), uc.reg_read(RegisterPPC::R21).unwrap(), uc.reg_read(RegisterPPC::R29).unwrap()); 259 | println!(" R06: {:08x} / R14: {:08x} / R22: {:08x} / R30: {:08x}", uc.reg_read(RegisterPPC::R6).unwrap(), uc.reg_read(RegisterPPC::R14).unwrap(), uc.reg_read(RegisterPPC::R22).unwrap(), uc.reg_read(RegisterPPC::R30).unwrap()); 260 | println!(" R07: {:08x} / R15: {:08x} / R23: {:08x} / R31: {:08x}", uc.reg_read(RegisterPPC::R7).unwrap(), uc.reg_read(RegisterPPC::R15).unwrap(), uc.reg_read(RegisterPPC::R23).unwrap(), uc.reg_read(RegisterPPC::R31).unwrap()); 261 | } 262 | 263 | pub fn emulate(exe: &linker::Executable, resources: Resources, args: &[String], env_vars: &[(String, String)]) -> UcResult { 264 | let state = Rc::new(RefCell::new(EmuState::new(exe, resources))); 265 | let mut uc = Unicorn::new_with_data(Arch::PPC, Mode::BIG_ENDIAN | Mode::PPC32, Rc::clone(&state))?; 266 | 267 | // place some garbage at 0 because DeRez derefs a null pointer 268 | uc.mem_map(0, 0x1000, Permission::READ)?; 269 | 270 | uc.mem_map(exe.memory_base as u64, (exe.memory.len() + 0x3FFF) & !0x3FFF, Permission::ALL)?; 271 | uc.mem_write(exe.memory_base as u64, &exe.memory)?; 272 | 273 | // enable floating point 274 | uc.reg_write(RegisterPPC::MSR, uc.reg_read(RegisterPPC::MSR)? | (1 << 13))?; 275 | 276 | // set up the stack 277 | uc.reg_write(RegisterPPC::R1, (exe.stack_addr + exe.stack_size - 0x20).into())?; 278 | 279 | // uc.add_code_hook(0, 0xFFFFFFFF, code_hook)?; 280 | uc.add_intr_hook(intr_hook)?; 281 | 282 | let exec_end_address = exe.memory_end_addr(); 283 | 284 | { 285 | let mut state = state.borrow_mut(); 286 | 287 | state.heap.init(&mut uc)?; 288 | 289 | // populate IntEnv 290 | c_stdlib::setup_environment(&mut uc, &mut state, args, env_vars)?; 291 | 292 | // inject shim functions 293 | c_ctype::install_shims(&mut uc, &mut state)?; 294 | c_fenv::install_shims(&mut state); 295 | c_stdio::install_shims(&mut uc, &mut state)?; 296 | c_stdlib::install_shims(&mut uc, &mut state)?; 297 | c_string::install_shims(&mut state); 298 | c_time::install_shims(&mut state); 299 | flex_lm::install_shims(&mut state); 300 | interface_lib::install_shims(&mut state); 301 | mac_files::install_shims(&mut state); 302 | mac_fp::install_shims(&mut state); 303 | mac_gestalt::install_shims(&mut state); 304 | mac_low_mem::install_shims(&mut state); 305 | mac_memory::install_shims(&mut state); 306 | mac_os_utils::install_shims(&mut state); 307 | mac_quickdraw::install_shims(&mut state); 308 | mac_resources::install_shims(&mut state); 309 | mac_text_utils::install_shims(&mut state); 310 | std_c_lib::install_shims(&mut state); 311 | 312 | for symbol in &state.imports { 313 | if symbol.func.is_none() && symbol.class == pef::SymbolClass::TVect { 314 | warn!(target: "emulator", "Executable imports unimplemented function from {}: {}", symbol.library_name, symbol.name); 315 | } 316 | } 317 | } 318 | 319 | if exe.init_vector > 0 { 320 | // TODO: C++ binaries will probably need this 321 | let code = exe.get_u32(exe.init_vector); 322 | let rtoc = exe.get_u32(exe.init_vector + 4); 323 | warn!(target: "emulator", "Init: code={:08X}, rtoc={:08x}", code, rtoc); 324 | warn!(target: "emulator", " !!! Not implemented !!!"); 325 | } 326 | 327 | if exe.main_vector > 0 { 328 | let code = exe.get_u32(exe.main_vector); 329 | let rtoc = exe.get_u32(exe.main_vector + 4); 330 | debug!(target: "emulator", "Main: code={:08X}, rtoc={:08x}", code, rtoc); 331 | 332 | uc.reg_write(RegisterPPC::R2, rtoc.into())?; 333 | uc.reg_write(RegisterPPC::LR, exec_end_address.into())?; // LR 334 | 335 | if let Err(e) = uc.emu_start(code.into(), exec_end_address.into(), 0, 0) { 336 | let state = state.borrow(); 337 | if state.exit_status.is_none() { 338 | error!(target: "emulator", "Main execution failed: {:?}", e); 339 | dump_context(&uc); 340 | return Err(e); 341 | } 342 | } 343 | } 344 | 345 | let exit_status = state.borrow().exit_status.unwrap_or(0); 346 | Ok(exit_status) 347 | } 348 | -------------------------------------------------------------------------------- /src/emulator/std_c_lib.rs: -------------------------------------------------------------------------------- 1 | use crate::emulator::{EmuState, EmuUC, FuncResult}; 2 | use crate::emulator::helpers::ArgReader; 3 | use crate::mac_roman; 4 | 5 | fn write(uc: &mut EmuUC, state: &mut EmuState, reader: &mut ArgReader) -> FuncResult { 6 | let (fildes, buf, count): (u32, u32, u32) = reader.read3(uc)?; 7 | let output = uc.mem_read_as_vec(buf.into(), count as usize)?; 8 | 9 | match state.stdio_files.get_mut(&fildes) { 10 | Some(f) => { 11 | if f.is_terminal() { 12 | Ok(Some(f.generic_write(&mac_roman::decode_buffer(&output, true)))) 13 | } else { 14 | Ok(Some(f.generic_write(&output))) 15 | } 16 | } 17 | None => { 18 | warn!(target: "StdCLib", "write() is writing to invalid file {fildes:08X}"); 19 | // set errno later? 20 | Ok(Some(0)) 21 | } 22 | } 23 | } 24 | 25 | pub(super) fn install_shims(state: &mut EmuState) { 26 | state.install_shim_function("write", write); 27 | } 28 | -------------------------------------------------------------------------------- /src/filesystem.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, collections::HashMap, path::{PathBuf, Path, Prefix}, io::{Read, Cursor, Write}, fs::File, ffi::OsString, rc::Rc}; 2 | 3 | use anyhow::{anyhow, Result}; 4 | use bimap::BiHashMap; 5 | use binread::{BinRead, BinReaderExt}; 6 | use xattr::FileExt; 7 | 8 | use crate::{common::{FourCC, four_cc, lf_to_cr}, macbinary, mac_roman}; 9 | 10 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 11 | pub enum Fork { 12 | Data, 13 | Resource 14 | } 15 | 16 | type VolumeRef = i16; 17 | type DirID = i32; 18 | type VolumeAndDir = (VolumeRef, DirID); 19 | 20 | const ROOT_PARENT_DIR_ID: DirID = 1; 21 | const ROOT_DIR_ID: DirID = 2; 22 | 23 | enum FileMode { 24 | // Data fork only, type/creator ID determined from extension 25 | Automatic, 26 | // MacBinary III 27 | MacBinary, 28 | // Use the native info in file system attributes 29 | Native 30 | } 31 | 32 | #[derive(BinRead)] 33 | pub struct FileInfo { 34 | pub file_type: FourCC, 35 | pub file_creator: FourCC, 36 | pub finder_flags: u16, 37 | pub location: (i16, i16), 38 | pub reserved_field: u16, 39 | pub extended_data: [u8; 16] 40 | } 41 | 42 | impl FileInfo { 43 | fn pack(&self) -> Vec { 44 | let mut data = Vec::with_capacity(32); 45 | data.extend(self.file_type.0.to_be_bytes()); 46 | data.extend(self.file_creator.0.to_be_bytes()); 47 | data.extend(self.finder_flags.to_be_bytes()); 48 | data.extend(self.location.0.to_be_bytes()); 49 | data.extend(self.location.1.to_be_bytes()); 50 | data.extend(self.reserved_field.to_be_bytes()); 51 | data.extend(self.extended_data); 52 | data 53 | } 54 | } 55 | 56 | pub struct MacFile { 57 | pub path: PathBuf, 58 | mode: FileMode, 59 | dirty: bool, 60 | pub file_info: FileInfo, 61 | pub data_fork: Vec, 62 | pub resource_fork: Vec 63 | } 64 | 65 | impl MacFile { 66 | pub fn len(&self, fork: Fork) -> usize { 67 | match fork { 68 | Fork::Data => self.data_fork.len(), 69 | Fork::Resource => self.resource_fork.len() 70 | } 71 | } 72 | 73 | pub fn get_fork(&self, fork: Fork) -> &Vec { 74 | match fork { 75 | Fork::Data => &self.data_fork, 76 | Fork::Resource => &self.resource_fork 77 | } 78 | } 79 | 80 | pub fn get_fork_mut(&mut self, fork: Fork) -> &mut Vec { 81 | match fork { 82 | Fork::Data => &mut self.data_fork, 83 | Fork::Resource => &mut self.resource_fork 84 | } 85 | } 86 | 87 | pub fn create>(path: P, creator_id: FourCC, type_id: FourCC) -> MacFile { 88 | let path: &Path = path.as_ref(); 89 | 90 | let mode = if xattr::SUPPORTED_PLATFORM { 91 | FileMode::Native 92 | } else if type_id == four_cc(*b"TEXT") { 93 | FileMode::Automatic 94 | } else { 95 | FileMode::MacBinary 96 | }; 97 | 98 | MacFile { 99 | path: path.to_path_buf(), 100 | mode, 101 | dirty: true, 102 | file_info: FileInfo { 103 | file_type: type_id, 104 | file_creator: creator_id, 105 | finder_flags: 0, 106 | location: (0, 0), 107 | reserved_field: 0, 108 | extended_data: [0; 16] 109 | }, 110 | data_fork: Vec::new(), 111 | resource_fork: Vec::new() 112 | } 113 | } 114 | 115 | pub fn open>(path: P) -> Result { 116 | let path: &Path = path.as_ref(); 117 | let mut file = File::open(path)?; 118 | let mut data = Vec::new(); 119 | file.read_to_end(&mut data)?; 120 | let path = path.to_path_buf(); 121 | 122 | // Does this file have native metadata? 123 | if xattr::SUPPORTED_PLATFORM { 124 | if let Some(metadata) = file.get_xattr("com.apple.FinderInfo")? { 125 | // Yes, let's do it 126 | let mut cursor = Cursor::new(&metadata); 127 | let file_info: FileInfo = cursor.read_be()?; 128 | let resource_fork = file.get_xattr("com.apple.ResourceFork")?.unwrap_or_default(); 129 | 130 | if file_info.file_type == four_cc(*b"TEXT") { 131 | lf_to_cr(&mut data); 132 | } 133 | 134 | return Ok(MacFile { 135 | path, 136 | mode: FileMode::Native, 137 | dirty: false, 138 | file_info, 139 | data_fork: data, 140 | resource_fork, 141 | }); 142 | } 143 | } 144 | 145 | // Is this a MacBinary file? 146 | if macbinary::probe(&data) { 147 | let mb = macbinary::unpack(&data)?; 148 | 149 | return Ok(MacFile { 150 | path, 151 | mode: FileMode::MacBinary, 152 | dirty: false, 153 | file_info: FileInfo { 154 | file_type: FourCC(mb.type_id), 155 | file_creator: FourCC(mb.creator_id), 156 | finder_flags: mb.finder_flags, 157 | location: mb.location, 158 | reserved_field: 0, 159 | extended_data: [0; 16] 160 | }, 161 | data_fork: mb.data, 162 | resource_fork: mb.resource 163 | }); 164 | } 165 | 166 | // It's something else entirely, let's just assume text for now 167 | lf_to_cr(&mut data); 168 | 169 | Ok(MacFile { 170 | path, 171 | mode: FileMode::Automatic, 172 | dirty: false, 173 | file_info: FileInfo { 174 | file_type: four_cc(*b"TEXT"), 175 | file_creator: four_cc(*b"ttxt"), 176 | finder_flags: 0, 177 | location: (0, 0), 178 | reserved_field: 0, 179 | extended_data:[0; 16] 180 | }, 181 | data_fork: data, 182 | resource_fork: Vec::new() 183 | }) 184 | } 185 | 186 | fn save(&self) -> Result<()> { 187 | let mut file = File::create(&self.path)?; 188 | 189 | match self.mode { 190 | FileMode::Automatic => { 191 | // simplest mode 192 | file.write_all(&self.data_fork)?; 193 | } 194 | FileMode::MacBinary => { 195 | unimplemented!(); 196 | } 197 | FileMode::Native => { 198 | file.write_all(&self.data_fork)?; 199 | file.set_xattr("com.apple.FinderInfo", &self.file_info.pack())?; 200 | if !self.resource_fork.is_empty() { 201 | file.set_xattr("com.apple.ResourceFork", &self.resource_fork)?; 202 | } 203 | } 204 | } 205 | 206 | Ok(()) 207 | } 208 | 209 | pub fn set_dirty(&mut self) { 210 | self.dirty = true; 211 | } 212 | 213 | pub fn save_if_dirty(&mut self) -> Result<()> { 214 | if self.dirty { 215 | self.save()?; 216 | self.dirty = false; 217 | } 218 | Ok(()) 219 | } 220 | } 221 | 222 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 223 | enum Volume { 224 | // Used on Unix systems 225 | Root, 226 | // Used on Windows systems 227 | Verbatim(OsString), 228 | VerbatimUNC(OsString, OsString), 229 | VerbatimDisk(u8) 230 | } 231 | 232 | impl Volume { 233 | fn containing_path(path: &Path) -> Result { 234 | assert!(path.is_absolute() && path.has_root()); 235 | 236 | match path.components().next().unwrap() { 237 | std::path::Component::Prefix(prefix) => { 238 | match prefix.kind() { 239 | Prefix::Verbatim(a) => Ok(Volume::Verbatim(a.to_owned())), 240 | Prefix::VerbatimUNC(a, b) => Ok(Volume::VerbatimUNC(a.to_owned(), b.to_owned())), 241 | Prefix::VerbatimDisk(letter) => Ok(Volume::VerbatimDisk(letter)), 242 | _ => Err(anyhow!("unexpected prefix in path")) 243 | } 244 | } 245 | std::path::Component::RootDir => Ok(Volume::Root), 246 | _ => Err(anyhow!("unexpected element at start of path")) 247 | } 248 | } 249 | 250 | fn get_root(&self) -> PathBuf { 251 | match self { 252 | Volume::Root => PathBuf::from("/"), 253 | Volume::Verbatim(a) => { 254 | let mut s = OsString::from(r"\\?\"); 255 | s.push(&a); 256 | s.push(r"\"); 257 | PathBuf::from(s) 258 | } 259 | Volume::VerbatimUNC(a, b) => { 260 | let mut s = OsString::from(r"\\?\UNC\"); 261 | s.push(&a); 262 | s.push(r"\"); 263 | s.push(&b); 264 | s.push(r"\"); 265 | PathBuf::from(s) 266 | } 267 | Volume::VerbatimDisk(letter) => { 268 | let mut s = OsString::from(r"\\?\"); 269 | s.push(std::str::from_utf8(&[*letter]).unwrap()); 270 | s.push(r":\"); 271 | PathBuf::from(s) 272 | } 273 | } 274 | } 275 | 276 | fn get_name(&self) -> Option { 277 | match self { 278 | Volume::Root => Some(String::from("Root")), 279 | Volume::VerbatimDisk(letter) => { 280 | Some(String::from_utf8(vec![*letter]).unwrap()) 281 | } 282 | _ => None 283 | } 284 | } 285 | } 286 | 287 | 288 | pub struct FileSystem { 289 | // should this store a Weak instead of Rc? 290 | files: HashMap>>, 291 | nodes: BiHashMap, 292 | next_node_id: DirID, 293 | volume_names: BiHashMap, 294 | volumes: BiHashMap, 295 | default_volume: VolumeRef, 296 | next_volume_ref: VolumeRef 297 | } 298 | 299 | impl FileSystem { 300 | pub fn new() -> Self { 301 | FileSystem { 302 | files: HashMap::new(), 303 | nodes: BiHashMap::new(), 304 | next_node_id: 3, // skip 1+2 as these are reserved by Mac OS 305 | volume_names: BiHashMap::new(), 306 | volumes: BiHashMap::new(), 307 | default_volume: -1, 308 | next_volume_ref: -1 309 | } 310 | } 311 | 312 | pub fn get_volume_info_by_drive_number(&self, drive_number: i16) -> Option<(String, i16)> { 313 | // assume drive numbers are just volume numbers (but positive) for now 314 | let volume_ref = if drive_number == 0 { 315 | self.default_volume 316 | } else { 317 | -drive_number 318 | }; 319 | 320 | if let Some(name) = self.volume_names.get_by_left(&volume_ref) { 321 | Some((name.clone(), volume_ref)) 322 | } else { 323 | None 324 | } 325 | } 326 | 327 | fn get_volume_by_name(&mut self, name: &[u8]) -> Result { 328 | let name = mac_roman::decode_string(name, false); 329 | 330 | if let Some(volume_ref) = self.volume_names.get_by_right(name.as_ref()) { 331 | Ok(self.volumes.get_by_left(volume_ref).unwrap().clone()) 332 | } else { 333 | // try to guess what this volume is 334 | if cfg!(windows) && name.len() == 1 && name.chars().next().unwrap().is_ascii_alphabetic() { 335 | // must be a drive 336 | let letter = name.chars().next().unwrap().to_ascii_uppercase(); 337 | let volume = Volume::VerbatimDisk(letter as u8); 338 | let volume_ref = self.next_volume_ref; 339 | debug!(target: "fs", "Registered volume {volume_ref} to be {volume:?} ({name})"); 340 | self.volume_names.insert(volume_ref, name.into_owned()); 341 | self.volumes.insert(volume_ref, volume.clone()); 342 | self.next_volume_ref -= 1; 343 | Ok(volume) 344 | } 345 | else if cfg!(unix) && name == "Root" { 346 | let volume = Volume::Root; 347 | let volume_ref = self.next_volume_ref; 348 | debug!(target: "fs", "Registered volume {volume_ref} to be {volume:?} ({name})"); 349 | self.volume_names.insert(volume_ref, name.into_owned()); 350 | self.volumes.insert(volume_ref, volume.clone()); 351 | self.next_volume_ref -= 1; 352 | Ok(volume) 353 | } else { 354 | Err(anyhow!("could not resolve volume")) 355 | } 356 | } 357 | } 358 | 359 | fn get_volume_by_ref(&self, volume_ref: VolumeRef) -> Result { 360 | if let Some(volume) = self.volumes.get_by_left(&volume_ref) { 361 | Ok(volume.clone()) 362 | } else { 363 | Err(anyhow!("unknown volume ref")) 364 | } 365 | } 366 | 367 | fn get_volume_ref_for_path(&mut self, path: &Path) -> Result { 368 | let volume = Volume::containing_path(path)?; 369 | if let Some(volume_ref) = self.volumes.get_by_right(&volume) { 370 | Ok(*volume_ref) 371 | } else { 372 | // register it 373 | let volume_ref = self.next_volume_ref; 374 | let name = volume.get_name().unwrap_or_else(|| format!("Volume {volume_ref}")); 375 | debug!(target: "fs", "Registered volume {volume_ref} to be {volume:?} ({name})"); 376 | self.volume_names.insert(volume_ref, name); 377 | self.volumes.insert(volume_ref, volume); 378 | self.next_volume_ref -= 1; 379 | Ok(volume_ref) 380 | } 381 | } 382 | 383 | pub fn get_directory_by_id(&self, volume_ref: VolumeRef, dir_id: DirID) -> Result { 384 | let volume_ref = if volume_ref == 0 { self.default_volume } else { volume_ref }; 385 | 386 | if let Some(path) = self.nodes.get_by_left(&(volume_ref, dir_id)) { 387 | Ok(path.clone()) 388 | } else { 389 | Err(anyhow!("unknown directory ID")) 390 | } 391 | } 392 | 393 | pub fn resolve_path(&mut self, volume_ref: VolumeRef, dir_id: DirID, name: &[u8]) -> Result { 394 | // https://web.archive.org/web/20011122070503/http://developer.apple.com/techpubs/mac/Files/Files-91.html#HEADING91-0 395 | trace!(target: "fs", "resolve_path({volume_ref}, {dir_id}, {name:?})"); 396 | 397 | let mut name = name; 398 | 399 | let mut path = if name.contains(&b':') && !name.starts_with(b":") { 400 | // Full pathname - take the volume off it and continue 401 | let split_point = name.iter().position(|c| *c == b':').unwrap(); 402 | let (volume_name, rest) = name.split_at(split_point); 403 | name = rest; 404 | self.get_volume_by_name(volume_name)?.get_root() 405 | } else if dir_id == 2 { 406 | // Root directory 407 | if volume_ref == 0 { 408 | std::env::current_dir()?.ancestors().last().unwrap().to_path_buf() 409 | } else { 410 | self.get_volume_by_ref(volume_ref)?.get_root() 411 | } 412 | } else if dir_id > 2 { 413 | // Relative pathname from a directory 414 | self.get_directory_by_id(volume_ref, dir_id)? 415 | } else { 416 | // Relative from current directory 417 | std::env::current_dir()? 418 | }; 419 | 420 | // Apply what's left 421 | for chunk in name.split(|&c| c == b':') { 422 | if chunk.is_empty() { 423 | // should we try to parse "::"? maybe later 424 | continue; 425 | } 426 | if chunk.iter().any(|&c| c == b'/' || c == b'\\' || c < b' ') { 427 | return Err(anyhow!("invalid character in path element")); 428 | } 429 | let chunk = mac_roman::decode_string(chunk, false); 430 | path.push(chunk.as_ref()); 431 | } 432 | 433 | Ok(path) 434 | } 435 | 436 | pub fn id_for_dir(&mut self, path: &Path) -> Result { 437 | trace!(target: "fs", "id_for_dir({path:?})"); 438 | 439 | if let Some(&vad) = self.nodes.get_by_right(path) { 440 | Ok(vad) 441 | } else { 442 | // Throw the lad in 443 | let volume = self.get_volume_ref_for_path(path)?; 444 | let dir = if path.parent().is_none() { 445 | ROOT_DIR_ID 446 | } else { 447 | self.next_node_id += 1; 448 | self.next_node_id - 1 449 | }; 450 | debug!(target: "fs", "Registered node ({volume}::{dir}) to {path:?}"); 451 | self.nodes.insert((volume, dir), path.to_path_buf()); 452 | Ok((volume, dir)) 453 | } 454 | } 455 | 456 | pub fn spec(&mut self, path: &Path) -> Result { 457 | trace!(target: "fs", "spec({path:?})"); 458 | 459 | match path.parent() { 460 | Some(parent) => { 461 | let (volume_ref, parent_id) = self.id_for_dir(parent)?; 462 | let (_, node_id) = self.id_for_dir(path)?; 463 | let name = path.file_name().unwrap().to_string_lossy(); 464 | let name_enc = mac_roman::encode_string(name.as_ref(), false); 465 | trace!(target: "fs", " ... => ({volume_ref}, parent={parent_id}, node={node_id}, {name})"); 466 | Ok(NodeRef { 467 | volume_ref, 468 | parent_id, 469 | node_id, 470 | node_name: name_enc.into_owned() 471 | }) 472 | } 473 | None => { 474 | let volume_ref = self.get_volume_ref_for_path(path)?; 475 | let name = self.volume_names.get_by_left(&volume_ref).unwrap(); 476 | let name_enc = mac_roman::encode_string(name.as_str(), false).into_owned(); 477 | trace!(target: "fs", " ... => ({volume_ref}, parent-{ROOT_PARENT_DIR_ID}, node={ROOT_DIR_ID}, {name})"); 478 | Ok(NodeRef { 479 | volume_ref, 480 | parent_id: ROOT_PARENT_DIR_ID, 481 | node_id: ROOT_DIR_ID, 482 | node_name: name_enc 483 | }) 484 | } 485 | } 486 | } 487 | 488 | pub fn create_file(&mut self, path: &Path, creator_id: FourCC, type_id: FourCC) -> Result<()> { 489 | if self.files.contains_key(path) { 490 | return Err(anyhow!("file already exists")); 491 | } 492 | 493 | let mut file = MacFile::create(path, creator_id, type_id); 494 | file.save_if_dirty()?; 495 | 496 | self.files.insert(path.to_path_buf(), Rc::new(RefCell::new(file))); 497 | Ok(()) 498 | } 499 | 500 | pub fn get_file(&mut self, path: &Path) -> Result>> { 501 | if let Some(file) = self.files.get(path) { 502 | Ok(Rc::clone(file)) 503 | } else { 504 | let file = MacFile::open(path)?; 505 | let file = Rc::new(RefCell::new(file)); 506 | self.files.insert(path.to_path_buf(), Rc::clone(&file)); 507 | Ok(file) 508 | } 509 | } 510 | 511 | pub fn delete_file(&mut self, path: &Path) -> Result<()> { 512 | self.files.remove(path); 513 | std::fs::remove_file(path)?; 514 | Ok(()) 515 | } 516 | } 517 | 518 | pub struct NodeRef { 519 | pub volume_ref: VolumeRef, 520 | pub parent_id: DirID, 521 | pub node_id: DirID, 522 | pub node_name: Vec 523 | } 524 | 525 | -------------------------------------------------------------------------------- /src/linker.rs: -------------------------------------------------------------------------------- 1 | use super::pef; 2 | 3 | pub struct Executable { 4 | pub memory: Vec, 5 | pub memory_base: u32, 6 | pub code_addr: u32, 7 | pub data_addr: u32, 8 | pub stack_addr: u32, 9 | pub stack_size: u32, 10 | pub init_vector: u32, 11 | pub main_vector: u32, 12 | pub sc_thunk_addr: u32, 13 | pub shim_addrs: Vec, 14 | pub imports: Vec, 15 | pub libraries: Vec 16 | } 17 | 18 | impl Executable { 19 | pub fn new() -> Self { 20 | Executable { 21 | memory: Vec::new(), 22 | memory_base: 0x10000000, 23 | code_addr: 0, 24 | data_addr: 0, 25 | stack_addr: 0, 26 | stack_size: 0, 27 | init_vector: 0, 28 | main_vector: 0, 29 | sc_thunk_addr: 0, 30 | shim_addrs: Vec::new(), 31 | imports: Vec::new(), 32 | libraries: Vec::new() 33 | } 34 | } 35 | 36 | pub fn memory_end_addr(&self) -> u32 { 37 | self.memory_base + (self.memory.len() as u32) 38 | } 39 | 40 | fn allocate_memory(&mut self, amount: usize) -> u32 { 41 | let addr = self.memory_end_addr(); 42 | self.memory.resize(self.memory.len() + amount, 0); 43 | addr 44 | } 45 | 46 | fn align_memory_to(&mut self, amount: usize) { 47 | while (self.memory.len() & (amount - 1)) != 0 { 48 | self.memory.push(0); 49 | } 50 | } 51 | 52 | pub fn load_pef(&mut self, pef: pef::PEF) { 53 | for section in &pef.sections { 54 | debug!( 55 | target: "linker", 56 | "Section: {:?} Default={:X} Size(Total={:X}, Unpacked={:X}, Packed={:X}) Kind(Section={:?}, Share={:?}) Align={:?}", 57 | section.name, section.default_address, 58 | section.total_size, section.unpacked_size, section.packed_size, 59 | section.section_kind, section.share_kind, section.alignment); 60 | } 61 | 62 | self.memory.clear(); 63 | 64 | // Load code 65 | self.code_addr = self.memory_end_addr(); 66 | self.memory.extend_from_slice(pef.sections[0].packed_contents.as_ref().unwrap()); 67 | self.align_memory_to(0x10); 68 | 69 | // Load data 70 | let data_start = self.memory.len(); 71 | self.data_addr = self.allocate_memory(pef.sections[1].total_size as usize); 72 | let data_end = self.memory.len(); 73 | pef::unpack_pattern_data(pef.sections[1].packed_contents.as_ref().unwrap(), &mut self.memory[data_start .. data_end]); 74 | 75 | self.align_memory_to(0x10); 76 | 77 | // Create a stack 78 | self.stack_size = 0x100000; 79 | self.stack_addr = self.allocate_memory(self.stack_size as usize); 80 | 81 | // Parse loader 82 | let loader = pef::parse_loader(pef.sections[2].packed_contents.as_ref().unwrap()).unwrap(); 83 | if loader.init_section > 0 { 84 | self.init_vector = self.data_addr + loader.init_offset; 85 | } 86 | if loader.main_section > 0 { 87 | self.main_vector = self.data_addr + loader.main_offset; 88 | } 89 | 90 | // Create shims for imported symbols 91 | self.sc_thunk_addr = self.memory_end_addr(); 92 | self.memory.push(0x44); 93 | self.memory.push(0); 94 | self.memory.push(0); 95 | self.memory.push(2); 96 | self.memory.push(0x4E); 97 | self.memory.push(0x80); 98 | self.memory.push(0); 99 | self.memory.push(0x20); 100 | self.memory.push(0x4E); // double to work around unicorn merging https://github.com/unicorn-engine/unicorn/pull/1558 101 | self.memory.push(0x80); 102 | self.memory.push(0); 103 | self.memory.push(0x20); 104 | 105 | for (i, sym) in loader.imported_symbols.iter().enumerate() { 106 | match sym.class { 107 | pef::SymbolClass::TVect => { 108 | let shim = self.allocate_memory(12); 109 | self.set_u32(shim, self.sc_thunk_addr); 110 | self.set_u32(shim + 4, i as u32); 111 | self.set_u32(shim + 8, 100); 112 | self.shim_addrs.push(shim); 113 | } 114 | pef::SymbolClass::Data => { 115 | let shim = self.allocate_memory(1024); 116 | self.shim_addrs.push(shim); 117 | } 118 | _ => panic!() 119 | } 120 | } 121 | 122 | for reloc_section in &loader.reloc_sections { 123 | self.handle_reloc_section(&loader, reloc_section); 124 | } 125 | 126 | self.imports = loader.imported_symbols; 127 | for lib in loader.imported_libraries { 128 | self.libraries.push(lib.name); 129 | } 130 | } 131 | 132 | pub fn get_u32(&self, address: u32) -> u32 { 133 | let offset = (address - self.memory_base) as usize; 134 | let bytes: [u8; 4] = self.memory[offset .. offset + 4].try_into().unwrap(); 135 | u32::from_be_bytes(bytes) 136 | } 137 | 138 | pub fn set_u32(&mut self, address: u32, value: u32) { 139 | let offset = (address - self.memory_base) as usize; 140 | self.memory[offset .. offset + 4].copy_from_slice(&value.to_be_bytes()); 141 | } 142 | 143 | fn handle_reloc_section(&mut self, loader: &pef::Loader, relocs: &pef::RelocSection) { 144 | let mut next_block = 0; 145 | let mut reloc_address = self.data_addr; 146 | let mut import_index = 0u32; 147 | let mut repeat_info = None; 148 | 149 | while next_block < relocs.data.len() { 150 | let block_pos = next_block; 151 | let block = relocs.data[next_block] as u32; 152 | next_block += 1; 153 | 154 | if (block & 0xC000) == 0 { 155 | // RelocBySectDWithSkip 156 | let skip_count = (block >> 6) & 0xFF; 157 | let reloc_count = block & 0x3F; 158 | reloc_address += skip_count * 4; 159 | trace!(target: "linker", "[{block_pos:04X}] BySectDWithSkip @ {reloc_address:X} (x{reloc_count})"); 160 | for _ in 0..reloc_count { 161 | self.set_u32(reloc_address, self.data_addr + self.get_u32(reloc_address)); 162 | reloc_address += 4; 163 | } 164 | } else if (block & 0xFE00) == 0x4000 { 165 | // RelocBySectC 166 | let run_length = (block & 0x1FF) + 1; 167 | trace!(target: "linker", "[{block_pos:04X}] BySectC @ {reloc_address:X} (x{run_length})"); 168 | for _ in 0..run_length { 169 | self.set_u32(reloc_address, self.code_addr + self.get_u32(reloc_address)); 170 | reloc_address += 4; 171 | } 172 | } else if (block & 0xFE00) == 0x4200 { 173 | // RelocBySectD 174 | let run_length = (block & 0x1FF) + 1; 175 | trace!(target: "linker", "[{block_pos:04X}] BySectD @ {reloc_address:X} (x{run_length})"); 176 | for _ in 0..run_length { 177 | self.set_u32(reloc_address, self.data_addr + self.get_u32(reloc_address)); 178 | reloc_address += 4; 179 | } 180 | } else if (block & 0xFE00) == 0x4400 { 181 | // RelocTVector12 182 | let run_length = (block & 0x1FF) + 1; 183 | trace!(target: "linker", "[{block_pos:04X}] TVector12 @ {reloc_address:X} (x{run_length})"); 184 | for _ in 0..run_length { 185 | self.set_u32(reloc_address, self.code_addr + self.get_u32(reloc_address)); 186 | reloc_address += 4; 187 | self.set_u32(reloc_address, self.data_addr + self.get_u32(reloc_address)); 188 | reloc_address += 8; 189 | } 190 | } else if (block & 0xFE00) == 0x4600 { 191 | // RelocTVector8 192 | let run_length = (block & 0x1FF) + 1; 193 | trace!(target: "linker", "[{block_pos:04X}] TVector8 @ {reloc_address:X} (x{run_length})"); 194 | for _ in 0..run_length { 195 | self.set_u32(reloc_address, self.code_addr + self.get_u32(reloc_address)); 196 | reloc_address += 4; 197 | self.set_u32(reloc_address, self.data_addr + self.get_u32(reloc_address)); 198 | reloc_address += 4; 199 | } 200 | } else if (block & 0xFE00) == 0x4800 { 201 | // RelocVTable8 202 | let run_length = (block & 0x1FF) + 1; 203 | trace!(target: "linker", "[{block_pos:04X}] VTable8 @ {reloc_address:X} (x{run_length})"); 204 | for _ in 0..run_length { 205 | self.set_u32(reloc_address, self.data_addr + self.get_u32(reloc_address)); 206 | reloc_address += 8; 207 | } 208 | } else if (block & 0xFE00) == 0x4A00 { 209 | // RelocImportRun 210 | let run_length = (block & 0x1FF) + 1; 211 | trace!(target: "linker", "[{block_pos:04X}] ImportRun @ {reloc_address:X} (x{run_length})"); 212 | for _ in 0..run_length { 213 | let symbol = &loader.imported_symbols[import_index as usize]; 214 | trace!(target: "linker", " {reloc_address:X} -> {import_index} - {}", &symbol.name); 215 | self.set_u32(reloc_address, self.shim_addrs[import_index as usize]); 216 | reloc_address += 4; 217 | import_index += 1; 218 | } 219 | } else if (block & 0xFE00) == 0x6000 { 220 | // RelocSmByImport 221 | let index = block & 0x1FF; 222 | let symbol = &loader.imported_symbols[index as usize]; 223 | trace!(target: "linker", "[{block_pos:04X}] SmByImport @ {reloc_address:X} (sym={index} - {})", &symbol.name); 224 | self.set_u32(reloc_address, self.shim_addrs[index as usize]); 225 | reloc_address += 4; 226 | import_index = index + 1; 227 | } else if (block & 0xFE00) == 0x6200 { 228 | // RelocSmSetSectC 229 | let index = block & 0x1FF; 230 | warn!(target: "linker", "[{block_pos:04X}] UNIMPLEMENTED SmSetSectC (sect={index})"); 231 | } else if (block & 0xFE00) == 0x6400 { 232 | // RelocSmSetSectD 233 | let index = block & 0x1FF; 234 | warn!(target: "linker", "[{block_pos:04X}] UNIMPLEMENTED SmSetSectD (sect={index})"); 235 | } else if (block & 0xFE00) == 0x6600 { 236 | // RelocSmBySection 237 | let index = block & 0x1FF; 238 | warn!(target: "linker", "[{block_pos:04X}] UNIMPLEMENTED SmBySection @ {reloc_address:X} (sect={index})"); 239 | reloc_address += 4; 240 | } else if (block & 0xF000) == 0x8000 { 241 | // RelocIncrPosition 242 | let offset = (block & 0xFFF) + 1; 243 | trace!(target: "linker", "[{block_pos:04X}] IncrPosition @ {reloc_address:X} += {offset:X} -> {:X}", reloc_address + offset); 244 | reloc_address += offset; 245 | } else if (block & 0xF000) == 0x9000 { 246 | // RelocSmRepeat 247 | let block_count = ((block >> 8) & 0xF) + 1; 248 | let repeat_count = (block & 0xFF) + 1; 249 | let repeat_start = block_pos - (block_count as usize); 250 | match repeat_info { 251 | Some((pos, counter)) if pos == block_pos => { 252 | // repeat, maybe 253 | trace!(target: "linker", "[{block_pos:04X}] SmRepeat from {repeat_start:04X}, iteration {counter}/{repeat_count}"); 254 | if counter < repeat_count { 255 | next_block = repeat_start; 256 | repeat_info = Some((pos, counter + 1)); 257 | } 258 | } 259 | _ => { 260 | // start a repeat 261 | trace!(target: "linker", "[{block_pos:04X}] SmRepeat from {repeat_start:04X}, iteration 0/{repeat_count}"); 262 | next_block = repeat_start; 263 | repeat_info = Some((block_pos, 1)); 264 | } 265 | } 266 | } else if (block & 0xFC00) == 0xA000 { 267 | // RelocSetPosition 268 | let offset = ((block & 0x3FF) << 16) | (relocs.data[next_block] as u32); 269 | next_block += 1; 270 | trace!(target: "linker", "[{block_pos:04X}] SetPosition = {offset:X}"); 271 | reloc_address = self.data_addr + offset; 272 | } else if (block & 0xFC00) == 0xA400 { 273 | // RelocLgByImport 274 | let index = ((block & 0x3FF) << 16) | (relocs.data[next_block] as u32); 275 | next_block += 1; 276 | let symbol = &loader.imported_symbols[index as usize]; 277 | trace!(target: "linker", "[{block_pos:04X}] LgByImport @ {reloc_address:X} (sym={index} - {})", &symbol.name); 278 | self.set_u32(reloc_address, self.shim_addrs[index as usize]); 279 | reloc_address += 4; 280 | import_index = index + 1; 281 | } else if (block & 0xFC00) == 0xB000 { 282 | // RelocSmRepeat 283 | let block_count = ((block >> 8) & 0xF) + 1; 284 | let repeat_count = ((block & 0xFF) << 16) | (relocs.data[next_block] as u32); 285 | next_block += 1; 286 | let repeat_start = block_pos - (block_count as usize); 287 | warn!(target: "linker", "[{block_pos:04X}] UNIMPLEMENTED LgRepeat from {repeat_start:04X}, {repeat_count} times"); 288 | } else if (block & 0xFFC0) == 0xB400 { 289 | // RelocLgBySection 290 | let index = ((block & 0x3F) << 16) | (relocs.data[next_block] as u32); 291 | next_block += 1; 292 | warn!(target: "linker", "[{block_pos:04X}] UNIMPLEMENTED LgBySection @ {reloc_address:X} (sect={index})"); 293 | reloc_address += 4; 294 | } else if (block & 0xFFC0) == 0xB440 { 295 | // RelocLgSetSectC 296 | let index = ((block & 0x3F) << 16) | (relocs.data[next_block] as u32); 297 | next_block += 1; 298 | warn!(target: "linker", "[{block_pos:04X}] UNIMPLEMENTED LgSetSectC (sect={index})"); 299 | } else if (block & 0xFFC0) == 0xB480 { 300 | // RelocLgSetSectD 301 | let index = ((block & 0x3F) << 16) | (relocs.data[next_block] as u32); 302 | next_block += 1; 303 | warn!(target: "linker", "[{block_pos:04X}] UNIMPLEMENTED LgSetSectD (sect={index})"); 304 | } else { 305 | warn!(target: "linker", "[{block_pos:04X}] UNKNOWN OPCODE {block:04X}"); 306 | } 307 | } 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/mac_roman.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, collections::HashMap}; 2 | 3 | use lazy_static::lazy_static; 4 | 5 | const CONVERSIONS: [char; 128] = [ 6 | 'Ä', 'Å', 'Ç', 'É', 'Ñ', 'Ö', 'Ü', 'á', 'à', 'â', 'ä', 'ã', 'å', 'ç', 'é', 'è', 7 | 'ê', 'ë', 'í', 'ì', 'î', 'ï', 'ñ', 'ó', 'ò', 'ô', 'ö', 'õ', 'ú', 'ù', 'û', 'ü', 8 | '†', '°', '¢', '£', '§', '•', '¶', 'ß', '®', '©', '™', '´', '¨', '≠', 'Æ', 'Ø', 9 | '∞', '±', '≤', '≥', '¥', 'µ', '∂', '∑', '∏', 'π', '∫', 'ª', 'º', 'Ω', 'æ', 'ø', 10 | '¿', '¡', '¬', '√', 'ƒ', '≈', '∆', '«', '»', '…', '\u{A0}', 'À', 'Ã', 'Õ', 'Œ', 'œ', 11 | '–', '—', '“', '”', '‘', '’', '÷', '◊', 'ÿ', 'Ÿ', '⁄', '€', '‹', '›', 'fi', 'fl', 12 | '‡', '·', '‚', '„', '‰', 'Â', 'Ê', 'Á', 'Ë', 'È', 'Í', 'Î', 'Ï', 'Ì', 'Ó', 'Ô', 13 | '\u{F8FF}', 'Ò', 'Ú', 'Û', 'Ù', 'ı', 'ˆ', '˜', '¯', '˘', '˙', '˚', '¸', '˝', '˛', 'ˇ' 14 | ]; 15 | 16 | lazy_static! { 17 | static ref INVERSE_CONVERSIONS: HashMap = { 18 | let mut m = HashMap::new(); 19 | for i in 0..0x80 { 20 | m.insert(CONVERSIONS[i], (i as u8) + 0x80); 21 | } 22 | m 23 | }; 24 | } 25 | 26 | pub fn to_lower(ch: u8) -> u8 { 27 | match ch { 28 | b'A'..=b'Z' => ch + (b'a' - b'A'), 29 | 0x80 => 0x8A, 30 | 0x81 => 0x8C, 31 | 0x82 => 0x8D, 32 | 0x83 => 0x8E, 33 | 0x84 => 0x96, 34 | 0x85 => 0x9A, 35 | 0x86 => 0x9F, 36 | 0xAE => 0xBE, 37 | 0xAF => 0xBF, 38 | 0xCB => 0x88, 39 | 0xCC => 0x8B, 40 | 0xCD => 0x9B, 41 | 0xCE => 0xCF, 42 | 0xD9 => 0xD8, 43 | 0xE5 => 0x89, 44 | 0xE6 => 0x90, 45 | 0xE7 => 0x87, 46 | 0xE8 => 0x91, 47 | 0xE9 => 0x8F, 48 | 0xEA => 0x92, 49 | 0xEB => 0x94, 50 | 0xEC => 0x95, 51 | 0xED => 0x93, 52 | 0xEE => 0x97, 53 | 0xEF => 0x99, 54 | 0xF1 => 0x98, 55 | 0xF2 => 0x9C, 56 | 0xF3 => 0x9E, 57 | 0xF4 => 0x9D, 58 | _ => ch 59 | } 60 | } 61 | 62 | pub fn to_upper(ch: u8) -> u8 { 63 | match ch { 64 | b'a'..=b'z' => ch - (b'a' - b'A'), 65 | 0x87 => 0xE7, 66 | 0x88 => 0xCB, 67 | 0x89 => 0xE5, 68 | 0x8A => 0x80, 69 | 0x8B => 0xCC, 70 | 0x8C => 0x81, 71 | 0x8D => 0x82, 72 | 0x8E => 0x83, 73 | 0x8F => 0xE9, 74 | 0x90 => 0xE6, 75 | 0x91 => 0xE8, 76 | 0x92 => 0xEA, 77 | 0x93 => 0xED, 78 | 0x94 => 0xEB, 79 | 0x95 => 0xEC, 80 | 0x96 => 0x84, 81 | 0x97 => 0xEE, 82 | 0x98 => 0xF1, 83 | 0x99 => 0xEF, 84 | 0x9A => 0x85, 85 | 0x9B => 0xCD, 86 | 0x9C => 0xF2, 87 | 0x9D => 0xF4, 88 | 0x9E => 0xF3, 89 | 0x9F => 0x86, 90 | 0xBE => 0xAE, 91 | 0xBF => 0xAF, 92 | 0xCF => 0xCE, 93 | 0xD8 => 0xD9, 94 | _ => ch 95 | } 96 | } 97 | 98 | pub fn decode_string(buf: &[u8], cr_to_lf: bool) -> Cow { 99 | let mut safe = true; 100 | for &c in buf { 101 | if !c.is_ascii() || (cr_to_lf && c == b'\r') { 102 | safe = false; 103 | break; 104 | } 105 | } 106 | 107 | if safe { 108 | Cow::Borrowed(unsafe { std::str::from_utf8_unchecked(buf) }) 109 | } else { 110 | let mut s = String::with_capacity(buf.len() + (buf.len() / 2)); 111 | 112 | for &c in buf { 113 | s.push(decode_char(c, cr_to_lf)); 114 | } 115 | 116 | Cow::Owned(s) 117 | } 118 | } 119 | 120 | pub fn decode_buffer(buf: &[u8], cr_to_lf: bool) -> Cow<[u8]> { 121 | match decode_string(buf, cr_to_lf) { 122 | Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()), 123 | Cow::Owned(s) => Cow::Owned(s.into_bytes()) 124 | } 125 | } 126 | 127 | pub fn decode_char(ch: u8, cr_to_lf: bool) -> char { 128 | if cr_to_lf && ch == b'\r' { 129 | '\n' 130 | } else if ch < 0x80 { 131 | ch as char 132 | } else { 133 | CONVERSIONS[(ch - 0x80) as usize] 134 | } 135 | } 136 | 137 | pub fn encode_string(s: &str, lf_to_cr: bool) -> Cow<[u8]> { 138 | let mut safe = true; 139 | for c in s.chars() { 140 | if !c.is_ascii() || (lf_to_cr && c == '\n') { 141 | safe = false; 142 | break; 143 | } 144 | } 145 | 146 | if safe { 147 | Cow::Borrowed(s.as_bytes()) 148 | } else { 149 | let mut buf = Vec::new(); 150 | 151 | for c in s.chars() { 152 | buf.push(encode_char(c, lf_to_cr).unwrap_or(0xE0)); 153 | } 154 | 155 | Cow::Owned(buf) 156 | } 157 | } 158 | 159 | pub fn encode_char(ch: char, lf_to_cr: bool) -> Option { 160 | if lf_to_cr && ch == '\n' { 161 | Some(b'\r') 162 | } else if ch.is_ascii() { 163 | Some(ch as u8) 164 | } else { 165 | INVERSE_CONVERSIONS.get(&ch).cloned() 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/macbinary.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use crc::{Crc, CRC_16_XMODEM}; 4 | use binread::{BinRead, BinReaderExt, BinResult}; 5 | use num::Integer; 6 | 7 | /// MacBinary header 8 | /// 9 | /// 128-byte MacBinary header. The header has the same size on MacBinary I, II, and III. There 10 | /// are some unused portions which were given meaning in later versions. 11 | /// 12 | /// 13 | #[derive(BinRead, Debug)] 14 | #[br(big)] 15 | struct Header { 16 | _old_version_number: u8, 17 | filename_part_1: [u8; 32], 18 | filename_part_2: [u8; 32], 19 | file_type: u32, 20 | file_creator: u32, 21 | finder_flags: u8, 22 | _pad: u8, 23 | v_pos: i16, 24 | h_pos: i16, 25 | _folder_id: u16, 26 | _protected: u8, 27 | _pad2: u8, 28 | data_length: u32, 29 | resource_length: u32, 30 | _creation_date: u32, 31 | _modified_date: u32, 32 | _comment_length: u16, 33 | finder_flags_2: u8, 34 | _pad3: [u8; 14], 35 | _unused_74: u32, 36 | _secondary_header_length: u16, 37 | _version_number: u8, 38 | _minimum_version_number: u8, 39 | _crc: u16 40 | } 41 | 42 | pub struct File { 43 | pub name: String, 44 | pub type_id: u32, 45 | pub creator_id: u32, 46 | pub finder_flags: u16, 47 | pub location: (i16, i16), 48 | pub data: Vec, 49 | pub resource: Vec 50 | } 51 | 52 | // Determine whether a file looks like a valid MacBinary file 53 | // 54 | // > To determine if a header is a valid MacBinary header, first take advantage of the new 55 | // > MacBinary III signature located at offset 102. If it matches, then you know that this is a 56 | // > valid MacBinary III header and can continue as such including the restoration of the new 57 | // > extended Finder info. 58 | // > 59 | // > If it is not a MacBinary III header, start by checking bytes 0 and 74 - they should both be 60 | // > zero. If they are both zero, either (a) the CRC should match, which means it is a MacBinary II 61 | // > file, or (b) byte 82 is zero, which means it may be a MacBinary I file. (Note that, at the 62 | // > current version level, byte 82 is kept zero to maintain compatibility with MacBinary I. If at 63 | // > some point the MacBinary versions change sufficiently that it is necessary to keep MacBinary I 64 | // > programs from downloading these files, we can change byte 82 to non-zero.) 65 | // 66 | // -- 67 | pub fn probe(file: &[u8]) -> bool { 68 | if file.len() < 0x80 { 69 | return false; 70 | } 71 | 72 | let data_size = u32::from_be_bytes(file[0x53 .. 0x57].try_into().unwrap()).next_multiple_of(&0x80); 73 | let resource_size = u32::from_be_bytes(file[0x57 .. 0x5B].try_into().unwrap()).next_multiple_of(&0x80); 74 | let expected_size = 0x80 + data_size as usize + resource_size as usize; 75 | trace!(target: "macbinary", "probe: data_size={data_size:X} resource_size={resource_size:X} expected_size={expected_size:X}"); 76 | 77 | if file[0x66..0x6A] == *b"mBIN" && file.len() == expected_size { 78 | // MacBinary III 79 | return true; 80 | } 81 | 82 | // Maybe MacBinary II, check CRC 83 | file[0] == 0 84 | && file[74] == 0 85 | && u16::from_be_bytes(file[124..][..2].try_into().unwrap()) == crc(&file[..124]) 86 | } 87 | 88 | pub fn unpack(file: &[u8]) -> BinResult { 89 | let mut cursor = io::Cursor::new(file); 90 | let header: Header = cursor.read_be()?; 91 | 92 | let mut name_bytes = [0u8; 64]; 93 | name_bytes[0..32].copy_from_slice(&header.filename_part_1); 94 | name_bytes[32..64].copy_from_slice(&header.filename_part_2); 95 | let name_len = name_bytes[0] as usize; 96 | let name = String::from_utf8(name_bytes[1 .. name_len + 1].to_vec()).unwrap(); 97 | 98 | let data_start = 0x80usize; 99 | let resource_start = (data_start + (header.data_length as usize + 0x7F)) & !0x7F; 100 | 101 | let data_end = data_start + (header.data_length as usize); 102 | let data = file[data_start .. data_end].to_vec(); 103 | 104 | let resource_end = resource_start + (header.resource_length as usize); 105 | let resource = file[resource_start .. resource_end].to_vec(); 106 | 107 | Ok(File { 108 | name, 109 | type_id: header.file_type, 110 | creator_id: header.file_creator, 111 | finder_flags: ((header.finder_flags as u16) << 8) | (header.finder_flags_2 as u16), 112 | location: (header.h_pos, header.v_pos), 113 | data, 114 | resource 115 | }) 116 | } 117 | 118 | fn crc(file: &[u8]) -> u16 { 119 | let crc: Crc = Crc::::new(&CRC_16_XMODEM); 120 | crc.checksum(file) 121 | } 122 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{rc::Rc, cell::RefCell}; 2 | #[macro_use] 3 | extern crate log; 4 | 5 | mod common; 6 | mod emulator; 7 | mod linker; 8 | mod macbinary; 9 | mod mac_roman; 10 | mod filesystem; 11 | mod pef; 12 | mod resources; 13 | 14 | fn main() { 15 | env_logger::init(); 16 | 17 | let env_vars = std::env::vars().collect::>(); 18 | let args = std::env::args().skip(1).collect::>(); 19 | if args.is_empty() { 20 | eprintln!("No executable specified"); 21 | return; 22 | } 23 | 24 | let file = match filesystem::MacFile::open(&args[0]) { 25 | Ok(f) => f, 26 | Err(e) => { 27 | eprintln!("Cannot read executable: {:?}", args[0]); 28 | eprintln!("{}", e); 29 | return; 30 | } 31 | }; 32 | let pef = pef::read_pef(&file.data_fork).expect("PEF parsing failed"); 33 | 34 | let file = Rc::new(RefCell::new(file)); 35 | let res = resources::parse_resources(file).expect("Resource fork loading failed"); 36 | 37 | let mut exe = linker::Executable::new(); 38 | exe.load_pef(pef); 39 | 40 | let code = emulator::emulate(&exe, res, &args, &env_vars).unwrap(); 41 | std::process::exit(code); 42 | } 43 | -------------------------------------------------------------------------------- /src/pef/data.rs: -------------------------------------------------------------------------------- 1 | use binread::BinRead; 2 | 3 | #[derive(BinRead, Debug)] 4 | #[br(big, magic = b"Joy!peff")] 5 | pub struct ContainerHeader { 6 | pub architecture: Architecture, 7 | pub format_version: u32, 8 | pub date_time_stamp: u32, 9 | pub old_def_version: u32, 10 | pub old_imp_version: u32, 11 | pub current_version: u32, 12 | pub section_count: u16, 13 | pub inst_section_count: u16, 14 | pub reserved_a: u32 15 | } 16 | 17 | #[derive(BinRead, Debug)] 18 | #[br(big)] 19 | pub struct SectionHeader { 20 | pub name_offset: i32, 21 | pub default_address: u32, 22 | pub total_size: u32, 23 | pub unpacked_size: u32, 24 | pub packed_size: u32, 25 | pub container_offset: u32, 26 | pub section_kind: SectionType, 27 | pub share_kind: ShareType, 28 | pub alignment: u8, 29 | pub reserved_a: u8 30 | } 31 | 32 | #[derive(BinRead, Copy, Clone, Debug, PartialEq, Eq)] 33 | #[br(big, repr = u32)] 34 | #[allow(non_camel_case_types)] 35 | pub enum Architecture { 36 | PowerPC_CFM = 0x70777063, // 'pwpc' 37 | CFM_68K = 0x6d36386b, // 'm68k' 38 | } 39 | 40 | #[derive(BinRead, Copy, Clone, Debug, PartialEq, Eq)] 41 | #[br(big, repr = u8)] 42 | pub enum SectionType { 43 | Code = 0, 44 | UnpackedData = 1, 45 | PatternInitData = 2, 46 | Constant = 3, 47 | Loader = 4, 48 | Debug = 5, 49 | ExecutableData = 6, 50 | Exception = 7, 51 | Traceback = 8 52 | } 53 | 54 | #[derive(BinRead, Copy, Clone, Debug, PartialEq, Eq)] 55 | #[br(big, repr = u8)] 56 | pub enum ShareType { 57 | ProcessShare = 1, 58 | GlobalShare = 4, 59 | ProtectedShare = 5 60 | } 61 | 62 | #[derive(BinRead, Debug)] 63 | #[br(big)] 64 | pub struct LoaderHeader { 65 | pub main_section: i32, 66 | pub main_offset: u32, 67 | pub init_section: i32, 68 | pub init_offset: u32, 69 | pub term_section: i32, 70 | pub term_offset: u32, 71 | pub imported_library_count: u32, 72 | pub total_imported_symbol_count: u32, 73 | pub reloc_section_count: u32, 74 | pub reloc_instr_offset: u32, 75 | pub loader_strings_offset: u32, 76 | pub export_hash_offset: u32, 77 | pub export_hash_table_power: u32, 78 | pub exported_symbol_count: u32 79 | } 80 | 81 | #[derive(BinRead, Debug)] 82 | #[br(big)] 83 | pub struct ImportedLibrary { 84 | pub name_offset: u32, 85 | pub old_imp_version: u32, 86 | pub current_version: u32, 87 | pub imported_symbol_count: u32, 88 | pub first_imported_symbol: u32, 89 | pub options: u8, 90 | pub reserved_a: u8, 91 | pub reserved_b: u16 92 | } 93 | 94 | #[derive(BinRead, Debug)] 95 | #[br(big)] 96 | pub struct RelocationHeader { 97 | pub section_index: u16, 98 | pub reserved_a: u16, 99 | pub reloc_count: u32, 100 | pub first_reloc_offset: u32 101 | } 102 | 103 | #[derive(BinRead, Debug)] 104 | #[br(big)] 105 | pub struct ExportedSymbol { 106 | pub class_and_name: u32, 107 | pub symbol_value: u32, 108 | pub section_index: i16 109 | } 110 | -------------------------------------------------------------------------------- /src/pef/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::Cursor; 2 | 3 | use binread::{BinReaderExt, NullString}; 4 | 5 | mod data; 6 | pub use data::{Architecture, SectionType, ShareType}; 7 | 8 | #[derive(Debug)] 9 | pub struct PEF { 10 | pub sections: Vec
11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct Section { 15 | pub name: Option, 16 | pub default_address: u32, 17 | pub total_size: u32, 18 | pub unpacked_size: u32, 19 | pub packed_size: u32, 20 | pub packed_contents: Option>, 21 | pub section_kind: SectionType, 22 | pub share_kind: ShareType, 23 | pub alignment: u8 24 | } 25 | 26 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 27 | #[repr(u8)] 28 | pub enum SymbolClass { 29 | Code = 0, 30 | Data = 1, 31 | TVect = 2, 32 | TOC = 3, 33 | Glue = 4 34 | } 35 | impl SymbolClass { 36 | fn parse(value: u32) -> Self { 37 | match value { 38 | 0 => Self::Code, 39 | 1 => Self::Data, 40 | 2 => Self::TVect, 41 | 3 => Self::TOC, 42 | 4 => Self::Glue, 43 | _ => panic!() 44 | } 45 | } 46 | } 47 | 48 | #[derive(Debug, Clone)] 49 | pub struct ImportedSymbol { 50 | pub library: usize, 51 | pub name: String, 52 | pub class: SymbolClass, 53 | pub weak: bool 54 | } 55 | 56 | #[derive(Debug)] 57 | pub struct ImportedLibrary { 58 | pub name: String, 59 | pub old_imp_version: u32, 60 | pub current_version: u32, 61 | pub imported_symbols: Vec, 62 | pub import_order: bool, 63 | pub is_weak: bool 64 | } 65 | 66 | #[derive(Debug)] 67 | pub struct RelocSection { 68 | pub section_index: u16, 69 | pub data: Vec 70 | } 71 | 72 | #[derive(Debug)] 73 | pub struct ExportedSymbol { 74 | pub name: String, 75 | pub class: SymbolClass, 76 | pub value: u32, 77 | pub section: i16 78 | } 79 | 80 | #[derive(Debug)] 81 | pub struct Loader { 82 | pub main_section: i32, 83 | pub main_offset: u32, 84 | pub init_section: i32, 85 | pub init_offset: u32, 86 | pub term_section: i32, 87 | pub term_offset: u32, 88 | pub imported_libraries: Vec, 89 | pub imported_symbols: Vec, 90 | pub reloc_sections: Vec, 91 | pub exported_symbols: Vec, 92 | } 93 | 94 | pub fn read_pef(data: &[u8]) -> Result { 95 | let mut cursor = Cursor::new(data); 96 | let container_header: data::ContainerHeader = cursor.read_be()?; 97 | 98 | let mut section_headers: Vec = Vec::new(); 99 | let mut sections: Vec
= Vec::new(); 100 | for _ in 0..container_header.section_count { 101 | let hdr: data::SectionHeader = cursor.read_be()?; 102 | sections.push(Section { 103 | name: None, 104 | default_address: hdr.default_address, 105 | total_size: hdr.total_size, 106 | unpacked_size: hdr.unpacked_size, 107 | packed_size: hdr.packed_size, 108 | packed_contents: None, 109 | section_kind: hdr.section_kind, 110 | share_kind: hdr.share_kind, 111 | alignment: hdr.alignment 112 | }); 113 | section_headers.push(hdr); 114 | } 115 | 116 | // get names 117 | let name_base = cursor.position(); 118 | for (hdr, section) in section_headers.iter().zip(&mut sections) { 119 | if hdr.name_offset >= 0 { 120 | cursor.set_position(name_base + (hdr.name_offset as u64)); 121 | let name: NullString = cursor.read_be()?; 122 | section.name = Some(name.into_string()); 123 | } 124 | 125 | if hdr.packed_size > 0 { 126 | let start = hdr.container_offset as usize; 127 | let end = start + (hdr.packed_size as usize); 128 | section.packed_contents = Some(Vec::from(&data[start .. end])); 129 | } 130 | } 131 | 132 | Ok(PEF { 133 | sections 134 | }) 135 | } 136 | 137 | 138 | struct PatternReader<'a> { 139 | data: &'a [u8], 140 | position: usize 141 | } 142 | impl PatternReader<'_> { 143 | fn remaining(&self) -> bool { 144 | self.position < self.data.len() 145 | } 146 | 147 | fn read_byte(&mut self) -> u8 { 148 | let byte = self.data[self.position]; 149 | self.position += 1; 150 | byte 151 | } 152 | 153 | fn read_arg(&mut self) -> usize { 154 | let mut arg = 0; 155 | while self.position < self.data.len() { 156 | let byte = self.data[self.position]; 157 | self.position += 1; 158 | arg = (arg << 7) | (byte & 0x7F) as usize; 159 | if (byte & 0x80) == 0 { 160 | break; 161 | } 162 | } 163 | arg 164 | } 165 | } 166 | 167 | pub fn unpack_pattern_data(input: &[u8], output: &mut [u8]) { 168 | let mut reader: PatternReader = PatternReader { data: input, position: 0 }; 169 | let mut out_pos = 0usize; 170 | 171 | while reader.remaining() { 172 | let insn = reader.read_byte(); 173 | let opcode = insn >> 5; 174 | let count = if (insn & 31) == 0 { reader.read_arg() } else { (insn & 31) as usize }; 175 | 176 | match opcode { 177 | 0 => { 178 | // Zero 179 | for i in 0..count { 180 | output[out_pos + i] = 0; 181 | } 182 | out_pos += count; 183 | } 184 | 1 => { 185 | // BlockCopy 186 | for i in 0..count { 187 | output[out_pos + i] = reader.read_byte(); 188 | } 189 | out_pos += count; 190 | } 191 | 2 => { 192 | // RepeatedBlock 193 | let repeat_count = reader.read_arg(); 194 | let first_pos = out_pos; 195 | for i in 0..count { 196 | output[out_pos + i] = reader.read_byte(); 197 | } 198 | out_pos += count; 199 | for _ in 0..repeat_count { 200 | output.copy_within(first_pos .. first_pos + count, out_pos); 201 | out_pos += count; 202 | } 203 | } 204 | 3 => { 205 | // InterleaveRepeatBlockWithBlockCopy 206 | let common_size = count; 207 | let custom_size = reader.read_arg(); 208 | let repeat_count = reader.read_arg(); 209 | 210 | let first_pos = out_pos; 211 | for i in 0..common_size { 212 | output[out_pos + i] = reader.read_byte(); 213 | } 214 | out_pos += common_size; 215 | 216 | for _ in 0..repeat_count { 217 | for i in 0..custom_size { 218 | output[out_pos + i] = reader.read_byte(); 219 | } 220 | out_pos += custom_size; 221 | output.copy_within(first_pos .. first_pos + common_size, out_pos); 222 | out_pos += common_size; 223 | } 224 | } 225 | 4 => { 226 | // InterleaveRepeatBlockWithZero 227 | let common_size = count; 228 | let custom_size = reader.read_arg(); 229 | let repeat_count = reader.read_arg(); 230 | 231 | for i in 0..common_size { 232 | output[out_pos + i] = 0; 233 | } 234 | out_pos += common_size; 235 | 236 | for _ in 0..repeat_count { 237 | for i in 0..custom_size { 238 | output[out_pos + i] = reader.read_byte(); 239 | } 240 | out_pos += custom_size; 241 | for i in 0..common_size { 242 | output[out_pos + i] = 0; 243 | } 244 | out_pos += common_size; 245 | } 246 | } 247 | _ => unreachable!() 248 | } 249 | } 250 | } 251 | 252 | pub fn parse_loader(data: &[u8]) -> binread::BinResult { 253 | let mut cursor = Cursor::new(data); 254 | let loader_header: data::LoaderHeader = cursor.read_be()?; 255 | 256 | let mut imported_libraries_data: Vec = Vec::new(); 257 | let mut imported_symbols_data: Vec = Vec::new(); 258 | let mut relocation_headers_data: Vec = Vec::new(); 259 | 260 | for _ in 0..loader_header.imported_library_count { 261 | imported_libraries_data.push(cursor.read_be()?); 262 | } 263 | for _ in 0..loader_header.total_imported_symbol_count { 264 | imported_symbols_data.push(cursor.read_be()?); 265 | } 266 | for _ in 0..loader_header.reloc_section_count { 267 | relocation_headers_data.push(cursor.read_be()?); 268 | } 269 | 270 | // -- 271 | // IMPORTS 272 | 273 | let mut imported_symbols: Vec = Vec::new(); 274 | for symbol in imported_symbols_data { 275 | cursor.set_position((loader_header.loader_strings_offset + (symbol & 0xFFFFFF)).into()); 276 | let name = cursor.read_be::()?.to_string(); 277 | 278 | let class = SymbolClass::parse((symbol >> 24) & 0xF); 279 | let weak = (symbol & 0x80000000) != 0; 280 | imported_symbols.push(ImportedSymbol { library: 0, name, class, weak }) 281 | } 282 | 283 | let mut imported_libraries: Vec = Vec::new(); 284 | for lib in imported_libraries_data { 285 | cursor.set_position((loader_header.loader_strings_offset + lib.name_offset).into()); 286 | let name = cursor.read_be::()?.to_string(); 287 | 288 | let sym_start = lib.first_imported_symbol as usize; 289 | let sym_end = (lib.first_imported_symbol + lib.imported_symbol_count) as usize; 290 | 291 | let index = imported_libraries.len(); 292 | for sym in &mut imported_symbols[sym_start .. sym_end] { 293 | sym.library = index; 294 | } 295 | 296 | let imported_symbols = imported_symbols[sym_start .. sym_end].to_vec(); 297 | 298 | imported_libraries.push(ImportedLibrary { 299 | name, 300 | old_imp_version: lib.old_imp_version, 301 | current_version: lib.current_version, 302 | imported_symbols, 303 | import_order: (lib.options & 0x80) != 0, 304 | is_weak: (lib.options & 0x40) != 0 305 | }); 306 | } 307 | 308 | // -- 309 | // RELOCATIONS 310 | 311 | let mut reloc_sections: Vec = Vec::new(); 312 | for header in relocation_headers_data { 313 | cursor.set_position((loader_header.reloc_instr_offset + header.first_reloc_offset).into()); 314 | 315 | let mut data = Vec::new(); 316 | for _ in 0..header.reloc_count { 317 | data.push(cursor.read_be()?); 318 | } 319 | 320 | reloc_sections.push(RelocSection { 321 | section_index: header.section_index, 322 | data 323 | }); 324 | } 325 | 326 | // -- 327 | // EXPORTS 328 | 329 | let hash_table_entry_count = 1u32 << loader_header.export_hash_table_power; 330 | 331 | let key_table_pos = loader_header.export_hash_offset + hash_table_entry_count * 4; 332 | let exports_pos = key_table_pos + loader_header.exported_symbol_count * 4; 333 | 334 | let mut exported_symbols = Vec::new(); 335 | 336 | for i in 0..loader_header.exported_symbol_count { 337 | cursor.set_position((key_table_pos + i * 4).into()); 338 | let key: u32 = cursor.read_be()?; 339 | 340 | cursor.set_position((exports_pos + i * 10).into()); 341 | let export: data::ExportedSymbol = cursor.read_be()?; 342 | 343 | let name_offset = (loader_header.loader_strings_offset + (export.class_and_name & 0xFFFFFF)) as usize; 344 | let name_length = key >> 16; 345 | let name = data[name_offset .. name_offset + name_length as usize].to_vec(); 346 | let name = String::from_utf8(name).unwrap(); 347 | 348 | let class = SymbolClass::parse((export.class_and_name >> 24) & 0xF); 349 | 350 | exported_symbols.push(ExportedSymbol { 351 | name, 352 | class, 353 | value: export.symbol_value, 354 | section: export.section_index 355 | }); 356 | } 357 | 358 | Ok(Loader { 359 | main_section: loader_header.main_section, 360 | main_offset: loader_header.main_offset, 361 | init_section: loader_header.init_section, 362 | init_offset: loader_header.init_offset, 363 | term_section: loader_header.term_section, 364 | term_offset: loader_header.term_offset, 365 | imported_libraries, 366 | imported_symbols, 367 | reloc_sections, 368 | exported_symbols 369 | }) 370 | } 371 | 372 | -------------------------------------------------------------------------------- /src/resources.rs: -------------------------------------------------------------------------------- 1 | use std::{io::{Cursor, Read}, collections::HashMap, rc::Rc, cell::RefCell}; 2 | 3 | use binread::{BinRead, BinReaderExt, BinResult}; 4 | 5 | use crate::{common::FourCC, filesystem::MacFile}; 6 | 7 | #[derive(BinRead, Debug)] 8 | struct Header { 9 | data_offset: u32, 10 | map_offset: u32, 11 | _data_size: u32, 12 | _map_size: u32, 13 | } 14 | 15 | #[derive(BinRead, Debug)] 16 | struct Map { 17 | _header_copy: Header, 18 | _reserved_next: u32, 19 | _reserved_refnum: u16, 20 | attributes: u16, 21 | type_list_offset: u16, 22 | name_list_offset: u16, 23 | #[br(map = |x: i16| (x + 1) as u32)] 24 | type_count: u32 25 | } 26 | 27 | #[derive(BinRead, Debug)] 28 | struct TypeListEntry { 29 | type_id: FourCC, 30 | #[br(map = |x: u16| (x as u32) + 1)] 31 | resource_count: u32, 32 | ref_list_offset: u16 33 | } 34 | 35 | #[derive(BinRead, Debug)] 36 | struct RefListEntry { 37 | id: i16, 38 | name_offset: u16, 39 | attributes_and_data_offset: u32, 40 | _reserved: u32 41 | } 42 | 43 | pub struct Resource { 44 | pub id: i16, 45 | pub name: Option>, 46 | pub attributes: u8, 47 | pub data: Vec 48 | } 49 | 50 | pub struct Resources { 51 | pub file: Rc>, 52 | pub attributes: u16, 53 | pub types: HashMap>>> 54 | } 55 | 56 | impl Resources { 57 | pub fn add(&mut self, ty: FourCC, id: i16, name: Option>) -> Option>> { 58 | if !self.types.contains_key(&ty) { 59 | self.types.insert(ty, Vec::new()); 60 | } 61 | 62 | let list = self.types.get_mut(&ty).unwrap(); 63 | let mut insert_pos = list.len(); 64 | for (pos, res) in list.iter().enumerate() { 65 | let res_id = res.borrow().id; 66 | if res_id == id { 67 | // resource is already there 68 | return None; 69 | } else if res_id > id { 70 | // we've found the place to insert the new resource at 71 | insert_pos = pos; 72 | break; 73 | } 74 | } 75 | 76 | let res = Resource { 77 | id, 78 | name, 79 | attributes: 0, 80 | data: Vec::new() 81 | }; 82 | let res = Rc::new(RefCell::new(res)); 83 | list.insert(insert_pos, Rc::clone(&res)); 84 | Some(res) 85 | } 86 | 87 | pub fn get(&self, ty: FourCC, id: i16) -> Option>> { 88 | if let Some(list) = self.types.get(&ty) { 89 | for res in list { 90 | if res.borrow().id == id { 91 | return Some(Rc::clone(res)); 92 | } 93 | } 94 | } 95 | None 96 | } 97 | 98 | pub fn remove(&mut self, ty: FourCC, id: i16) { 99 | let list = match self.types.get_mut(&ty) { 100 | Some(l) => l, 101 | None => return 102 | }; 103 | 104 | list.retain(|e| e.borrow().id != id); 105 | if list.is_empty() { 106 | self.types.remove(&ty); 107 | } 108 | } 109 | 110 | pub fn pack(&self) -> Vec { 111 | // rebuild the resource fork 112 | let mut buffer = Vec::new(); 113 | 114 | let data_offset = 256; 115 | buffer.resize(data_offset, 0); 116 | 117 | // construct a map 118 | let mut map_buffer = Vec::new(); 119 | map_buffer.resize(30 + 8 * self.types.len(), 0); 120 | 121 | let mut name_buffer = Vec::new(); 122 | 123 | map_buffer[22] = (self.attributes >> 8) as u8; 124 | map_buffer[23] = (self.attributes & 0xFF) as u8; 125 | 126 | let type_list_offset = 28; 127 | map_buffer[24] = (type_list_offset >> 8) as u8; 128 | map_buffer[25] = (type_list_offset & 0xFF) as u8; 129 | 130 | // we'll put in the name list offset later 131 | 132 | let number_of_types_minus_1 = (self.types.len() as u16).wrapping_sub(1); 133 | map_buffer[28] = (number_of_types_minus_1 >> 8) as u8; 134 | map_buffer[29] = (number_of_types_minus_1 & 0xFF) as u8; 135 | 136 | // write all types 137 | for (type_index, (&type_id, list)) in self.types.iter().enumerate() { 138 | // type header 139 | let type_offset = type_list_offset + 2 + 8 * type_index; 140 | 141 | map_buffer[type_offset] = (type_id.0 >> 24) as u8; 142 | map_buffer[type_offset + 1] = (type_id.0 >> 16) as u8; 143 | map_buffer[type_offset + 2] = (type_id.0 >> 8) as u8; 144 | map_buffer[type_offset + 3] = type_id.0 as u8; 145 | 146 | let number_minus_1 = (list.len() as u16).wrapping_sub(1); 147 | map_buffer[type_offset + 4] = (number_minus_1 >> 8) as u8; 148 | map_buffer[type_offset + 5] = (number_minus_1 & 0xFF) as u8; 149 | 150 | let relative_ref_offset = map_buffer.len() - type_list_offset; 151 | map_buffer[type_offset + 6] = (relative_ref_offset >> 8) as u8; 152 | map_buffer[type_offset + 7] = (relative_ref_offset & 0xFF) as u8; 153 | 154 | // produce ref lists 155 | let ref_list_offset = map_buffer.len(); 156 | map_buffer.resize(map_buffer.len() + 12 * list.len(), 0); 157 | 158 | for (res_index, res) in list.iter().enumerate() { 159 | let res = res.borrow(); 160 | let ref_offset = ref_list_offset + 12 * res_index; 161 | 162 | map_buffer[ref_offset] = (res.id >> 8) as u8; 163 | map_buffer[ref_offset + 1] = res.id as u8; 164 | 165 | // append name 166 | let name_offset = match &res.name { 167 | Some(name) => { 168 | let o = name_buffer.len(); 169 | name_buffer.push(name.len() as u8); 170 | name_buffer.extend_from_slice(&name); 171 | o as u16 172 | } 173 | None => 0xFFFF 174 | }; 175 | map_buffer[ref_offset + 2] = (name_offset >> 8) as u8; 176 | map_buffer[ref_offset + 3] = name_offset as u8; 177 | 178 | map_buffer[ref_offset + 4] = res.attributes; 179 | 180 | let res_data_offset = buffer.len() - data_offset; 181 | map_buffer[ref_offset + 5] = (res_data_offset >> 16) as u8; 182 | map_buffer[ref_offset + 6] = (res_data_offset >> 8) as u8; 183 | map_buffer[ref_offset + 7] = res_data_offset as u8; 184 | 185 | // append data 186 | let res_size = res.data.len(); 187 | buffer.push((res_size >> 24) as u8); 188 | buffer.push((res_size >> 16) as u8); 189 | buffer.push((res_size >> 8) as u8); 190 | buffer.push(res_size as u8); 191 | buffer.extend_from_slice(&res.data); 192 | } 193 | } 194 | 195 | // all done, now add the name list offset 196 | let name_list_offset = map_buffer.len(); 197 | map_buffer[26] = (name_list_offset >> 8) as u8; 198 | map_buffer[27] = (name_list_offset & 0xFF) as u8; 199 | 200 | // we can now assemble the file 201 | // build the resource header 202 | buffer[0] = (data_offset >> 24) as u8; 203 | buffer[1] = (data_offset >> 16) as u8; 204 | buffer[2] = (data_offset >> 8) as u8; 205 | buffer[3] = data_offset as u8; 206 | 207 | let map_offset = buffer.len(); 208 | buffer[4] = (map_offset >> 24) as u8; 209 | buffer[5] = (map_offset >> 16) as u8; 210 | buffer[6] = (map_offset >> 8) as u8; 211 | buffer[7] = map_offset as u8; 212 | 213 | let data_size = buffer.len() - data_offset; 214 | buffer[8] = (data_size >> 24) as u8; 215 | buffer[9] = (data_size >> 16) as u8; 216 | buffer[10] = (data_size >> 8) as u8; 217 | buffer[11] = data_size as u8; 218 | 219 | let map_size = map_buffer.len() + name_buffer.len(); 220 | buffer[12] = (map_size >> 24) as u8; 221 | buffer[13] = (map_size >> 16) as u8; 222 | buffer[14] = (map_size >> 8) as u8; 223 | buffer[15] = map_size as u8; 224 | 225 | // copy it to the map 226 | map_buffer[0..16].copy_from_slice(&buffer[0..16]); 227 | 228 | // and finally get it all sorted out 229 | buffer.extend_from_slice(&map_buffer); 230 | buffer.extend_from_slice(&name_buffer); 231 | 232 | buffer 233 | } 234 | 235 | pub fn save_to_file(&self) { 236 | let mut file = self.file.borrow_mut(); 237 | file.resource_fork = self.pack(); 238 | file.set_dirty(); 239 | } 240 | } 241 | 242 | pub fn parse_resources(file: Rc>) -> BinResult { 243 | let file_ref = file.borrow(); 244 | let mut cursor = Cursor::new(&file_ref.resource_fork); 245 | 246 | let header: Header = cursor.read_be()?; 247 | let data_offset = header.data_offset as u64; 248 | let map_offset = header.map_offset as u64; 249 | cursor.set_position(map_offset); 250 | 251 | let map: Map = cursor.read_be()?; 252 | let type_list_offset = map_offset + map.type_list_offset as u64; 253 | let name_list_offset = map_offset + map.name_list_offset as u64; 254 | 255 | let mut types = HashMap::new(); 256 | 257 | for i in 0 .. map.type_count.into() { 258 | cursor.set_position(type_list_offset + 2 + 8 * i); 259 | let type_list_entry: TypeListEntry = cursor.read_be()?; 260 | 261 | let mut resources = Vec::new(); 262 | 263 | let ref_list_offset = type_list_offset + type_list_entry.ref_list_offset as u64; 264 | for j in 0 .. type_list_entry.resource_count.into() { 265 | cursor.set_position(ref_list_offset + 12 * j); 266 | let ref_list_entry: RefListEntry = cursor.read_be()?; 267 | 268 | let name = if ref_list_entry.name_offset != 0xFFFF { 269 | cursor.set_position(name_list_offset + ref_list_entry.name_offset as u64); 270 | let len: u8 = cursor.read_be()?; 271 | let mut buf = Vec::new(); 272 | buf.resize(len as usize, 0u8); 273 | cursor.read_exact(&mut buf)?; 274 | Some(buf) 275 | } else { 276 | None 277 | }; 278 | 279 | let res_header = data_offset + (ref_list_entry.attributes_and_data_offset & 0xFFFFFF) as u64; 280 | cursor.set_position(res_header); 281 | let res_size: u32 = cursor.read_be()?; 282 | let res_start = res_header + 4; 283 | let res_end = res_start + res_size as u64; 284 | 285 | let data = file_ref.resource_fork[res_start as usize .. res_end as usize].to_vec(); 286 | 287 | let res = Resource { 288 | id: ref_list_entry.id, 289 | name, 290 | attributes: (ref_list_entry.attributes_and_data_offset >> 24) as u8, 291 | data 292 | }; 293 | resources.push(Rc::new(RefCell::new(res))); 294 | } 295 | 296 | types.insert(type_list_entry.type_id, resources); 297 | } 298 | drop(file_ref); 299 | 300 | Ok(Resources { 301 | file, 302 | attributes: map.attributes, 303 | types 304 | }) 305 | } 306 | 307 | --------------------------------------------------------------------------------