From fe3492a98de29942477b061cd02c92246f4bf85a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 28 Mar 2016 15:36:42 +0200 Subject: Initial commit, new website system --- talks/reverse-engineering/reverse-engineering.html | 483 ++++++++++++++++++ talks/reverse-engineering/ui/default/blank.gif | Bin 0 -> 49 bytes talks/reverse-engineering/ui/default/bodybg.gif | Bin 0 -> 10119 bytes talks/reverse-engineering/ui/default/framing.css | 23 + talks/reverse-engineering/ui/default/iepngfix.htc | 42 ++ talks/reverse-engineering/ui/default/opera.css | 7 + talks/reverse-engineering/ui/default/outline.css | 15 + talks/reverse-engineering/ui/default/pretty.css | 254 ++++++++++ talks/reverse-engineering/ui/default/print.css | 1 + talks/reverse-engineering/ui/default/s5-core.css | 9 + talks/reverse-engineering/ui/default/slides.css | 3 + talks/reverse-engineering/ui/default/slides.js | 545 +++++++++++++++++++++ talks/reverse-engineering/ui/img/footer_bg.png | Bin 0 -> 978 bytes talks/reverse-engineering/ui/img/footer_logo.png | Bin 0 -> 2314 bytes talks/reverse-engineering/ui/img/footer_shadow.png | Bin 0 -> 949 bytes talks/reverse-engineering/ui/img/logo.png | Bin 0 -> 4379 bytes talks/reverse-engineering/ui/img/logo.svg | 44 ++ 17 files changed, 1426 insertions(+) create mode 100644 talks/reverse-engineering/reverse-engineering.html create mode 100644 talks/reverse-engineering/ui/default/blank.gif create mode 100755 talks/reverse-engineering/ui/default/bodybg.gif create mode 100644 talks/reverse-engineering/ui/default/framing.css create mode 100644 talks/reverse-engineering/ui/default/iepngfix.htc create mode 100644 talks/reverse-engineering/ui/default/opera.css create mode 100644 talks/reverse-engineering/ui/default/outline.css create mode 100644 talks/reverse-engineering/ui/default/pretty.css create mode 100644 talks/reverse-engineering/ui/default/print.css create mode 100644 talks/reverse-engineering/ui/default/s5-core.css create mode 100644 talks/reverse-engineering/ui/default/slides.css create mode 100644 talks/reverse-engineering/ui/default/slides.js create mode 100644 talks/reverse-engineering/ui/img/footer_bg.png create mode 100644 talks/reverse-engineering/ui/img/footer_logo.png create mode 100644 talks/reverse-engineering/ui/img/footer_shadow.png create mode 100644 talks/reverse-engineering/ui/img/logo.png create mode 100644 talks/reverse-engineering/ui/img/logo.svg (limited to 'talks/reverse-engineering') diff --git a/talks/reverse-engineering/reverse-engineering.html b/talks/reverse-engineering/reverse-engineering.html new file mode 100644 index 00000000..ade14cc5 --- /dev/null +++ b/talks/reverse-engineering/reverse-engineering.html @@ -0,0 +1,483 @@ + + + + + +Reverse-engineering a proprietary game server with Erlang + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
+ + +
+ +
+

Reverse-engineering a proprietary game server with Erlang

+

Erlang, the fear of game developers...

+

Loïc Hoguin - @lhoguin

+

Erlang Cowboy and Nine Nines Founder

+
+ + +
+

Background

+
+ + +
+

Why reverse-engineer?

+
    +
  • Curiosity
  • +
  • Research the game
  • +
  • Build your own server
      +
    • Because you want a challenge
    • +
    • Because the official server is discontinued
    • +
  • +
+
+ + +
+

Frowned upon

+
    +
  • Few game companies understand
  • +
  • Data mining is cheating
  • +
  • Reverse-engineering is cheating
  • +
  • Even if you don't take advantage of your knowledge
  • +
+
+ + +
+

Anticheat technology

+
    +
  • Online games feature anticheat technology
  • +
  • Often instead of real security
  • +
  • You have to bypass it, undetected
  • +
  • It's OK, anticheat systems are full of flaws
  • +
+
+ + +
+

Windows

+
    +
  • Online games usually only run on Windows
  • +
  • Wine doesn't work because of anticheat programs
  • +
  • A few steps require a Windows box
  • +
  • If you're doing any gaming you probably have one
  • +
+
+ + +
+

Phantasy Star Universe (PSU)

+
    +
  • SEGA game released in 2006
  • +
  • US version shutdown in early 2010
  • +
  • JP version still running
  • +
  • Protected by GameGuard (check files, memory, cheat tools...)
  • +
  • TCP for patch server, SSL for login/game servers
  • +
  • My example for this talk
  • +
+
+ + +
+

Packet logging

+
+ + +
+

Undetected logging

+
    +
  • Methods available can vary depending on the game
  • +
  • Common methods:
      +
    • Snooping
    • +
    • Hooking a function on packet receive/send
    • +
    • Man in the middle
    • +
  • +
+
+ + +
+

Breaking through PSU's SSL

+
    +
  • Find the client SSL certificate
  • +
  • Try connecting to the server from Erlang
  • +
  • Use hosts file to redirect the client to localhost
  • +
  • Make the client connect to Erlang and redirect the packets to the server
  • +
  • You just built a proxy for the game
  • +
  • Packets going through the proxy are readable
  • +
+
+ + +
+

Tee

+
    +
  • A command that redirects input to both standard output and file
  • +
  • Make the proxy save to a file at the same time as redirecting
  • +
+
+ + +
+

Proxy hint

+
    +
  • You can always send the server whatever you want now
  • +
  • You can read, modify, filter packets
  • +
  • You can send any packet anytime
  • +
  • You get more control than you would through the client
  • +
  • Though you need to know the protocol first
  • +
+
+ + +
+

Packet analysis

+
+ + +
+

Protocol

+
    +
  • First we need to figure out the general framing by hand
  • +
  • Open the log file with a hex editor and find packet boundaries
  • +
  • We got a packet size followed by a command number and the packet
  • +
  • The protocol is 32bit
  • +
+
+ + +
+

Spreadsheet help

+
    +
  • Parse the file and aggregate the info to CSV files
  • +
  • Figure out the field boundaries
  • +
  • We got 8bit, 16bit, 32bit data, 32bit floats, ASCII and UCS2 strings
  • +
  • Always take notes of what you are doing
  • +
+
+ + +
+

Aggregate the field values

+
    +
  • Get a clear view of what values a field can take
  • +
  • Sometimes the value never changes
  • +
  • Knowing the values allow you to guess the field purpose
  • +
  • Example: player ID, player level...
  • +
  • It's actually not that hard to figure out most of them
  • +
+
+ + +
+

Game mechanics

+
    +
  • A few key values hide a lot of secrets
  • +
  • Player ID identifies a player account
  • +
  • Quest, zone, map and entry IDs identify the map you play
  • +
  • Target ID identifies the player object in the zone
  • +
  • Client and file analysis helps figure some of these out
  • +
+
+ + +
+

Extracting files

+
+ + +
+

What files?

+
    +
  • Quest and zone files
  • +
  • Quest files identify the mission played
  • +
  • Zone files define the scripts and objects in a set of area
  • +
  • If we are to write a server, we must understand those
  • +
+
+ + +
+

File extraction

+
    +
  • The files aren't always named or identified
  • +
  • Extract and tag them as properly as possible
  • +
  • Ignore duplicates
  • +
  • Get a good SSD for this, it can represent GBs of data
  • +
+
+ + +
+

Files analysis

+
+ + +
+

Client files too

+
    +
  • Analyze both client and server files
  • +
  • They share file formats
  • +
  • PSU has an offline mode where more missions can be found
  • +
  • Sometimes file formats can be found on Google, often not
  • +
+
+ + +
+

Start with a debugger

+
    +
  • First, remove the anticheat technology
  • +
  • Load the game up to the game title
  • +
  • Break at all file loadings (find them with a dissassembler)
  • +
  • Press Enter, loading the login screen
  • +
  • You now have the ASM for loading the file
  • +
+
+ + +
+

Step by step

+
    +
  • Tediously advance step by step to find the interesting functions
  • +
  • The main archive format was encrypted using a blowfish variant
  • +
  • It was also compressed using a custom LZE algorithm
  • +
  • We got through their "security", let's extract the files
  • +
+
+ + +
+

Concurrent extraction

+
    +
  • Use Erlang to concurrently extract all the files you have
  • +
  • This can take some time, but less than if you had to do it on 1 core
  • +
  • Chances are your extraction code is wrong and doesn't handle edge cases
  • +
+
+ + +
+

Continue with an hex editor

+
    +
  • Hex editing allow you to isolate values and group of values
  • +
  • Problem: some files are just structs and arrays with pointers
  • +
  • Pointers get converted to real memory addresses on load
  • +
  • We still need to use a debugger to figure these out
  • +
+
+ + +
+

File parser

+
    +
  • We now have enough info to write a parser for all files
  • +
  • We should make sure the parser gets values in the right range
  • +
  • Pattern matching allow us to crash on unexpected values
  • +
  • Also crash on values that don't seem to change
  • +
+
+ + +
+

Concurrently check our assumptions

+
    +
  • Parse all files concurrently with range checking
  • +
  • If all files pass, then all our assumptions are verified
  • +
  • Bonus: convert the files to readable formats
  • +
+
+ + +
+

Prototype server

+
+ + +
+

Validating protocol assumptions

+
    +
  • Using the proxy would be too limited
  • +
  • We need a valid implementation checked against the client
  • +
+
+ + +
+

PSU's protocols

+
    +
  • Patch, login and game servers
  • +
  • Patch is a very simple TCP protocol
  • +
  • Login and game are the same SSL protocols
  • +
  • Login just redirects to the game server on successful auth
  • +
+
+ + +
+

First implementation

+
    +
  • Make use of the previously logged packets
  • +
  • Take one log and just send all the packets unmodified
  • +
  • Reach in-game and stop there
  • +
  • Figure out the packet order
  • +
  • Try modifying values and check that nothing went wrong
  • +
+
+ + +
+

Trial and error

+
    +
  • Figuring out values and testing them is a trial and error process
  • +
  • We're developers, we're used to do this
  • +
  • It gets easier when we properly reach in-game
  • +
+
+ + +
+

Trial and error

+
+ + +
+

In-game

+
    +
  • Open the menus, move the character, enter rooms
  • +
  • In other words: make the client send packets!
  • +
  • Note what packets are sent when you do something
  • +
  • Find in the logs what is replied when it happens
  • +
+
+ + +
+

Responses

+
    +
  • Same as before, start sending a logged packet
  • +
  • Then figure out the values and test things out
  • +
  • Write a function that does it for you for next times
  • +
+
+ + +
+

Shell testing

+
    +
  • You don't have to wait for client actions
  • +
  • Use the shell to send packets directly from the server
  • +
  • Make your character warp around!
  • +
  • Test things out thoroughly
  • +
+
+ + +
+

Warping is good

+
    +
  • Make sure to write a quick command to warp around
  • +
  • Changing areas allow you to unstuck yourself
  • +
  • The client doesn't do everything asynchronously
  • +
+
+ + +
+

Feedback loop

+
+ + +
+

Lengthy process

+
    +
  • We need early feedback
  • +
  • We must not make the client disconnect
  • +
  • Reconnecting makes us lose at least 1 minute!
  • +
+
+ + +
+

Reloading

+
    +
  • Code reloading allows us to test fixes right away
  • +
  • Data files can be reloaded too
  • +
  • Client can be forced to reload an area through warping
  • +
+
+ + +
+

Don't crash in the network layer

+
    +
  • We must not kill the socket
  • +
  • When something bad happens, print the error in the console!
  • +
  • If a packet can't be parsed, print its hex representation!
  • +
  • Tips: also print when something is parsed properly
  • +
  • You can always crash after you finished working on the server
  • +
+
+ + +
+

Still trial and error

+
    +
  • Someone can help by figuring out values in the client
  • +
  • But this is still mostly trial and error
  • +
  • Although it's much better thanks to Erlang
  • +
+
+ + +
+

Demo

+
+ + +
+

Questions?

+
+ + +
+ + + diff --git a/talks/reverse-engineering/ui/default/blank.gif b/talks/reverse-engineering/ui/default/blank.gif new file mode 100644 index 00000000..75b945d2 Binary files /dev/null and b/talks/reverse-engineering/ui/default/blank.gif differ diff --git a/talks/reverse-engineering/ui/default/bodybg.gif b/talks/reverse-engineering/ui/default/bodybg.gif new file mode 100755 index 00000000..5f448a16 Binary files /dev/null and b/talks/reverse-engineering/ui/default/bodybg.gif differ diff --git a/talks/reverse-engineering/ui/default/framing.css b/talks/reverse-engineering/ui/default/framing.css new file mode 100644 index 00000000..14d8509e --- /dev/null +++ b/talks/reverse-engineering/ui/default/framing.css @@ -0,0 +1,23 @@ +/* The following styles size, place, and layer the slide components. + Edit these if you want to change the overall slide layout. + The commented lines can be uncommented (and modified, if necessary) + to help you with the rearrangement process. */ + +/* target = 1024x768 */ + +div#header, div#footer, .slide {width: 100%; top: 0; left: 0;} +div#header {top: 0; height: 3em; z-index: 1;} +div#footer {top: auto; bottom: 0; height: 2.5em; z-index: 5;} +.slide {top: 0; width: 92%; padding: 3.5em 4% 4%; z-index: 2; list-style: none;} +div#controls {left: 50%; bottom: 0; width: 50%; z-index: 100;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0;} +#currentSlide {position: absolute; width: 10%; left: 45%; bottom: 1em; z-index: 10;} +html>body #currentSlide {position: fixed;} + +/* +div#header {background: #FCC;} +div#footer {background: #CCF;} +div#controls {background: #BBD;} +div#currentSlide {background: #FFC;} +*/ diff --git a/talks/reverse-engineering/ui/default/iepngfix.htc b/talks/reverse-engineering/ui/default/iepngfix.htc new file mode 100644 index 00000000..bba2db75 --- /dev/null +++ b/talks/reverse-engineering/ui/default/iepngfix.htc @@ -0,0 +1,42 @@ + + + + + \ No newline at end of file diff --git a/talks/reverse-engineering/ui/default/opera.css b/talks/reverse-engineering/ui/default/opera.css new file mode 100644 index 00000000..9e9d2a3c --- /dev/null +++ b/talks/reverse-engineering/ui/default/opera.css @@ -0,0 +1,7 @@ +/* DO NOT CHANGE THESE unless you really want to break Opera Show */ +.slide { + visibility: visible !important; + position: static !important; + page-break-before: always; +} +#slide0 {page-break-before: avoid;} diff --git a/talks/reverse-engineering/ui/default/outline.css b/talks/reverse-engineering/ui/default/outline.css new file mode 100644 index 00000000..62db519e --- /dev/null +++ b/talks/reverse-engineering/ui/default/outline.css @@ -0,0 +1,15 @@ +/* don't change this unless you want the layout stuff to show up in the outline view! */ + +.layout div, #footer *, #controlForm * {display: none;} +#footer, #controls, #controlForm, #navLinks, #toggle { + display: block; visibility: visible; margin: 0; padding: 0;} +#toggle {float: right; padding: 0.5em;} +html>body #toggle {position: fixed; top: 0; right: 0;} + +/* making the outline look pretty-ish */ + +#slide0 h1, #slide0 h2, #slide0 h3, #slide0 h4 {border: none; margin: 0;} +#slide0 h1 {padding-top: 1.5em;} +.slide h1 {margin: 1.5em 0 0; padding-top: 0.25em; + border-top: 1px solid #888; border-bottom: 1px solid #AAA;} +#toggle {border: 1px solid; border-width: 0 0 1px 1px; background: #FFF;} diff --git a/talks/reverse-engineering/ui/default/pretty.css b/talks/reverse-engineering/ui/default/pretty.css new file mode 100644 index 00000000..e556f7c3 --- /dev/null +++ b/talks/reverse-engineering/ui/default/pretty.css @@ -0,0 +1,254 @@ +/* Following are the presentation styles -- edit away! */ + +body {background: #FFF -16px 0 no-repeat; color: #000; font-size: 2em;} +:link, :visited {text-decoration: none; color: #00C;} +#controls :active {color: #88A !important;} +#controls :focus {outline: 1px dotted #227;} +h1, h2, h3, h4 {font-size: 100%; margin: 0; padding: 0; font-weight: inherit;} +ul, pre {margin: 0; line-height: 1em;} +html, body {margin: 0; padding: 0;} + +blockquote, q {font-style: italic;} +blockquote {padding: 0 2em 0.5em; margin: 0 1.5em 0.5em; text-align: center; font-size: 1em;} +blockquote p {margin: 0;} +blockquote i {font-style: normal;} +blockquote b {display: block; margin-top: 0.5em; font-weight: normal; font-size: smaller; font-style: normal;} +blockquote b i {font-style: italic;} + +kbd {font-weight: bold; font-size: 1em;} +sup {font-size: smaller; line-height: 1px;} + +.slide code {padding: 2px 0.25em; font-weight: bold; color: #533;} +.slide code.bad, code del {color: red;} +.slide code.old {color: silver;} +.slide pre {padding: 0; margin: 0.25em 0 0.5em 0.5em; color: #533; font-size: 90%;} +.slide pre code {display: block;} +.slide ul {margin-left: 5%; margin-right: 7%; list-style: disc;} +.slide li {margin-top: 0.75em; margin-right: 0;} +.slide ul ul {line-height: 1;} +.slide ul ul li {margin: .2em; font-size: 85%; list-style: square;} +.slide img.leader {display: block; margin: 0 auto;} + +div#header, div#footer {color: #ccc; + font-family: Verdana, Helvetica, sans-serif; background: url("../img/footer_bg.png") repeat scroll 0 0 transparent; + +} +div#header { + +background-image: linear-gradient(bottom, rgb(234,234,234) 26%, rgb(246,246,246) 49%, rgb(252,252,252) 83%); +background-image: -o-linear-gradient(bottom, rgb(234,234,234) 26%, rgb(246,246,246) 49%, rgb(252,252,252) 83%); +background-image: -moz-linear-gradient(bottom, rgb(234,234,234) 26%, rgb(246,246,246) 49%, rgb(252,252,252) 83%); +background-image: -webkit-linear-gradient(bottom, rgb(234,234,234) 26%, rgb(246,246,246) 49%, rgb(252,252,252) 83%); +background-image: -ms-linear-gradient(bottom, rgb(234,234,234) 26%, rgb(246,246,246) 49%, rgb(252,252,252) 83%); +line-height: 1px; +border-bottom: 1px solid #ccc; +} + +div#sub_header { + display: block; z-index: 2; top: 0pt; background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.4); left: 50%; position: fixed; border-radius: 100em 100em 100em 100em; height: 80em; width: 80em; margin-top: -77.3em; margin-left: -40em; +} + +div#footer {font-size: 0.5em; font-weight: bold; padding: 0 0 1em; height: 5em;} +#footer h1, #footer h2 {display: block; padding: 0 1em;} +#footer h2 {font-style: italic;} + +#footer_shadow { + background: url("../img/footer_shadow.png") repeat scroll 0 0 transparent; + width: 100%; + height: 7px; + margin-bottom: 1em; +} + + +div.long {font-size: 0.75em;} +.slide h1 {position: absolute; top: 0.3em; left: 87px; z-index: 1; + margin: 0; padding: 0.3em 0 0 15px; white-space: nowrap; + font: bold 150%/1em Helvetica, sans-serif; text-transform: capitalize; + color: #333;} +.slide h3 {font-size: 130%;} +h1 abbr {font-variant: small-caps;} + +div#controls {position: absolute; left: 50%; bottom: 0; + width: 50%; + text-align: right; font: bold 0.9em Verdana, Helvetica, sans-serif;} +html>body div#controls {position: fixed; padding: 0 0 1em 0; + top: auto;} +div#controls form {position: absolute; bottom: 0; right: 0; width: 100%; + margin: 0; padding: 0;} +#controls #navLinks a {padding: 0; margin: 0 0.5em; + border: none; color: #ccc; + cursor: pointer;} +#controls #navList {height: 1em;} +#controls #navList #jumplist {position: absolute; bottom: 0; right: 0; background: #DDD; color: #227;} + +#currentSlide {text-align: center; font-size: 0.5em; color: #ccc;} + +#logo {text-align: right; position: fixed; width: 100%; bottom: 0pt;} +#logo img { height: 18em; width: 24em; margin-right: 0em; } + +#slide0 {padding-top: 3.5em; font-size: 90%;} +#slide0 h1 {position: static; margin: 1em 0 0; padding: 0; + font: bold 2em Helvetica, sans-serif; white-space: normal; + color: #000; background: transparent;} +#slide0 h2 {font: bold italic 1em Helvetica, sans-serif; margin: 0.25em;} +#slide0 h3 {margin-top: 1.5em; font-size: 1.5em;} +#slide0 h4 {margin-top: 0; font-size: 1em;} + +ul.urls {list-style: none; display: inline; margin: 0;} +.urls li {display: inline; margin: 0;} +.note {display: none;} +.external {border-bottom: 1px dotted gray;} +html>body .external {border-bottom: none;} +.external:after {content: " \274F"; font-size: smaller; color: #77B;} + +.incremental, .incremental *, .incremental *:after {color: #DDE; visibility: visible;} +img.incremental {visibility: hidden;} +.slide .current {color: #B02;} + +button.btn, input[type="submit"].btn { + *padding-top: 2px; + *padding-bottom: 2px; +} +button.btn::-moz-focus-inner, input[type="submit"].btn::-moz-focus-inner { + padding: 0; + border: 0; +} +button.btn.large, input[type="submit"].btn.large { + *padding-top: 7px; + *padding-bottom: 7px; +} +button.btn.small, input[type="submit"].btn.small { + *padding-top: 3px; + *padding-bottom: 3px; +} +.btn-group { + position: relative; + *zoom: 1; + *margin-left: .3em; +} +.btn-group:before, .btn-group:after { + display: table; + content: ""; +} +.btn-group:after { + clear: both; +} +.btn-group:first-child { + *margin-left: 0; +} +.btn-group + .btn-group { + margin-left: 5px; +} +.btn-toolbar { + margin-top: 9px; + margin-bottom: 9px; +} +.btn-toolbar .btn-group { + display: inline-block; + *display: inline; + /* IE7 inline-block hack */ + + *zoom: 1; +} +.btn-group .btn { + position: relative; + float: left; + margin-left: -1px; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} +.btn-group .btn:first-child { + margin-left: 0; + -webkit-border-top-left-radius: 4px; + -moz-border-radius-topleft: 4px; + border-top-left-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + -moz-border-radius-bottomleft: 4px; + border-bottom-left-radius: 4px; +} +.btn-group .btn:last-child, .btn-group .dropdown-toggle { + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topright: 4px; + border-top-right-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + -moz-border-radius-bottomright: 4px; + border-bottom-right-radius: 4px; +} +.btn-group .btn.large:first-child { + margin-left: 0; + -webkit-border-top-left-radius: 6px; + -moz-border-radius-topleft: 6px; + border-top-left-radius: 6px; + -webkit-border-bottom-left-radius: 6px; + -moz-border-radius-bottomleft: 6px; + border-bottom-left-radius: 6px; +} +.btn-group .btn.large:last-child, .btn-group .large.dropdown-toggle { + -webkit-border-top-right-radius: 6px; + -moz-border-radius-topright: 6px; + border-top-right-radius: 6px; + -webkit-border-bottom-right-radius: 6px; + -moz-border-radius-bottomright: 6px; + border-bottom-right-radius: 6px; +} +.btn-group .btn:hover, +.btn-group .btn:focus, +.btn-group .btn:active, +.btn-group .btn.active { + z-index: 2; +} +.btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; + -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + *padding-top: 5px; + *padding-bottom: 5px; +} +.btn-group.open { + *z-index: 1000; +} +.btn-group.open .dropdown-menu { + display: block; + margin-top: 1px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; +} +.btn-group.open .dropdown-toggle { + background-image: none; + -webkit-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + -moz-box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + box-shadow: inset 0 1px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); +} +.btn .caret { + margin-top: 7px; + margin-left: 0; +} +.btn:hover .caret, .open.btn-group .caret { + opacity: 1; + filter: alpha(opacity=100); +} +.btn-primary .caret, +.btn-danger .caret, +.btn-info .caret, +.btn-success .caret, +.btn-inverse .caret { + border-top-color: #ffffff; + opacity: 0.75; + filter: alpha(opacity=75); +} +.btn-small .caret { + margin-top: 4px; +} + + +/* diagnostics + +li:after {content: " [" attr(class) "]"; color: #F88;} + */ \ No newline at end of file diff --git a/talks/reverse-engineering/ui/default/print.css b/talks/reverse-engineering/ui/default/print.css new file mode 100644 index 00000000..e7a71d14 --- /dev/null +++ b/talks/reverse-engineering/ui/default/print.css @@ -0,0 +1 @@ +/* The following rule is necessary to have all slides appear in print! DO NOT REMOVE IT! */ .slide, ul {page-break-inside: avoid; visibility: visible !important;} h1 {page-break-after: avoid;} body {font-size: 12pt; background: white;} * {color: black;} #slide0 h1 {font-size: 200%; border: none; margin: 0.5em 0 0.25em;} #slide0 h3 {margin: 0; padding: 0;} #slide0 h4 {margin: 0 0 0.5em; padding: 0;} #slide0 {margin-bottom: 3em;} h1 {border-top: 2pt solid gray; border-bottom: 1px dotted silver;} .extra {background: transparent !important;} div.extra, pre.extra, .example {font-size: 10pt; color: #333;} ul.extra a {font-weight: bold;} p.example {display: none;} #header {display: none;} #footer h1 {margin: 0; border-bottom: 1px solid; color: gray; font-style: italic;} #footer h2, #controls {display: none;} /* The following rule keeps the layout stuff out of print. Remove at your own risk! */ .layout, .layout * {display: none !important;} \ No newline at end of file diff --git a/talks/reverse-engineering/ui/default/s5-core.css b/talks/reverse-engineering/ui/default/s5-core.css new file mode 100644 index 00000000..86444e04 --- /dev/null +++ b/talks/reverse-engineering/ui/default/s5-core.css @@ -0,0 +1,9 @@ +/* Do not edit or override these styles! The system will likely break if you do. */ + +div#header, div#footer, div#controls, .slide {position: absolute;} +html>body div#header, html>body div#footer, + html>body div#controls, html>body .slide {position: fixed;} +.handout {display: none;} +.layout {display: block;} +.slide, .hideme, .incremental {visibility: hidden;} +#slide0 {visibility: visible;} diff --git a/talks/reverse-engineering/ui/default/slides.css b/talks/reverse-engineering/ui/default/slides.css new file mode 100644 index 00000000..0786d7db --- /dev/null +++ b/talks/reverse-engineering/ui/default/slides.css @@ -0,0 +1,3 @@ +@import url(s5-core.css); /* required to make the slide show run at all */ +@import url(framing.css); /* sets basic placement and size of slide components */ +@import url(pretty.css); /* stuff that makes the slides look better than blah */ \ No newline at end of file diff --git a/talks/reverse-engineering/ui/default/slides.js b/talks/reverse-engineering/ui/default/slides.js new file mode 100644 index 00000000..3d9ad756 --- /dev/null +++ b/talks/reverse-engineering/ui/default/slides.js @@ -0,0 +1,545 @@ +// S5 v1.1 slides.js -- released into the Public Domain +// +// Please see http://www.meyerweb.com/eric/tools/s5/credits.html for information +// about all the wonderful and talented contributors to this code! + +var undef; +var slideCSS = ''; +var snum = 0; +var smax = 1; +var incpos = 0; +var number = undef; +var s5mode = true; +var defaultView = 'slideshow'; +var controlVis = 'visible'; + +var isIE = navigator.appName == 'Microsoft Internet Explorer' && navigator.userAgent.indexOf('Opera') < 1 ? 1 : 0; +var isOp = navigator.userAgent.indexOf('Opera') > -1 ? 1 : 0; +var isGe = navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('Safari') < 1 ? 1 : 0; + +function hasClass(object, className) { + if (!object.className) return false; + return (object.className.search('(^|\\s)' + className + '(\\s|$)') != -1); +} + +function hasValue(object, value) { + if (!object) return false; + return (object.search('(^|\\s)' + value + '(\\s|$)') != -1); +} + +function removeClass(object,className) { + if (!object) return; + object.className = object.className.replace(new RegExp('(^|\\s)'+className+'(\\s|$)'), RegExp.$1+RegExp.$2); +} + +function addClass(object,className) { + if (!object || hasClass(object, className)) return; + if (object.className) { + object.className += ' '+className; + } else { + object.className = className; + } +} + +function GetElementsWithClassName(elementName,className) { + var allElements = document.getElementsByTagName(elementName); + var elemColl = new Array(); + for (var i = 0; i< allElements.length; i++) { + if (hasClass(allElements[i], className)) { + elemColl[elemColl.length] = allElements[i]; + } + } + return elemColl; +} + +function isParentOrSelf(element, id) { + if (element == null || element.nodeName=='BODY') return false; + else if (element.id == id) return true; + else return isParentOrSelf(element.parentNode, id); +} + +function nodeValue(node) { + var result = ""; + if (node.nodeType == 1) { + var children = node.childNodes; + for (var i = 0; i < children.length; ++i) { + result += nodeValue(children[i]); + } + } + else if (node.nodeType == 3) { + result = node.nodeValue; + } + return(result); +} + +function slideLabel() { + var slideColl = GetElementsWithClassName('*','slide'); + var list = document.getElementById('jumplist'); + smax = slideColl.length; + for (var n = 0; n < smax; n++) { + var obj = slideColl[n]; + + var did = 'slide' + n.toString(); + obj.setAttribute('id',did); + if (isOp) continue; + + var otext = ''; + var menu = obj.firstChild; + if (!menu) continue; // to cope with empty slides + while (menu && menu.nodeType == 3) { + menu = menu.nextSibling; + } + if (!menu) continue; // to cope with slides with only text nodes + + var menunodes = menu.childNodes; + for (var o = 0; o < menunodes.length; o++) { + otext += nodeValue(menunodes[o]); + } + list.options[list.length] = new Option(n + ' : ' + otext, n); + } +} + +function currentSlide() { + var cs; + if (document.getElementById) { + cs = document.getElementById('currentSlide'); + } else { + cs = document.currentSlide; + } + cs.innerHTML = '' + snum + '<\/span> ' + + '\/<\/span> ' + + '' + (smax-1) + '<\/span>'; + if (snum == 0) { + cs.style.visibility = 'hidden'; + } else { + cs.style.visibility = 'visible'; + } +} + +function go(step) { + if (document.getElementById('slideProj').disabled || step == 0) return; + var jl = document.getElementById('jumplist'); + var cid = 'slide' + snum; + var ce = document.getElementById(cid); + if (incrementals[snum].length > 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + removeClass(incrementals[snum][i], 'current'); + removeClass(incrementals[snum][i], 'incremental'); + } + } + if (step != 'j') { + snum += step; + lmax = smax - 1; + if (snum > lmax) snum = lmax; + if (snum < 0) snum = 0; + } else + snum = parseInt(jl.value); + var nid = 'slide' + snum; + var ne = document.getElementById(nid); + if (!ne) { + ne = document.getElementById('slide0'); + snum = 0; + } + if (step < 0) {incpos = incrementals[snum].length} else {incpos = 0;} + if (incrementals[snum].length > 0 && incpos == 0) { + for (var i = 0; i < incrementals[snum].length; i++) { + if (hasClass(incrementals[snum][i], 'current')) + incpos = i + 1; + else + addClass(incrementals[snum][i], 'incremental'); + } + } + if (incrementals[snum].length > 0 && incpos > 0) + addClass(incrementals[snum][incpos - 1], 'current'); + ce.style.visibility = 'hidden'; + ne.style.visibility = 'visible'; + jl.selectedIndex = snum; + currentSlide(); + number = 0; +} + +function goTo(target) { + if (target >= smax || target == snum) return; + go(target - snum); +} + +function subgo(step) { + if (step > 0) { + removeClass(incrementals[snum][incpos - 1],'current'); + removeClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos],'current'); + incpos++; + } else { + incpos--; + removeClass(incrementals[snum][incpos],'current'); + addClass(incrementals[snum][incpos], 'incremental'); + addClass(incrementals[snum][incpos - 1],'current'); + } +} + +function toggle() { + var slideColl = GetElementsWithClassName('*','slide'); + var slides = document.getElementById('slideProj'); + var outline = document.getElementById('outlineStyle'); + if (!slides.disabled) { + slides.disabled = true; + outline.disabled = false; + s5mode = false; + fontSize('1em'); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'visible'; + } + } else { + slides.disabled = false; + outline.disabled = true; + s5mode = true; + fontScale(); + for (var n = 0; n < smax; n++) { + var slide = slideColl[n]; + slide.style.visibility = 'hidden'; + } + slideColl[snum].style.visibility = 'visible'; + } +} + +function showHide(action) { + var obj = GetElementsWithClassName('*','hideme')[0]; + switch (action) { + case 's': obj.style.visibility = 'visible'; break; + case 'h': obj.style.visibility = 'hidden'; break; + case 'k': + if (obj.style.visibility != 'visible') { + obj.style.visibility = 'visible'; + } else { + obj.style.visibility = 'hidden'; + } + break; + } +} + +// 'keys' code adapted from MozPoint (http://mozpoint.mozdev.org/) +function keys(key) { + if (!key) { + key = event; + key.which = key.keyCode; + } + if (key.which == 84) { + toggle(); + return; + } + if (s5mode) { + switch (key.which) { + case 10: // return + case 13: // enter + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + if(number != undef) { + goTo(number); + break; + } + case 32: // spacebar + case 34: // page down + case 39: // rightkey + case 40: // downkey + if(number != undef) { + go(number); + } else if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + break; + case 33: // page up + case 37: // leftkey + case 38: // upkey + if(number != undef) { + go(-1 * number); + } else if (!incrementals[snum] || incpos <= 0) { + go(-1); + } else { + subgo(-1); + } + break; + case 36: // home + goTo(0); + break; + case 35: // end + goTo(smax-1); + break; + case 67: // c + showHide('k'); + break; + } + if (key.which < 48 || key.which > 57) { + number = undef; + } else { + if (window.event && isParentOrSelf(window.event.srcElement, 'controls')) return; + if (key.target && isParentOrSelf(key.target, 'controls')) return; + number = (((number != undef) ? number : 0) * 10) + (key.which - 48); + } + } + return false; +} + +function clicker(e) { + number = undef; + var target; + if (window.event) { + target = window.event.srcElement; + e = window.event; + } else target = e.target; + if (target.getAttribute('href') != null || hasValue(target.rel, 'external') || isParentOrSelf(target, 'controls') || isParentOrSelf(target,'embed') || isParentOrSelf(target,'object')) return true; + if (!e.which || e.which == 1) { + if (!incrementals[snum] || incpos >= incrementals[snum].length) { + go(1); + } else { + subgo(1); + } + } +} + +function findSlide(hash) { + var target = null; + var slides = GetElementsWithClassName('*','slide'); + for (var i = 0; i < slides.length; i++) { + var targetSlide = slides[i]; + if ( (targetSlide.name && targetSlide.name == hash) + || (targetSlide.id && targetSlide.id == hash) ) { + target = targetSlide; + break; + } + } + while(target != null && target.nodeName != 'BODY') { + if (hasClass(target, 'slide')) { + return parseInt(target.id.slice(5)); + } + target = target.parentNode; + } + return null; +} + +function slideJump() { + if (window.location.hash == null) return; + var sregex = /^#slide(\d+)$/; + var matches = sregex.exec(window.location.hash); + var dest = null; + if (matches != null) { + dest = parseInt(matches[1]); + } else { + dest = findSlide(window.location.hash.slice(1)); + } + if (dest != null) + go(dest - snum); +} + +function fixLinks() { + var thisUri = window.location.href; + thisUri = thisUri.slice(0, thisUri.length - window.location.hash.length); + var aelements = document.getElementsByTagName('A'); + for (var i = 0; i < aelements.length; i++) { + var a = aelements[i].href; + var slideID = a.match('\#slide[0-9]{1,2}'); + if ((slideID) && (slideID[0].slice(0,1) == '#')) { + var dest = findSlide(slideID[0].slice(1)); + if (dest != null) { + if (aelements[i].addEventListener) { + aelements[i].addEventListener("click", new Function("e", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "if (e.preventDefault) e.preventDefault();"), true); + } else if (aelements[i].attachEvent) { + aelements[i].attachEvent("onclick", new Function("", + "if (document.getElementById('slideProj').disabled) return;" + + "go("+dest+" - snum); " + + "event.returnValue = false;")); + } + } + } + } +} + +function externalLinks() { + if (!document.getElementsByTagName) return; + var anchors = document.getElementsByTagName('a'); + for (var i=0; i' + + '