Overview
This page contains a code in R that finds which clothes were created with the Pets EP Bodyshop with a bug.
Use at your own risk. Any comments please use the Talk:Detect Pets Body Shop bug/Source Code page.
Source
# # read_package: reads a file (passed by filename) # and returns a structure # library("bitops") # required for bit operations library("survival") # required for date/time operations debug.everything <- F read_package <- function(filename, get.str = F, get.everything = F) { # if (debug.everything) f <- file(filename, open="rb") # open as binary x <- read_package_header(f) # get the header if (is.null(x)) { cat("Error in", filename, "\n") return(NULL) } x$index <- read_package_index_table(f, x) # get index table x$dir <- read_package_dir(f, x) # get DIR resource (but won't use it) # set compressed flag whenever index != dir # but won't use this (and this logic is wrong anyway) x$compressed <- NULL imax <- ifelse(x$n.index <= 0, 1, x$n.index) for (i in 1:imax) x$compressed[[i]] <- !(same_index(x$index[[i]], x$dir[[i]])) x$files <- NULL for (i in 1:imax) { x$files[[i]] <- NULL } # get the property set x$property.set <- read_package_property_set(f, x) # if this is a CASThumbnails package, do something with it x$thumb1 <- read_package_CASThumbnails(f, x) # get all files (not recommended: this is too slow) if (get.everything) { x1 <- x for (i in 1:imax) { cat("reading file", i, "\n") x$files[[i]] <- read_package_file(f, x1, i) } } # get all STR# resources if (get.str && !get.everything) { x1 <- x for (i in 1:imax) { if (x$index[[i]]$type.id == "53545223") { x$files[[i]] <- read_package_file(f, x1, i) } } } # game over close(f) return(x) } read_package_header <- function(f) { x <- NULL # return value x$dbpf <- readChar(f, 4) # le char[4] if (x$dbpf != "DBPF") { cat("Error. Instead of DBPF, I got ", x$dbpf, "\n") return(NULL) } x$major <- readBin(f, "int") # major version x$minor <- readBin(f, "int") # minor version unused <- readBin(f, "int", n = 5) # unused x$index.major <- readBin(f, "int") # major version of index; always 7 in TS2 x$n.index <- readBin(f, "int") # number of entries in the index x$first.index <- readBin(f, "int") # location of first index entry x$size.index <- readBin(f, "int") # size of index x$n.hole <- readBin(f, "int") # number of hole entries in Hole record x$location.hole <- readBin(f, "int") # location of Hole record x$size.hole <- readBin(f, "int") # size of Hole record x$index.minor <- readBin(f, "int") # Minor Version of index + 1 x$index.minor <- x$index.minor - 1 reserved <- readBin(f, "int", n = 8) # 32 bytes (reserved) return(x) } read_package_file <- function(f, x1, i.index) { file.index <- x1$index[[i.index]] seek(f, file.index$location) f1 <- NULL # compressed files must have a header f1$size <- readBin(f, "int") # compressed size of file n <- file.index$size if (n <= 0) return(NULL) if (n == f1$size) { if (debug.everything) { cat("file[", i.index, "] is probably compressed\n") cat(sprintf("First 4 bytes at offset %08X = %08X\n", file.index$location, n)) } f1$raw <- read_compressed_file(f, n - 4) } else { seek(f, -4, origin="current") f1$raw <- readBin(f, "raw", n) } f1$human <- convert_raw(f1$raw, file.index$type.id) # convert raw to human-readable format return(f1) } read_package_index_table <- function(f, x1) { seek(f, where = x1$first.index) t <- NULL imax <- ifelse(x1$n.index <= 0, 1, x1$n.index) for (i in 1:imax) { t[[i]] <- read_package_indexlike_entry(f, x1) } return(t) } read_package_dir <- function(f, x1) { # find if any type.id in index is a DIR i.dir <- 0 imax <- ifelse(x1$n.index <= 0, 1, x1$n.index) for (i in 1:imax) { if (x1$index[[i]]$type.id == "E86B1EEF") { # DIR i.dir <- i break; } } if (i.dir == 0) return(NULL) seek(f, x1$index[[i.dir]]$location) d <- NULL for (i in 1:imax) { d[[i]] <- read_package_indexlike_entry(f, x1, location = x1$index[[i]]$location) } return(d) } # # read one block of indexlike entries - used for Index and DIR # read_package_indexlike_entry <- function(f, x1, location) { t1 <- NULL r1 <- readBin(f, "int", 4, size=1) # Type ID if (length(r1) < 4) { # error return(NULL) } r1 <- r1 %% 256 t1$type.id <- sprintf("%02X%02X%02X%02X", r1[4], r1[3], r1[2], r1[1]) t1$group.id <- readBin(f, "raw", 4) # Group ID t1$instance.id <- readBin(f, "raw", 4) # instance ID if (x1$index.minor == 1) # version 7.1 t1$second.instance.id <- readBin(f, "raw", 4) # second instance ID if (missing(location)) # index t1$location <- readBin(f, "int") # location of the file else t1$location <- location # DIR t1$size <- readBin(f, "int") # size of the file return(t1) } read_package_property_set <- function(f, x1) { # find if any type.id in index is a Property Set i.property.set <- 0 imax <- ifelse(x1$n.index <= 0, 1, x1$n.index) for (i in 1:imax) { if (x1$index[[i]]$type.id == "EBCF3E27") { # Property Set i.property.set <- i break; } } if (debug.everything) cat("i.property.set =", i.property.set, "\n") if (i.property.set == 0) return(NULL) f1 <- NULL # if (is.null(f1)) { f1 <- read_package_file(f, x1, i.property.set) return(f1) } # compressed files must have a header cat("These lines are never executed. Why I don't delete them?") f1$size <- readBin(f, "int") # compressed size of file f1$qfs <- readBin(f, "raw", 2) # 0x10FB f1$uncompressed.size <- readBin(f, "int") # 3 bytes or 4 bytes ??? r1 <- readBin(f, "int", 4, size=1) # Type ID r1 <- r1 %% 256 f1$type.id <- sprintf("%02X%02X%02X%02X", r1[4], r1[3], r1[2], r1[1]) if (f1$type.id != "CBE7505E" && f1$type.id != "CBE750E0") return (f1) # CPF f1$version <- readBin(f, "int", size = 2) f1$n.items <- readBin(f, "int") f1$items <- NULL for (i in 1:(f1$n.items)) { f2 <- NULL r1 <- readBin(f, "int", 4, size=1) # Type ID r1 <- r1 %% 256 f2$data.type <- sprintf("%02X%02X%02X%02X", r1[4], r1[3], r1[2], r1[1]) f2$length <- readBin(f, "int", size=1) if (debug.everything || f2$length <= 0) cat("data.type=", f2$data.type, "length =", f2$length, "\n") f2$name <- readBin(f, "character", f2$length) if (f2$data.type == "EB61E4F7" || f2$data.type == "0C264712") { # int f2$data <- readBin(f, "int", f2$length) } else if (f2$data.type == "0B8BEA18") { # string f2$data <- readBin(f, "character", f2$length) } else if (f2$data.type == "ABC78708") { # float (4 bytes) f2$data <- readBin(f, "double", f2$length, size=4) } else if (f2$data.type == "CBA908E1") { # boolean (1 byte) f2$data <- readBin(f, "raw", f2$length) } f1$items[[i]] <- f2 } return(f1) } # depois de pegar o Index tem que pegar o DIR resource # que comeca com ef 1e 6b e8 # CTSS = 53 53 54 43 # XML = 25 e9 a8 cc # STR# = 23 52 54 53 # Property Set = EBCF3E27 # read_package_CASThumbnails <- function(f, x1) { if (x1$n.index <= 0) return(NULL) if (x1$index[[1]]$type.id != "0C7E9A76") return(NULL) f1 <- NULL # if (is.null(f1)) { f1 <- read_package_file(f, x1, 1) } return(f1) } # # Check if the entry from "index" is the same as from "dir" # same_index <- function(index, dir) { if (is.null(dir)) return(T) if (is.null(index$type.id) | is.null(dir$type.id) | is.null(index$group.id)| is.null(dir$group.id) | is.null(index$instance.id) | is.null(dir$instance.id) | is.null(index$location) | is.null(dir$location) | is.null(index$size) | is.null(dir$size)) return(T) if (index$type.id != dir$type.id) return(F) if (any(index$group.id != dir$group.id)) return(F) if (any(index$instance.id != dir$instance.id)) return(F) if (index$location != dir$location) return(F) if (index$size != dir$size) return(F) return(T) } # # read_compressed_file is dmchess's try_decompress # read_compressed_file <- function(FH, len, try_decompress = T) { if (!try_decompress) { if (debug.everything || len <= 0) cat("Reading", len, "bytes as raw\n") answer <- readBin(FH, "raw", len - 4) return(answer) } header <- readBin(FH, "int", 5, size=1) %% 256 # first five bytes if (debug.everything) cat(sprintf("header = %02X %02X %02X %02X %02X\n", header[1], header[2], header[3], header[4], header[5])); len <- len - 5 answer <- NULL answerlen <- 0 byte1 <- byte2 <- byte3 <- 0 # begin of dmchess's try_decompress perl code converted to R sp <- seek(FH, where=NA) # my $sp = tell FH while (len > 0) { if (len %% 1000 == 0) cat(".") # pacifier for very long files cc <- readBin(FH, "int", 1, size=1) %% 256 # read FH,$buf,1; my $cc = unpack "C", $buf; len <- len - 1 # $len -= 1; # printf " Control char is %02x, len remaining is %08x. \n",$cc,$len; if (cc >= 0xfc) { numplain <- bitAnd(cc, 0x03) # $numplain = $cc & 0x03; if (numplain > len) numplain <- len # $numplain = $len if ($numplain > $len); numcopy <- 0 offset <- 0 } else if (cc >= 0xe0) { numplain <- bitShiftL(cc - 0xdf, 2) # $numplain = ($cc - 0xdf) << 2; numcopy <- 0 offset <- 0 } else if (cc >= 0xc0) { len <- len - 3 # $len -= 3; byte1 <- readBin(FH, "int", 1, size=1) %% 256 # read FH,$buf,1; my $byte1 = unpack "C", $buf; byte2 <- readBin(FH, "int", 1, size=1) %% 256 # read FH,$buf,1; my $byte2 = unpack "C", $buf; byte3 <- readBin(FH, "int", 1, size=1) %% 256 # read FH,$buf,1; my $byte3 = unpack "C", $buf; numplain <- bitAnd(cc, 0x03) # $numplain = $cc & 0x03; numcopy <- bitShiftL(bitAnd(cc, 0x0c), 6) + 5 + byte3 # $numcopy = (($cc & 0x0c) <<6) + 5 + $byte3; offset <- bitShiftL(bitAnd(cc, 0x10), 12) + bitShiftL(byte1, 8) + byte2 # ... # $offset = (($cc & 0x10) << 12 ) + ($byte1 << 8) + $byte2; } else if (cc >= 0x80) { len <- len - 2 # $len -= 2; byte1 <- readBin(FH, "int", 1, size=1) %% 256 # read FH,$buf,1; my $byte1 = unpack "C", $buf; byte2 <- readBin(FH, "int", 1, size=1) %% 256 # read FH,$buf,1; my $byte2 = unpack "C", $buf; numplain <- bitShiftR(bitAnd(byte1, 0xc0), 6) # $numplain = ($byte1 & 0xc0) >> 6; numcopy <- bitAnd(cc, 0x3f) + 4 # $numcopy = ($cc & 0x3f) + 4; offset <- bitShiftL(bitAnd(byte1, 0x3f), 8) + byte2 # $offset = (($byte1 & 0x3f) << 8) + $byte2; } else { byte1 <- readBin(FH, "int", 1, size=1) %% 256 # read FH,$buf,1; my $byte1 = unpack "C", $buf; # cat("byte1 = ", byte1, "\n") len <- len - 1 # $len -= 1; numplain <- bitAnd(cc, 0x03) # $numplain = ($cc & 0x03); numcopy <- bitShiftR(bitAnd(cc, 0x1c), 2) + 3 # $numcopy = (($cc & 0x1c) >> 2) + 3; offset <- bitShiftL(bitAnd(cc, 0x60), 3) + byte1 # $offset = (($cc & 0x60) << 3) + $byte1; } # printf " plain, copy, offset: $numplain, $numcopy, $offset \n"; len <- len - numplain # $len -= $numplain; if (numplain > 0) { # perl didn't check this buf <- readBin(FH, "raw", numplain) # read FH,$buf,$numplain; answer <- c(answer, buf) # $answer = $answer.$buf; } fromoffset <- length(answer) - (offset + 1) # my $fromoffset = length($answer) - ($offset + 1); # 0 == last char if (numcopy > 0) answer <- c(answer, answer[(fromoffset+1):(fromoffset+numcopy)]) # # for ($i=0;$i<$numcopy;$i++) { # $answer = $answer.substr($answer,$fromoffset+$i,1); # } answerlen <- answerlen + numplain # $answerlen += $numplain; answerlen <- answerlen + numcopy # $answerlen += $numcopy; if (debug.everything) { cat(" cc=", sprintf("%x", cc), "\n") cat(" byte1 =", byte1, "byte2 =", byte2, "\n") cat(" numplain =", numplain, "numcopy =", numcopy, "offset =" , offset, "fromoffset =", fromoffset, "\n") } if (len < 0) cat(" UNDERFLOW\n") # if ($len<0) { printf " UNDERFLOW \n"; } } # printf( " Answer length is %08x (%08x). \n", answerlen, length(answer)) # ... # printf " Answer length is %08x (%08x). \n",$answerlen,length($answer); seek(FH, sp) # seek FH,$sp,0; return(answer) # return $answer; } convert_raw <- function(raw, type.id) { if (type.id == "EBCF3E27" & all(as.integer(raw[1:4]) == c(0xe0, 0x50, 0xe7, 0xcb))) return(convert_cpf(raw[-(1:4)])) if (type.id == "53545223") return(convert_str(raw)) return(NULL) } convert_cpf <- function(raw) { if (debug.everything) cat("converting raw to cpf\n") cpf <- NULL cpf$id <- "CPF" cpf$version <- get_little_endian(raw, 2) n <- get_little_endian(raw[3:6], 4) cpf$data <- NULL pos <- 7 for (i in 1:n) { xtype <- get_little_endian(raw[pos:(pos+3)], 4) pos <- pos + 4 nlen <- get_little_endian(raw[pos:(pos+3)], 4) # len of field name pos <- pos + 4 # the code below is very stupid, but I don't know how to do it in an intelligent way name <- rawToChar(raw[pos:(pos+nlen-1)]) pos <- pos + nlen if (xtype == 0xEB61E4F7 || xtype == 0x0C264712 || xtype == 0xABC78708) { # integer or float data <- get_little_endian(raw[pos:(pos+3)], 4) pos <- pos + 4 cpf[[name]] <- data } else if (xtype == 0x0B8BEA18) { # string slen <- get_little_endian(raw[pos:(pos+3)], 4) pos <- pos + 4 str <- rawToChar(raw[pos:(pos+slen-1)]) pos <- pos + slen cpf[[name]] <- str } else if (xtype == 0xCBA908E1) { # boolean cpf[[name]] <- raw[pos] pos <- pos + 1 } } return(cpf) } convert_str <- function(raw) { # not ok, but will do for now return(rawToChar(raw[70:length(raw)])) } get_little_endian <- function(bytes, n) { # I bet there's a more elegant way to do this return(sum(256^(0:(n-1)) * as.integer(bytes[1:n]))) } # # The next routine opens _all_ package files since # some date and tests if it was infected by the bug # in the Pets EP Bodyshop. start_date must be passed # as an ISO-8601 string. # find_pets_ep_bug <- function(start_date = "2006-10-01") { # create vector with all filenames that end in .package s_date <- as.POSIXlt(start_date, "GMT") fnlist <- list.files(pattern = ".package$") for (i in 1:length(fnlist)) { filename <- fnlist[i] # get modification time of package m_date <- as.POSIXlt(file.info(filename)$mtime, "GMT") # skip if package is older than start date if (m_date < s_date) next # in C or similar languages, this would be a continue # now let's do the hard work x <- try(read_package(filename, get.everything = F)) # there should be some sanity checks here! if (is.null(x)) { cat(filename, "seems to have another error\n") next } if (is.null(x$property.set)) next if (is.null(x$property.set$human)) next if (is.null(x$property.set$human$type)) next if (is.null(x$property.set$human$type != "skin")) next # everything suggests that this is a recolor / outfit / clothing if (is.null(x$property.set$human$outfit)) cat(filename, "seems to include the Pets EP Bodyshop bug\n") } # done cat("Total of", length(fnlist), "files processed\n") }