diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World.htm b/Docs/Chapter 1.1 Setting up Graphics and Hello World.htm new file mode 100644 index 0000000..8c865ad --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World.htm @@ -0,0 +1,796 @@ + + + + + + + + + + + +Chapter 1.1: Setting up Graphics and Hello World + + + + + +
+
The Wayback Machine - +http://web.archive.org/web/20220827033533/http://lameguy64.net/tutorials/pstutorials/chapter1/1-display.html
+ + + + +

1.1. Setting up Graphics and Hello World

+

+This chapter teaches the basics of initializing and setting up the GPU of the +PSX. Areas covered in this chapter include the GPU, VRAM and the concept of +display and drawing environments which are essential for better understanding +how the GPU works when programming for the PSX. +

+

+This tutorial series does not and will not cover graphics programming using the +LIBGS library which is a high level graphics library that adds about 100KB +worth of overhead to your project (remember that the PSX only has 2MB of RAM) +and hides many of the inner workings of the GPU, making it difficult to +understand how to use the GPU directly and would often get in the way when +trying to write your own graphics routines... While it may sound elitist +discouraging the use of LIBGS as it provides facilities for rendering 3D models +easily, it is better to learn how to work with LIBGPU (or PSXGPU in PSn00bSDK) +in the long term as the knowledge gained from learning how to use it directly +will become very important in the future for either better performance, or to +accomplish special graphical effects. +

+

Tutorial compatible with PSn00bSDK:Yes

+ +

Chapter Index

+ + + +

Why Start with Graphics?

+

+This tutorial series begins with graphics programming as displaying a hello +world message requires a bit of graphics programming, as neither the official +SDK or PSn00bSDK provide a framebuffer text console for displaying text largely +due to the way how the hardware works. But both provide a means of basic text +drawing functions intended for debugging purposes which will be demonstrated +for this chapter. +

+

+Besides, most of the satisfaction gained when learning to develop for a new +platform comes from seeing the result displayed on the television screen by the +console you're learning to develop for and you're going need to learn graphics +programming anyway to really be able to do anything interesting with the PSX. +It is a video game console afterall. +

+ + +

Brief Summary of the GPU

+

+The GPU, as the name suggests, is the graphics processor responsible for doing +the graphics rendering work for both 2D and 3D visuals. The block diagram shown +below illustrates the relevant components that make up the entirerity of the +PSX's graphics subsystem, including the MDEC and GTE hardware responsible for +video playback and 3D graphics capabilities respectively. +

+ + + +

The GPU is not 3D

+

+A worrying amount often believed that the PSX's GPU is capable of rendering 3D +polygons. While it may seem to be the case to the uneducated, the GPU is in +fact only capable of drawing primitives in 2D space. Therefore, the GPU is +incapable of perspective correct texture mapping which results in affine +texture distortion and the occasional buggy near clipping seen in a lot of 3D +PSX games. It was common for games to minimize the distortion by subdividing +large polygons into smaller polygons with perspective correct subdivision. +

+

+The bulk of the 3D processing is instead done on the CPU which features a +co-processor suited for such tasks known as the GTE or Geometry Transform +Engine. The GTE is effectively an all integer math co-processor that provides +matrix multiplication, vector transformations and projection operations at high +speed which the CPU alone couldn't do at reasonable speed. Software would then +assemble primitive packets using results from the GTE which are then sent to +the GPU as a linked list via high speed DMA transfer for the GPU to draw said +primitives. +

+

+The GTE will not be covered in this chapter as it is not required for simple +2D graphics programming. Whilst in the topic of the GTE, a very important +issue must be taken care of and that is the GTE is NOT a Geometry +Transfer Engine. An equally concerning amount belive that the GTE +stands for Geometry Transfer Engine and not a Transform Engine when in fact, +the GTE only does math calculations and is not capable of doing any +"transfer" operations whatsoever. Also, the GTE can only be accessed +by the CPU through standard co-processor related instructions. Just wanted to +clarify this before going any further in learning how to program for the PSX +as this bothers me to no end. Suppose it can be used to identify if one is a +real PSX programmer or not. +

+ +

Only 16-bit Color Rendering

+

+Some often mistake that the PSX renders graphics in 24-bit color depth. Whilst +the GPU is capable of 24-bit true color output, it cannot draw primitives in +24-bit color. So the 24-bit color mode is only really useful for MDEC video +sequences or static images in 24-bit color. +

+

+The GPU can only draw at 16-bit color depth in a RGB5I1 pixel format (five bits +per RGB component plus one mask bit used as a mask for semi-transparency +effects). This aligns perfectly with the 16-bit data bus of the VRAM. +

+ +

The VRAM

+

+The GPU features it's own 1MB of VRAM. If you look back at the block diagram +shown earlier you'll see that the VRAM is connected to the GPU and is not +connected the system bus at all. This means the VRAM is not memory mapped and +the CPU cannot access it directly. Instead, VRAM access can only be done +through the GPU. Fortunately, transfers are done via high speed DMA. +

+

+Naturally, the GPU uses the VRAM for storing framebuffers and texture images. +The most interesting aspect about the VRAM is how the GPU treats it like a +1024x512 16-bit image, with areas of VRAM addressed by (X,Y) coordinates +instead of a linear memory address range. Display and drawing framebuffers as +well as texture images reside as rectangular images in the VRAM. The following +illustrates a typical VRAM layout for framebuffers and texture images in PSX +games. +

+ + +

Primitive Packets

+

+The GPU is instructed through the use of primitive packets, which are small +bits of formatted data that make up a GPU command which are sent to the GPU +via DMA for drawing. The primitive packets not only specify the type of +primitive but also the color, semi-transparency flags, (X,Y) coordinates, +texture coordinates, color look-up table to use and so forth. Think of them +like arguments for a function call. +

+

+The number of arguments a primitive packet requires depends on the type of +primitive. The simplest primitives such as a texture page primitive only takes +one argument while the most sophisticated primitive; a texture mapped, gouraud +shaded, 4-point polygon takes as many as 14 arguments. This also means the +size of the primitive varies, with the texture page primitive being only a +word in size while a 4-point, texture mapped and gouraud shaded polygon is +12 words long on top of the tag word present at the start of all primitives. +

+

+Further details about primitive packets will be covered in a future chapter. +

+ +

Framebuffers

+

+Framebuffers for graphics display and drawing reside in VRAM as rectangular +areas defined in (X,Y) coordinates. The size of the display area is based on +the video mode (ie. the display area is 320x240 pixels when running in +320x240 mode). On the other hand, the size for the drawing area can be of any +arbitrary size which may be used to render texture images for accomplishing +certain render to texture based visual effects. Normally, the size of the +drawing area should typically be equal to the display area. +

+

+The display and drawing areas are defined as an environment using the +DISPENV and DRAWENV structs. Each struct defines various +parameters that are relevant to their respective environment. The +DISPENV and DRAWENV structs are initialized using +SetDefDispEnv() and SetDefDrawEnv() respectively which defines +the default parameters for each environment, which may be customized later for +your project's requirements. Once the environments have been defined they can +then be applied to the GPU using PutDispEnv() and PutDrawEnv() +to make each environment effective. +

+

+Learning the display and drawing environments are mandatory for being able to +get any graphical output from the PSX and this will be covered extensively in +this chapter. +

+ +

Writing the Hello World Program

+

+As obligatory as it may sound, a hello world is an effective and simple enough +example to get started with PSX homebrew programming. A hello world program for +the PSX can't be a simple call of printf(), unless you only want to see the +output through a tty which, in all honesty, won't look as interesting as +showing the message on the TV screen or emulator window. +

+

+The basic run-down of accomplishing a hello world on the PSX is to perform the +following operations: +

+ +

+Break out your preferred text editor and begin writing the hello world program +as you go along this chapter. +

+ +

Required Headers

+

+First, include some necessary headers in your C file. The most important of which is +libgpu.h as it provides various definitions provided by the libgpu library. libgte.h +is required even if you don't plan to use the GTE as libgpu.h depends on some +definitions provided in it. +

+
#include <sys/types.h>	// This provides typedefs needed by libgte.h and libgpu.h
+#include <stdio.h>	// Not necessary but include it anyway
+#include <libetc.h>	// Includes some functions that controls the display
+#include <libgte.h>	// GTE header, not really used but libgpu.h depends on it
+#include <libgpu.h>	// GPU library header
+
+

+If using PSn00bSDK, replace the lib prefix with psx instead. Most +of the definitions between the official SDK and PSn00bSDK are identical, so the +syntax of things shouldn't be much to worry much about. +

+ +

Write the main() Function

+

+Like with any C program, you must define a main() function somewhere in your +project. You can omit the argc and argv definitions if desired as they don't +work on the PSX. +

+
int main()
+{
+    return 0;
+}
+
+

+A return value cannot be passed to a parent executable as the kernel does not +save the value for the caller to receive it, but is required nonetheless to +keep the compiler from complaining about returning without a value. +

+ + +

Setting up Graphics

+

+The very first thing to do before anything else in your PSX program is to +call ResetGraph(). ResetGraph() not only resets the GPU as the +name suggests, but it also enables interrupts which are mandatory for getting +anything done on the PSX. If ResetGraph() is not called functions that +depend on interrupts such as VSync() and several other things will not +work, and will usually appear as though your program just crashed. +

+
// Reset GPU and enable interrupts
+ResetGraph(0);
+
+

+Resetting the GPU also masks out the video output which results in a black +screen, but the console still produces video sync. The video output is enabled +again using SetDispMask(), ideally after a DISPENV has been +defined and applied to the GPU for a smooth transition from a previously +running program (ie. the startup screen). As ResetGraph() does not clear +the contents of VRAM, so whatever was drawn by the previous program still +remains when your program takes over. Clearing the VRAM is generally not +required in most well coded scenarios. +

+ +

Setting up the DISPENV and DRAWENV Environments

+

+Start by defining two DISPENV and DRAWENV variables as arrays in +your code. Define an int which will be used for keeping track of things which +will make total sense later on. This value must be set to zero as part of your +graphics initialization code just to make sure the variable starts with a value +of zero. +

+
// Define environment pairs and buffer counter
+DISPENV disp[2];
+DRAWENV draw[2];
+int db;
+
+

+Next is to define the DISPENV pair. Initializing the DISPENV +structure is easiest done using the SetDefDispEnv() function with simple +arguments. +

+For NTSC users: +
// Configures the pair of DISPENVs for 320x240 mode (NTSC)
+SetDefDispEnv(&disp;[0], 0, 0, 320, 240);
+SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
+
+For PAL users: +
// Configures the pair of DISPENVs for 320x256 mode (PAL)
+SetDefDispEnv(&disp;[0], 0, 0, 320, 256);
+SetDefDispEnv(&disp;[1], 0, 256, 320, 256);
+
+// Screen offset to center the picture vertically
+disp[0].screen.y = 24;
+disp[1].screen.y = disp[0].screen.y;
+
+// Forces PAL video standard
+SetVideoMode(MODE_PAL);
+
+

+This defines both DISPENVs for 320x240 resolution mode (320x256 if you +used the PAL snippet) which is the most commonly used video mode. The first +DISPENV is set with a position of (0,0) while the second DISPENV +is set with a VRAM offset of (0,240). You might be wondering why it has to be +defined that way. +

+

+Next is to define the DRAWENVs. Much like the DISPENVs you use +SetDefDrawEnv() to initialize the DRAWENV structure with +simple arguments. +

+For NTSC users: +
// Configures the pair of DRAWENVs for the DISPENVs
+SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
+SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
+
+For PAL users: +
// Configures the pair of DRAWENVs for the DISPENVs
+SetDefDrawEnv(&draw;[0], 0, 256, 320, 256);
+SetDefDrawEnv(&draw;[1], 0, 0, 320, 256);
+
+

+Unlike DISPENV, DRAWENV can be of any arbitrary size for the +drawing environment. But generally it should be equal to the size of the +DISPENV defined, so the drawn area will align perfectly with the display +environments. Additionally, you'll want to set some parameters within the +DRAWENV struct to enable background clearing as otherwise you'll get a +hall of mirrors effect. The background clear takes effect as soon as the +DRAWENV is applied to the GPU using PutDrawEnv(). +

+
// Specifies the clear color of the DRAWENV
+setRGB0(&draw;[0], 63, 0, 127);
+setRGB0(&draw;[1], 63, 0, 127);
+// Enable background clear
+draw[0].isbg = 1;
+draw[1].isbg = 1;
+
+

+Finally, apply the DISPENV/DRAWENV environments to the GPU to +apply the new video mode and drawing environment to achieve a seamless +transition. +

+
// Apply environments
+PutDispEnv(&disp;[0]);
+PutDrawEnv(&draw;[0]);
+
+

+To keep your code look neat and tidy, you'll want to put all the graphics init +code inside a function, to keep your main() function from looking messy +and will make expanding the program easier in the future. +

+ +

The Concept of Double Buffered Rendering

+

+You may have noticed that the DRAWENV pairs are somewhat positioned in +the opposite order as the DISPENV whilst writing the graphics init code +and are probably wondering why this is the case instead of a DRAWENV +directly overlapping a DISPENV. This is how you implement double +buffered rendering on the PSX and is a standard feature to have when writing +real-time graphics code, not just on the PSX but on almost any system with a +framebuffer. +

+

+If you're not familiar with the concept, you basically need to allocate two +framebuffer areas; one for display and one for drawing. The way you use these +framebuffers is you use one buffer as the display buffer, which is the buffer +the graphics hardware will display on the screen and the other as the drawing +buffer, where all your graphics operations should draw to and is not shown on +the screen. Once drawing is complete the two buffers switch places, the drawing +buffer becomes the display buffer and the display buffer become the drawing +buffer for the next frame to be drawn and the cycle repeats. This basically +guarantees that only completed frames are shown on the screen which yields +seamless graphical animations even during framerate drops when intensive +visuals are being drawn aside from the natural reduction of smoothness. +

+ +

+Having a DISPENV and a DRAWENV simply overlap one another on the +same area counts as single buffered rendering and whilst you may get away with +it, the amount of things you can draw/process will be severely limited as you'll +get nasty flicker if the drawing/processing does not complete before the v-blank +period ends. So, a double buffered rendering scheme is much preferred. +

+ +

The Display Function

+

+Now the last thing that deals with the DISPENV and DRAWENV +environments to write is a so called display function, which is basically +a function that does the all buffer swap stuff in a single call for +convenience. Calling the display function should be done at the end of your +loop as that is usually where all graphics operations have completed +and are ready for drawing or display. For this tutorial, the display function +will be named display(). +

+

+Before performing a buffer swap, you must first call DrawSync() then +VSync(). As the names suggests these functions waits for the GPU to +complete any drawing operations and waits for the vertical blanking period +respectively. Waiting for DrawSync() is important as it makes sure that +the GPU has completed drawing any primitives as otherwise you may get flicker +or possibly instability. Waiting for VSync() is also important as it +not only caps your program loop to the TV refresh rate (60fps for NTSC, 50 +for PAL) but it also prevents screen tearing which will happen if you swap +buffers without waiting for the v-blank period. +

+
// Wait for GPU to finish drawing and V-Blank
+DrawSync(0);
+VSync(0);
+
+

+Now the next step is to perform the buffer swap. If you remember that variable +named db earlier, this variable will be used as a counter to keep track +of which buffer pair to switch to. Since there are only two DISPENV and +DRAWENV pairs this variable will simply alternate between 1 and 0 on +every call of the display function. This easily achieved using a NOT (!) +operator. +

+
// Flip buffer counter
+db = !db;
+
+

+Then apply the environment pair based on the value of db to the GPU. +

+
// Apply environments
+PutDispEnv(&disp;[db]);
+PutDrawEnv(&draw;[db]);
+
+

+And finally, call SetDispMask() to lift the display mask so you get +picture instead of a black screen, as ResetGraph() masks the display +by default. +

+
// Enable display
+SetDispMask(1);
+
+ +

Verifying the Code

+

+To make sure you're right on track, here's a listing of what the code should +look like. +

+
#include <sys/types.h>	// This provides typedefs needed by libgte.h and libgpu.h
+#include <stdio.h>	// Not necessary but include it anyway
+#include <libetc.h>	// Includes some functions that controls the display
+#include <libgte.h>	// GTE header, not really used but libgpu.h depends on it
+#include <libgpu.h>	// GPU library header
+
+// Define environment pairs and buffer counter
+DISPENV disp[2];
+DRAWENV draw[2];
+int db;
+
+void init(void)
+{
+    // Reset GPU and enable interrupts
+    ResetGraph(0);
+    
+    // Configures the pair of DISPENVs for 320x240 mode (NTSC)
+    SetDefDispEnv(&disp;[0], 0, 0, 320, 240);
+    SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
+    
+    // Configures the pair of DRAWENVs for the DISPENVs
+    SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
+    SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
+    
+    // Specifies the clear color of the DRAWENV
+    setRGB0(&draw;[0], 63, 0, 127);
+    setRGB0(&draw;[1], 63, 0, 127);
+    
+    // Enable background clear
+    draw[0].isbg = 1;
+    draw[1].isbg = 1;
+    
+    // Apply environments
+    PutDispEnv(&disp;[0]);
+    PutDrawEnv(&draw;[0]);
+
+    // Make sure db starts with zero
+    db = 0;
+}
+
+void display(void)
+{
+    // Wait for GPU to finish drawing and V-Blank
+    DrawSync(0);
+    VSync(0);
+    
+    // Flip buffer counter
+    db = !db;
+    
+    // Apply environments
+    PutDispEnv(&disp;[db]);
+    PutDrawEnv(&draw;[db]);
+    
+    // Enable display
+    SetDispMask(1);
+}
+
+int main()
+{
+    // Initialize graphics and stuff
+    init();
+    
+    // Main loop
+    while(1)
+    {
+        display();
+    }
+    
+    return 0;
+}
+
+ +

Compiling and Testing

+

Now that you've written a considerable amount of code for the graphics +init and buffer swapping stuff, it should be possible to compile the code +and see an output to check if you're right on track. In the official +PsyQ/Programmers' Tool SDK compiling can be done through ccpsx with +a single command line. ccpsx is actually a front for the C, C++ and +linker that makes using the SDK's toolchain easier. +

+
ccpsx -O2 -Xo0x80010000 hello.c -o hello.cpe
+
+

+If you've worked with C before, you should already know that -O2 +specifies the optimization level for the compiler generated code just like +in most other C compilers. The -Xo0x80010000 parameter would likely +be unfamiliar to you and this specifies the target address your executable's +program text is going to reside when it is being loaded by the console. +The target address is usually set to 0x80010000 which is the start of user +space memory on the PSX as the first 64KB is reserved by the kernel. +

+

+The executables ccpsx produces are in CPE format which cannot be +booted from the CD by the PSX and most emulators don't support it. You'll +have to convert the executable into the usable PS-EXE format which can be +done using cpe2x. +

+
cpe2x hello.cpe
+
+

Don't be fooled by the .EXE file extension of a PS-EXE executable file. The +executable files cpe2x produces are neither MS-DOS or Windows +executable programs. +

+

If using PSn00bSDK. It is more complicated to compile a program from within +the command line alone due to the way how the toolchain is currently set up. +It is much easier to just copy a makefile from one of the examples included +in the SDK and tweak the variables for your project. Then use make +to compile the program and it should produce an ELF file and a PS-EXE file. +

+

Run the program and you should get a solid blue image. +

+ + + +

Displaying the Hello World

+

Now that the graphics environment and display routines have been implemented, +the next step is to draw the hello world message. This is easily done by using +the debug font functions FntLoad(), FntOpen() and +FntPrint(). These functions are not suited for general use. Such as in +a game title as it does not support any form of customization and does not +support lowercase letters, but is good enough for testing and debugging +purposes which will be useful later. These functions are provided by libetc, +or psxetc in PSn00bSDK. +

+ +

Setting up the Debug Font

+

The first thing to do before the debug font functions can be used is to is +load the font texture onto VRAM using FntLoad(). The font texture can be +placed anywhere in the VRAM area provided it doesn't get overwritten by +display/draw areas, or textures being loaded onto VRAM. For this tutorial, the +font texture will be placed at (960,0). +

+

Next is to create a text stream using FntOpen() which defines the +area where the text will be drawn at. For this tutorial a text stream of 100 +characters is defined to fill the entire screen, with some overscan +compensation to ensure that the text is visible on the TV. +

+
// Load the internal font texture
+FntLoad(960, 0);
+// Create the text stream
+FntOpen(0, 8, 320, 224, 0, 100);
+
+

The font area coordinates are draw-area relative and not VRAM relative, so +there is no need to define another font area for each of the display/draw areas +in the VRAM. The third argument specifies if a black background for the font +area should be drawn. In PSn00bSDK, you can specify 2 for a semi-transparent +black background. If you try to set it to a non-zero value, the background will +turn black because the font area covers the entire screen. +

+

FntOpen() allows defining multiple font areas by saving the return +value to a persistent variable and passing it as the first argument of +FntPrint() and FntFlush(). Up to eight font areas can be defined +at once, but it cannot be closed or redefined later in your program. +

+

Printing and Displaying Hello World

+

FntPrint() prints text to the last defined font area and works more +or less like printf() and you can also use the newline character +sequence \n to move a line of text down. A font handle can be specified +as an optional first argument of FntPrint(), but in PSn00bSDK a handle +from FntOpen() or -1 must be specified as the first argument. This +

+
FntPrint("HELLO WORLD!");
+
+FntFlush(-1);
+display();
+
+

Calling FntPrint() alone is not enough to display text to the +current draw area, so FntFlush() needs to be called to actually draw +the characters printed from FntPrint() calls. +

+ +

Final Sample Code

+

Compare your code against this listing to make sure you're right on +track. +

+
#include <sys/types.h>	// This provides typedefs needed by libgte.h and libgpu.h
+#include <stdio.h>	// Not necessary but include it anyway
+#include <libetc.h>	// Includes some functions that controls the display
+#include <libgte.h>	// GTE header, not really used but libgpu.h depends on it
+#include <libgpu.h>	// GPU library header
+
+// Define environment pairs and buffer counter
+DISPENV disp[2];
+DRAWENV draw[2];
+int db;
+
+void init(void)
+{
+    // Reset GPU and enable interrupts
+    ResetGraph(0);
+    
+    // Configures the pair of DISPENVs for 320x240 mode (NTSC)
+    SetDefDispEnv(&disp;[0], 0, 0, 320, 240);
+    SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
+    
+    // Configures the pair of DRAWENVs for the DISPENVs
+    SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
+    SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
+    
+    // Specifies the clear color of the DRAWENV
+    setRGB0(&draw;[0], 63, 0, 127);
+    setRGB0(&draw;[1], 63, 0, 127);
+    
+    // Enable background clear
+    draw[0].isbg = 1;
+    draw[1].isbg = 1;
+    
+    // Apply environments
+    PutDispEnv(&disp;[0]);
+    PutDrawEnv(&draw;[0]);
+
+    // Make sure db starts with zero
+    db = 0;
+    
+    // Load the internal font texture
+    FntLoad(960, 0);
+    // Create the text stream
+    FntOpen(0, 8, 320, 224, 0, 100);
+}
+
+void display(void)
+{
+    // Wait for GPU to finish drawing and V-Blank
+    DrawSync(0);
+    VSync(0);
+    
+    // Flip buffer counter
+    db = !db;
+    
+    // Apply environments
+    PutDispEnv(&disp;[db]);
+    PutDrawEnv(&draw;[db]);
+    
+    // Enable display
+    SetDispMask(1);
+}
+
+int main()
+{
+    // Initialize graphics and stuff
+    init();
+    
+    // Main loop
+    while(1)
+    {
+        FntPrint("HELLO WORLD!");
+
+        FntFlush(-1);
+        display();
+    }
+    
+    return 0;
+}
+
+ +

Result

+

Run the program on the console or emulator and you should see a +dark purple picture:

+ + +

Conclusion

+

This concludes this chapter of Lameguy64's PSX Tutorial series. You should +be very familiar of how framebuffers are handled on the PSX hardware and +should be ready to take on drawing proper graphics primitives.

+

A few things you may want to experiment with this example yourself:

+ +

The next tutorial will cover how to draw graphics with the GPU using +ordering tables and primitive packets.

+
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/analytics.js b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/analytics.js new file mode 100644 index 0000000..10400f0 --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/analytics.js @@ -0,0 +1,474 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0 +/* eslint-disable no-var, semi, prefer-arrow-callback, prefer-template */ + +/** + * Collection of methods for sending analytics events to Archive.org's analytics server. + * + * These events are used for internal stats and sent (in anonymized form) to Google Analytics. + * + * @see analytics.md + * + * @type {Object} + */ +window.archive_analytics = (function defineArchiveAnalytics() { + // keep orignal Date object so as not to be affected by wayback's + // hijacking global Date object + var Date = window.Date; + var ARCHIVE_ANALYTICS_VERSION = 2; + var DEFAULT_SERVICE = 'ao_2'; + var NO_SAMPLING_SERVICE = 'ao_no_sampling'; // sends every event instead of a percentage + + var startTime = new Date(); + + /** + * @return {Boolean} + */ + function isPerformanceTimingApiSupported() { + return 'performance' in window && 'timing' in window.performance; + } + + /** + * Determines how many milliseconds elapsed between the browser starting to parse the DOM and + * the current time. + * + * Uses the Performance API or a fallback value if it's not available. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API + * + * @return {Number} + */ + function getLoadTime() { + var start; + + if (isPerformanceTimingApiSupported()) + start = window.performance.timing.domLoading; + else + start = startTime.getTime(); + + return new Date().getTime() - start; + } + + /** + * Determines how many milliseconds elapsed between the user navigating to the page and + * the current time. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API + * + * @return {Number|null} null if the browser doesn't support the Performance API + */ + function getNavToDoneTime() { + if (!isPerformanceTimingApiSupported()) + return null; + + return new Date().getTime() - window.performance.timing.navigationStart; + } + + /** + * Performs an arithmetic calculation on a string with a number and unit, while maintaining + * the unit. + * + * @param {String} original value to modify, with a unit + * @param {Function} doOperation accepts one Number parameter, returns a Number + * @returns {String} + */ + function computeWithUnit(original, doOperation) { + var number = parseFloat(original, 10); + var unit = original.replace(/(\d*\.\d+)|\d+/, ''); + + return doOperation(number) + unit; + } + + /** + * Computes the default font size of the browser. + * + * @returns {String|null} computed font-size with units (typically pixels), null if it cannot be computed + */ + function getDefaultFontSize() { + var fontSizeStr; + + if (!('getComputedStyle' in window)) + return null; + + var style = window.getComputedStyle(document.documentElement); + if (!style) + return null; + + fontSizeStr = style.fontSize; + + // Don't modify the value if tracking book reader. + if (document.querySelector('#BookReader')) + return fontSizeStr; + + return computeWithUnit(fontSizeStr, function reverseBootstrapFontSize(number) { + // Undo the 62.5% size applied in the Bootstrap CSS. + return number * 1.6; + }); + } + + /** + * Get the URL parameters for a given Location + * @param {Location} + * @return {Object} The URL parameters + */ + function getParams(location) { + if (!location) location = window.location; + var vars; + var i; + var pair; + var params = {}; + var query = location.search; + if (!query) return params; + vars = query.substring(1).split('&'); + for (i = 0; i < vars.length; i++) { + pair = vars[i].split('='); + params[pair[0]] = decodeURIComponent(pair[1]); + } + return params; + } + + function getMetaProp(name) { + var metaTag = document.querySelector('meta[property=' + name + ']'); + return metaTag ? metaTag.getAttribute('content') || null : null; + } + + var ArchiveAnalytics = { + /** + * @type {String|null} + */ + service: getMetaProp('service'), + mediaType: getMetaProp('mediatype'), + primaryCollection: getMetaProp('primary_collection'), + + /** + * Key-value pairs to send in pageviews (you can read this after a pageview to see what was + * sent). + * + * @type {Object} + */ + values: {}, + + /** + * Sends an analytics ping, preferably using navigator.sendBeacon() + * @param {Object} values + * @param {Function} [onload_callback] (deprecated) callback to invoke once ping to analytics server is done + * @param {Boolean} [augment_for_ao_site] (deprecated) if true, add some archive.org site-specific values + */ + send_ping: function send_ping(values, onload_callback, augment_for_ao_site) { + if (typeof window.navigator !== 'undefined' && typeof window.navigator.sendBeacon !== 'undefined') + this.send_ping_via_beacon(values); + else + this.send_ping_via_image(values); + }, + + /** + * Sends a ping via Beacon API + * NOTE: Assumes window.navigator.sendBeacon exists + * @param {Object} values Tracking parameters to pass + */ + send_ping_via_beacon: function send_ping_via_beacon(values) { + var url = this.generate_tracking_url(values || {}); + window.navigator.sendBeacon(url); + }, + + /** + * Sends a ping via Image object + * @param {Object} values Tracking parameters to pass + */ + send_ping_via_image: function send_ping_via_image(values) { + var url = this.generate_tracking_url(values || {}); + var loadtime_img = new Image(1, 1); + loadtime_img.src = url; + loadtime_img.alt = ''; + }, + + /** + * Construct complete tracking URL containing payload + * @param {Object} params Tracking parameters to pass + * @return {String} URL to use for tracking call + */ + generate_tracking_url: function generate_tracking_url(params) { + var baseUrl = '//analytics.archive.org/0.gif'; + var keys; + var outputParams = params; + var outputParamsArray = []; + + outputParams.service = outputParams.service || this.service || DEFAULT_SERVICE; + + // Build array of querystring parameters + keys = Object.keys(outputParams); + keys.forEach(function keyIteration(key) { + outputParamsArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(outputParams[key])); + }); + outputParamsArray.push('version=' + ARCHIVE_ANALYTICS_VERSION); + outputParamsArray.push('count=' + (keys.length + 2)); // Include `version` and `count` in count + + return baseUrl + '?' + outputParamsArray.join('&'); + }, + + /** + * @param {int} page Page number + */ + send_scroll_fetch_event: function send_scroll_fetch_event(page) { + var additionalValues = { ev: page }; + var loadTime = getLoadTime(); + var navToDoneTime = getNavToDoneTime(); + if (loadTime) additionalValues.loadtime = loadTime; + if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime; + this.send_event('page_action', 'scroll_fetch', location.pathname, additionalValues); + }, + + send_scroll_fetch_base_event: function send_scroll_fetch_base_event() { + var additionalValues = {}; + var loadTime = getLoadTime(); + var navToDoneTime = getNavToDoneTime(); + if (loadTime) additionalValues.loadtime = loadTime; + if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime; + this.send_event('page_action', 'scroll_fetch_base', location.pathname, additionalValues); + }, + + /** + * @param {Object} [options] + * @param {String} [options.mediaType] + * @param {String} [options.mediaLanguage] + * @param {String} [options.page] The path portion of the page URL + */ + send_pageview: function send_pageview(options) { + var settings = options || {}; + + var defaultFontSize; + var loadTime = getLoadTime(); + var mediaType = settings.mediaType; + var primaryCollection = settings.primaryCollection; + var page = settings.page; + var navToDoneTime = getNavToDoneTime(); + + /** + * @return {String} + */ + function get_locale() { + if (navigator) { + if (navigator.language) + return navigator.language; + + else if (navigator.browserLanguage) + return navigator.browserLanguage; + + else if (navigator.systemLanguage) + return navigator.systemLanguage; + + else if (navigator.userLanguage) + return navigator.userLanguage; + } + return ''; + } + + defaultFontSize = getDefaultFontSize(); + + // Set field values + this.values.kind = 'pageview'; + this.values.timediff = (new Date().getTimezoneOffset()/60)*(-1); // *timezone* diff from UTC + this.values.locale = get_locale(); + this.values.referrer = (document.referrer == '' ? '-' : document.referrer); + + if (loadTime) + this.values.loadtime = loadTime; + + if (navToDoneTime) + this.values.nav_to_done_ms = navToDoneTime; + + if (settings.trackingId) { + this.values.ga_tid = settings.trackingId; + } + + /* START CUSTOM DIMENSIONS */ + if (defaultFontSize) + this.values.ga_cd1 = defaultFontSize; + + if ('devicePixelRatio' in window) + this.values.ga_cd2 = window.devicePixelRatio; + + if (mediaType) + this.values.ga_cd3 = mediaType; + + if (settings.mediaLanguage) { + this.values.ga_cd4 = settings.mediaLanguage; + } + + if (primaryCollection) { + this.values.ga_cd5 = primaryCollection; + } + /* END CUSTOM DIMENSIONS */ + + if (page) + this.values.page = page; + + this.send_ping(this.values); + }, + + /** + * Sends a tracking "Event". + * @param {string} category + * @param {string} action + * @param {string} label + * @param {Object} additionalEventParams + */ + send_event: function send_event( + category, + action, + label, + additionalEventParams + ) { + if (!label) label = window.location.pathname; + if (!additionalEventParams) additionalEventParams = {}; + if (additionalEventParams.mediaLanguage) { + additionalEventParams.ga_cd4 = additionalEventParams.mediaLanguage; + delete additionalEventParams.mediaLanguage; + } + var eventParams = Object.assign( + { + kind: 'event', + ec: category, + ea: action, + el: label, + cache_bust: Math.random(), + }, + additionalEventParams + ); + this.send_ping(eventParams); + }, + + /** + * Sends every event instead of a small percentage. + * + * Use this sparingly as it can generate a lot of events. + * + * @param {string} category + * @param {string} action + * @param {string} label + * @param {Object} additionalEventParams + */ + send_event_no_sampling: function send_event_no_sampling( + category, + action, + label, + additionalEventParams + ) { + var extraParams = additionalEventParams || {}; + extraParams.service = NO_SAMPLING_SERVICE; + this.send_event(category, action, label, extraParams); + }, + + /** + * @param {Object} options see this.send_pageview options + */ + send_pageview_on_load: function send_pageview_on_load(options) { + var self = this; + window.addEventListener('load', function send_pageview_with_options() { + self.send_pageview(options); + }); + }, + + /** + * Handles tracking events passed in URL. + * Assumes category and action values are separated by a "|" character. + * NOTE: Uses the unsampled analytics property. Watch out for future high click links! + * @param {Location} + */ + process_url_events: function process_url_events(location) { + var eventValues; + var actionValue; + var eventValue = getParams(location).iax; + if (!eventValue) return; + eventValues = eventValue.split('|'); + actionValue = eventValues.length >= 1 ? eventValues[1] : ''; + this.send_event_no_sampling( + eventValues[0], + actionValue, + window.location.pathname + ); + }, + + /** + * Attaches handlers for event tracking. + * + * To enable click tracking for a link, add a `data-event-click-tracking` + * attribute containing the Google Analytics Event Category and Action, separated + * by a vertical pipe (|). + * e.g. `` + * + * To enable form submit tracking, add a `data-event-form-tracking` attribute + * to the `form` tag. + * e.g. `
` + * + * Additional tracking options can be added via a `data-event-tracking-options` + * parameter. This parameter, if included, should be a JSON string of the parameters. + * Valid parameters are: + * - service {string}: Corresponds to the Google Analytics property data values flow into + */ + set_up_event_tracking: function set_up_event_tracking() { + var self = this; + var clickTrackingAttributeName = 'event-click-tracking'; + var formTrackingAttributeName = 'event-form-tracking'; + var trackingOptionsAttributeName = 'event-tracking-options'; + + function handleAction(event, attributeName) { + var selector = '[data-' + attributeName + ']'; + var eventTarget = event.target; + if (!eventTarget) return; + var target = eventTarget.closest(selector); + if (!target) return; + var categoryAction; + var categoryActionParts; + var options; + categoryAction = target.dataset[toCamelCase(attributeName)]; + if (!categoryAction) return; + categoryActionParts = categoryAction.split('|'); + options = target.dataset[toCamelCase(trackingOptionsAttributeName)]; + options = options ? JSON.parse(options) : {}; + self.send_event( + categoryActionParts[0], + categoryActionParts[1], + categoryActionParts[2] || window.location.pathname, + options.service ? { service: options.service } : {} + ); + } + + function toCamelCase(str) { + return str.replace(/\W+(.)/g, function (match, chr) { + return chr.toUpperCase(); + }); + }; + + document.addEventListener('click', function(e) { + handleAction(e, clickTrackingAttributeName); + }); + + document.addEventListener('submit', function(e) { + handleAction(e, formTrackingAttributeName); + }); + }, + + /** + * @returns {Object[]} + */ + get_data_packets: function get_data_packets() { + return [this.values]; + }, + + /** + * Creates a tracking image for tracking JS compatibility. + * + * @param {string} type The type value for track_js_case in query params for 0.gif + */ + create_tracking_image: function create_tracking_image(type) { + this.send_ping_via_image({ + cache_bust: Math.random(), + kind: 'track_js', + track_js_case: type, + }); + } + }; + + return ArchiveAnalytics; +}()); +// @license-end diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/banner-styles.css b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/banner-styles.css new file mode 100644 index 0000000..8d39621 --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/banner-styles.css @@ -0,0 +1,500 @@ +@import 'record.css'; /* for SPN1 */ + +#wm-ipp-base { + height:65px;/* initial height just in case js code fails */ + padding:0; + margin:0; + border:none; + background:none transparent; +} +#wm-ipp { + z-index: 2147483647; +} +#wm-ipp, #wm-ipp * { + font-family:Lucida Grande, Helvetica, Arial, sans-serif; + font-size:12px; + line-height:1.2; + letter-spacing:0; + width:auto; + height:auto; + max-width:none; + max-height:none; + min-width:0 !important; + min-height:0; + outline:none; + float:none; + text-align:left; + border:none; + color: #000; + text-indent: 0; + position: initial; + background: none; +} +#wm-ipp div, #wm-ipp canvas { + display: block; +} +#wm-ipp div, #wm-ipp tr, #wm-ipp td, #wm-ipp a, #wm-ipp form { + padding:0; + margin:0; + border:none; + border-radius:0; + background-color:transparent; + background-image:none; + /*z-index:2147483640;*/ + height:auto; +} +#wm-ipp table { + border:none; + border-collapse:collapse; + margin:0; + padding:0; + width:auto; + font-size:inherit; +} +#wm-ipp form input { + padding:1px !important; + height:auto; + display:inline; + margin:0; + color: #000; + background: none #fff; + border: 1px solid #666; +} +#wm-ipp form input[type=submit] { + padding:0 8px !important; + margin:1px 0 1px 5px !important; + width:auto !important; + border: 1px solid #000 !important; + background: #fff !important; + color: #000 !important; +} +#wm-ipp a { + display: inline; +} +#wm-ipp a:hover{ + text-decoration:underline; +} +#wm-ipp a.wm-btn:hover { + text-decoration:none; + color:#ff0 !important; +} +#wm-ipp a.wm-btn:hover span { + color:#ff0 !important; +} +#wm-ipp #wm-ipp-inside { + margin: 0 6px; + border:5px solid #000; + border-top:none; + background-color:rgba(255,255,255,0.9); + -moz-box-shadow:1px 1px 4px #333; + -webkit-box-shadow:1px 1px 4px #333; + box-shadow:1px 1px 4px #333; + border-radius:0 0 8px 8px; +} +/* selectors are intentionally verbose to ensure priority */ +#wm-ipp #wm-logo { + padding:0 10px; + vertical-align:middle; + min-width:100px; + flex: 0 0 100px; +} +#wm-ipp .c { + padding-left: 4px; +} +#wm-ipp .c .u { + margin-top: 4px !important; +} +#wm-ipp .n { + padding:0 0 0 5px !important; + vertical-align: bottom; +} +#wm-ipp .n a { + text-decoration:none; + color:#33f; + font-weight:bold; +} +#wm-ipp .n .b { + padding:0 6px 0 0 !important; + text-align:right !important; + overflow:visible; + white-space:nowrap; + color:#99a; + vertical-align:middle; +} +#wm-ipp .n .y .b { + padding:0 6px 2px 0 !important; +} +#wm-ipp .n .c { + background:#000; + color:#ff0; + font-weight:bold; + padding:0 !important; + text-align:center; +} +#wm-ipp.hi .n td.c { + color:#ec008c; +} +#wm-ipp .n td.f { + padding:0 0 0 6px !important; + text-align:left !important; + overflow:visible; + white-space:nowrap; + color:#99a; + vertical-align:middle; +} +#wm-ipp .n tr.m td { + text-transform:uppercase; + white-space:nowrap; + padding:2px 0; +} +#wm-ipp .c .s { + padding:0 5px 0 0 !important; + vertical-align:bottom; +} +#wm-ipp #wm-nav-captures { + white-space: nowrap; +} +#wm-ipp .c .s a.t { + color:#33f; + font-weight:bold; + line-height: 1.8; +} +#wm-ipp .c .s div.r { + color: #666; + font-size:9px; + white-space:nowrap; +} +#wm-ipp .c .k { + padding-bottom:1px; +} +#wm-ipp .c .s { + padding:0 5px 2px 0 !important; +} +#wm-ipp td#displayMonthEl { + padding: 2px 0 !important; +} +#wm-ipp td#displayYearEl { + padding: 0 0 2px 0 !important; +} + +div#wm-ipp-sparkline { + position:relative;/* for positioning markers */ + white-space:nowrap; + background-color:#fff; + cursor:pointer; + line-height:0.9; +} +#sparklineImgId, #wm-sparkline-canvas { + position:relative; + z-index:9012; + max-width:none; +} +#wm-ipp-sparkline div.yt { + position:absolute; + z-index:9010 !important; + background-color:#ff0 !important; + top: 0; +} +#wm-ipp-sparkline div.mt { + position:absolute; + z-index:9013 !important; + background-color:#ec008c !important; + top: 0; +} +#wm-ipp .r { + margin-left: 4px; +} +#wm-ipp .r a { + color:#33f; + border:none; + position:relative; + background-color:transparent; + background-repeat:no-repeat !important; + background-position:100% 100% !important; + text-decoration: none; +} +#wm-ipp #wm-capinfo { + /* prevents notice div background from sticking into round corners of + #wm-ipp-inside */ + border-radius: 0 0 4px 4px; +} +#wm-ipp #wm-capinfo .c-logo { + display:block; + float:left; + margin-right:3px; + width:90px; + min-height:90px; + max-height: 290px; + border-radius:45px; + overflow:hidden; + background-position:50%; + background-size:auto 90px; + box-shadow: 0 0 2px 2px rgba(208,208,208,128) inset; +} +#wm-ipp #wm-capinfo .c-logo span { + display:inline-block; +} +#wm-ipp #wm-capinfo .c-logo img { + height:90px; + position:relative; + left:-50%; +} +#wm-ipp #wm-capinfo .wm-title { + font-size:130%; +} +#wm-ipp #wm-capinfo a.wm-selector { + display:inline-block; + color: #aaa; + text-decoration:none !important; + padding: 2px 8px; +} +#wm-ipp #wm-capinfo a.wm-selector.selected { + background-color:#666; +} +#wm-ipp #wm-capinfo a.wm-selector:hover { + color: #fff; +} +#wm-ipp #wm-capinfo.notice-only #wm-capinfo-collected-by, +#wm-ipp #wm-capinfo.notice-only #wm-capinfo-timestamps { + display: none; +} +#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content { + background-color:#ff0; + padding:5px; + font-size:14px; + text-align:center; +} +#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content * { + font-size:14px; + text-align:center; +} +#wm-ipp #wm-expand { + right: 1px; + bottom: -1px; + color: #ffffff; + background-color: #666 !important; + padding:0 5px 0 3px !important; + border-radius: 3px 3px 0 0 !important; +} +#wm-ipp #wm-expand span { + color: #ffffff; +} +#wm-ipp #wm-expand #wm-expand-icon { + display: inline-block; + transition: transform 0.5s; + transform-origin: 50% 45%; +} +#wm-ipp #wm-expand.wm-open #wm-expand-icon { + transform: rotate(180deg); +} +#wm-ipp #wmtb { + text-align:right; +} +#wm-ipp #wmtb #wmtbURL { + width: calc(100% - 45px); +} +#wm-ipp #wm-graph-anchor { + border-right:1px solid #ccc; +} +/* time coherence */ +html.wb-highlight { + box-shadow: inset 0 0 0 3px #a50e3a !important; +} +.wb-highlight { + outline: 3px solid #a50e3a !important; +} +#wm-ipp-print { + display:none !important; +} +@media print { +#wm-ipp-base { + display:none !important; +} +#wm-ipp-print { + display:block !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +} +@media (max-width:414px) { + #wm-ipp .xxs { + display:none !important; + } +} +@media (min-width:1055px) { +#wm-ipp #wm-graph-anchor { + display:block !important; +} +} +@media (max-width:1054px) { +#wm-ipp #wm-graph-anchor { + display:none !important; +} +} +@media (max-width:1163px) { +#wm-logo { + display:none !important; +} +} + +#wm-btns { + white-space: nowrap; + margin-top: -2px; +} + +#wm-btns #wm-save-snapshot-open { + margin-right: 7px; + top: -6px; +} + +#wm-btns #wm-sign-in { + box-sizing: content-box; + display: none; + margin-right: 7px; + top: -8px; + + /* + round border around sign in button + */ + border: 2px #000 solid; + border-radius: 14px; + padding-right: 2px; + padding-bottom: 2px; + width: 11px; + height: 11px; +} + +#wm-btns #wm-sign-in>.iconochive-person { + font-size: 12.5px; +} + +#wm-save-snapshot-open > .iconochive-web { + color:#000; + font-size:160%; +} + +#wm-ipp #wm-share { + display: flex; + align-items: flex-end; + justify-content: space-between; +} + +#wm-share > #wm-screenshot { + display: inline-block; + margin-right: 3px; + visibility: hidden; +} + +#wm-screenshot > .iconochive-image { + color:#000; + font-size:160%; +} + +#wm-share > #wm-video { + display: inline-block; + margin-right: 3px; + visibility: hidden; +} + +#wm-video > .iconochive-movies { + color: #000; + display: inline-block; + font-size: 150%; + margin-bottom: 2px; +} + +#wm-btns #wm-save-snapshot-in-progress { + display: none; + font-size:160%; + opacity: 0.5; + position: relative; + margin-right: 7px; + top: -5px; +} + +#wm-btns #wm-save-snapshot-success { + display: none; + color: green; + position: relative; + top: -7px; +} + +#wm-btns #wm-save-snapshot-fail { + display: none; + color: red; + position: relative; + top: -7px; +} + +.wm-icon-screen-shot { + background: url("../images/web-screenshot.svg") no-repeat !important; + background-size: contain !important; + width: 22px !important; + height: 19px !important; + + display: inline-block; +} +#donato { + /* transition effect is disable so as to simplify height adjustment */ + /*transition: height 0.5s;*/ + height: 0; + margin: 0; + padding: 0; + border-bottom: 1px solid #999 !important; +} +body.wm-modal { + height: auto !important; + overflow: hidden !important; +} +#donato #donato-base { + width: 100%; + height: 100%; + /*bottom: 0;*/ + margin: 0; + padding: 0; + position: absolute; + z-index: 2147483639; +} +body.wm-modal #donato #donato-base { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 2147483640; +} + +.wb-autocomplete-suggestions { + font-family: Lucida Grande, Helvetica, Arial, sans-serif; + font-size: 12px; + text-align: left; + cursor: default; + border: 1px solid #ccc; + border-top: 0; + background: #fff; + box-shadow: -1px 1px 3px rgba(0,0,0,.1); + position: absolute; + display: none; + z-index: 2147483647; + max-height: 254px; + overflow: hidden; + overflow-y: auto; + box-sizing: border-box; +} +.wb-autocomplete-suggestion { + position: relative; + padding: 0 .6em; + line-height: 23px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 1.02em; + color: #333; +} +.wb-autocomplete-suggestion b { + font-weight: bold; +} +.wb-autocomplete-suggestion.selected { + background: #f0f0f0; +} diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/bufferlayout.svg b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/bufferlayout.svg new file mode 100644 index 0000000..0e343cd --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/bufferlayout.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + (0,0) + (1023,511) + Buffer A + Buffer B + + diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/bundle-playback.js b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/bundle-playback.js new file mode 100644 index 0000000..390894d --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/bundle-playback.js @@ -0,0 +1,3 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=9)}([function(e,t,n){"use strict";function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){return!t||"object"!==o(t)&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}function s(e){var t="function"==typeof Map?new Map:void 0;return(s=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,o)}function o(){return a(e,arguments,u(this).constructor)}return o.prototype=Object.create(e.prototype,{constructor:{value:o,enumerable:!1,writable:!0,configurable:!0}}),l(o,e)})(e)}function a(e,t,n){return(a=c()?Reflect.construct:function(e,t,n){var o=[null];o.push.apply(o,t);var i=new(Function.bind.apply(e,o));return n&&l(i,n.prototype),i}).apply(null,arguments)}function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function l(e,t){return(l=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}if(n.d(t,"a",(function(){return f})),n.d(t,"d",(function(){return p})),n.d(t,"c",(function(){return d})),n.d(t,"b",(function(){return v})),"undefiend"!=typeof window.XMLHttpRequest){var h=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&l(e,t)}(o,e);var t,n=(t=o,function(){var e,n=u(t);if(c()){var o=u(this).constructor;e=Reflect.construct(n,arguments,o)}else e=n.apply(this,arguments);return r(this,e)});function o(){return i(this,o),n.apply(this,arguments)}return o}(s(XMLHttpRequest));Object.defineProperty(h.prototype,"responseURL",Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype,"responseURL"))}function f(e,t,n,o,i){var r;if((r=window.XMLHttpRequest?new h:new ActiveXObject("Microsoft.XMLHTTP")).onreadystatechange=function(){4==this.readyState&&n(r)},r.open(e,t,!0),o)for(var s in o)o.hasOwnProperty(s)&&r.setRequestHeader(s,o[s]);r.withCredentials=!0,r.send(i)}function p(e){return void 0!==e&&e&&e.constructor===Array}function d(e){return document.cookie.search(e)>=0}function m(e,t){var n=window["HTML".concat(e,"Element")];if(void 0!==n){var o=Object.getOwnPropertyDescriptor(n.prototype,t);void 0!==o&&Object.defineProperty(n.prototype,"_wm_".concat(t),o)}}function v(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"src",n="_wm_".concat(t);return n in e.__proto__?e[n]:e[t]}m("Image","src"),m("Media","src"),m("Embed","src"),m("IFrame","src"),m("Script","src"),m("Link","href"),m("Anchor","href")},function(e,t,n){"use strict";n.d(t,"c",(function(){return s})),n.d(t,"b",(function(){return a})),n.d(t,"a",(function(){return c}));var o=["January","February","March","April","May","June","July","August","September","October","November","December"],i=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],r={Y:function(e){return e.getUTCFullYear()},m:function(e){return e.getUTCMonth()+1},b:function(e){return i[e.getUTCMonth()]},B:function(e){return o[e.getUTCMonth()]},d:function(e){return e.getUTCDate()},H:function(e){return("0"+e.getUTCHours()).slice(-2)},M:function(e){return("0"+e.getUTCMinutes()).slice(-2)},S:function(e){return("0"+e.getUTCSeconds()).slice(-2)},"%":function(){return"%"}};function s(e){var t=function(e){return"number"==typeof e&&(e=e.toString()),[e.slice(-14,-10),e.slice(-10,-8),e.slice(-8,-6),e.slice(-6,-4),e.slice(-4,-2),e.slice(-2)]}(e);return new Date(Date.UTC(t[0],t[1]-1,t[2],t[3],t[4],t[5]))}function a(e){return i[e]}function c(e,t){return t.replace(/%./g,(function(t){var n=r[t[1]];return n?n(s(e)):t}))}},function(e,t,n){"use strict";n.d(t,"b",(function(){return s})),n.d(t,"a",(function(){return a}));var o=n(0);function i(e,t){for(var n=0;n=400?i.failure&&i.failure(e):i.success&&i.success(e)}),{"Content-Type":"application/json"},r.stringify({url:e,snapshot:t,tags:n||[]})),!1}var a=function(){function e(t,n,i){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.el=t,this.url=n,this.timestamp=i,t.onclick=this.save.bind(this),document.addEventListener("DOMContentLoaded",(function(){Object(o.c)("logged-in-user")&&Object(o.c)("logged-in-sig")?r.userIsLoggedIn():r.userIsNotLoggedIn()}))}var t,n,r;return t=e,(n=[{key:"save",value:function(e){this.start(),s(this.url,this.timestamp,[],{failure:this.failure.bind(this),success:this.success.bind(this)})}},{key:"start",value:function(){this.hide(["wm-save-snapshot-fail","wm-save-snapshot-open","wm-save-snapshot-success"]),this.show(["wm-save-snapshot-in-progress"])}},{key:"failure",value:function(e){401==e.status?this.userNotLoggedIn(e):(this.hide(["wm-save-snapshot-in-progress","wm-save-snapshot-success"]),this.show(["wm-save-snapshot-fail","wm-save-snapshot-open"]),console.log("You have got an error."),console.log("If you think something wrong here please send it to support."),console.log('Response: "'+e.responseText+'"'),console.log('status: "'+e.status+'"'))}},{key:"success",value:function(e){this.hide(["wm-save-snapshot-fail","wm-save-snapshot-in-progress"]),this.show(["wm-save-snapshot-open","wm-save-snapshot-success"])}},{key:"userIsLoggedIn",value:function(){this.show("wm-save-snapshot-open"),this.hide("wm-sign-in")}},{key:"userIsNotLoggedIn",value:function(){this.hide(["wm-save-snapshot-open","wm-save-snapshot-in-progress"]),this.show("wm-sign-in")}},{key:"show",value:function(e){this.setDisplayStyle(e,"inline-block")}},{key:"hide",value:function(e){this.setDisplayStyle(e,"none")}},{key:"setDisplayStyle",value:function(e,t){var n=this;(Object(o.d)(e)?e:[e]).forEach((function(e){var o=n.el.getRootNode().getElementById(e);o&&(o.style.display=t)}))}}])&&i(t.prototype,n),r&&i(t,r),e}()},,,,,,,function(e,t,n){"use strict";var o;n.r(t);var i,r={createElementNS:document.createElementNS};var s=!0;function a(e){!function(e,t,n){if(n){var o=new Date;o.setTime(o.getTime()+24*n*60*60*1e3);var i="; expires="+o.toGMTString()}else i="";document.cookie=e+"="+t+i+"; path=/"}(e,"",-1)}var c=n(0),l=n(1),u=window.Date;function h(e,t){return(e=e.toString()).length>=t?e:"00000000".substring(0,t-e.length)+e}function f(e){for(var t=0,n=0;n3}(e)){var o=[];for(n=0;n=e.length?{done:!0}:{done:!1,value:e[t++]}},e:function(e){throw e},f:n}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i,r=!0,s=!1;return{s:function(){o=e[Symbol.iterator]()},n:function(){var e=o.next();return r=e.done,e},e:function(e){s=!0,i=e},f:function(){try{r||null==o.return||o.return()}finally{if(s)throw i}}}}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n2&&void 0!==arguments[2]?arguments[2]:"src",r=window.location.origin,s=b(window,e),l=p(s);try{for(l.s();!(o=l.n()).done;){var u=o.value;if(!n||n(u)){var h=Object(c.b)(u,i);h&&!h.startsWith(t)&&h.startsWith(r)&&(h.startsWith("data:")||a.push(h))}}}catch(e){l.e(e)}finally{l.f()}}u("img"),u("frame"),u("iframe",(function(e){return"playback"!==e.id})),u("script"),u("link",(function(e){return"stylesheet"===e.rel}),"href");var h=a.filter((function(e,t,n){return n.indexOf(e)===t}));h.length>0?(s=0,h.map((function(e){e.match("^https?://")&&(s++,Object(c.a)("HEAD",e,(function(e){if(200==e.status){var t=e.getResponseHeader("Memento-Datetime");if(null==t)console.log("%s: no Memento-Datetime",u);else{var n=document.createElement("span"),a=function(e,t){var n=new Date(e).getTime()-t,o="";n<0?(o+="-",n=Math.abs(n)):o+="+";var i=!1;if(n<1e3)return{delta:n,text:"",highlight:i};var r=n,s=Math.floor(n/1e3/60/60/24/30/12);n-=1e3*s*60*60*24*30*12;var a=Math.floor(n/1e3/60/60/24/30);n-=1e3*a*60*60*24*30;var c=Math.floor(n/1e3/60/60/24);n-=1e3*c*60*60*24;var l=Math.floor(n/1e3/60/60);n-=1e3*l*60*60;var u=Math.floor(n/1e3/60);n-=1e3*u*60;var h=Math.floor(n/1e3),f=[];s>1?(f.push(s+" years"),i=!0):1==s&&(f.push(s+" year"),i=!0);a>1?(f.push(a+" months"),i=!0):1==a&&(f.push(a+" month"),i=!0);c>1?f.push(c+" days"):1==c&&f.push(c+" day");l>1?f.push(l+" hours"):1==l&&f.push(l+" hour");u>1?f.push(u+" minutes"):1==u&&f.push(u+" minute");h>1?f.push(h+" seconds"):1==h&&f.push(h+" second");f.length>2&&(f=f.slice(0,2));return{delta:r,text:o+f.join(" "),highlight:i}}(t,r),c=a.highlight?"color:red;":"";n.innerHTML=" "+a.text,n.title=t,n.setAttribute("style",c);var l=e.getResponseHeader("Content-Type"),u=e.responseURL.replace(window.location.origin,""),h=document.createElement("a");h.innerHTML=u.split("/").splice(3).join("/"),h._wm_href=u,h.title=l,h.onmouseover=y,h.onmouseout=w;var f=document.createElement("div");f.setAttribute("data-delta",a.delta),f.appendChild(h),f.append(n),o.appendChild(f);var p=Array.prototype.slice.call(o.childNodes,0);p.sort((function(e,t){return t.getAttribute("data-delta")-e.getAttribute("data-delta")})),o.innerHTML="";for(var d=0,m=p.length;d0)for(var n=0;n0)for(var n=0;n0?this.sc.scrollTop=i+this.sc.suggestionHeight+o-this.sc.maxHeight:i<0&&(this.sc.scrollTop=i+o)}}},{key:"blurHandler",value:function(){var e=this;try{var t=this.root.querySelector(".wb-autocomplete-suggestions:hover")}catch(e){t=null}t?this.input!==document.activeElement&&setTimeout((function(){return e.focus()}),20):(this.last_val=this.input.value,this.sc.style.display="none",setTimeout((function(){return e.sc.style.display="none"}),350))}},{key:"suggest",value:function(e){var t=this.input.value;if(this.cache[t]=e,e.length&&t.length>=this.minChars){for(var n="",o=0;o40)&&13!=n&&27!=n){var o=this.input.value;if(o.length>=this.minChars){if(o!=this.last_val){if(this.last_val=o,clearTimeout(this.timer),this.cache){if(o in this.cache)return void this.suggest(this.cache[o]);for(var i=1;i'+e.replace(n,"$1")+""}},{key:"onSelect",value:function(e,t,n){}}]),e}(),j=function(){function e(t,n){S(this,e);var o=t.getRootNode();if(o.querySelector){var i="object"==_(t)?[t]:o.querySelectorAll(t);this.elems=i.map((function(e){return new E(e,n)}))}}return T(e,[{key:"destroy",value:function(){for(;this.elems.length>0;)this.elems.pop().unload()}}]),e}(),R=n(2),A=window.JSON,N=window.open,U=window.Date,I=document,D=document,P=function(e){return D.getElementById(e)};var q,F="/static/";function B(e){L.classList.contains("wm-closed")?(e&&O.classList.add("notice-only"),L.classList.replace("wm-closed","wm-open"),O.style.display="block",m(q,F,P)):(L.classList.replace("wm-open","wm-closed"),O.style.display="none",O.classList.remove("notice-only"))}function W(e,t){var n=P(e);n&&(n.style.visibility=t?"visible":"hidden")}function X(e,t){Object(c.d)(e)||(e=[e]);for(var n=0;n0&&r<60,r)}))}window.__wm={init:function(e){!function(){var e=document.cookie.split(";");if(e.length>40)for(var t=0;t1?t-1:0),o=1;o0;)x.appendChild(C.children[0]);if(d)for(var H=0;H'+((""+n).replace(/\B(?=(\d{3})+$)/g,",")+" ")+(n>1?"captures":"capture")+"",f=l.a(i,"%d %b %Y");s!=i&&(f+=" - "+l.a(s,"%d %b %Y")),h+='
'+f+"
",t.innerHTML=h}(o),function(e,t,n,o,i,r,s){var a=o.getContext("2d");if(a){a.fillStyle="#FFF";var c=(new u).getUTCFullYear(),l=t/(c-i+1),h=f(e.years),p=h[0],d=n/h[1];if(r>=i){var m=T(r);a.fillStyle="#FFFFA5",a.fillRect(m,0,l,n)}for(var v=i;v<=c;v++){m=T(v);a.beginPath(),a.moveTo(m,0),a.lineTo(m,n),a.lineWidth=1,a.strokeStyle="#CCC",a.stroke()}s=parseInt(s)-1;for(var g=(l-1)/12,y=0;y0){var M=Math.ceil(S*d);a.fillStyle=v==r&&_==s?"#EC008C":"#000",a.fillRect(Math.round(b),Math.ceil(n-M),Math.ceil(g),Math.round(M))}b+=g}}}function T(e){return Math.ceil((e-i)*l)+.5}}(o,e,t,Z,a,_,S)}}))}else{var te=new Image;te.src="/__wb/sparkline?url="+encodeURIComponent(r)+"&width="+e+"&height="+t+"&selected_year="+_+"&selected_month="+S+(i&&"&collection="+i||""),te.alt="sparkline",te.width=e,te.height=t,te.id="sparklineImgId",te.border="0",Q.parentNode.replaceChild(te,Q)}function ne(e){for(var t=[],n=e.length,o=0;o + + + + CPU - GPU Block Diagram + + + + + + + + image/svg+xml + + CPU - GPU Block Diagram + + + + + + + + + + + + + + + + + + GPU + CPU + GTE + RAM + + + + + + VRAM + + + Bus + MDEC + + Video + + diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/iconochive.css b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/iconochive.css new file mode 100644 index 0000000..7a95ea7 --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/iconochive.css @@ -0,0 +1,116 @@ +@font-face{font-family:'Iconochive-Regular';src:url('https://archive.org/includes/fonts/Iconochive-Regular.eot?-ccsheb');src:url('https://archive.org/includes/fonts/Iconochive-Regular.eot?#iefix-ccsheb') format('embedded-opentype'),url('https://archive.org/includes/fonts/Iconochive-Regular.woff?-ccsheb') format('woff'),url('https://archive.org/includes/fonts/Iconochive-Regular.ttf?-ccsheb') format('truetype'),url('https://archive.org/includes/fonts/Iconochive-Regular.svg?-ccsheb#Iconochive-Regular') format('svg');font-weight:normal;font-style:normal} +[class^="iconochive-"],[class*=" iconochive-"]{font-family:'Iconochive-Regular'!important;speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} +.iconochive-Uplevel:before{content:"\21b5"} +.iconochive-exit:before{content:"\1f6a3"} +.iconochive-beta:before{content:"\3b2"} +.iconochive-logo:before{content:"\1f3db"} +.iconochive-audio:before{content:"\1f568"} +.iconochive-movies:before{content:"\1f39e"} +.iconochive-software:before{content:"\1f4be"} +.iconochive-texts:before{content:"\1f56e"} +.iconochive-etree:before{content:"\1f3a4"} +.iconochive-image:before{content:"\1f5bc"} +.iconochive-web:before{content:"\1f5d4"} +.iconochive-collection:before{content:"\2211"} +.iconochive-folder:before{content:"\1f4c2"} +.iconochive-data:before{content:"\1f5c3"} +.iconochive-tv:before{content:"\1f4fa"} +.iconochive-article:before{content:"\1f5cf"} +.iconochive-question:before{content:"\2370"} +.iconochive-question-dark:before{content:"\3f"} +.iconochive-info:before{content:"\69"} +.iconochive-info-small:before{content:"\24d8"} +.iconochive-comment:before{content:"\1f5e9"} +.iconochive-comments:before{content:"\1f5ea"} +.iconochive-person:before{content:"\1f464"} +.iconochive-people:before{content:"\1f465"} +.iconochive-eye:before{content:"\1f441"} +.iconochive-rss:before{content:"\221e"} +.iconochive-time:before{content:"\1f551"} +.iconochive-quote:before{content:"\275d"} +.iconochive-disc:before{content:"\1f4bf"} +.iconochive-tv-commercial:before{content:"\1f4b0"} +.iconochive-search:before{content:"\1f50d"} +.iconochive-search-star:before{content:"\273d"} +.iconochive-tiles:before{content:"\229e"} +.iconochive-list:before{content:"\21f6"} +.iconochive-list-bulleted:before{content:"\2317"} +.iconochive-latest:before{content:"\2208"} +.iconochive-left:before{content:"\2c2"} +.iconochive-right:before{content:"\2c3"} +.iconochive-left-solid:before{content:"\25c2"} +.iconochive-right-solid:before{content:"\25b8"} +.iconochive-up-solid:before{content:"\25b4"} +.iconochive-down-solid:before{content:"\25be"} +.iconochive-dot:before{content:"\23e4"} +.iconochive-dots:before{content:"\25a6"} +.iconochive-columns:before{content:"\25af"} +.iconochive-sort:before{content:"\21d5"} +.iconochive-atoz:before{content:"\1f524"} +.iconochive-ztoa:before{content:"\1f525"} +.iconochive-upload:before{content:"\1f4e4"} +.iconochive-download:before{content:"\1f4e5"} +.iconochive-favorite:before{content:"\2605"} +.iconochive-heart:before{content:"\2665"} +.iconochive-play:before{content:"\25b6"} +.iconochive-play-framed:before{content:"\1f3ac"} +.iconochive-fullscreen:before{content:"\26f6"} +.iconochive-mute:before{content:"\1f507"} +.iconochive-unmute:before{content:"\1f50a"} +.iconochive-share:before{content:"\1f381"} +.iconochive-edit:before{content:"\270e"} +.iconochive-reedit:before{content:"\2710"} +.iconochive-gear:before{content:"\2699"} +.iconochive-remove-circle:before{content:"\274e"} +.iconochive-plus-circle:before{content:"\1f5d6"} +.iconochive-minus-circle:before{content:"\1f5d5"} +.iconochive-x:before{content:"\1f5d9"} +.iconochive-fork:before{content:"\22d4"} +.iconochive-trash:before{content:"\1f5d1"} +.iconochive-warning:before{content:"\26a0"} +.iconochive-flash:before{content:"\1f5f2"} +.iconochive-world:before{content:"\1f5fa"} +.iconochive-lock:before{content:"\1f512"} +.iconochive-unlock:before{content:"\1f513"} +.iconochive-twitter:before{content:"\1f426"} +.iconochive-facebook:before{content:"\66"} +.iconochive-googleplus:before{content:"\67"} +.iconochive-reddit:before{content:"\1f47d"} +.iconochive-tumblr:before{content:"\54"} +.iconochive-pinterest:before{content:"\1d4df"} +.iconochive-popcorn:before{content:"\1f4a5"} +.iconochive-email:before{content:"\1f4e7"} +.iconochive-embed:before{content:"\1f517"} +.iconochive-gamepad:before{content:"\1f579"} +.iconochive-Zoom_In:before{content:"\2b"} +.iconochive-Zoom_Out:before{content:"\2d"} +.iconochive-RSS:before{content:"\1f4e8"} +.iconochive-Light_Bulb:before{content:"\1f4a1"} +.iconochive-Add:before{content:"\2295"} +.iconochive-Tab_Activity:before{content:"\2318"} +.iconochive-Forward:before{content:"\23e9"} +.iconochive-Backward:before{content:"\23ea"} +.iconochive-No_Audio:before{content:"\1f508"} +.iconochive-Pause:before{content:"\23f8"} +.iconochive-No_Favorite:before{content:"\2606"} +.iconochive-Unike:before{content:"\2661"} +.iconochive-Song:before{content:"\266b"} +.iconochive-No_Flag:before{content:"\2690"} +.iconochive-Flag:before{content:"\2691"} +.iconochive-Done:before{content:"\2713"} +.iconochive-Check:before{content:"\2714"} +.iconochive-Refresh:before{content:"\27f3"} +.iconochive-Headphones:before{content:"\1f3a7"} +.iconochive-Chart:before{content:"\1f4c8"} +.iconochive-Bookmark:before{content:"\1f4d1"} +.iconochive-Documents:before{content:"\1f4da"} +.iconochive-Newspaper:before{content:"\1f4f0"} +.iconochive-Podcast:before{content:"\1f4f6"} +.iconochive-Radio:before{content:"\1f4fb"} +.iconochive-Cassette:before{content:"\1f4fc"} +.iconochive-Shuffle:before{content:"\1f500"} +.iconochive-Loop:before{content:"\1f501"} +.iconochive-Low_Audio:before{content:"\1f509"} +.iconochive-First:before{content:"\1f396"} +.iconochive-Invisible:before{content:"\1f576"} +.iconochive-Computer:before{content:"\1f5b3"} diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/style.css b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/style.css new file mode 100644 index 0000000..1eac881 --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/style.css @@ -0,0 +1,70 @@ +body { + max-width: 900px; + margin: auto; + padding: 8px; + font-family: sans-serif; + font-size: 14px; + //color: white; + //background: black; +} + +h { + background-color: #e0e0e0; + padding: 2px; +} + +h2 { + border-bottom: 1px solid; + //padding-left: 8px; +} + +img { + display: block; +} + +.footer-table { + font-size: 14px; +} + +.bordered-table { + border-collapse: collapse; + font-size: 11px; +} + +.bordered-table th, .bordered-table td { + border: 1px solid; + padding: 4px; +} + +hr { + border: none; + border-bottom: 1px solid black; +} + +pre { + padding: 8px; + font-size: 12px; + color: black; + background-color: LightGray; +} +/* + FILE ARCHIVED ON 03:35:25 Aug 27, 2022 AND RETRIEVED FROM THE + INTERNET ARCHIVE ON 15:39:09 Sep 05, 2022. + JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE. + + ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C. + SECTION 108(a)(3)). +*/ +/* +playback timings (ms): + captures_list: 107.165 + exclusion.robots: 0.073 + exclusion.robots.policy: 0.067 + cdx.remote: 0.064 + esindex: 0.009 + LoadShardBlock: 52.713 (3) + PetaboxLoader3.datanode: 140.466 (4) + CDXLines.iter: 14.771 (3) + load_resource: 139.896 + PetaboxLoader3.resolve: 47.824 +*/ \ No newline at end of file diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/vram-layout.svg b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/vram-layout.svg new file mode 100644 index 0000000..882055c --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/vram-layout.svg @@ -0,0 +1,261 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + (0,0) + (1023,511) + Buffer A + Buffer B + + CLUTS + + Textures + + Textures + + Textures + + Textures + + + Textures + Textures + + diff --git a/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/wombat.js b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/wombat.js new file mode 100644 index 0000000..19d1a47 --- /dev/null +++ b/Docs/Chapter 1.1 Setting up Graphics and Hello World_files/wombat.js @@ -0,0 +1,21 @@ +/* +Wombat.js client-side rewriting engine for web archive replay +Copyright (C) 2014-2020 Webrecorder Software, Rhizome, and Contributors. Released under the GNU Affero General Public License. + +This file is part of wombat.js, see https://github.com/webrecorder/wombat.js for the full source +Wombat.js is part of the Webrecorder project (https://github.com/webrecorder) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + */ +(function(){function FuncMap(){this._map=[]}function ensureNumber(maybeNumber){try{switch(typeof maybeNumber){case"number":case"bigint":return maybeNumber;}var converted=Number(maybeNumber);return isNaN(converted)?null:converted}catch(e){}return null}function addToStringTagToClass(clazz,tag){typeof self.Symbol!=="undefined"&&typeof self.Symbol.toStringTag!=="undefined"&&Object.defineProperty(clazz.prototype,self.Symbol.toStringTag,{value:tag,enumerable:false})}function autobind(clazz){for(var prop,propValue,proto=clazz.__proto__||clazz.constructor.prototype||clazz.prototype,clazzProps=Object.getOwnPropertyNames(proto),len=clazzProps.length,i=0;i=0){var fnMapping=this._map.splice(idx,1);return fnMapping[0][1]}return null},FuncMap.prototype.map=function(param){for(var i=0;i0&&afw.preserveMedia(media)})},AutoFetcher.prototype.terminate=function(){this.worker.terminate()},AutoFetcher.prototype.justFetch=function(urls){this.worker.postMessage({type:"fetch-all",values:urls})},AutoFetcher.prototype.fetchAsPage=function(url,originalUrl,title){if(url){var headers={"X-Wombat-History-Page":originalUrl};if(title){var encodedTitle=encodeURIComponent(title.trim());title&&(headers["X-Wombat-History-Title"]=encodedTitle)}var fetchData={url:url,options:{headers:headers,cache:"no-store"}};this.justFetch([fetchData])}},AutoFetcher.prototype.postMessage=function(msg,deferred){if(deferred){var afWorker=this;return void Promise.resolve().then(function(){afWorker.worker.postMessage(msg)})}this.worker.postMessage(msg)},AutoFetcher.prototype.preserveSrcset=function(srcset,mod){this.postMessage({type:"values",srcset:{value:srcset,mod:mod,presplit:true}},true)},AutoFetcher.prototype.preserveDataSrcset=function(elem){this.postMessage({type:"values",srcset:{value:elem.dataset.srcset,mod:this.rwMod(elem),presplit:false}},true)},AutoFetcher.prototype.preserveMedia=function(media){this.postMessage({type:"values",media:media},true)},AutoFetcher.prototype.getSrcset=function(elem){return this.wombat.wb_getAttribute?this.wombat.wb_getAttribute.call(elem,"srcset"):elem.getAttribute("srcset")},AutoFetcher.prototype.rwMod=function(elem){switch(elem.tagName){case"SOURCE":return elem.parentElement&&elem.parentElement.tagName==="PICTURE"?"im_":"oe_";case"IMG":return"im_";}return"oe_"},AutoFetcher.prototype.extractFromLocalDoc=function(){var afw=this;Promise.resolve().then(function(){for(var msg={type:"values",context:{docBaseURI:document.baseURI}},media=[],i=0,sheets=document.styleSheets;i=0)||scriptType.indexOf("text/template")>=0)},Wombat.prototype.skipWrapScriptTextBasedOnText=function(text){if(!text||text.indexOf(this.WB_ASSIGN_FUNC)>=0||text.indexOf("<")===0)return true;for(var override_props=["window","self","document","location","top","parent","frames","opener"],i=0;i=0)return false;return true},Wombat.prototype.nodeHasChildren=function(node){if(!node)return false;if(typeof node.hasChildNodes==="function")return node.hasChildNodes();var kids=node.children||node.childNodes;return!!kids&&kids.length>0},Wombat.prototype.rwModForElement=function(elem,attrName){if(!elem)return undefined;var mod="mp_";if(!(elem.tagName==="LINK"&&attrName==="href")){var maybeMod=this.tagToMod[elem.tagName];maybeMod!=null&&(mod=maybeMod[attrName])}else if(elem.rel){var relV=elem.rel.trim().toLowerCase(),asV=this.wb_getAttribute.call(elem,"as");if(asV&&this.linkTagMods.linkRelToAs[relV]!=null){var asMods=this.linkTagMods.linkRelToAs[relV];mod=asMods[asV.toLowerCase()]}else this.linkTagMods[relV]!=null&&(mod=this.linkTagMods[relV])}return mod},Wombat.prototype.removeWBOSRC=function(elem){elem.tagName!=="SCRIPT"||elem.__$removedWBOSRC$__||(elem.hasAttribute("__wb_orig_src")&&elem.removeAttribute("__wb_orig_src"),elem.__$removedWBOSRC$__=true)},Wombat.prototype.retrieveWBOSRC=function(elem){if(elem.tagName==="SCRIPT"&&!elem.__$removedWBOSRC$__){var maybeWBOSRC;return maybeWBOSRC=this.wb_getAttribute?this.wb_getAttribute.call(elem,"__wb_orig_src"):elem.getAttribute("__wb_orig_src"),maybeWBOSRC==null&&(elem.__$removedWBOSRC$__=true),maybeWBOSRC}return undefined},Wombat.prototype.wrapScriptTextJsProxy=function(scriptText){return"var _____WB$wombat$assign$function_____ = function(name) {return (self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init(name)) || self[name]; };\nif (!self.__WB_pmw) { self.__WB_pmw = function(obj) { this.__WB_source = obj; return this; } }\n{\nlet window = _____WB$wombat$assign$function_____(\"window\");\nlet self = _____WB$wombat$assign$function_____(\"self\");\nlet document = _____WB$wombat$assign$function_____(\"document\");\nlet location = _____WB$wombat$assign$function_____(\"location\");\nlet top = _____WB$wombat$assign$function_____(\"top\");\nlet parent = _____WB$wombat$assign$function_____(\"parent\");\nlet frames = _____WB$wombat$assign$function_____(\"frames\");\nlet opener = _____WB$wombat$assign$function_____(\"opener\");\n"+scriptText.replace(this.DotPostMessageRe,".__WB_pmw(self.window)$1")+"\n\n}"},Wombat.prototype.watchElem=function(elem,func){if(!this.$wbwindow.MutationObserver)return false;var m=new this.$wbwindow.MutationObserver(function(records,observer){for(var r,i=0;i"},Wombat.prototype.getFinalUrl=function(useRel,mod,url){var prefix=useRel?this.wb_rel_prefix:this.wb_abs_prefix;return mod==null&&(mod=this.wb_info.mod),this.wb_info.is_live||(prefix+=this.wb_info.wombat_ts),prefix+=mod,prefix[prefix.length-1]!=="/"&&(prefix+="/"),prefix+url},Wombat.prototype.resolveRelUrl=function(url,doc){var docObj=doc||this.$wbwindow.document,parser=this.makeParser(docObj.baseURI,docObj),hash=parser.href.lastIndexOf("#"),href=hash>=0?parser.href.substring(0,hash):parser.href,lastslash=href.lastIndexOf("/");return parser.href=lastslash>=0&&lastslash!==href.length-1?href.substring(0,lastslash+1)+url:href+url,parser.href},Wombat.prototype.extractOriginalURL=function(rewrittenUrl){if(!rewrittenUrl)return"";if(this.wb_is_proxy)return rewrittenUrl;var rwURLString=rewrittenUrl.toString(),url=rwURLString;if(this.startsWithOneOf(url,this.IGNORE_PREFIXES))return url;var start;start=this.startsWith(url,this.wb_abs_prefix)?this.wb_abs_prefix.length:this.wb_rel_prefix&&this.startsWith(url,this.wb_rel_prefix)?this.wb_rel_prefix.length:this.wb_rel_prefix?1:0;var index=url.indexOf("/http",start);return index<0&&(index=url.indexOf("///",start)),index<0&&(index=url.indexOf("/blob:",start)),index<0&&(index=url.indexOf("/about:blank",start)),index>=0?url=url.substr(index+1):(index=url.indexOf(this.wb_replay_prefix),index>=0&&(url=url.substr(index+this.wb_replay_prefix.length)),url.length>4&&url.charAt(2)==="_"&&url.charAt(3)==="/"&&(url=url.substr(4)),url!==rwURLString&&!this.startsWithOneOf(url,this.VALID_PREFIXES)&&!this.startsWith(url,"blob:")&&(url=this.wb_orig_scheme+url)),rwURLString.charAt(0)==="/"&&rwURLString.charAt(1)!=="/"&&this.startsWith(url,this.wb_orig_origin)&&(url=url.substr(this.wb_orig_origin.length)),this.startsWith(url,this.REL_PREFIX)?this.wb_info.wombat_scheme+":"+url:url},Wombat.prototype.makeParser=function(maybeRewrittenURL,doc){var originalURL=this.extractOriginalURL(maybeRewrittenURL),docElem=doc;return doc||(this.$wbwindow.location.href==="about:blank"&&this.$wbwindow.opener?docElem=this.$wbwindow.opener.document:docElem=this.$wbwindow.document),this._makeURLParser(originalURL,docElem)},Wombat.prototype._makeURLParser=function(url,docElem){try{return new this.$wbwindow.URL(url,docElem.baseURI)}catch(e){}var p=docElem.createElement("a");return p._no_rewrite=true,p.href=url,p},Wombat.prototype.defProp=function(obj,prop,setFunc,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;var descriptor={configurable:true,enumerable:enumerable||false,get:getFunc};setFunc&&(descriptor.set=setFunc);try{return Object.defineProperty(obj,prop,descriptor),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.defGetterProp=function(obj,prop,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;try{return Object.defineProperty(obj,prop,{configurable:true,enumerable:enumerable||false,get:getFunc}),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.getOrigGetter=function(obj,prop){var orig_getter;if(obj.__lookupGetter__&&(orig_getter=obj.__lookupGetter__(prop)),!orig_getter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_getter=props.get)}return orig_getter},Wombat.prototype.getOrigSetter=function(obj,prop){var orig_setter;if(obj.__lookupSetter__&&(orig_setter=obj.__lookupSetter__(prop)),!orig_setter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_setter=props.set)}return orig_setter},Wombat.prototype.getAllOwnProps=function(obj){for(var ownProps=[],props=Object.getOwnPropertyNames(obj),i=0;i "+final_href),actualLocation.href=final_href}}},Wombat.prototype.checkLocationChange=function(wombatLoc,isTop){var locType=typeof wombatLoc,actual_location=isTop?this.$wbwindow.__WB_replay_top.location:this.$wbwindow.location;locType==="string"?this.updateLocation(wombatLoc,actual_location.href,actual_location):locType==="object"&&this.updateLocation(wombatLoc.href,wombatLoc._orig_href,actual_location)},Wombat.prototype.checkAllLocations=function(){return!this.wb_wombat_updating&&void(this.wb_wombat_updating=true,this.checkLocationChange(this.$wbwindow.WB_wombat_location,false),this.$wbwindow.WB_wombat_location!=this.$wbwindow.__WB_replay_top.WB_wombat_location&&this.checkLocationChange(this.$wbwindow.__WB_replay_top.WB_wombat_location,true),this.wb_wombat_updating=false)},Wombat.prototype.proxyToObj=function(source){if(source)try{var proxyRealObj=source.__WBProxyRealObj__;if(proxyRealObj)return proxyRealObj}catch(e){}return source},Wombat.prototype.objToProxy=function(obj){if(obj)try{var maybeWbProxy=obj._WB_wombat_obj_proxy;if(maybeWbProxy)return maybeWbProxy}catch(e){}return obj},Wombat.prototype.defaultProxyGet=function(obj,prop,ownProps,fnCache){switch(prop){case"__WBProxyRealObj__":return obj;case"location":case"WB_wombat_location":return obj.WB_wombat_location;case"_WB_wombat_obj_proxy":return obj._WB_wombat_obj_proxy;case"__WB_pmw":case"WB_wombat_eval":case this.WB_ASSIGN_FUNC:case this.WB_CHECK_THIS_FUNC:return obj[prop];case"origin":return obj.WB_wombat_location.origin;case"constructor":if(obj.constructor===Window)return obj.constructor;}var retVal=obj[prop],type=typeof retVal;if(type==="function"&&ownProps.indexOf(prop)!==-1){switch(prop){case"requestAnimationFrame":case"cancelAnimationFrame":{if(!this.isNativeFunction(retVal))return retVal;break}}var cachedFN=fnCache[prop];return cachedFN&&cachedFN.original===retVal||(cachedFN={original:retVal,boundFn:retVal.bind(obj)},fnCache[prop]=cachedFN),cachedFN.boundFn}return type==="object"&&retVal&&retVal._WB_wombat_obj_proxy?(retVal instanceof Window&&this.initNewWindowWombat(retVal),retVal._WB_wombat_obj_proxy):retVal},Wombat.prototype.setLoc=function(loc,originalURL){var parser=this.makeParser(originalURL,loc.ownerDocument);loc._orig_href=originalURL,loc._parser=parser;var href=parser.href;loc._hash=parser.hash,loc._href=href,loc._host=parser.host,loc._hostname=parser.hostname,loc._origin=parser.origin?parser.host?parser.origin:"null":parser.protocol+"//"+parser.hostname+(parser.port?":"+parser.port:""),loc._pathname=parser.pathname,loc._port=parser.port,loc._protocol=parser.protocol,loc._search=parser.search,Object.defineProperty||(loc.href=href,loc.hash=parser.hash,loc.host=loc._host,loc.hostname=loc._hostname,loc.origin=loc._origin,loc.pathname=loc._pathname,loc.port=loc._port,loc.protocol=loc._protocol,loc.search=loc._search)},Wombat.prototype.makeGetLocProp=function(prop,origGetter){var wombat=this;return function newGetLocProp(){if(this._no_rewrite)return origGetter.call(this,prop);var curr_orig_href=origGetter.call(this,"href");return prop==="href"?wombat.extractOriginalURL(curr_orig_href):prop==="ancestorOrigins"?[]:(this._orig_href!==curr_orig_href&&wombat.setLoc(this,curr_orig_href),this["_"+prop])}},Wombat.prototype.makeSetLocProp=function(prop,origSetter,origGetter){var wombat=this;return function newSetLocProp(value){if(this._no_rewrite)return origSetter.call(this,prop,value);if(this["_"+prop]!==value){if(this["_"+prop]=value,!this._parser){var href=origGetter.call(this);this._parser=wombat.makeParser(href,this.ownerDocument)}var rel=false;prop==="href"&&typeof value==="string"&&value&&(value[0]==="."?value=wombat.resolveRelUrl(value,this.ownerDocument):value[0]==="/"&&(value.length<=1||value[1]!=="/")&&(rel=true,value=WB_wombat_location.origin+value));try{this._parser[prop]=value}catch(e){console.log("Error setting "+prop+" = "+value)}prop==="hash"?(value=this._parser[prop],origSetter.call(this,"hash",value)):(rel=rel||value===this._parser.pathname,value=wombat.rewriteUrl(this._parser.href,rel),origSetter.call(this,"href",value))}}},Wombat.prototype.styleReplacer=function(match,n1,n2,n3,offset,string){return n1+this.rewriteUrl(n2)+n3},Wombat.prototype.domConstructorErrorChecker=function(thisObj,what,args,numRequiredArgs){var errorMsg,needArgs=typeof numRequiredArgs==="number"?numRequiredArgs:1;if(thisObj instanceof Window?errorMsg="Failed to construct '"+what+"': Please use the 'new' operator, this DOM object constructor cannot be called as a function.":args&&args.length=0)return url;if(url.indexOf(this.wb_rel_prefix)===0&&url.indexOf("http")>1){var scheme_sep=url.indexOf(":/");return scheme_sep>0&&url[scheme_sep+2]!=="/"?url.substring(0,scheme_sep+2)+"/"+url.substring(scheme_sep+2):url}return this.getFinalUrl(true,mod,this.wb_orig_origin+url)}url.charAt(0)==="."&&(url=this.resolveRelUrl(url,doc));var prefix=this.startsWithOneOf(url.toLowerCase(),this.VALID_PREFIXES);if(prefix){var orig_host=this.$wbwindow.__WB_replay_top.location.host,orig_protocol=this.$wbwindow.__WB_replay_top.location.protocol,prefix_host=prefix+orig_host+"/";if(this.startsWith(url,prefix_host)){if(this.startsWith(url,this.wb_replay_prefix))return url;var curr_scheme=orig_protocol+"//",path=url.substring(prefix_host.length),rebuild=false;return path.indexOf(this.wb_rel_prefix)<0&&url.indexOf("/static/")<0&&(path=this.getFinalUrl(true,mod,WB_wombat_location.origin+"/"+path),rebuild=true),prefix!==curr_scheme&&prefix!==this.REL_PREFIX&&(rebuild=true),rebuild&&(url=useRel?"":curr_scheme+orig_host,path&&path[0]!=="/"&&(url+="/"),url+=path),url}return this.getFinalUrl(useRel,mod,url)}return prefix=this.startsWithOneOf(url,this.BAD_PREFIXES),prefix?this.getFinalUrl(useRel,mod,this.extractOriginalURL(url)):this.isHostUrl(url)&&!this.startsWith(url,originalLoc.host+"/")?this.getFinalUrl(useRel,mod,this.wb_orig_scheme+url):url},Wombat.prototype.rewriteUrl=function(url,useRel,mod,doc){var rewritten=this.rewriteUrl_(url,useRel,mod,doc);return this.debug_rw&&(url===rewritten?console.log("NOT REWRITTEN "+url):console.log("REWRITE: "+url+" -> "+rewritten)),rewritten},Wombat.prototype.performAttributeRewrite=function(elem,name,value,absUrlOnly){switch(name){case"innerHTML":case"outerHTML":return this.rewriteHtml(value);case"filter":return this.rewriteInlineStyle(value);case"style":return this.rewriteStyle(value);case"srcset":return this.rewriteSrcset(value,elem);}if(absUrlOnly&&!this.startsWithOneOf(value,this.VALID_PREFIXES))return value;var mod=this.rwModForElement(elem,name);return this.wbUseAFWorker&&this.WBAutoFetchWorker&&this.isSavedDataSrcSrcset(elem)&&this.WBAutoFetchWorker.preserveDataSrcset(elem),this.rewriteUrl(value,false,mod,elem.ownerDocument)},Wombat.prototype.rewriteAttr=function(elem,name,absUrlOnly){var changed=false;if(!elem||!elem.getAttribute||elem._no_rewrite||elem["_"+name])return changed;var value=this.wb_getAttribute.call(elem,name);if(!value||this.startsWith(value,"javascript:"))return changed;var new_value=this.performAttributeRewrite(elem,name,value,absUrlOnly);return new_value!==value&&(this.removeWBOSRC(elem),this.wb_setAttribute.call(elem,name,new_value),changed=true),changed},Wombat.prototype.noExceptRewriteStyle=function(style){try{return this.rewriteStyle(style)}catch(e){return style}},Wombat.prototype.rewriteStyle=function(style){if(!style)return style;var value=style;return typeof style==="object"&&(value=style.toString()),typeof value==="string"?value.replace(this.STYLE_REGEX,this.styleReplacer).replace(this.IMPORT_REGEX,this.styleReplacer).replace(this.no_wombatRe,""):value},Wombat.prototype.rewriteSrcset=function(value,elem){if(!value)return"";for(var split=value.split(this.srcsetRe),values=[],mod=this.rwModForElement(elem,"srcset"),i=0;i=0){var JS="javascript:";new_value="javascript:window.parent._wb_wombat.initNewWindowWombat(window);"+value.substr(11)}return new_value||(new_value=this.rewriteUrl(value,false,this.rwModForElement(elem,attrName))),new_value!==value&&(this.wb_setAttribute.call(elem,attrName,new_value),true)},Wombat.prototype.rewriteScript=function(elem){if(elem.hasAttribute("src")||!elem.textContent||!this.$wbwindow.Proxy)return this.rewriteAttr(elem,"src");if(this.skipWrapScriptBasedOnType(elem.type))return false;var text=elem.textContent.trim();return!this.skipWrapScriptTextBasedOnText(text)&&(elem.textContent=this.wrapScriptTextJsProxy(text),true)},Wombat.prototype.rewriteSVGElem=function(elem){var changed=this.rewriteAttr(elem,"filter");return changed=this.rewriteAttr(elem,"style")||changed,changed=this.rewriteAttr(elem,"xlink:href")||changed,changed=this.rewriteAttr(elem,"href")||changed,changed=this.rewriteAttr(elem,"src")||changed,changed},Wombat.prototype.rewriteElem=function(elem){var changed=false;if(!elem)return changed;if(elem instanceof SVGElement)changed=this.rewriteSVGElem(elem);else switch(elem.tagName){case"META":var maybeCSP=this.wb_getAttribute.call(elem,"http-equiv");maybeCSP&&maybeCSP.toLowerCase()==="content-security-policy"&&(this.wb_setAttribute.call(elem,"http-equiv","_"+maybeCSP),changed=true);break;case"STYLE":var new_content=this.rewriteStyle(elem.textContent);elem.textContent!==new_content&&(elem.textContent=new_content,changed=true,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.sheet!=null&&this.WBAutoFetchWorker.deferredSheetExtraction(elem.sheet));break;case"LINK":changed=this.rewriteAttr(elem,"href"),this.wbUseAFWorker&&elem.rel==="stylesheet"&&this._addEventListener(elem,"load",this.utilFns.wbSheetMediaQChecker);break;case"IMG":changed=this.rewriteAttr(elem,"src"),changed=this.rewriteAttr(elem,"srcset")||changed,changed=this.rewriteAttr(elem,"style")||changed,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.dataset.srcset&&this.WBAutoFetchWorker.preserveDataSrcset(elem);break;case"OBJECT":if(this.wb_info.isSW&&elem.parentElement&&elem.getAttribute("type")==="application/pdf"){for(var iframe=this.$wbwindow.document.createElement("IFRAME"),i=0;i0;)for(var child,children=rewriteQ.shift(),i=0;i"+rwString+"","text/html");if(!inner_doc||!this.nodeHasChildren(inner_doc.head)||!inner_doc.head.children[0].content)return rwString;var template=inner_doc.head.children[0];if(template._no_rewrite=true,this.recurseRewriteElem(template.content)){var new_html=template.innerHTML;if(checkEndTag){var first_elem=template.content.children&&template.content.children[0];if(first_elem){var end_tag="";this.endsWith(new_html,end_tag)&&!this.endsWith(rwString.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(rwString[0]!=="<"||rwString[rwString.length-1]!==">")return this.write_buff+=rwString,undefined}return new_html}return rwString},Wombat.prototype.rewriteHtmlFull=function(string,checkEndTag){var inner_doc=new DOMParser().parseFromString(string,"text/html");if(!inner_doc)return string;for(var changed=false,i=0;i=0)inner_doc.documentElement._no_rewrite=true,new_html=this.reconstructDocType(inner_doc.doctype)+inner_doc.documentElement.outerHTML;else{inner_doc.head._no_rewrite=true,inner_doc.body._no_rewrite=true;var headHasKids=this.nodeHasChildren(inner_doc.head),bodyHasKids=this.nodeHasChildren(inner_doc.body);if(new_html=(headHasKids?inner_doc.head.outerHTML:"")+(bodyHasKids?inner_doc.body.outerHTML:""),checkEndTag)if(inner_doc.all.length>3){var end_tag="";this.endsWith(new_html,end_tag)&&!this.endsWith(string.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(string[0]!=="<"||string[string.length-1]!==">")return void(this.write_buff+=string);new_html=this.reconstructDocType(inner_doc.doctype)+new_html}return new_html}return string},Wombat.prototype.rewriteInlineStyle=function(orig){var decoded;try{decoded=decodeURIComponent(orig)}catch(e){decoded=orig}if(decoded!==orig){var parts=this.rewriteStyle(decoded).split(",",2);return parts[0]+","+encodeURIComponent(parts[1])}return this.rewriteStyle(orig)},Wombat.prototype.rewriteCookie=function(cookie){var wombat=this,rwCookie=cookie.replace(this.wb_abs_prefix,"").replace(this.wb_rel_prefix,"");return rwCookie=rwCookie.replace(this.cookie_domain_regex,function(m,m1){var message={domain:m1,cookie:rwCookie,wb_type:"cookie"};return wombat.sendTopMessage(message,true),wombat.$wbwindow.location.hostname.indexOf(".")>=0&&!wombat.IP_RX.test(wombat.$wbwindow.location.hostname)?"Domain=."+wombat.$wbwindow.location.hostname:""}).replace(this.cookie_path_regex,function(m,m1){var rewritten=wombat.rewriteUrl(m1);return rewritten.indexOf(wombat.wb_curr_host)===0&&(rewritten=rewritten.substring(wombat.wb_curr_host.length)),"Path="+rewritten}),wombat.$wbwindow.location.protocol!=="https:"&&(rwCookie=rwCookie.replace("secure","")),rwCookie.replace(",|",",")},Wombat.prototype.rewriteWorker=function(workerUrl){if(!workerUrl)return workerUrl;var isBlob=workerUrl.indexOf("blob:")===0,isJS=workerUrl.indexOf("javascript:")===0;if(!isBlob&&!isJS){if(!this.startsWithOneOf(workerUrl,this.VALID_PREFIXES)&&!this.startsWith(workerUrl,"/")&&!this.startsWithOneOf(workerUrl,this.BAD_PREFIXES)){var rurl=this.resolveRelUrl(workerUrl,this.$wbwindow.document);return this.rewriteUrl(rurl,false,"wkr_",this.$wbwindow.document)}return this.rewriteUrl(workerUrl,false,"wkr_",this.$wbwindow.document)}var workerCode=isJS?workerUrl.replace("javascript:",""):null;if(isBlob){var x=new XMLHttpRequest;this.utilFns.XHRopen.call(x,"GET",workerUrl,false),this.utilFns.XHRsend.call(x),workerCode=x.responseText.replace(this.workerBlobRe,"").replace(this.rmCheckThisInjectRe,"this")}if(this.wb_info.static_prefix||this.wb_info.ww_rw_script){var originalURL=this.$wbwindow.document.baseURI,ww_rw=this.wb_info.ww_rw_script||this.wb_info.static_prefix+"wombatWorkers.js",rw="(function() { self.importScripts('"+ww_rw+"'); new WBWombat({'prefix': '"+this.wb_abs_prefix+"', 'prefixMod': '"+this.wb_abs_prefix+"wkrf_/', 'originalURL': '"+originalURL+"'}); })();";workerCode=rw+workerCode}var blob=new Blob([workerCode],{type:"application/javascript"});return URL.createObjectURL(blob)},Wombat.prototype.rewriteTextNodeFn=function(fnThis,originalFn,argsObj){var args,deproxiedThis=this.proxyToObj(fnThis);if(argsObj.length>0&&deproxiedThis.parentElement&&deproxiedThis.parentElement.tagName==="STYLE"){args=new Array(argsObj.length);var dataIndex=argsObj.length-1;dataIndex===2?(args[0]=argsObj[0],args[1]=argsObj[1]):dataIndex===1&&(args[0]=argsObj[0]),args[dataIndex]=this.rewriteStyle(argsObj[dataIndex])}else args=argsObj;return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(deproxiedThis,args):originalFn.apply(deproxiedThis,args)},Wombat.prototype.rewriteDocWriteWriteln=function(fnThis,originalFn,argsObj){var string,thisObj=this.proxyToObj(fnThis),argLen=argsObj.length;if(argLen===0)return originalFn.call(thisObj);string=argLen===1?argsObj[0]:Array.prototype.join.call(argsObj,"");var new_buff=this.rewriteHtml(string,true),res=originalFn.call(thisObj,new_buff);return this.initNewWindowWombat(thisObj.defaultView),res},Wombat.prototype.rewriteChildNodeFn=function(fnThis,originalFn,argsObj){var thisObj=this.proxyToObj(fnThis);if(argsObj.length===0)return originalFn.call(thisObj);var newArgs=this.rewriteElementsInArguments(argsObj);return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(thisObj,newArgs):originalFn.apply(thisObj,newArgs)},Wombat.prototype.rewriteInsertAdjHTMLOrElemArgs=function(fnThis,originalFn,position,textOrElem,rwHTML){var fnThisObj=this.proxyToObj(fnThis);return fnThisObj._no_rewrite?originalFn.call(fnThisObj,position,textOrElem):rwHTML?originalFn.call(fnThisObj,position,this.rewriteHtml(textOrElem)):(this.rewriteElemComplete(textOrElem),originalFn.call(fnThisObj,position,textOrElem))},Wombat.prototype.rewriteSetTimeoutInterval=function(fnThis,originalFn,argsObj){var rw=this.isString(argsObj[0]),args=rw?new Array(argsObj.length):argsObj;if(rw){args[0]=this.$wbwindow.Proxy?this.wrapScriptTextJsProxy(argsObj[0]):argsObj[0].replace(/\blocation\b/g,"WB_wombat_$&");for(var i=1;i0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parse"),this.$wbwindow.CSSStyleValue.parseAll&&this.$wbwindow.CSSStyleValue.parseAll.toString().indexOf("[native code]")>0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parseAll")}if(this.$wbwindow.CSSKeywordValue&&this.$wbwindow.CSSKeywordValue.prototype){var oCSSKV=this.$wbwindow.CSSKeywordValue;this.$wbwindow.CSSKeywordValue=function(CSSKeywordValue_){return function CSSKeywordValue(cssValue){return wombat.domConstructorErrorChecker(this,"CSSKeywordValue",arguments),new CSSKeywordValue_(wombat.rewriteStyle(cssValue))}}(this.$wbwindow.CSSKeywordValue),this.$wbwindow.CSSKeywordValue.prototype=oCSSKV.prototype,Object.defineProperty(this.$wbwindow.CSSKeywordValue.prototype,"constructor",{value:this.$wbwindow.CSSKeywordValue}),addToStringTagToClass(this.$wbwindow.CSSKeywordValue,"CSSKeywordValue")}if(this.$wbwindow.StylePropertyMap&&this.$wbwindow.StylePropertyMap.prototype){var originalSet=this.$wbwindow.StylePropertyMap.prototype.set;this.$wbwindow.StylePropertyMap.prototype.set=function set(){if(arguments.length<=1)return originalSet.__WB_orig_apply?originalSet.__WB_orig_apply(this,arguments):originalSet.apply(this,arguments);var newArgs=new Array(arguments.length);newArgs[0]=arguments[0];for(var i=1;i")&&(array[0]=wombat.rewriteHtml(array[0]),options.type="text/html"),new Blob_(array,options)}}(this.$wbwindow.Blob),this.$wbwindow.Blob.prototype=orig_blob.prototype}},Wombat.prototype.initDocTitleOverride=function(){var orig_get_title=this.getOrigGetter(this.$wbwindow.document,"title"),orig_set_title=this.getOrigSetter(this.$wbwindow.document,"title"),wombat=this,set_title=function title(value){var res=orig_set_title.call(this,value),message={wb_type:"title",title:value};return wombat.sendTopMessage(message),res};this.defProp(this.$wbwindow.document,"title",set_title,orig_get_title)},Wombat.prototype.initFontFaceOverride=function(){if(this.$wbwindow.FontFace){var wombat=this,origFontFace=this.$wbwindow.FontFace;this.$wbwindow.FontFace=function(FontFace_){return function FontFace(family,source,descriptors){wombat.domConstructorErrorChecker(this,"FontFace",arguments,2);var rwSource=source;return source!=null&&(typeof source==="string"?rwSource=wombat.rewriteInlineStyle(source):rwSource=wombat.rewriteInlineStyle(source.toString())),new FontFace_(family,rwSource,descriptors)}}(this.$wbwindow.FontFace),this.$wbwindow.FontFace.prototype=origFontFace.prototype,Object.defineProperty(this.$wbwindow.FontFace.prototype,"constructor",{value:this.$wbwindow.FontFace}),addToStringTagToClass(this.$wbwindow.FontFace,"FontFace")}},Wombat.prototype.initFixedRatio=function(){try{this.$wbwindow.devicePixelRatio=1}catch(e){}if(Object.defineProperty)try{Object.defineProperty(this.$wbwindow,"devicePixelRatio",{value:1,writable:false})}catch(e){}},Wombat.prototype.initPaths=function(wbinfo){wbinfo.wombat_opts=wbinfo.wombat_opts||{},Object.assign(this.wb_info,wbinfo),this.wb_opts=wbinfo.wombat_opts,this.wb_replay_prefix=wbinfo.prefix,this.wb_is_proxy=wbinfo.proxy_magic||!this.wb_replay_prefix,this.wb_info.top_host=this.wb_info.top_host||"*",this.wb_curr_host=this.$wbwindow.location.protocol+"//"+this.$wbwindow.location.host,this.wb_info.wombat_opts=this.wb_info.wombat_opts||{},this.wb_orig_scheme=wbinfo.wombat_scheme+"://",this.wb_orig_origin=this.wb_orig_scheme+wbinfo.wombat_host,this.wb_abs_prefix=this.wb_replay_prefix,this.wb_capture_date_part=!wbinfo.is_live&&wbinfo.wombat_ts?"/"+wbinfo.wombat_ts+"/":"",this.initBadPrefixes(this.wb_replay_prefix),this.initCookiePreset()},Wombat.prototype.initSeededRandom=function(seed){this.$wbwindow.Math.seed=parseInt(seed);var wombat=this;this.$wbwindow.Math.random=function random(){return wombat.$wbwindow.Math.seed=(wombat.$wbwindow.Math.seed*9301+49297)%233280,wombat.$wbwindow.Math.seed/233280}},Wombat.prototype.initHistoryOverrides=function(){this.overrideHistoryFunc("pushState"),this.overrideHistoryFunc("replaceState");var wombat=this;this.$wbwindow.addEventListener("popstate",function(event){wombat.sendHistoryUpdate(wombat.$wbwindow.WB_wombat_location.href,wombat.$wbwindow.document.title)})},Wombat.prototype.initCookiePreset=function(){if(this.wb_info.presetCookie)for(var splitCookies=this.wb_info.presetCookie.split(";"),i=0;i0?"&":"?")+value.toString(),value=null):contentType==="application/json"||contentType==="text/plain"?(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+jsonToQueryString(value),value=null):wombat.startsWith(contentType,"multipart/form-data")&&(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+mfdToQueryString(value,contentType))}if(this.__WB_xhr_open_arguments.length>2&&(this.__WB_xhr_open_arguments[2]=true),this._no_rewrite||(this.__WB_xhr_open_arguments[1]=wombat.rewriteUrl(this.__WB_xhr_open_arguments[1])),origOpen.apply(this,this.__WB_xhr_open_arguments),!wombat.startsWith(this.__WB_xhr_open_arguments[1],"data:")){for(const[name,value]of this.__WB_xhr_headers.entries())origSetRequestHeader.call(this,name,value);origSetRequestHeader.call(this,"X-Pywb-Requested-With","XMLHttpRequest")}origSend.call(this,value)}}if(this.$wbwindow.fetch){var orig_fetch=this.$wbwindow.fetch;this.$wbwindow.fetch=function fetch(input,init_opts){var rwInput=input,inputType=typeof input;if(inputType==="string")rwInput=wombat.rewriteUrl(input);else if(inputType==="object"&&input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(rwInput=new Request(new_url,init_opts))}else inputType==="object"&&input.href&&(rwInput=wombat.rewriteUrl(input.href));if(init_opts||(init_opts={}),init_opts.credentials===undefined)try{init_opts.credentials="include"}catch(e){}return orig_fetch.call(wombat.proxyToObj(this),rwInput,init_opts)}}if(this.$wbwindow.Request&&this.$wbwindow.Request.prototype){var orig_request=this.$wbwindow.Request;this.$wbwindow.Request=function(Request_){return function Request(input,init_opts){wombat.domConstructorErrorChecker(this,"Request",arguments);var newInitOpts=init_opts||{},newInput=input,inputType=typeof input;switch(inputType){case"string":newInput=wombat.rewriteUrl(input);break;case"object":if(newInput=input,input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(newInput=new Request_(new_url,input))}else input.href&&(newInput=wombat.rewriteUrl(input.toString(),true));}return newInitOpts.credentials="include",new Request_(newInput,newInitOpts)}}(this.$wbwindow.Request),this.$wbwindow.Request.prototype=orig_request.prototype,Object.defineProperty(this.$wbwindow.Request.prototype,"constructor",{value:this.$wbwindow.Request})}if(this.$wbwindow.Response&&this.$wbwindow.Response.prototype){var originalRedirect=this.$wbwindow.Response.prototype.redirect;this.$wbwindow.Response.prototype.redirect=function redirect(url,status){var rwURL=wombat.rewriteUrl(url,true,null,wombat.$wbwindow.document);return originalRedirect.call(this,rwURL,status)}}if(this.$wbwindow.EventSource&&this.$wbwindow.EventSource.prototype){var origEventSource=this.$wbwindow.EventSource;this.$wbwindow.EventSource=function(EventSource_){return function EventSource(url,configuration){wombat.domConstructorErrorChecker(this,"EventSource",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteUrl(url)),new EventSource_(rwURL,configuration)}}(this.$wbwindow.EventSource),this.$wbwindow.EventSource.prototype=origEventSource.prototype,Object.defineProperty(this.$wbwindow.EventSource.prototype,"constructor",{value:this.$wbwindow.EventSource}),addToStringTagToClass(this.$wbwindow.EventSource,"EventSource")}if(this.$wbwindow.WebSocket&&this.$wbwindow.WebSocket.prototype){var origWebSocket=this.$wbwindow.WebSocket;this.$wbwindow.WebSocket=function(WebSocket_){return function WebSocket(url,configuration){wombat.domConstructorErrorChecker(this,"WebSocket",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteWSURL(url)),new WebSocket_(rwURL,configuration)}}(this.$wbwindow.WebSocket),this.$wbwindow.WebSocket.prototype=origWebSocket.prototype,Object.defineProperty(this.$wbwindow.WebSocket.prototype,"constructor",{value:this.$wbwindow.WebSocket}),addToStringTagToClass(this.$wbwindow.WebSocket,"WebSocket")}},Wombat.prototype.initElementGetSetAttributeOverride=function(){if(!this.wb_opts.skip_setAttribute&&this.$wbwindow.Element&&this.$wbwindow.Element.prototype){var wombat=this,ElementProto=this.$wbwindow.Element.prototype;if(ElementProto.setAttribute){var orig_setAttribute=ElementProto.setAttribute;ElementProto._orig_setAttribute=orig_setAttribute,ElementProto.setAttribute=function setAttribute(name,value){var rwValue=value;if(name&&typeof rwValue==="string"){var lowername=name.toLowerCase();if(this.tagName==="LINK"&&lowername==="href"&&rwValue.indexOf("data:text/css")===0)rwValue=wombat.rewriteInlineStyle(value);else if(lowername==="style")rwValue=wombat.rewriteStyle(value);else if(lowername==="srcset")rwValue=wombat.rewriteSrcset(value,this);else{var shouldRW=wombat.shouldRewriteAttr(this.tagName,lowername);shouldRW&&(wombat.removeWBOSRC(this),!this._no_rewrite&&(rwValue=wombat.rewriteUrl(value,false,wombat.rwModForElement(this,lowername))))}}return orig_setAttribute.call(this,name,rwValue)}}if(ElementProto.getAttribute){var orig_getAttribute=ElementProto.getAttribute;this.wb_getAttribute=orig_getAttribute,ElementProto.getAttribute=function getAttribute(name){var result=orig_getAttribute.call(this,name);if(result===null)return result;var lowerName=name;if(name&&(lowerName=name.toLowerCase()),wombat.shouldRewriteAttr(this.tagName,lowerName)){var maybeWBOSRC=wombat.retrieveWBOSRC(this);return maybeWBOSRC?maybeWBOSRC:wombat.extractOriginalURL(result)}return wombat.startsWith(lowerName,"data-")&&wombat.startsWithOneOf(result,wombat.wb_prefixes)?wombat.extractOriginalURL(result):result}}}},Wombat.prototype.initSvgImageOverrides=function(){if(this.$wbwindow.SVGImageElement){var svgImgProto=this.$wbwindow.SVGImageElement.prototype,orig_getAttr=svgImgProto.getAttribute,orig_getAttrNS=svgImgProto.getAttributeNS,orig_setAttr=svgImgProto.setAttribute,orig_setAttrNS=svgImgProto.setAttributeNS,wombat=this;svgImgProto.getAttribute=function getAttribute(name){var value=orig_getAttr.call(this,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.getAttributeNS=function getAttributeNS(ns,name){var value=orig_getAttrNS.call(this,ns,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.setAttribute=function setAttribute(name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttr.call(this,name,rwValue)},svgImgProto.setAttributeNS=function setAttributeNS(ns,name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttrNS.call(this,ns,name,rwValue)}}},Wombat.prototype.initCreateElementNSFix=function(){if(this.$wbwindow.document.createElementNS&&this.$wbwindow.Document.prototype.createElementNS){var orig_createElementNS=this.$wbwindow.document.createElementNS,wombat=this,createElementNS=function createElementNS(namespaceURI,qualifiedName){return orig_createElementNS.call(wombat.proxyToObj(this),wombat.extractOriginalURL(namespaceURI),qualifiedName)};this.$wbwindow.Document.prototype.createElementNS=createElementNS,this.$wbwindow.document.createElementNS=createElementNS}},Wombat.prototype.initInsertAdjacentElementHTMLOverrides=function(){var Element=this.$wbwindow.Element;if(Element&&Element.prototype){var elementProto=Element.prototype,rewriteFn=this.rewriteInsertAdjHTMLOrElemArgs;if(elementProto.insertAdjacentHTML){var origInsertAdjacentHTML=elementProto.insertAdjacentHTML;elementProto.insertAdjacentHTML=function insertAdjacentHTML(position,text){return rewriteFn(this,origInsertAdjacentHTML,position,text,true)}}if(elementProto.insertAdjacentElement){var origIAdjElem=elementProto.insertAdjacentElement;elementProto.insertAdjacentElement=function insertAdjacentElement(position,element){return rewriteFn(this,origIAdjElem,position,element,false)}}}},Wombat.prototype.initDomOverride=function(){var Node=this.$wbwindow.Node;if(Node&&Node.prototype){var rewriteFn=this.rewriteNodeFuncArgs;if(Node.prototype.appendChild){var originalAppendChild=Node.prototype.appendChild;Node.prototype.appendChild=function appendChild(newNode,oldNode){return rewriteFn(this,originalAppendChild,newNode,oldNode)}}if(Node.prototype.insertBefore){var originalInsertBefore=Node.prototype.insertBefore;Node.prototype.insertBefore=function insertBefore(newNode,oldNode){return rewriteFn(this,originalInsertBefore,newNode,oldNode)}}if(Node.prototype.replaceChild){var originalReplaceChild=Node.prototype.replaceChild;Node.prototype.replaceChild=function replaceChild(newNode,oldNode){return rewriteFn(this,originalReplaceChild,newNode,oldNode)}}this.overridePropToProxy(Node.prototype,"ownerDocument"),this.overridePropToProxy(this.$wbwindow.HTMLHtmlElement.prototype,"parentNode"),this.overridePropToProxy(this.$wbwindow.Event.prototype,"target")}this.$wbwindow.Element&&this.$wbwindow.Element.prototype&&(this.overrideParentNodeAppendPrepend(this.$wbwindow.Element),this.overrideChildNodeInterface(this.$wbwindow.Element,false)),this.$wbwindow.DocumentFragment&&this.$wbwindow.DocumentFragment.prototype&&this.overrideParentNodeAppendPrepend(this.$wbwindow.DocumentFragment)},Wombat.prototype.initDocOverrides=function($document){if(Object.defineProperty){this.overrideReferrer($document),this.defGetterProp($document,"origin",function origin(){return this.WB_wombat_location.origin}),this.defGetterProp(this.$wbwindow,"origin",function origin(){return this.WB_wombat_location.origin});var wombat=this,domain_setter=function domain(val){var loc=this.WB_wombat_location;loc&&wombat.endsWith(loc.hostname,val)&&(this.__wb_domain=val)},domain_getter=function domain(){return this.__wb_domain||this.WB_wombat_location.hostname};this.defProp($document,"domain",domain_setter,domain_getter)}},Wombat.prototype.initDocWriteOpenCloseOverride=function(){if(this.$wbwindow.DOMParser){var DocumentProto=this.$wbwindow.Document.prototype,$wbDocument=this.$wbwindow.document,docWriteWritelnRWFn=this.rewriteDocWriteWriteln,orig_doc_write=$wbDocument.write,new_write=function write(){return docWriteWritelnRWFn(this,orig_doc_write,arguments)};$wbDocument.write=new_write,DocumentProto.write=new_write;var orig_doc_writeln=$wbDocument.writeln,new_writeln=function writeln(){return docWriteWritelnRWFn(this,orig_doc_writeln,arguments)};$wbDocument.writeln=new_writeln,DocumentProto.writeln=new_writeln;var wombat=this,orig_doc_open=$wbDocument.open,new_open=function open(){var res,thisObj=wombat.proxyToObj(this);if(arguments.length===3){var rwUrl=wombat.rewriteUrl(arguments[0],false,"mp_");res=orig_doc_open.call(thisObj,rwUrl,arguments[1],arguments[2]),wombat.initNewWindowWombat(res,arguments[0])}else res=orig_doc_open.call(thisObj),wombat.initNewWindowWombat(thisObj.defaultView);return res};$wbDocument.open=new_open,DocumentProto.open=new_open;var originalClose=$wbDocument.close,newClose=function close(){var thisObj=wombat.proxyToObj(this);return wombat.initNewWindowWombat(thisObj.defaultView),originalClose.__WB_orig_apply?originalClose.__WB_orig_apply(thisObj,arguments):originalClose.apply(thisObj,arguments)};$wbDocument.close=newClose,DocumentProto.close=newClose;var oBodyGetter=this.getOrigGetter(DocumentProto,"body"),oBodySetter=this.getOrigSetter(DocumentProto,"body");oBodyGetter&&oBodySetter&&this.defProp(DocumentProto,"body",function body(newBody){return newBody&&(newBody instanceof HTMLBodyElement||newBody instanceof HTMLFrameSetElement)&&wombat.rewriteElemComplete(newBody),oBodySetter.call(wombat.proxyToObj(this),newBody)},oBodyGetter)}},Wombat.prototype.initIframeWombat=function(iframe){var win;win=iframe._get_contentWindow?iframe._get_contentWindow.call(iframe):iframe.contentWindow;try{if(!win||win===this.$wbwindow||win._skip_wombat||win._wb_wombat)return}catch(e){return}var src=iframe.src;this.initNewWindowWombat(win,src)},Wombat.prototype.initNewWindowWombat=function(win,src){var fullWombat=false;if(win&&!win._wb_wombat){if((!src||src===""||this.startsWithOneOf(src,["about:blank","javascript:"]))&&(fullWombat=true),!fullWombat&&this.wb_info.isSW){var origURL=this.extractOriginalURL(src);(origURL==="about:blank"||origURL.startsWith("srcdoc:")||origURL.startsWith("blob:"))&&(fullWombat=true)}if(fullWombat){var newInfo={};Object.assign(newInfo,this.wb_info);var wombat=new Wombat(win,newInfo);win._wb_wombat=wombat.wombatInit()}else this.initProtoPmOrigin(win),this.initPostMessageOverride(win),this.initMessageEventOverride(win),this.initCheckThisFunc(win)}},Wombat.prototype.initTimeoutIntervalOverrides=function(){var rewriteFn=this.rewriteSetTimeoutInterval;if(this.$wbwindow.setTimeout&&!this.$wbwindow.setTimeout.__$wbpatched$__){var originalSetTimeout=this.$wbwindow.setTimeout;this.$wbwindow.setTimeout=function setTimeout(){return rewriteFn(this,originalSetTimeout,arguments)},this.$wbwindow.setTimeout.__$wbpatched$__=true}if(this.$wbwindow.setInterval&&!this.$wbwindow.setInterval.__$wbpatched$__){var originalSetInterval=this.$wbwindow.setInterval;this.$wbwindow.setInterval=function setInterval(){return rewriteFn(this,originalSetInterval,arguments)},this.$wbwindow.setInterval.__$wbpatched$__=true}},Wombat.prototype.initWorkerOverrides=function(){var wombat=this;if(this.$wbwindow.Worker&&!this.$wbwindow.Worker._wb_worker_overriden){var orig_worker=this.$wbwindow.Worker;this.$wbwindow.Worker=function(Worker_){return function Worker(url,options){return wombat.domConstructorErrorChecker(this,"Worker",arguments),new Worker_(wombat.rewriteWorker(url),options)}}(orig_worker),this.$wbwindow.Worker.prototype=orig_worker.prototype,Object.defineProperty(this.$wbwindow.Worker.prototype,"constructor",{value:this.$wbwindow.Worker}),this.$wbwindow.Worker._wb_worker_overriden=true}if(this.$wbwindow.SharedWorker&&!this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden){var oSharedWorker=this.$wbwindow.SharedWorker;this.$wbwindow.SharedWorker=function(SharedWorker_){return function SharedWorker(url,options){return wombat.domConstructorErrorChecker(this,"SharedWorker",arguments),new SharedWorker_(wombat.rewriteWorker(url),options)}}(oSharedWorker),this.$wbwindow.SharedWorker.prototype=oSharedWorker.prototype,Object.defineProperty(this.$wbwindow.SharedWorker.prototype,"constructor",{value:this.$wbwindow.SharedWorker}),this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden=true}if(this.$wbwindow.ServiceWorkerContainer&&this.$wbwindow.ServiceWorkerContainer.prototype&&this.$wbwindow.ServiceWorkerContainer.prototype.register){var orig_register=this.$wbwindow.ServiceWorkerContainer.prototype.register;this.$wbwindow.ServiceWorkerContainer.prototype.register=function register(scriptURL,options){var newScriptURL=new URL(scriptURL,wombat.$wbwindow.document.baseURI).href,mod=wombat.getPageUnderModifier();return options&&options.scope?options.scope=wombat.rewriteUrl(options.scope,false,mod):options={scope:wombat.rewriteUrl("/",false,mod)},orig_register.call(this,wombat.rewriteUrl(newScriptURL,false,"sw_"),options)}}if(this.$wbwindow.Worklet&&this.$wbwindow.Worklet.prototype&&this.$wbwindow.Worklet.prototype.addModule&&!this.$wbwindow.Worklet.__wb_workerlet_overriden){var oAddModule=this.$wbwindow.Worklet.prototype.addModule;this.$wbwindow.Worklet.prototype.addModule=function addModule(moduleURL,options){var rwModuleURL=wombat.rewriteUrl(moduleURL,false,"js_");return oAddModule.call(this,rwModuleURL,options)},this.$wbwindow.Worklet.__wb_workerlet_overriden=true}},Wombat.prototype.initLocOverride=function(loc,oSetter,oGetter){if(Object.defineProperty)for(var prop,i=0;i=0&&props.splice(foundInx,1);return props}})}catch(e){console.log(e)}},Wombat.prototype.initHashChange=function(){if(this.$wbwindow.__WB_top_frame){var wombat=this,receive_hash_change=function receive_hash_change(event){if(event.data&&event.data.from_top){var message=event.data.message;message.wb_type&&(message.wb_type!=="outer_hashchange"||wombat.$wbwindow.location.hash==message.hash||(wombat.$wbwindow.location.hash=message.hash))}},send_hash_change=function send_hash_change(){var message={wb_type:"hashchange",hash:wombat.$wbwindow.location.hash};wombat.sendTopMessage(message)};this.$wbwindow.addEventListener("message",receive_hash_change),this.$wbwindow.addEventListener("hashchange",send_hash_change)}},Wombat.prototype.initPostMessageOverride=function($wbwindow){if($wbwindow.postMessage&&!$wbwindow.__orig_postMessage){var orig=$wbwindow.postMessage,wombat=this;$wbwindow.__orig_postMessage=orig;var postmessage_rewritten=function postMessage(message,targetOrigin,transfer,from_top){var from,src_id,this_obj=wombat.proxyToObj(this);if(this_obj.__WB_source&&this_obj.__WB_source.WB_wombat_location){var source=this_obj.__WB_source;if(from=source.WB_wombat_location.origin,this_obj.__WB_win_id||(this_obj.__WB_win_id={},this_obj.__WB_counter=0),!source.__WB_id){var id=this_obj.__WB_counter;source.__WB_id=id+source.WB_wombat_location.href,this_obj.__WB_counter+=1}this_obj.__WB_win_id[source.__WB_id]=source,src_id=source.__WB_id,this_obj.__WB_source=undefined}else from=window.WB_wombat_location.origin;var to_origin=targetOrigin;to_origin===this_obj.location.origin&&(to_origin=from);var new_message={from:from,to_origin:to_origin,src_id:src_id,message:message,from_top:from_top};if(targetOrigin!=="*"){if(this_obj.location.origin==="null"||this_obj.location.origin==="")return;targetOrigin=this_obj.location.origin}return orig.call(this_obj,new_message,targetOrigin,transfer)};$wbwindow.postMessage=postmessage_rewritten,$wbwindow.Window.prototype.postMessage=postmessage_rewritten;var eventTarget=null;eventTarget=$wbwindow.EventTarget&&$wbwindow.EventTarget.prototype?$wbwindow.EventTarget.prototype:$wbwindow;var _oAddEventListener=eventTarget.addEventListener;eventTarget.addEventListener=function addEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.add_or_get(listener,function(){return wrapEventListener(listener,obj,wombat)}):type==="storage"?wombat.storage_listeners.add_or_get(listener,function(){return wrapSameOriginEventListener(listener,obj)}):rwListener=listener,rwListener)return _oAddEventListener.call(obj,type,rwListener,useCapture)};var _oRemoveEventListener=eventTarget.removeEventListener;eventTarget.removeEventListener=function removeEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.remove(listener):type==="storage"?wombat.storage_listeners.remove(listener):rwListener=listener,rwListener)return _oRemoveEventListener.call(obj,type,rwListener,useCapture)};var override_on_prop=function(onevent,wrapperFN){var orig_setter=wombat.getOrigSetter($wbwindow,onevent),setter=function(value){this["__orig_"+onevent]=value;var obj=wombat.proxyToObj(this),listener=value?wrapperFN(value,obj,wombat):value;return orig_setter.call(obj,listener)},getter=function(){return this["__orig_"+onevent]};wombat.defProp($wbwindow,onevent,setter,getter)};override_on_prop("onmessage",wrapEventListener),override_on_prop("onstorage",wrapSameOriginEventListener)}},Wombat.prototype.initMessageEventOverride=function($wbwindow){!$wbwindow.MessageEvent||$wbwindow.MessageEvent.prototype.__extended||(this.addEventOverride("target"),this.addEventOverride("srcElement"),this.addEventOverride("currentTarget"),this.addEventOverride("eventPhase"),this.addEventOverride("path"),this.overridePropToProxy($wbwindow.MessageEvent.prototype,"source"),$wbwindow.MessageEvent.prototype.__extended=true)},Wombat.prototype.initUIEventsOverrides=function(){this.overrideAnUIEvent("UIEvent"),this.overrideAnUIEvent("MouseEvent"),this.overrideAnUIEvent("TouchEvent"),this.overrideAnUIEvent("FocusEvent"),this.overrideAnUIEvent("KeyboardEvent"),this.overrideAnUIEvent("WheelEvent"),this.overrideAnUIEvent("InputEvent"),this.overrideAnUIEvent("CompositionEvent")},Wombat.prototype.initOpenOverride=function(){var orig=this.$wbwindow.open;this.$wbwindow.Window.prototype.open&&(orig=this.$wbwindow.Window.prototype.open);var wombat=this,open_rewritten=function open(strUrl,strWindowName,strWindowFeatures){var rwStrUrl=wombat.rewriteUrl(strUrl,false,""),res=orig.call(wombat.proxyToObj(this),rwStrUrl,strWindowName,strWindowFeatures);return wombat.initNewWindowWombat(res,strUrl),res};this.$wbwindow.open=open_rewritten,this.$wbwindow.Window.prototype.open&&(this.$wbwindow.Window.prototype.open=open_rewritten);for(var i=0;i + + + + + + + + + + +Chapter 1.2: Drawing Graphics + + + + + +
+
The Wayback Machine - +http://web.archive.org/web/20220827033534/http://lameguy64.net/tutorials/pstutorials/chapter1/2-graphics.html
+ + + +

1.2. Drawing Graphics

+

This tutorial will teach you how to draw graphics primitives with the GPU +using an ordering table and primitive packets. This is a very essential part +to learn about the PS1 as you need graphics to do anything on the console +really.

+

Just like in the last tutorial, libgs will not be covered here. Also, the +GPU is not responsible for 3D graphics. While it does render out (affine) +polygon all the 3D processing is actually done in a co-processor, called +the GTE or Geometry Transformation Engine.

+

Trivia: The easiest way to tell if a person is not a PS1 programmer +is if they call the GTE the Geometry Transfer Engine instead of +Transformation Engine. The GTE does NOT transfer anything, it is +essentially like a math co-processor on the CPU as nothing but the CPU has +access to it, and it does nothing else but vector transformations that helps +greatly in 3D graphics processing.

+

Tutorial compatible with PSn00bSDK:Yes

+ +

Tutorial Index

+ + +

Theory of Operation

+

The PS1 GPU draws graphics by means of primitive packets, which are +essentially messages that command the GPU to draw a specified primitive +in a specified color to specified screen coordinates to name a few.

+

Drawing packets are normally sent to the GPU by an ordering table, +which is essentially an array of pointers that form a chain. Primitive +packets are normally linked to the ordering table to draw them.

+ +

Ordering Tables

+

An ordering table is an array of elements of pointers that point from +one element to another, the ordering table usually ends with an array element +with the value of 0xFFFFFFFF which is used as a terminator value for ordering +table processing. An ordering table is normally created using ClearOTagR() +on an array of 32-bit ints.

+

The following figure visually describes the general structure of an ordering table.

+ +

Adding primitives to an ordering table is normally achieved using +addPrim()/AddPrim() functions. DrawOTag() is used to begin +processing of an ordering table.

+

The type of ordering table shown above is called a reverse ordering table +because the chain starts at the end of the array and ends at the beginning of +the array. This type of ordering table is most commonly used as the reverse +order allows for depth ordering of polygons which is essential for 3D graphics. +Use ClearOTag() if you wish to use a non-reverse ordering table but the +initialization speed won't be as fast as ClearOTagR() for larger tables +as ClearOTagR() is DMA accelerated, but it can only generate reverse +ordering tables.

+

For simplicity, a global ordering table array of 8 elements should suffice +for this tutorial.

+
int ot[2][8];
+
+

The reason two arrays of ot are defined is for double buffered +rendering reasons, which will be explained later in this tutorial.

+ +

Primitive Packets

+

Primitive packets are more or less commands that instruct the GPU to draw +a specified primitive to specified coordinates of the drawing area carried by +the packet. Primitive packets are always aligned 4-byte aligned.

+

The following illustrates the typical structure of a primitive packet +(the packet represents a SPRT primitive):

+ +

C implementation of the above structure

+
typedef struct {
+    unsigned int  tag;             // First 24 bits are address, last 8 bits are length
+    unsigned char  r0,g0,b0,code;  // RGB color and primitive code
+    short          x0,y0;          // X,Y coordinates of sprite
+    unsigned char  u0,v0;          // U,V texture coordinates of sprite
+    unsigned short clut;           // Texture CLUT
+    unsigned short w,h;            // Sprite width and height
+} SPRT;
+
+

The number of parameter words of a primitive packet varies on the primitive +command. Simpler commands such as a fixed sized, solid color sprite is only 3 +words long whereas a shaded, textured 4-point polygon is 13 words long, the +length of the primitive in words is specified to the Len field minus the +tag word. The Next Pointer field is an address to the next primitive or +return pointer to an ordering table it was sorted to.

+

A word in this context is a 4 byte integer.

+

Primitives are normally defined using primitive structures and macros +defined in libgpu.h, or psxgpu.h in PSn00bSDK.

+ +

Preparing a Primitive

+

Primitive packets are normally prepared using primitive structures and +macros. A solid color rectangular sprite (TILE) will be used for this +tutorial.

+

Primitive preparation is as follows.

+
TILE tile;                      // Primitive structure
+
+setTile(&tile;);                 // Initialize the primitive (very important)
+setXY0(&tile;, 32, 32);          // Set primitive (x,y) position
+setWH(&tile;, 64, 64);           // Set primitive size
+setRGB0(&tile;, 255, 255, 0);    // Set color yellow
+
+

The setTile() macro simply fills in the appropriate values to the tag +length and primitive code fields to the specified primitive. These values are +mandatory and must be correct, otherwise the GPU will lock up. Always use the +appropriate initializer macro for a primitive.

+

The setXY0(), setWH() and setRGB0() macros sets the +(x,y) coordinates, size and color of the primitive respectively. While the +fields of a primitive can be set directly via struct elements of the +primitive, using macros helps make the code looking tidy.

+

Normally, primitive packets are defined to a primitive buffer.

+ +

Primitive Buffers

+

A primitive buffer is simply a global array of char elements used as a +buffer to define primitive packets to. It is also a lot less wasteful than +defining multiple arrays for different primitive types as all primitive types +can be defined in just a single global buffer.

+

The reason it is recommended that primitive packets must be defined in a +global buffer is because the primitives must exist in memory until the GPU +gets around to processing it. If you were to define primitives as a local +variable in a function and register it to an ordering table, that primitive +has most likely been overwritten by other things (since locals are generally +temporary variables), resulting in a corrupted ordering table which would +result to a GPU lock up or crash.

+

A primitive buffer can be defined as such, 32KB buffer for primitives +should be plenty.

+
char primbuff[2][32768];
+char *nextpri;
+
+

The nextpri variable will be used to keep track where the next +primitive should be written to. Therefore, this variable must be set to +the first primitive buffer and reset in your display function.

+

To prepare a primitive to the primitive buffer, simply cast the +nextpri pointer into a primitive pointer.

+
TILE *tile;                    // Primitive pointer
+
+tile = (TILE*)nextpri;         // Cast hext primitive
+
+setTile(tile);                 // Initialize the primitive (very important)
+setXY0(tile, 32, 32);          // Set primitive (x,y) position
+setWH(tile, 64, 64);           // Set primitive size
+setRGB0(tile, 255, 255, 0);    // Set color yellow
+
+

After registering the primitive to the ordering table, you'll want to +advance the nextpri pointer for another primitive to be written to.

+
nextpri += sizeof(TILE);
+
+

It is very important to advance this pointer, otherwise previously defined +packets would get overwritten, corrupting the primitive buffer.

+ +

Sorting a Primitive to an Ordering Table

+

The term 'sorting' in the context of PS1 graphics programming refers to +linking a primitive to an ordering table element. Its often called sorting +because an ordering table is also used to control the order of which +primitives are drawn. In a reverse ordering table (initialized using +ClearOTagR()) a primitive sorted highest will be drawn first and primitives +sorted lowest will be drawn last, which would make a lot of sense in 3D +graphics.

+

Sorting a primitive to an ordering table is achieved using the +addPrim() macro.

+
addPrim(ot[db], tile);
+
+

There's also a function version called AddPrim() but the macro version +would be faster in the long run, mainly because code jumps are kept a minimum when +using macros.

+

Taking the code snippet earlier, this is the process of how to create and sort +a primitive to be drawn.

+
tile = (TILE*)nextpri;         // Cast next primitive
+
+setTile(tile);                 // Initialize the primitive (very important)
+setXY0(tile, 32, 32);          // Set primitive (x,y) position
+setWH(tile, 64, 64);           // Set primitive size
+setRGB0(tile, 255, 255, 0);    // Set color yellow
+
+addPrim(ot[db], tile);         // Add primitive to the ordering table
+
+nextpri += sizeof(TILE);       // Advance the next primitive pointer
+
+

Primitives need to be sorted to an ordering table for the GPU to process it. +To sort the primitive to a higher ordering table element, simply use (+) to +increment the ordering table address. But don't increment by a value that +will exceed the length of your ordering table, or that will result to memory +overflow corruption.

+

Sorting a primitive to an ordering table links the specified primitive into +the chain, so it gets processed when the OT is proceessed by the GPU. The +following figure visually describes the result of sorting a primitive to ordering +table element 4.

+ +

A very common misconception among many PS1 homebrew programmers is they believe +only a single primitive can be sorted to an ordering table element. This is +absolutely untrue because sorting a primitive to an element that has a primitive +already linked to it will only add further to the chain, not replace the previous +element.

+ +

You can sort any number of primitives to a single ordering table, so an ordering +table of 4 to 8 elements should be sufficient for 2D projects. But remember that the +newest primitive sorted will be the first to be processed, as shown in the figure +earlier.

+ +

Drawing an Ordering Table

+

Ordering table processing is done using the DrawOTag() function. +DrawOTag() starts a DMA transfer process of the specified ordering +table for the GPU to process. This is the most recommended method of +processing ordering tables as this is the fastest way to deliver graphics +primitives to the GPU.

+

Since this tutorial demonstrates the use of a reverse ordering table, the +last element of the ordering table must be specified to DrawOTag(), as +the chain starts at the last element and ends at the first.

+
DrawOTag(ot[db]+31);
+
+

The transfer operation is asynchronous so the function returns very +quickly. It is recommended to wait for the transfer and the GPU to finish +processing which is achieved using DrawSync() before processing another +ordering table.

+ +

Double Ordering Tables/Primitive Buffers

+

You may have noticed by now that ordering tables and primitive buffers are +defined in pairs. This is so that a commonly used optimization trick can be +achieved and is another form of double buffering. Because the CPU can continue +running while the GPU is busy processing graphics, you can utilize this to +continue processing graphics primitives for drawing the next frame while the +GPU is drawing. The following figure visually describes the performance +difference.

+ +

Having double buffers essentially minimizes keeping the GPU idle which +comes off as being most efficient, as having single buffers would prevent +you from preparing primitives for the next frame which results to leaving +the GPU in an idle state. While this may not be apparent in small programs, +it will be once you start pushing the GPU with thousands of primitives.

+ +

Sample Code

+

The sample code works off of the one from the previous example, but with +graphics drawing code implemented.

+
#include <sys/types.h>	// This provides typedefs needed by libgte.h and libgpu.h
+#include <stdio.h>	// Not necessary but include it anyway
+#include <libetc.h>	// Includes some functions that controls the display
+#include <libgte.h>	// GTE header, not really used but libgpu.h depends on it
+#include <libgpu.h>	// GPU library header
+
+#define OTLEN 8         // Ordering table length (recommended to set as a define
+                        // so it can be changed easily)
+
+DISPENV disp[2];
+DRAWENV draw[2];
+int db = 0;
+
+u_long ot[2][OTLEN];    // Ordering table length
+char pribuff[2][32768]; // Primitive buffer
+char *nextpri;          // Next primitive pointer
+
+void display() {
+    
+    DrawSync(0);                // Wait for any graphics processing to finish
+    
+    VSync(0);                   // Wait for vertical retrace
+
+    PutDispEnv(&disp;[db]);      // Apply the DISPENV/DRAWENVs
+    PutDrawEnv(&draw;[db]);
+
+    SetDispMask(1);             // Enable the display
+
+    DrawOTag(ot[db]+OTLEN-1);   // Draw the ordering table
+    
+    db = !db;                   // Swap buffers on every pass (alternates between 1 and 0)
+    nextpri = pribuff[db];      // Reset next primitive pointer
+    
+}
+
+int main() {
+    
+    TILE *tile;                     // Primitive pointer
+    
+    // Reset graphics
+    ResetGraph(0);
+
+    // First buffer
+    SetDefDispEnv(&disp;[0], 0, 0, 320, 240);
+    SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
+    // Second buffer
+    SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
+    SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
+
+    draw[0].isbg = 1;               // Enable clear
+    setRGB0(&draw;[0], 63, 0, 127);  // Set clear color (dark purple)
+    draw[1].isbg = 1;
+    setRGB0(&draw;[1], 63, 0, 127);
+
+    nextpri = pribuff[0];           // Set initial primitive pointer address
+    
+    while(1) {
+
+        ClearOTagR(ot[db], OTLEN);  // Clear ordering table
+    
+        
+        tile = (TILE*)nextpri;      // Cast next primitive
+
+        setTile(tile);              // Initialize the primitive (very important)
+        setXY0(tile, 32, 32);       // Set primitive (x,y) position
+        setWH(tile, 64, 64);        // Set primitive size
+        setRGB0(tile, 255, 255, 0); // Set color yellow
+        addPrim(ot[db], tile);      // Add primitive to the ordering table
+        
+        nextpri += sizeof(TILE);    // Advance the next primitive pointer
+        
+    
+        // Update the display
+        display();
+        
+    }
+    
+    return 0;
+}
+
+

Compile and run the program and you should get a yellow square. +Your very first graphic done with the GPU!

+ + +

Conclusion

+

This concludes Chapter 1.2. of Lameguy64's PSX Tutorial series. +You should know more about drawing graphics on the PS1 after reading +through this chapter.

+

A few things you may want to experiment with yourself for further +learning:

+
    +
  • Play around with the values specified in setXY0(), setRGB0() +and setWH() to change the position, color and size of the sprite +respectively.
  • +
  • Try drawing more sprites by repeating the primitive creation process. +Make sure the nextpri and tile pointers have been advanced before +creating a new primitive.
  • +
  • You can advance the primitive pointer with tile++;, and set the +updated address to nextpri by converting types back +(nextpri = (char*)tile;)
  • +
  • Try making the yellow square bounce around the screen, by defining +two variables for (x,y) coordinates and two more for the direction flags, +and write some logic that makes the (x,y) coordinates move and bounce +around the screen.
  • +
+

The next tutorial will cover how to convert and upload texture data, as +well as drawing said textures with sprite and polygon primitives.

+
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/analytics.js b/Docs/Chapter 1.2 Drawing Graphics_files/analytics.js new file mode 100644 index 0000000..10400f0 --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/analytics.js @@ -0,0 +1,474 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0 +/* eslint-disable no-var, semi, prefer-arrow-callback, prefer-template */ + +/** + * Collection of methods for sending analytics events to Archive.org's analytics server. + * + * These events are used for internal stats and sent (in anonymized form) to Google Analytics. + * + * @see analytics.md + * + * @type {Object} + */ +window.archive_analytics = (function defineArchiveAnalytics() { + // keep orignal Date object so as not to be affected by wayback's + // hijacking global Date object + var Date = window.Date; + var ARCHIVE_ANALYTICS_VERSION = 2; + var DEFAULT_SERVICE = 'ao_2'; + var NO_SAMPLING_SERVICE = 'ao_no_sampling'; // sends every event instead of a percentage + + var startTime = new Date(); + + /** + * @return {Boolean} + */ + function isPerformanceTimingApiSupported() { + return 'performance' in window && 'timing' in window.performance; + } + + /** + * Determines how many milliseconds elapsed between the browser starting to parse the DOM and + * the current time. + * + * Uses the Performance API or a fallback value if it's not available. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API + * + * @return {Number} + */ + function getLoadTime() { + var start; + + if (isPerformanceTimingApiSupported()) + start = window.performance.timing.domLoading; + else + start = startTime.getTime(); + + return new Date().getTime() - start; + } + + /** + * Determines how many milliseconds elapsed between the user navigating to the page and + * the current time. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API + * + * @return {Number|null} null if the browser doesn't support the Performance API + */ + function getNavToDoneTime() { + if (!isPerformanceTimingApiSupported()) + return null; + + return new Date().getTime() - window.performance.timing.navigationStart; + } + + /** + * Performs an arithmetic calculation on a string with a number and unit, while maintaining + * the unit. + * + * @param {String} original value to modify, with a unit + * @param {Function} doOperation accepts one Number parameter, returns a Number + * @returns {String} + */ + function computeWithUnit(original, doOperation) { + var number = parseFloat(original, 10); + var unit = original.replace(/(\d*\.\d+)|\d+/, ''); + + return doOperation(number) + unit; + } + + /** + * Computes the default font size of the browser. + * + * @returns {String|null} computed font-size with units (typically pixels), null if it cannot be computed + */ + function getDefaultFontSize() { + var fontSizeStr; + + if (!('getComputedStyle' in window)) + return null; + + var style = window.getComputedStyle(document.documentElement); + if (!style) + return null; + + fontSizeStr = style.fontSize; + + // Don't modify the value if tracking book reader. + if (document.querySelector('#BookReader')) + return fontSizeStr; + + return computeWithUnit(fontSizeStr, function reverseBootstrapFontSize(number) { + // Undo the 62.5% size applied in the Bootstrap CSS. + return number * 1.6; + }); + } + + /** + * Get the URL parameters for a given Location + * @param {Location} + * @return {Object} The URL parameters + */ + function getParams(location) { + if (!location) location = window.location; + var vars; + var i; + var pair; + var params = {}; + var query = location.search; + if (!query) return params; + vars = query.substring(1).split('&'); + for (i = 0; i < vars.length; i++) { + pair = vars[i].split('='); + params[pair[0]] = decodeURIComponent(pair[1]); + } + return params; + } + + function getMetaProp(name) { + var metaTag = document.querySelector('meta[property=' + name + ']'); + return metaTag ? metaTag.getAttribute('content') || null : null; + } + + var ArchiveAnalytics = { + /** + * @type {String|null} + */ + service: getMetaProp('service'), + mediaType: getMetaProp('mediatype'), + primaryCollection: getMetaProp('primary_collection'), + + /** + * Key-value pairs to send in pageviews (you can read this after a pageview to see what was + * sent). + * + * @type {Object} + */ + values: {}, + + /** + * Sends an analytics ping, preferably using navigator.sendBeacon() + * @param {Object} values + * @param {Function} [onload_callback] (deprecated) callback to invoke once ping to analytics server is done + * @param {Boolean} [augment_for_ao_site] (deprecated) if true, add some archive.org site-specific values + */ + send_ping: function send_ping(values, onload_callback, augment_for_ao_site) { + if (typeof window.navigator !== 'undefined' && typeof window.navigator.sendBeacon !== 'undefined') + this.send_ping_via_beacon(values); + else + this.send_ping_via_image(values); + }, + + /** + * Sends a ping via Beacon API + * NOTE: Assumes window.navigator.sendBeacon exists + * @param {Object} values Tracking parameters to pass + */ + send_ping_via_beacon: function send_ping_via_beacon(values) { + var url = this.generate_tracking_url(values || {}); + window.navigator.sendBeacon(url); + }, + + /** + * Sends a ping via Image object + * @param {Object} values Tracking parameters to pass + */ + send_ping_via_image: function send_ping_via_image(values) { + var url = this.generate_tracking_url(values || {}); + var loadtime_img = new Image(1, 1); + loadtime_img.src = url; + loadtime_img.alt = ''; + }, + + /** + * Construct complete tracking URL containing payload + * @param {Object} params Tracking parameters to pass + * @return {String} URL to use for tracking call + */ + generate_tracking_url: function generate_tracking_url(params) { + var baseUrl = '//analytics.archive.org/0.gif'; + var keys; + var outputParams = params; + var outputParamsArray = []; + + outputParams.service = outputParams.service || this.service || DEFAULT_SERVICE; + + // Build array of querystring parameters + keys = Object.keys(outputParams); + keys.forEach(function keyIteration(key) { + outputParamsArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(outputParams[key])); + }); + outputParamsArray.push('version=' + ARCHIVE_ANALYTICS_VERSION); + outputParamsArray.push('count=' + (keys.length + 2)); // Include `version` and `count` in count + + return baseUrl + '?' + outputParamsArray.join('&'); + }, + + /** + * @param {int} page Page number + */ + send_scroll_fetch_event: function send_scroll_fetch_event(page) { + var additionalValues = { ev: page }; + var loadTime = getLoadTime(); + var navToDoneTime = getNavToDoneTime(); + if (loadTime) additionalValues.loadtime = loadTime; + if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime; + this.send_event('page_action', 'scroll_fetch', location.pathname, additionalValues); + }, + + send_scroll_fetch_base_event: function send_scroll_fetch_base_event() { + var additionalValues = {}; + var loadTime = getLoadTime(); + var navToDoneTime = getNavToDoneTime(); + if (loadTime) additionalValues.loadtime = loadTime; + if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime; + this.send_event('page_action', 'scroll_fetch_base', location.pathname, additionalValues); + }, + + /** + * @param {Object} [options] + * @param {String} [options.mediaType] + * @param {String} [options.mediaLanguage] + * @param {String} [options.page] The path portion of the page URL + */ + send_pageview: function send_pageview(options) { + var settings = options || {}; + + var defaultFontSize; + var loadTime = getLoadTime(); + var mediaType = settings.mediaType; + var primaryCollection = settings.primaryCollection; + var page = settings.page; + var navToDoneTime = getNavToDoneTime(); + + /** + * @return {String} + */ + function get_locale() { + if (navigator) { + if (navigator.language) + return navigator.language; + + else if (navigator.browserLanguage) + return navigator.browserLanguage; + + else if (navigator.systemLanguage) + return navigator.systemLanguage; + + else if (navigator.userLanguage) + return navigator.userLanguage; + } + return ''; + } + + defaultFontSize = getDefaultFontSize(); + + // Set field values + this.values.kind = 'pageview'; + this.values.timediff = (new Date().getTimezoneOffset()/60)*(-1); // *timezone* diff from UTC + this.values.locale = get_locale(); + this.values.referrer = (document.referrer == '' ? '-' : document.referrer); + + if (loadTime) + this.values.loadtime = loadTime; + + if (navToDoneTime) + this.values.nav_to_done_ms = navToDoneTime; + + if (settings.trackingId) { + this.values.ga_tid = settings.trackingId; + } + + /* START CUSTOM DIMENSIONS */ + if (defaultFontSize) + this.values.ga_cd1 = defaultFontSize; + + if ('devicePixelRatio' in window) + this.values.ga_cd2 = window.devicePixelRatio; + + if (mediaType) + this.values.ga_cd3 = mediaType; + + if (settings.mediaLanguage) { + this.values.ga_cd4 = settings.mediaLanguage; + } + + if (primaryCollection) { + this.values.ga_cd5 = primaryCollection; + } + /* END CUSTOM DIMENSIONS */ + + if (page) + this.values.page = page; + + this.send_ping(this.values); + }, + + /** + * Sends a tracking "Event". + * @param {string} category + * @param {string} action + * @param {string} label + * @param {Object} additionalEventParams + */ + send_event: function send_event( + category, + action, + label, + additionalEventParams + ) { + if (!label) label = window.location.pathname; + if (!additionalEventParams) additionalEventParams = {}; + if (additionalEventParams.mediaLanguage) { + additionalEventParams.ga_cd4 = additionalEventParams.mediaLanguage; + delete additionalEventParams.mediaLanguage; + } + var eventParams = Object.assign( + { + kind: 'event', + ec: category, + ea: action, + el: label, + cache_bust: Math.random(), + }, + additionalEventParams + ); + this.send_ping(eventParams); + }, + + /** + * Sends every event instead of a small percentage. + * + * Use this sparingly as it can generate a lot of events. + * + * @param {string} category + * @param {string} action + * @param {string} label + * @param {Object} additionalEventParams + */ + send_event_no_sampling: function send_event_no_sampling( + category, + action, + label, + additionalEventParams + ) { + var extraParams = additionalEventParams || {}; + extraParams.service = NO_SAMPLING_SERVICE; + this.send_event(category, action, label, extraParams); + }, + + /** + * @param {Object} options see this.send_pageview options + */ + send_pageview_on_load: function send_pageview_on_load(options) { + var self = this; + window.addEventListener('load', function send_pageview_with_options() { + self.send_pageview(options); + }); + }, + + /** + * Handles tracking events passed in URL. + * Assumes category and action values are separated by a "|" character. + * NOTE: Uses the unsampled analytics property. Watch out for future high click links! + * @param {Location} + */ + process_url_events: function process_url_events(location) { + var eventValues; + var actionValue; + var eventValue = getParams(location).iax; + if (!eventValue) return; + eventValues = eventValue.split('|'); + actionValue = eventValues.length >= 1 ? eventValues[1] : ''; + this.send_event_no_sampling( + eventValues[0], + actionValue, + window.location.pathname + ); + }, + + /** + * Attaches handlers for event tracking. + * + * To enable click tracking for a link, add a `data-event-click-tracking` + * attribute containing the Google Analytics Event Category and Action, separated + * by a vertical pipe (|). + * e.g. `` + * + * To enable form submit tracking, add a `data-event-form-tracking` attribute + * to the `form` tag. + * e.g. `` + * + * Additional tracking options can be added via a `data-event-tracking-options` + * parameter. This parameter, if included, should be a JSON string of the parameters. + * Valid parameters are: + * - service {string}: Corresponds to the Google Analytics property data values flow into + */ + set_up_event_tracking: function set_up_event_tracking() { + var self = this; + var clickTrackingAttributeName = 'event-click-tracking'; + var formTrackingAttributeName = 'event-form-tracking'; + var trackingOptionsAttributeName = 'event-tracking-options'; + + function handleAction(event, attributeName) { + var selector = '[data-' + attributeName + ']'; + var eventTarget = event.target; + if (!eventTarget) return; + var target = eventTarget.closest(selector); + if (!target) return; + var categoryAction; + var categoryActionParts; + var options; + categoryAction = target.dataset[toCamelCase(attributeName)]; + if (!categoryAction) return; + categoryActionParts = categoryAction.split('|'); + options = target.dataset[toCamelCase(trackingOptionsAttributeName)]; + options = options ? JSON.parse(options) : {}; + self.send_event( + categoryActionParts[0], + categoryActionParts[1], + categoryActionParts[2] || window.location.pathname, + options.service ? { service: options.service } : {} + ); + } + + function toCamelCase(str) { + return str.replace(/\W+(.)/g, function (match, chr) { + return chr.toUpperCase(); + }); + }; + + document.addEventListener('click', function(e) { + handleAction(e, clickTrackingAttributeName); + }); + + document.addEventListener('submit', function(e) { + handleAction(e, formTrackingAttributeName); + }); + }, + + /** + * @returns {Object[]} + */ + get_data_packets: function get_data_packets() { + return [this.values]; + }, + + /** + * Creates a tracking image for tracking JS compatibility. + * + * @param {string} type The type value for track_js_case in query params for 0.gif + */ + create_tracking_image: function create_tracking_image(type) { + this.send_ping_via_image({ + cache_bust: Math.random(), + kind: 'track_js', + track_js_case: type, + }); + } + }; + + return ArchiveAnalytics; +}()); +// @license-end diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/banner-styles.css b/Docs/Chapter 1.2 Drawing Graphics_files/banner-styles.css new file mode 100644 index 0000000..8d39621 --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/banner-styles.css @@ -0,0 +1,500 @@ +@import 'record.css'; /* for SPN1 */ + +#wm-ipp-base { + height:65px;/* initial height just in case js code fails */ + padding:0; + margin:0; + border:none; + background:none transparent; +} +#wm-ipp { + z-index: 2147483647; +} +#wm-ipp, #wm-ipp * { + font-family:Lucida Grande, Helvetica, Arial, sans-serif; + font-size:12px; + line-height:1.2; + letter-spacing:0; + width:auto; + height:auto; + max-width:none; + max-height:none; + min-width:0 !important; + min-height:0; + outline:none; + float:none; + text-align:left; + border:none; + color: #000; + text-indent: 0; + position: initial; + background: none; +} +#wm-ipp div, #wm-ipp canvas { + display: block; +} +#wm-ipp div, #wm-ipp tr, #wm-ipp td, #wm-ipp a, #wm-ipp form { + padding:0; + margin:0; + border:none; + border-radius:0; + background-color:transparent; + background-image:none; + /*z-index:2147483640;*/ + height:auto; +} +#wm-ipp table { + border:none; + border-collapse:collapse; + margin:0; + padding:0; + width:auto; + font-size:inherit; +} +#wm-ipp form input { + padding:1px !important; + height:auto; + display:inline; + margin:0; + color: #000; + background: none #fff; + border: 1px solid #666; +} +#wm-ipp form input[type=submit] { + padding:0 8px !important; + margin:1px 0 1px 5px !important; + width:auto !important; + border: 1px solid #000 !important; + background: #fff !important; + color: #000 !important; +} +#wm-ipp a { + display: inline; +} +#wm-ipp a:hover{ + text-decoration:underline; +} +#wm-ipp a.wm-btn:hover { + text-decoration:none; + color:#ff0 !important; +} +#wm-ipp a.wm-btn:hover span { + color:#ff0 !important; +} +#wm-ipp #wm-ipp-inside { + margin: 0 6px; + border:5px solid #000; + border-top:none; + background-color:rgba(255,255,255,0.9); + -moz-box-shadow:1px 1px 4px #333; + -webkit-box-shadow:1px 1px 4px #333; + box-shadow:1px 1px 4px #333; + border-radius:0 0 8px 8px; +} +/* selectors are intentionally verbose to ensure priority */ +#wm-ipp #wm-logo { + padding:0 10px; + vertical-align:middle; + min-width:100px; + flex: 0 0 100px; +} +#wm-ipp .c { + padding-left: 4px; +} +#wm-ipp .c .u { + margin-top: 4px !important; +} +#wm-ipp .n { + padding:0 0 0 5px !important; + vertical-align: bottom; +} +#wm-ipp .n a { + text-decoration:none; + color:#33f; + font-weight:bold; +} +#wm-ipp .n .b { + padding:0 6px 0 0 !important; + text-align:right !important; + overflow:visible; + white-space:nowrap; + color:#99a; + vertical-align:middle; +} +#wm-ipp .n .y .b { + padding:0 6px 2px 0 !important; +} +#wm-ipp .n .c { + background:#000; + color:#ff0; + font-weight:bold; + padding:0 !important; + text-align:center; +} +#wm-ipp.hi .n td.c { + color:#ec008c; +} +#wm-ipp .n td.f { + padding:0 0 0 6px !important; + text-align:left !important; + overflow:visible; + white-space:nowrap; + color:#99a; + vertical-align:middle; +} +#wm-ipp .n tr.m td { + text-transform:uppercase; + white-space:nowrap; + padding:2px 0; +} +#wm-ipp .c .s { + padding:0 5px 0 0 !important; + vertical-align:bottom; +} +#wm-ipp #wm-nav-captures { + white-space: nowrap; +} +#wm-ipp .c .s a.t { + color:#33f; + font-weight:bold; + line-height: 1.8; +} +#wm-ipp .c .s div.r { + color: #666; + font-size:9px; + white-space:nowrap; +} +#wm-ipp .c .k { + padding-bottom:1px; +} +#wm-ipp .c .s { + padding:0 5px 2px 0 !important; +} +#wm-ipp td#displayMonthEl { + padding: 2px 0 !important; +} +#wm-ipp td#displayYearEl { + padding: 0 0 2px 0 !important; +} + +div#wm-ipp-sparkline { + position:relative;/* for positioning markers */ + white-space:nowrap; + background-color:#fff; + cursor:pointer; + line-height:0.9; +} +#sparklineImgId, #wm-sparkline-canvas { + position:relative; + z-index:9012; + max-width:none; +} +#wm-ipp-sparkline div.yt { + position:absolute; + z-index:9010 !important; + background-color:#ff0 !important; + top: 0; +} +#wm-ipp-sparkline div.mt { + position:absolute; + z-index:9013 !important; + background-color:#ec008c !important; + top: 0; +} +#wm-ipp .r { + margin-left: 4px; +} +#wm-ipp .r a { + color:#33f; + border:none; + position:relative; + background-color:transparent; + background-repeat:no-repeat !important; + background-position:100% 100% !important; + text-decoration: none; +} +#wm-ipp #wm-capinfo { + /* prevents notice div background from sticking into round corners of + #wm-ipp-inside */ + border-radius: 0 0 4px 4px; +} +#wm-ipp #wm-capinfo .c-logo { + display:block; + float:left; + margin-right:3px; + width:90px; + min-height:90px; + max-height: 290px; + border-radius:45px; + overflow:hidden; + background-position:50%; + background-size:auto 90px; + box-shadow: 0 0 2px 2px rgba(208,208,208,128) inset; +} +#wm-ipp #wm-capinfo .c-logo span { + display:inline-block; +} +#wm-ipp #wm-capinfo .c-logo img { + height:90px; + position:relative; + left:-50%; +} +#wm-ipp #wm-capinfo .wm-title { + font-size:130%; +} +#wm-ipp #wm-capinfo a.wm-selector { + display:inline-block; + color: #aaa; + text-decoration:none !important; + padding: 2px 8px; +} +#wm-ipp #wm-capinfo a.wm-selector.selected { + background-color:#666; +} +#wm-ipp #wm-capinfo a.wm-selector:hover { + color: #fff; +} +#wm-ipp #wm-capinfo.notice-only #wm-capinfo-collected-by, +#wm-ipp #wm-capinfo.notice-only #wm-capinfo-timestamps { + display: none; +} +#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content { + background-color:#ff0; + padding:5px; + font-size:14px; + text-align:center; +} +#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content * { + font-size:14px; + text-align:center; +} +#wm-ipp #wm-expand { + right: 1px; + bottom: -1px; + color: #ffffff; + background-color: #666 !important; + padding:0 5px 0 3px !important; + border-radius: 3px 3px 0 0 !important; +} +#wm-ipp #wm-expand span { + color: #ffffff; +} +#wm-ipp #wm-expand #wm-expand-icon { + display: inline-block; + transition: transform 0.5s; + transform-origin: 50% 45%; +} +#wm-ipp #wm-expand.wm-open #wm-expand-icon { + transform: rotate(180deg); +} +#wm-ipp #wmtb { + text-align:right; +} +#wm-ipp #wmtb #wmtbURL { + width: calc(100% - 45px); +} +#wm-ipp #wm-graph-anchor { + border-right:1px solid #ccc; +} +/* time coherence */ +html.wb-highlight { + box-shadow: inset 0 0 0 3px #a50e3a !important; +} +.wb-highlight { + outline: 3px solid #a50e3a !important; +} +#wm-ipp-print { + display:none !important; +} +@media print { +#wm-ipp-base { + display:none !important; +} +#wm-ipp-print { + display:block !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +} +@media (max-width:414px) { + #wm-ipp .xxs { + display:none !important; + } +} +@media (min-width:1055px) { +#wm-ipp #wm-graph-anchor { + display:block !important; +} +} +@media (max-width:1054px) { +#wm-ipp #wm-graph-anchor { + display:none !important; +} +} +@media (max-width:1163px) { +#wm-logo { + display:none !important; +} +} + +#wm-btns { + white-space: nowrap; + margin-top: -2px; +} + +#wm-btns #wm-save-snapshot-open { + margin-right: 7px; + top: -6px; +} + +#wm-btns #wm-sign-in { + box-sizing: content-box; + display: none; + margin-right: 7px; + top: -8px; + + /* + round border around sign in button + */ + border: 2px #000 solid; + border-radius: 14px; + padding-right: 2px; + padding-bottom: 2px; + width: 11px; + height: 11px; +} + +#wm-btns #wm-sign-in>.iconochive-person { + font-size: 12.5px; +} + +#wm-save-snapshot-open > .iconochive-web { + color:#000; + font-size:160%; +} + +#wm-ipp #wm-share { + display: flex; + align-items: flex-end; + justify-content: space-between; +} + +#wm-share > #wm-screenshot { + display: inline-block; + margin-right: 3px; + visibility: hidden; +} + +#wm-screenshot > .iconochive-image { + color:#000; + font-size:160%; +} + +#wm-share > #wm-video { + display: inline-block; + margin-right: 3px; + visibility: hidden; +} + +#wm-video > .iconochive-movies { + color: #000; + display: inline-block; + font-size: 150%; + margin-bottom: 2px; +} + +#wm-btns #wm-save-snapshot-in-progress { + display: none; + font-size:160%; + opacity: 0.5; + position: relative; + margin-right: 7px; + top: -5px; +} + +#wm-btns #wm-save-snapshot-success { + display: none; + color: green; + position: relative; + top: -7px; +} + +#wm-btns #wm-save-snapshot-fail { + display: none; + color: red; + position: relative; + top: -7px; +} + +.wm-icon-screen-shot { + background: url("../images/web-screenshot.svg") no-repeat !important; + background-size: contain !important; + width: 22px !important; + height: 19px !important; + + display: inline-block; +} +#donato { + /* transition effect is disable so as to simplify height adjustment */ + /*transition: height 0.5s;*/ + height: 0; + margin: 0; + padding: 0; + border-bottom: 1px solid #999 !important; +} +body.wm-modal { + height: auto !important; + overflow: hidden !important; +} +#donato #donato-base { + width: 100%; + height: 100%; + /*bottom: 0;*/ + margin: 0; + padding: 0; + position: absolute; + z-index: 2147483639; +} +body.wm-modal #donato #donato-base { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 2147483640; +} + +.wb-autocomplete-suggestions { + font-family: Lucida Grande, Helvetica, Arial, sans-serif; + font-size: 12px; + text-align: left; + cursor: default; + border: 1px solid #ccc; + border-top: 0; + background: #fff; + box-shadow: -1px 1px 3px rgba(0,0,0,.1); + position: absolute; + display: none; + z-index: 2147483647; + max-height: 254px; + overflow: hidden; + overflow-y: auto; + box-sizing: border-box; +} +.wb-autocomplete-suggestion { + position: relative; + padding: 0 .6em; + line-height: 23px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 1.02em; + color: #333; +} +.wb-autocomplete-suggestion b { + font-weight: bold; +} +.wb-autocomplete-suggestion.selected { + background: #f0f0f0; +} diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/bundle-playback.js b/Docs/Chapter 1.2 Drawing Graphics_files/bundle-playback.js new file mode 100644 index 0000000..390894d --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/bundle-playback.js @@ -0,0 +1,3 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=9)}([function(e,t,n){"use strict";function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){return!t||"object"!==o(t)&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}function s(e){var t="function"==typeof Map?new Map:void 0;return(s=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,o)}function o(){return a(e,arguments,u(this).constructor)}return o.prototype=Object.create(e.prototype,{constructor:{value:o,enumerable:!1,writable:!0,configurable:!0}}),l(o,e)})(e)}function a(e,t,n){return(a=c()?Reflect.construct:function(e,t,n){var o=[null];o.push.apply(o,t);var i=new(Function.bind.apply(e,o));return n&&l(i,n.prototype),i}).apply(null,arguments)}function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function l(e,t){return(l=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}if(n.d(t,"a",(function(){return f})),n.d(t,"d",(function(){return p})),n.d(t,"c",(function(){return d})),n.d(t,"b",(function(){return v})),"undefiend"!=typeof window.XMLHttpRequest){var h=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&l(e,t)}(o,e);var t,n=(t=o,function(){var e,n=u(t);if(c()){var o=u(this).constructor;e=Reflect.construct(n,arguments,o)}else e=n.apply(this,arguments);return r(this,e)});function o(){return i(this,o),n.apply(this,arguments)}return o}(s(XMLHttpRequest));Object.defineProperty(h.prototype,"responseURL",Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype,"responseURL"))}function f(e,t,n,o,i){var r;if((r=window.XMLHttpRequest?new h:new ActiveXObject("Microsoft.XMLHTTP")).onreadystatechange=function(){4==this.readyState&&n(r)},r.open(e,t,!0),o)for(var s in o)o.hasOwnProperty(s)&&r.setRequestHeader(s,o[s]);r.withCredentials=!0,r.send(i)}function p(e){return void 0!==e&&e&&e.constructor===Array}function d(e){return document.cookie.search(e)>=0}function m(e,t){var n=window["HTML".concat(e,"Element")];if(void 0!==n){var o=Object.getOwnPropertyDescriptor(n.prototype,t);void 0!==o&&Object.defineProperty(n.prototype,"_wm_".concat(t),o)}}function v(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"src",n="_wm_".concat(t);return n in e.__proto__?e[n]:e[t]}m("Image","src"),m("Media","src"),m("Embed","src"),m("IFrame","src"),m("Script","src"),m("Link","href"),m("Anchor","href")},function(e,t,n){"use strict";n.d(t,"c",(function(){return s})),n.d(t,"b",(function(){return a})),n.d(t,"a",(function(){return c}));var o=["January","February","March","April","May","June","July","August","September","October","November","December"],i=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],r={Y:function(e){return e.getUTCFullYear()},m:function(e){return e.getUTCMonth()+1},b:function(e){return i[e.getUTCMonth()]},B:function(e){return o[e.getUTCMonth()]},d:function(e){return e.getUTCDate()},H:function(e){return("0"+e.getUTCHours()).slice(-2)},M:function(e){return("0"+e.getUTCMinutes()).slice(-2)},S:function(e){return("0"+e.getUTCSeconds()).slice(-2)},"%":function(){return"%"}};function s(e){var t=function(e){return"number"==typeof e&&(e=e.toString()),[e.slice(-14,-10),e.slice(-10,-8),e.slice(-8,-6),e.slice(-6,-4),e.slice(-4,-2),e.slice(-2)]}(e);return new Date(Date.UTC(t[0],t[1]-1,t[2],t[3],t[4],t[5]))}function a(e){return i[e]}function c(e,t){return t.replace(/%./g,(function(t){var n=r[t[1]];return n?n(s(e)):t}))}},function(e,t,n){"use strict";n.d(t,"b",(function(){return s})),n.d(t,"a",(function(){return a}));var o=n(0);function i(e,t){for(var n=0;n=400?i.failure&&i.failure(e):i.success&&i.success(e)}),{"Content-Type":"application/json"},r.stringify({url:e,snapshot:t,tags:n||[]})),!1}var a=function(){function e(t,n,i){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.el=t,this.url=n,this.timestamp=i,t.onclick=this.save.bind(this),document.addEventListener("DOMContentLoaded",(function(){Object(o.c)("logged-in-user")&&Object(o.c)("logged-in-sig")?r.userIsLoggedIn():r.userIsNotLoggedIn()}))}var t,n,r;return t=e,(n=[{key:"save",value:function(e){this.start(),s(this.url,this.timestamp,[],{failure:this.failure.bind(this),success:this.success.bind(this)})}},{key:"start",value:function(){this.hide(["wm-save-snapshot-fail","wm-save-snapshot-open","wm-save-snapshot-success"]),this.show(["wm-save-snapshot-in-progress"])}},{key:"failure",value:function(e){401==e.status?this.userNotLoggedIn(e):(this.hide(["wm-save-snapshot-in-progress","wm-save-snapshot-success"]),this.show(["wm-save-snapshot-fail","wm-save-snapshot-open"]),console.log("You have got an error."),console.log("If you think something wrong here please send it to support."),console.log('Response: "'+e.responseText+'"'),console.log('status: "'+e.status+'"'))}},{key:"success",value:function(e){this.hide(["wm-save-snapshot-fail","wm-save-snapshot-in-progress"]),this.show(["wm-save-snapshot-open","wm-save-snapshot-success"])}},{key:"userIsLoggedIn",value:function(){this.show("wm-save-snapshot-open"),this.hide("wm-sign-in")}},{key:"userIsNotLoggedIn",value:function(){this.hide(["wm-save-snapshot-open","wm-save-snapshot-in-progress"]),this.show("wm-sign-in")}},{key:"show",value:function(e){this.setDisplayStyle(e,"inline-block")}},{key:"hide",value:function(e){this.setDisplayStyle(e,"none")}},{key:"setDisplayStyle",value:function(e,t){var n=this;(Object(o.d)(e)?e:[e]).forEach((function(e){var o=n.el.getRootNode().getElementById(e);o&&(o.style.display=t)}))}}])&&i(t.prototype,n),r&&i(t,r),e}()},,,,,,,function(e,t,n){"use strict";var o;n.r(t);var i,r={createElementNS:document.createElementNS};var s=!0;function a(e){!function(e,t,n){if(n){var o=new Date;o.setTime(o.getTime()+24*n*60*60*1e3);var i="; expires="+o.toGMTString()}else i="";document.cookie=e+"="+t+i+"; path=/"}(e,"",-1)}var c=n(0),l=n(1),u=window.Date;function h(e,t){return(e=e.toString()).length>=t?e:"00000000".substring(0,t-e.length)+e}function f(e){for(var t=0,n=0;n3}(e)){var o=[];for(n=0;n=e.length?{done:!0}:{done:!1,value:e[t++]}},e:function(e){throw e},f:n}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i,r=!0,s=!1;return{s:function(){o=e[Symbol.iterator]()},n:function(){var e=o.next();return r=e.done,e},e:function(e){s=!0,i=e},f:function(){try{r||null==o.return||o.return()}finally{if(s)throw i}}}}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n2&&void 0!==arguments[2]?arguments[2]:"src",r=window.location.origin,s=b(window,e),l=p(s);try{for(l.s();!(o=l.n()).done;){var u=o.value;if(!n||n(u)){var h=Object(c.b)(u,i);h&&!h.startsWith(t)&&h.startsWith(r)&&(h.startsWith("data:")||a.push(h))}}}catch(e){l.e(e)}finally{l.f()}}u("img"),u("frame"),u("iframe",(function(e){return"playback"!==e.id})),u("script"),u("link",(function(e){return"stylesheet"===e.rel}),"href");var h=a.filter((function(e,t,n){return n.indexOf(e)===t}));h.length>0?(s=0,h.map((function(e){e.match("^https?://")&&(s++,Object(c.a)("HEAD",e,(function(e){if(200==e.status){var t=e.getResponseHeader("Memento-Datetime");if(null==t)console.log("%s: no Memento-Datetime",u);else{var n=document.createElement("span"),a=function(e,t){var n=new Date(e).getTime()-t,o="";n<0?(o+="-",n=Math.abs(n)):o+="+";var i=!1;if(n<1e3)return{delta:n,text:"",highlight:i};var r=n,s=Math.floor(n/1e3/60/60/24/30/12);n-=1e3*s*60*60*24*30*12;var a=Math.floor(n/1e3/60/60/24/30);n-=1e3*a*60*60*24*30;var c=Math.floor(n/1e3/60/60/24);n-=1e3*c*60*60*24;var l=Math.floor(n/1e3/60/60);n-=1e3*l*60*60;var u=Math.floor(n/1e3/60);n-=1e3*u*60;var h=Math.floor(n/1e3),f=[];s>1?(f.push(s+" years"),i=!0):1==s&&(f.push(s+" year"),i=!0);a>1?(f.push(a+" months"),i=!0):1==a&&(f.push(a+" month"),i=!0);c>1?f.push(c+" days"):1==c&&f.push(c+" day");l>1?f.push(l+" hours"):1==l&&f.push(l+" hour");u>1?f.push(u+" minutes"):1==u&&f.push(u+" minute");h>1?f.push(h+" seconds"):1==h&&f.push(h+" second");f.length>2&&(f=f.slice(0,2));return{delta:r,text:o+f.join(" "),highlight:i}}(t,r),c=a.highlight?"color:red;":"";n.innerHTML=" "+a.text,n.title=t,n.setAttribute("style",c);var l=e.getResponseHeader("Content-Type"),u=e.responseURL.replace(window.location.origin,""),h=document.createElement("a");h.innerHTML=u.split("/").splice(3).join("/"),h._wm_href=u,h.title=l,h.onmouseover=y,h.onmouseout=w;var f=document.createElement("div");f.setAttribute("data-delta",a.delta),f.appendChild(h),f.append(n),o.appendChild(f);var p=Array.prototype.slice.call(o.childNodes,0);p.sort((function(e,t){return t.getAttribute("data-delta")-e.getAttribute("data-delta")})),o.innerHTML="";for(var d=0,m=p.length;d0)for(var n=0;n0)for(var n=0;n0?this.sc.scrollTop=i+this.sc.suggestionHeight+o-this.sc.maxHeight:i<0&&(this.sc.scrollTop=i+o)}}},{key:"blurHandler",value:function(){var e=this;try{var t=this.root.querySelector(".wb-autocomplete-suggestions:hover")}catch(e){t=null}t?this.input!==document.activeElement&&setTimeout((function(){return e.focus()}),20):(this.last_val=this.input.value,this.sc.style.display="none",setTimeout((function(){return e.sc.style.display="none"}),350))}},{key:"suggest",value:function(e){var t=this.input.value;if(this.cache[t]=e,e.length&&t.length>=this.minChars){for(var n="",o=0;o40)&&13!=n&&27!=n){var o=this.input.value;if(o.length>=this.minChars){if(o!=this.last_val){if(this.last_val=o,clearTimeout(this.timer),this.cache){if(o in this.cache)return void this.suggest(this.cache[o]);for(var i=1;i'+e.replace(n,"$1")+""}},{key:"onSelect",value:function(e,t,n){}}]),e}(),j=function(){function e(t,n){S(this,e);var o=t.getRootNode();if(o.querySelector){var i="object"==_(t)?[t]:o.querySelectorAll(t);this.elems=i.map((function(e){return new E(e,n)}))}}return T(e,[{key:"destroy",value:function(){for(;this.elems.length>0;)this.elems.pop().unload()}}]),e}(),R=n(2),A=window.JSON,N=window.open,U=window.Date,I=document,D=document,P=function(e){return D.getElementById(e)};var q,F="/static/";function B(e){L.classList.contains("wm-closed")?(e&&O.classList.add("notice-only"),L.classList.replace("wm-closed","wm-open"),O.style.display="block",m(q,F,P)):(L.classList.replace("wm-open","wm-closed"),O.style.display="none",O.classList.remove("notice-only"))}function W(e,t){var n=P(e);n&&(n.style.visibility=t?"visible":"hidden")}function X(e,t){Object(c.d)(e)||(e=[e]);for(var n=0;n0&&r<60,r)}))}window.__wm={init:function(e){!function(){var e=document.cookie.split(";");if(e.length>40)for(var t=0;t1?t-1:0),o=1;o0;)x.appendChild(C.children[0]);if(d)for(var H=0;H'+((""+n).replace(/\B(?=(\d{3})+$)/g,",")+" ")+(n>1?"captures":"capture")+"",f=l.a(i,"%d %b %Y");s!=i&&(f+=" - "+l.a(s,"%d %b %Y")),h+='
'+f+"
",t.innerHTML=h}(o),function(e,t,n,o,i,r,s){var a=o.getContext("2d");if(a){a.fillStyle="#FFF";var c=(new u).getUTCFullYear(),l=t/(c-i+1),h=f(e.years),p=h[0],d=n/h[1];if(r>=i){var m=T(r);a.fillStyle="#FFFFA5",a.fillRect(m,0,l,n)}for(var v=i;v<=c;v++){m=T(v);a.beginPath(),a.moveTo(m,0),a.lineTo(m,n),a.lineWidth=1,a.strokeStyle="#CCC",a.stroke()}s=parseInt(s)-1;for(var g=(l-1)/12,y=0;y0){var M=Math.ceil(S*d);a.fillStyle=v==r&&_==s?"#EC008C":"#000",a.fillRect(Math.round(b),Math.ceil(n-M),Math.ceil(g),Math.round(M))}b+=g}}}function T(e){return Math.ceil((e-i)*l)+.5}}(o,e,t,Z,a,_,S)}}))}else{var te=new Image;te.src="/__wb/sparkline?url="+encodeURIComponent(r)+"&width="+e+"&height="+t+"&selected_year="+_+"&selected_month="+S+(i&&"&collection="+i||""),te.alt="sparkline",te.width=e,te.height=t,te.id="sparklineImgId",te.border="0",Q.parentNode.replaceChild(te,Q)}function ne(e){for(var t=[],n=e.length,o=0;o + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Process graphics to OT + OT sent to GPU + GPU idle + GPU busy + Process graphics to OT + GPU busy + OT sent to GPU + GPU busy + + + Wait for GPU (DrawSync()) + GPU finished + + Process graphics to OT + GPU idle + OT sent to GPU + GPU busy + + Wait for GPU (DrawSync()) + GPU finished + GPU idle + Process graphics to OT + OT sent to GPU + GPU busy + + + Single OT+Prim. Buffer + Double OT+Prim. Buffer + + + + + + + + + + + + + + + + + + + + + diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/ordertable-multiprims.svg b/Docs/Chapter 1.2 Drawing Graphics_files/ordertable-multiprims.svg new file mode 100644 index 0000000..980bef1 --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/ordertable-multiprims.svg @@ -0,0 +1,705 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Primitive 2 + + OT[0]=0xffffffff (end) + Start of table + + + OT[1] + + OT[2] + + OT[3] + + OT[4] + + OT[5] + + OT[6] + + OT[7] + + + + + + + + + + + + + + Primitive 1 + Primitive 3 + + diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/ordertable-primitive.svg b/Docs/Chapter 1.2 Drawing Graphics_files/ordertable-primitive.svg new file mode 100644 index 0000000..d1987ae --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/ordertable-primitive.svg @@ -0,0 +1,657 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Primitive + + OT[0]=0xffffffff (end) + Start of table + + + OT[1] + + OT[2] + + OT[3] + + OT[4] + + OT[5] + + OT[6] + + OT[7] + + + + + + + + + + + diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/ordertable.svg b/Docs/Chapter 1.2 Drawing Graphics_files/ordertable.svg new file mode 100644 index 0000000..a11b238 --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/ordertable.svg @@ -0,0 +1,633 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + OT[0]=0xffffffff (end) + Start of table + + + OT[1] + + OT[2] + + OT[3] + + OT[4] + + OT[5] + + OT[6] + + OT[7] + + + + + + + + + diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/primstruct.svg b/Docs/Chapter 1.2 Drawing Graphics_files/primstruct.svg new file mode 100644 index 0000000..a4b5e44 --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/primstruct.svg @@ -0,0 +1,350 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + 31 + 0 + Next Pointer + Len + R + G + B + Code + X + Y + U + V + CLUT + Width + Height + + + + + + + + + + + + + + + + diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/style.css b/Docs/Chapter 1.2 Drawing Graphics_files/style.css new file mode 100644 index 0000000..1eac881 --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/style.css @@ -0,0 +1,70 @@ +body { + max-width: 900px; + margin: auto; + padding: 8px; + font-family: sans-serif; + font-size: 14px; + //color: white; + //background: black; +} + +h { + background-color: #e0e0e0; + padding: 2px; +} + +h2 { + border-bottom: 1px solid; + //padding-left: 8px; +} + +img { + display: block; +} + +.footer-table { + font-size: 14px; +} + +.bordered-table { + border-collapse: collapse; + font-size: 11px; +} + +.bordered-table th, .bordered-table td { + border: 1px solid; + padding: 4px; +} + +hr { + border: none; + border-bottom: 1px solid black; +} + +pre { + padding: 8px; + font-size: 12px; + color: black; + background-color: LightGray; +} +/* + FILE ARCHIVED ON 03:35:25 Aug 27, 2022 AND RETRIEVED FROM THE + INTERNET ARCHIVE ON 15:39:09 Sep 05, 2022. + JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE. + + ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C. + SECTION 108(a)(3)). +*/ +/* +playback timings (ms): + captures_list: 107.165 + exclusion.robots: 0.073 + exclusion.robots.policy: 0.067 + cdx.remote: 0.064 + esindex: 0.009 + LoadShardBlock: 52.713 (3) + PetaboxLoader3.datanode: 140.466 (4) + CDXLines.iter: 14.771 (3) + load_resource: 139.896 + PetaboxLoader3.resolve: 47.824 +*/ \ No newline at end of file diff --git a/Docs/Chapter 1.2 Drawing Graphics_files/wombat.js b/Docs/Chapter 1.2 Drawing Graphics_files/wombat.js new file mode 100644 index 0000000..19d1a47 --- /dev/null +++ b/Docs/Chapter 1.2 Drawing Graphics_files/wombat.js @@ -0,0 +1,21 @@ +/* +Wombat.js client-side rewriting engine for web archive replay +Copyright (C) 2014-2020 Webrecorder Software, Rhizome, and Contributors. Released under the GNU Affero General Public License. + +This file is part of wombat.js, see https://github.com/webrecorder/wombat.js for the full source +Wombat.js is part of the Webrecorder project (https://github.com/webrecorder) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + */ +(function(){function FuncMap(){this._map=[]}function ensureNumber(maybeNumber){try{switch(typeof maybeNumber){case"number":case"bigint":return maybeNumber;}var converted=Number(maybeNumber);return isNaN(converted)?null:converted}catch(e){}return null}function addToStringTagToClass(clazz,tag){typeof self.Symbol!=="undefined"&&typeof self.Symbol.toStringTag!=="undefined"&&Object.defineProperty(clazz.prototype,self.Symbol.toStringTag,{value:tag,enumerable:false})}function autobind(clazz){for(var prop,propValue,proto=clazz.__proto__||clazz.constructor.prototype||clazz.prototype,clazzProps=Object.getOwnPropertyNames(proto),len=clazzProps.length,i=0;i=0){var fnMapping=this._map.splice(idx,1);return fnMapping[0][1]}return null},FuncMap.prototype.map=function(param){for(var i=0;i0&&afw.preserveMedia(media)})},AutoFetcher.prototype.terminate=function(){this.worker.terminate()},AutoFetcher.prototype.justFetch=function(urls){this.worker.postMessage({type:"fetch-all",values:urls})},AutoFetcher.prototype.fetchAsPage=function(url,originalUrl,title){if(url){var headers={"X-Wombat-History-Page":originalUrl};if(title){var encodedTitle=encodeURIComponent(title.trim());title&&(headers["X-Wombat-History-Title"]=encodedTitle)}var fetchData={url:url,options:{headers:headers,cache:"no-store"}};this.justFetch([fetchData])}},AutoFetcher.prototype.postMessage=function(msg,deferred){if(deferred){var afWorker=this;return void Promise.resolve().then(function(){afWorker.worker.postMessage(msg)})}this.worker.postMessage(msg)},AutoFetcher.prototype.preserveSrcset=function(srcset,mod){this.postMessage({type:"values",srcset:{value:srcset,mod:mod,presplit:true}},true)},AutoFetcher.prototype.preserveDataSrcset=function(elem){this.postMessage({type:"values",srcset:{value:elem.dataset.srcset,mod:this.rwMod(elem),presplit:false}},true)},AutoFetcher.prototype.preserveMedia=function(media){this.postMessage({type:"values",media:media},true)},AutoFetcher.prototype.getSrcset=function(elem){return this.wombat.wb_getAttribute?this.wombat.wb_getAttribute.call(elem,"srcset"):elem.getAttribute("srcset")},AutoFetcher.prototype.rwMod=function(elem){switch(elem.tagName){case"SOURCE":return elem.parentElement&&elem.parentElement.tagName==="PICTURE"?"im_":"oe_";case"IMG":return"im_";}return"oe_"},AutoFetcher.prototype.extractFromLocalDoc=function(){var afw=this;Promise.resolve().then(function(){for(var msg={type:"values",context:{docBaseURI:document.baseURI}},media=[],i=0,sheets=document.styleSheets;i=0)||scriptType.indexOf("text/template")>=0)},Wombat.prototype.skipWrapScriptTextBasedOnText=function(text){if(!text||text.indexOf(this.WB_ASSIGN_FUNC)>=0||text.indexOf("<")===0)return true;for(var override_props=["window","self","document","location","top","parent","frames","opener"],i=0;i=0)return false;return true},Wombat.prototype.nodeHasChildren=function(node){if(!node)return false;if(typeof node.hasChildNodes==="function")return node.hasChildNodes();var kids=node.children||node.childNodes;return!!kids&&kids.length>0},Wombat.prototype.rwModForElement=function(elem,attrName){if(!elem)return undefined;var mod="mp_";if(!(elem.tagName==="LINK"&&attrName==="href")){var maybeMod=this.tagToMod[elem.tagName];maybeMod!=null&&(mod=maybeMod[attrName])}else if(elem.rel){var relV=elem.rel.trim().toLowerCase(),asV=this.wb_getAttribute.call(elem,"as");if(asV&&this.linkTagMods.linkRelToAs[relV]!=null){var asMods=this.linkTagMods.linkRelToAs[relV];mod=asMods[asV.toLowerCase()]}else this.linkTagMods[relV]!=null&&(mod=this.linkTagMods[relV])}return mod},Wombat.prototype.removeWBOSRC=function(elem){elem.tagName!=="SCRIPT"||elem.__$removedWBOSRC$__||(elem.hasAttribute("__wb_orig_src")&&elem.removeAttribute("__wb_orig_src"),elem.__$removedWBOSRC$__=true)},Wombat.prototype.retrieveWBOSRC=function(elem){if(elem.tagName==="SCRIPT"&&!elem.__$removedWBOSRC$__){var maybeWBOSRC;return maybeWBOSRC=this.wb_getAttribute?this.wb_getAttribute.call(elem,"__wb_orig_src"):elem.getAttribute("__wb_orig_src"),maybeWBOSRC==null&&(elem.__$removedWBOSRC$__=true),maybeWBOSRC}return undefined},Wombat.prototype.wrapScriptTextJsProxy=function(scriptText){return"var _____WB$wombat$assign$function_____ = function(name) {return (self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init(name)) || self[name]; };\nif (!self.__WB_pmw) { self.__WB_pmw = function(obj) { this.__WB_source = obj; return this; } }\n{\nlet window = _____WB$wombat$assign$function_____(\"window\");\nlet self = _____WB$wombat$assign$function_____(\"self\");\nlet document = _____WB$wombat$assign$function_____(\"document\");\nlet location = _____WB$wombat$assign$function_____(\"location\");\nlet top = _____WB$wombat$assign$function_____(\"top\");\nlet parent = _____WB$wombat$assign$function_____(\"parent\");\nlet frames = _____WB$wombat$assign$function_____(\"frames\");\nlet opener = _____WB$wombat$assign$function_____(\"opener\");\n"+scriptText.replace(this.DotPostMessageRe,".__WB_pmw(self.window)$1")+"\n\n}"},Wombat.prototype.watchElem=function(elem,func){if(!this.$wbwindow.MutationObserver)return false;var m=new this.$wbwindow.MutationObserver(function(records,observer){for(var r,i=0;i"},Wombat.prototype.getFinalUrl=function(useRel,mod,url){var prefix=useRel?this.wb_rel_prefix:this.wb_abs_prefix;return mod==null&&(mod=this.wb_info.mod),this.wb_info.is_live||(prefix+=this.wb_info.wombat_ts),prefix+=mod,prefix[prefix.length-1]!=="/"&&(prefix+="/"),prefix+url},Wombat.prototype.resolveRelUrl=function(url,doc){var docObj=doc||this.$wbwindow.document,parser=this.makeParser(docObj.baseURI,docObj),hash=parser.href.lastIndexOf("#"),href=hash>=0?parser.href.substring(0,hash):parser.href,lastslash=href.lastIndexOf("/");return parser.href=lastslash>=0&&lastslash!==href.length-1?href.substring(0,lastslash+1)+url:href+url,parser.href},Wombat.prototype.extractOriginalURL=function(rewrittenUrl){if(!rewrittenUrl)return"";if(this.wb_is_proxy)return rewrittenUrl;var rwURLString=rewrittenUrl.toString(),url=rwURLString;if(this.startsWithOneOf(url,this.IGNORE_PREFIXES))return url;var start;start=this.startsWith(url,this.wb_abs_prefix)?this.wb_abs_prefix.length:this.wb_rel_prefix&&this.startsWith(url,this.wb_rel_prefix)?this.wb_rel_prefix.length:this.wb_rel_prefix?1:0;var index=url.indexOf("/http",start);return index<0&&(index=url.indexOf("///",start)),index<0&&(index=url.indexOf("/blob:",start)),index<0&&(index=url.indexOf("/about:blank",start)),index>=0?url=url.substr(index+1):(index=url.indexOf(this.wb_replay_prefix),index>=0&&(url=url.substr(index+this.wb_replay_prefix.length)),url.length>4&&url.charAt(2)==="_"&&url.charAt(3)==="/"&&(url=url.substr(4)),url!==rwURLString&&!this.startsWithOneOf(url,this.VALID_PREFIXES)&&!this.startsWith(url,"blob:")&&(url=this.wb_orig_scheme+url)),rwURLString.charAt(0)==="/"&&rwURLString.charAt(1)!=="/"&&this.startsWith(url,this.wb_orig_origin)&&(url=url.substr(this.wb_orig_origin.length)),this.startsWith(url,this.REL_PREFIX)?this.wb_info.wombat_scheme+":"+url:url},Wombat.prototype.makeParser=function(maybeRewrittenURL,doc){var originalURL=this.extractOriginalURL(maybeRewrittenURL),docElem=doc;return doc||(this.$wbwindow.location.href==="about:blank"&&this.$wbwindow.opener?docElem=this.$wbwindow.opener.document:docElem=this.$wbwindow.document),this._makeURLParser(originalURL,docElem)},Wombat.prototype._makeURLParser=function(url,docElem){try{return new this.$wbwindow.URL(url,docElem.baseURI)}catch(e){}var p=docElem.createElement("a");return p._no_rewrite=true,p.href=url,p},Wombat.prototype.defProp=function(obj,prop,setFunc,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;var descriptor={configurable:true,enumerable:enumerable||false,get:getFunc};setFunc&&(descriptor.set=setFunc);try{return Object.defineProperty(obj,prop,descriptor),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.defGetterProp=function(obj,prop,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;try{return Object.defineProperty(obj,prop,{configurable:true,enumerable:enumerable||false,get:getFunc}),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.getOrigGetter=function(obj,prop){var orig_getter;if(obj.__lookupGetter__&&(orig_getter=obj.__lookupGetter__(prop)),!orig_getter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_getter=props.get)}return orig_getter},Wombat.prototype.getOrigSetter=function(obj,prop){var orig_setter;if(obj.__lookupSetter__&&(orig_setter=obj.__lookupSetter__(prop)),!orig_setter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_setter=props.set)}return orig_setter},Wombat.prototype.getAllOwnProps=function(obj){for(var ownProps=[],props=Object.getOwnPropertyNames(obj),i=0;i "+final_href),actualLocation.href=final_href}}},Wombat.prototype.checkLocationChange=function(wombatLoc,isTop){var locType=typeof wombatLoc,actual_location=isTop?this.$wbwindow.__WB_replay_top.location:this.$wbwindow.location;locType==="string"?this.updateLocation(wombatLoc,actual_location.href,actual_location):locType==="object"&&this.updateLocation(wombatLoc.href,wombatLoc._orig_href,actual_location)},Wombat.prototype.checkAllLocations=function(){return!this.wb_wombat_updating&&void(this.wb_wombat_updating=true,this.checkLocationChange(this.$wbwindow.WB_wombat_location,false),this.$wbwindow.WB_wombat_location!=this.$wbwindow.__WB_replay_top.WB_wombat_location&&this.checkLocationChange(this.$wbwindow.__WB_replay_top.WB_wombat_location,true),this.wb_wombat_updating=false)},Wombat.prototype.proxyToObj=function(source){if(source)try{var proxyRealObj=source.__WBProxyRealObj__;if(proxyRealObj)return proxyRealObj}catch(e){}return source},Wombat.prototype.objToProxy=function(obj){if(obj)try{var maybeWbProxy=obj._WB_wombat_obj_proxy;if(maybeWbProxy)return maybeWbProxy}catch(e){}return obj},Wombat.prototype.defaultProxyGet=function(obj,prop,ownProps,fnCache){switch(prop){case"__WBProxyRealObj__":return obj;case"location":case"WB_wombat_location":return obj.WB_wombat_location;case"_WB_wombat_obj_proxy":return obj._WB_wombat_obj_proxy;case"__WB_pmw":case"WB_wombat_eval":case this.WB_ASSIGN_FUNC:case this.WB_CHECK_THIS_FUNC:return obj[prop];case"origin":return obj.WB_wombat_location.origin;case"constructor":if(obj.constructor===Window)return obj.constructor;}var retVal=obj[prop],type=typeof retVal;if(type==="function"&&ownProps.indexOf(prop)!==-1){switch(prop){case"requestAnimationFrame":case"cancelAnimationFrame":{if(!this.isNativeFunction(retVal))return retVal;break}}var cachedFN=fnCache[prop];return cachedFN&&cachedFN.original===retVal||(cachedFN={original:retVal,boundFn:retVal.bind(obj)},fnCache[prop]=cachedFN),cachedFN.boundFn}return type==="object"&&retVal&&retVal._WB_wombat_obj_proxy?(retVal instanceof Window&&this.initNewWindowWombat(retVal),retVal._WB_wombat_obj_proxy):retVal},Wombat.prototype.setLoc=function(loc,originalURL){var parser=this.makeParser(originalURL,loc.ownerDocument);loc._orig_href=originalURL,loc._parser=parser;var href=parser.href;loc._hash=parser.hash,loc._href=href,loc._host=parser.host,loc._hostname=parser.hostname,loc._origin=parser.origin?parser.host?parser.origin:"null":parser.protocol+"//"+parser.hostname+(parser.port?":"+parser.port:""),loc._pathname=parser.pathname,loc._port=parser.port,loc._protocol=parser.protocol,loc._search=parser.search,Object.defineProperty||(loc.href=href,loc.hash=parser.hash,loc.host=loc._host,loc.hostname=loc._hostname,loc.origin=loc._origin,loc.pathname=loc._pathname,loc.port=loc._port,loc.protocol=loc._protocol,loc.search=loc._search)},Wombat.prototype.makeGetLocProp=function(prop,origGetter){var wombat=this;return function newGetLocProp(){if(this._no_rewrite)return origGetter.call(this,prop);var curr_orig_href=origGetter.call(this,"href");return prop==="href"?wombat.extractOriginalURL(curr_orig_href):prop==="ancestorOrigins"?[]:(this._orig_href!==curr_orig_href&&wombat.setLoc(this,curr_orig_href),this["_"+prop])}},Wombat.prototype.makeSetLocProp=function(prop,origSetter,origGetter){var wombat=this;return function newSetLocProp(value){if(this._no_rewrite)return origSetter.call(this,prop,value);if(this["_"+prop]!==value){if(this["_"+prop]=value,!this._parser){var href=origGetter.call(this);this._parser=wombat.makeParser(href,this.ownerDocument)}var rel=false;prop==="href"&&typeof value==="string"&&value&&(value[0]==="."?value=wombat.resolveRelUrl(value,this.ownerDocument):value[0]==="/"&&(value.length<=1||value[1]!=="/")&&(rel=true,value=WB_wombat_location.origin+value));try{this._parser[prop]=value}catch(e){console.log("Error setting "+prop+" = "+value)}prop==="hash"?(value=this._parser[prop],origSetter.call(this,"hash",value)):(rel=rel||value===this._parser.pathname,value=wombat.rewriteUrl(this._parser.href,rel),origSetter.call(this,"href",value))}}},Wombat.prototype.styleReplacer=function(match,n1,n2,n3,offset,string){return n1+this.rewriteUrl(n2)+n3},Wombat.prototype.domConstructorErrorChecker=function(thisObj,what,args,numRequiredArgs){var errorMsg,needArgs=typeof numRequiredArgs==="number"?numRequiredArgs:1;if(thisObj instanceof Window?errorMsg="Failed to construct '"+what+"': Please use the 'new' operator, this DOM object constructor cannot be called as a function.":args&&args.length=0)return url;if(url.indexOf(this.wb_rel_prefix)===0&&url.indexOf("http")>1){var scheme_sep=url.indexOf(":/");return scheme_sep>0&&url[scheme_sep+2]!=="/"?url.substring(0,scheme_sep+2)+"/"+url.substring(scheme_sep+2):url}return this.getFinalUrl(true,mod,this.wb_orig_origin+url)}url.charAt(0)==="."&&(url=this.resolveRelUrl(url,doc));var prefix=this.startsWithOneOf(url.toLowerCase(),this.VALID_PREFIXES);if(prefix){var orig_host=this.$wbwindow.__WB_replay_top.location.host,orig_protocol=this.$wbwindow.__WB_replay_top.location.protocol,prefix_host=prefix+orig_host+"/";if(this.startsWith(url,prefix_host)){if(this.startsWith(url,this.wb_replay_prefix))return url;var curr_scheme=orig_protocol+"//",path=url.substring(prefix_host.length),rebuild=false;return path.indexOf(this.wb_rel_prefix)<0&&url.indexOf("/static/")<0&&(path=this.getFinalUrl(true,mod,WB_wombat_location.origin+"/"+path),rebuild=true),prefix!==curr_scheme&&prefix!==this.REL_PREFIX&&(rebuild=true),rebuild&&(url=useRel?"":curr_scheme+orig_host,path&&path[0]!=="/"&&(url+="/"),url+=path),url}return this.getFinalUrl(useRel,mod,url)}return prefix=this.startsWithOneOf(url,this.BAD_PREFIXES),prefix?this.getFinalUrl(useRel,mod,this.extractOriginalURL(url)):this.isHostUrl(url)&&!this.startsWith(url,originalLoc.host+"/")?this.getFinalUrl(useRel,mod,this.wb_orig_scheme+url):url},Wombat.prototype.rewriteUrl=function(url,useRel,mod,doc){var rewritten=this.rewriteUrl_(url,useRel,mod,doc);return this.debug_rw&&(url===rewritten?console.log("NOT REWRITTEN "+url):console.log("REWRITE: "+url+" -> "+rewritten)),rewritten},Wombat.prototype.performAttributeRewrite=function(elem,name,value,absUrlOnly){switch(name){case"innerHTML":case"outerHTML":return this.rewriteHtml(value);case"filter":return this.rewriteInlineStyle(value);case"style":return this.rewriteStyle(value);case"srcset":return this.rewriteSrcset(value,elem);}if(absUrlOnly&&!this.startsWithOneOf(value,this.VALID_PREFIXES))return value;var mod=this.rwModForElement(elem,name);return this.wbUseAFWorker&&this.WBAutoFetchWorker&&this.isSavedDataSrcSrcset(elem)&&this.WBAutoFetchWorker.preserveDataSrcset(elem),this.rewriteUrl(value,false,mod,elem.ownerDocument)},Wombat.prototype.rewriteAttr=function(elem,name,absUrlOnly){var changed=false;if(!elem||!elem.getAttribute||elem._no_rewrite||elem["_"+name])return changed;var value=this.wb_getAttribute.call(elem,name);if(!value||this.startsWith(value,"javascript:"))return changed;var new_value=this.performAttributeRewrite(elem,name,value,absUrlOnly);return new_value!==value&&(this.removeWBOSRC(elem),this.wb_setAttribute.call(elem,name,new_value),changed=true),changed},Wombat.prototype.noExceptRewriteStyle=function(style){try{return this.rewriteStyle(style)}catch(e){return style}},Wombat.prototype.rewriteStyle=function(style){if(!style)return style;var value=style;return typeof style==="object"&&(value=style.toString()),typeof value==="string"?value.replace(this.STYLE_REGEX,this.styleReplacer).replace(this.IMPORT_REGEX,this.styleReplacer).replace(this.no_wombatRe,""):value},Wombat.prototype.rewriteSrcset=function(value,elem){if(!value)return"";for(var split=value.split(this.srcsetRe),values=[],mod=this.rwModForElement(elem,"srcset"),i=0;i=0){var JS="javascript:";new_value="javascript:window.parent._wb_wombat.initNewWindowWombat(window);"+value.substr(11)}return new_value||(new_value=this.rewriteUrl(value,false,this.rwModForElement(elem,attrName))),new_value!==value&&(this.wb_setAttribute.call(elem,attrName,new_value),true)},Wombat.prototype.rewriteScript=function(elem){if(elem.hasAttribute("src")||!elem.textContent||!this.$wbwindow.Proxy)return this.rewriteAttr(elem,"src");if(this.skipWrapScriptBasedOnType(elem.type))return false;var text=elem.textContent.trim();return!this.skipWrapScriptTextBasedOnText(text)&&(elem.textContent=this.wrapScriptTextJsProxy(text),true)},Wombat.prototype.rewriteSVGElem=function(elem){var changed=this.rewriteAttr(elem,"filter");return changed=this.rewriteAttr(elem,"style")||changed,changed=this.rewriteAttr(elem,"xlink:href")||changed,changed=this.rewriteAttr(elem,"href")||changed,changed=this.rewriteAttr(elem,"src")||changed,changed},Wombat.prototype.rewriteElem=function(elem){var changed=false;if(!elem)return changed;if(elem instanceof SVGElement)changed=this.rewriteSVGElem(elem);else switch(elem.tagName){case"META":var maybeCSP=this.wb_getAttribute.call(elem,"http-equiv");maybeCSP&&maybeCSP.toLowerCase()==="content-security-policy"&&(this.wb_setAttribute.call(elem,"http-equiv","_"+maybeCSP),changed=true);break;case"STYLE":var new_content=this.rewriteStyle(elem.textContent);elem.textContent!==new_content&&(elem.textContent=new_content,changed=true,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.sheet!=null&&this.WBAutoFetchWorker.deferredSheetExtraction(elem.sheet));break;case"LINK":changed=this.rewriteAttr(elem,"href"),this.wbUseAFWorker&&elem.rel==="stylesheet"&&this._addEventListener(elem,"load",this.utilFns.wbSheetMediaQChecker);break;case"IMG":changed=this.rewriteAttr(elem,"src"),changed=this.rewriteAttr(elem,"srcset")||changed,changed=this.rewriteAttr(elem,"style")||changed,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.dataset.srcset&&this.WBAutoFetchWorker.preserveDataSrcset(elem);break;case"OBJECT":if(this.wb_info.isSW&&elem.parentElement&&elem.getAttribute("type")==="application/pdf"){for(var iframe=this.$wbwindow.document.createElement("IFRAME"),i=0;i0;)for(var child,children=rewriteQ.shift(),i=0;i"+rwString+"","text/html");if(!inner_doc||!this.nodeHasChildren(inner_doc.head)||!inner_doc.head.children[0].content)return rwString;var template=inner_doc.head.children[0];if(template._no_rewrite=true,this.recurseRewriteElem(template.content)){var new_html=template.innerHTML;if(checkEndTag){var first_elem=template.content.children&&template.content.children[0];if(first_elem){var end_tag="";this.endsWith(new_html,end_tag)&&!this.endsWith(rwString.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(rwString[0]!=="<"||rwString[rwString.length-1]!==">")return this.write_buff+=rwString,undefined}return new_html}return rwString},Wombat.prototype.rewriteHtmlFull=function(string,checkEndTag){var inner_doc=new DOMParser().parseFromString(string,"text/html");if(!inner_doc)return string;for(var changed=false,i=0;i=0)inner_doc.documentElement._no_rewrite=true,new_html=this.reconstructDocType(inner_doc.doctype)+inner_doc.documentElement.outerHTML;else{inner_doc.head._no_rewrite=true,inner_doc.body._no_rewrite=true;var headHasKids=this.nodeHasChildren(inner_doc.head),bodyHasKids=this.nodeHasChildren(inner_doc.body);if(new_html=(headHasKids?inner_doc.head.outerHTML:"")+(bodyHasKids?inner_doc.body.outerHTML:""),checkEndTag)if(inner_doc.all.length>3){var end_tag="";this.endsWith(new_html,end_tag)&&!this.endsWith(string.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(string[0]!=="<"||string[string.length-1]!==">")return void(this.write_buff+=string);new_html=this.reconstructDocType(inner_doc.doctype)+new_html}return new_html}return string},Wombat.prototype.rewriteInlineStyle=function(orig){var decoded;try{decoded=decodeURIComponent(orig)}catch(e){decoded=orig}if(decoded!==orig){var parts=this.rewriteStyle(decoded).split(",",2);return parts[0]+","+encodeURIComponent(parts[1])}return this.rewriteStyle(orig)},Wombat.prototype.rewriteCookie=function(cookie){var wombat=this,rwCookie=cookie.replace(this.wb_abs_prefix,"").replace(this.wb_rel_prefix,"");return rwCookie=rwCookie.replace(this.cookie_domain_regex,function(m,m1){var message={domain:m1,cookie:rwCookie,wb_type:"cookie"};return wombat.sendTopMessage(message,true),wombat.$wbwindow.location.hostname.indexOf(".")>=0&&!wombat.IP_RX.test(wombat.$wbwindow.location.hostname)?"Domain=."+wombat.$wbwindow.location.hostname:""}).replace(this.cookie_path_regex,function(m,m1){var rewritten=wombat.rewriteUrl(m1);return rewritten.indexOf(wombat.wb_curr_host)===0&&(rewritten=rewritten.substring(wombat.wb_curr_host.length)),"Path="+rewritten}),wombat.$wbwindow.location.protocol!=="https:"&&(rwCookie=rwCookie.replace("secure","")),rwCookie.replace(",|",",")},Wombat.prototype.rewriteWorker=function(workerUrl){if(!workerUrl)return workerUrl;var isBlob=workerUrl.indexOf("blob:")===0,isJS=workerUrl.indexOf("javascript:")===0;if(!isBlob&&!isJS){if(!this.startsWithOneOf(workerUrl,this.VALID_PREFIXES)&&!this.startsWith(workerUrl,"/")&&!this.startsWithOneOf(workerUrl,this.BAD_PREFIXES)){var rurl=this.resolveRelUrl(workerUrl,this.$wbwindow.document);return this.rewriteUrl(rurl,false,"wkr_",this.$wbwindow.document)}return this.rewriteUrl(workerUrl,false,"wkr_",this.$wbwindow.document)}var workerCode=isJS?workerUrl.replace("javascript:",""):null;if(isBlob){var x=new XMLHttpRequest;this.utilFns.XHRopen.call(x,"GET",workerUrl,false),this.utilFns.XHRsend.call(x),workerCode=x.responseText.replace(this.workerBlobRe,"").replace(this.rmCheckThisInjectRe,"this")}if(this.wb_info.static_prefix||this.wb_info.ww_rw_script){var originalURL=this.$wbwindow.document.baseURI,ww_rw=this.wb_info.ww_rw_script||this.wb_info.static_prefix+"wombatWorkers.js",rw="(function() { self.importScripts('"+ww_rw+"'); new WBWombat({'prefix': '"+this.wb_abs_prefix+"', 'prefixMod': '"+this.wb_abs_prefix+"wkrf_/', 'originalURL': '"+originalURL+"'}); })();";workerCode=rw+workerCode}var blob=new Blob([workerCode],{type:"application/javascript"});return URL.createObjectURL(blob)},Wombat.prototype.rewriteTextNodeFn=function(fnThis,originalFn,argsObj){var args,deproxiedThis=this.proxyToObj(fnThis);if(argsObj.length>0&&deproxiedThis.parentElement&&deproxiedThis.parentElement.tagName==="STYLE"){args=new Array(argsObj.length);var dataIndex=argsObj.length-1;dataIndex===2?(args[0]=argsObj[0],args[1]=argsObj[1]):dataIndex===1&&(args[0]=argsObj[0]),args[dataIndex]=this.rewriteStyle(argsObj[dataIndex])}else args=argsObj;return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(deproxiedThis,args):originalFn.apply(deproxiedThis,args)},Wombat.prototype.rewriteDocWriteWriteln=function(fnThis,originalFn,argsObj){var string,thisObj=this.proxyToObj(fnThis),argLen=argsObj.length;if(argLen===0)return originalFn.call(thisObj);string=argLen===1?argsObj[0]:Array.prototype.join.call(argsObj,"");var new_buff=this.rewriteHtml(string,true),res=originalFn.call(thisObj,new_buff);return this.initNewWindowWombat(thisObj.defaultView),res},Wombat.prototype.rewriteChildNodeFn=function(fnThis,originalFn,argsObj){var thisObj=this.proxyToObj(fnThis);if(argsObj.length===0)return originalFn.call(thisObj);var newArgs=this.rewriteElementsInArguments(argsObj);return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(thisObj,newArgs):originalFn.apply(thisObj,newArgs)},Wombat.prototype.rewriteInsertAdjHTMLOrElemArgs=function(fnThis,originalFn,position,textOrElem,rwHTML){var fnThisObj=this.proxyToObj(fnThis);return fnThisObj._no_rewrite?originalFn.call(fnThisObj,position,textOrElem):rwHTML?originalFn.call(fnThisObj,position,this.rewriteHtml(textOrElem)):(this.rewriteElemComplete(textOrElem),originalFn.call(fnThisObj,position,textOrElem))},Wombat.prototype.rewriteSetTimeoutInterval=function(fnThis,originalFn,argsObj){var rw=this.isString(argsObj[0]),args=rw?new Array(argsObj.length):argsObj;if(rw){args[0]=this.$wbwindow.Proxy?this.wrapScriptTextJsProxy(argsObj[0]):argsObj[0].replace(/\blocation\b/g,"WB_wombat_$&");for(var i=1;i0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parse"),this.$wbwindow.CSSStyleValue.parseAll&&this.$wbwindow.CSSStyleValue.parseAll.toString().indexOf("[native code]")>0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parseAll")}if(this.$wbwindow.CSSKeywordValue&&this.$wbwindow.CSSKeywordValue.prototype){var oCSSKV=this.$wbwindow.CSSKeywordValue;this.$wbwindow.CSSKeywordValue=function(CSSKeywordValue_){return function CSSKeywordValue(cssValue){return wombat.domConstructorErrorChecker(this,"CSSKeywordValue",arguments),new CSSKeywordValue_(wombat.rewriteStyle(cssValue))}}(this.$wbwindow.CSSKeywordValue),this.$wbwindow.CSSKeywordValue.prototype=oCSSKV.prototype,Object.defineProperty(this.$wbwindow.CSSKeywordValue.prototype,"constructor",{value:this.$wbwindow.CSSKeywordValue}),addToStringTagToClass(this.$wbwindow.CSSKeywordValue,"CSSKeywordValue")}if(this.$wbwindow.StylePropertyMap&&this.$wbwindow.StylePropertyMap.prototype){var originalSet=this.$wbwindow.StylePropertyMap.prototype.set;this.$wbwindow.StylePropertyMap.prototype.set=function set(){if(arguments.length<=1)return originalSet.__WB_orig_apply?originalSet.__WB_orig_apply(this,arguments):originalSet.apply(this,arguments);var newArgs=new Array(arguments.length);newArgs[0]=arguments[0];for(var i=1;i")&&(array[0]=wombat.rewriteHtml(array[0]),options.type="text/html"),new Blob_(array,options)}}(this.$wbwindow.Blob),this.$wbwindow.Blob.prototype=orig_blob.prototype}},Wombat.prototype.initDocTitleOverride=function(){var orig_get_title=this.getOrigGetter(this.$wbwindow.document,"title"),orig_set_title=this.getOrigSetter(this.$wbwindow.document,"title"),wombat=this,set_title=function title(value){var res=orig_set_title.call(this,value),message={wb_type:"title",title:value};return wombat.sendTopMessage(message),res};this.defProp(this.$wbwindow.document,"title",set_title,orig_get_title)},Wombat.prototype.initFontFaceOverride=function(){if(this.$wbwindow.FontFace){var wombat=this,origFontFace=this.$wbwindow.FontFace;this.$wbwindow.FontFace=function(FontFace_){return function FontFace(family,source,descriptors){wombat.domConstructorErrorChecker(this,"FontFace",arguments,2);var rwSource=source;return source!=null&&(typeof source==="string"?rwSource=wombat.rewriteInlineStyle(source):rwSource=wombat.rewriteInlineStyle(source.toString())),new FontFace_(family,rwSource,descriptors)}}(this.$wbwindow.FontFace),this.$wbwindow.FontFace.prototype=origFontFace.prototype,Object.defineProperty(this.$wbwindow.FontFace.prototype,"constructor",{value:this.$wbwindow.FontFace}),addToStringTagToClass(this.$wbwindow.FontFace,"FontFace")}},Wombat.prototype.initFixedRatio=function(){try{this.$wbwindow.devicePixelRatio=1}catch(e){}if(Object.defineProperty)try{Object.defineProperty(this.$wbwindow,"devicePixelRatio",{value:1,writable:false})}catch(e){}},Wombat.prototype.initPaths=function(wbinfo){wbinfo.wombat_opts=wbinfo.wombat_opts||{},Object.assign(this.wb_info,wbinfo),this.wb_opts=wbinfo.wombat_opts,this.wb_replay_prefix=wbinfo.prefix,this.wb_is_proxy=wbinfo.proxy_magic||!this.wb_replay_prefix,this.wb_info.top_host=this.wb_info.top_host||"*",this.wb_curr_host=this.$wbwindow.location.protocol+"//"+this.$wbwindow.location.host,this.wb_info.wombat_opts=this.wb_info.wombat_opts||{},this.wb_orig_scheme=wbinfo.wombat_scheme+"://",this.wb_orig_origin=this.wb_orig_scheme+wbinfo.wombat_host,this.wb_abs_prefix=this.wb_replay_prefix,this.wb_capture_date_part=!wbinfo.is_live&&wbinfo.wombat_ts?"/"+wbinfo.wombat_ts+"/":"",this.initBadPrefixes(this.wb_replay_prefix),this.initCookiePreset()},Wombat.prototype.initSeededRandom=function(seed){this.$wbwindow.Math.seed=parseInt(seed);var wombat=this;this.$wbwindow.Math.random=function random(){return wombat.$wbwindow.Math.seed=(wombat.$wbwindow.Math.seed*9301+49297)%233280,wombat.$wbwindow.Math.seed/233280}},Wombat.prototype.initHistoryOverrides=function(){this.overrideHistoryFunc("pushState"),this.overrideHistoryFunc("replaceState");var wombat=this;this.$wbwindow.addEventListener("popstate",function(event){wombat.sendHistoryUpdate(wombat.$wbwindow.WB_wombat_location.href,wombat.$wbwindow.document.title)})},Wombat.prototype.initCookiePreset=function(){if(this.wb_info.presetCookie)for(var splitCookies=this.wb_info.presetCookie.split(";"),i=0;i0?"&":"?")+value.toString(),value=null):contentType==="application/json"||contentType==="text/plain"?(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+jsonToQueryString(value),value=null):wombat.startsWith(contentType,"multipart/form-data")&&(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+mfdToQueryString(value,contentType))}if(this.__WB_xhr_open_arguments.length>2&&(this.__WB_xhr_open_arguments[2]=true),this._no_rewrite||(this.__WB_xhr_open_arguments[1]=wombat.rewriteUrl(this.__WB_xhr_open_arguments[1])),origOpen.apply(this,this.__WB_xhr_open_arguments),!wombat.startsWith(this.__WB_xhr_open_arguments[1],"data:")){for(const[name,value]of this.__WB_xhr_headers.entries())origSetRequestHeader.call(this,name,value);origSetRequestHeader.call(this,"X-Pywb-Requested-With","XMLHttpRequest")}origSend.call(this,value)}}if(this.$wbwindow.fetch){var orig_fetch=this.$wbwindow.fetch;this.$wbwindow.fetch=function fetch(input,init_opts){var rwInput=input,inputType=typeof input;if(inputType==="string")rwInput=wombat.rewriteUrl(input);else if(inputType==="object"&&input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(rwInput=new Request(new_url,init_opts))}else inputType==="object"&&input.href&&(rwInput=wombat.rewriteUrl(input.href));if(init_opts||(init_opts={}),init_opts.credentials===undefined)try{init_opts.credentials="include"}catch(e){}return orig_fetch.call(wombat.proxyToObj(this),rwInput,init_opts)}}if(this.$wbwindow.Request&&this.$wbwindow.Request.prototype){var orig_request=this.$wbwindow.Request;this.$wbwindow.Request=function(Request_){return function Request(input,init_opts){wombat.domConstructorErrorChecker(this,"Request",arguments);var newInitOpts=init_opts||{},newInput=input,inputType=typeof input;switch(inputType){case"string":newInput=wombat.rewriteUrl(input);break;case"object":if(newInput=input,input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(newInput=new Request_(new_url,input))}else input.href&&(newInput=wombat.rewriteUrl(input.toString(),true));}return newInitOpts.credentials="include",new Request_(newInput,newInitOpts)}}(this.$wbwindow.Request),this.$wbwindow.Request.prototype=orig_request.prototype,Object.defineProperty(this.$wbwindow.Request.prototype,"constructor",{value:this.$wbwindow.Request})}if(this.$wbwindow.Response&&this.$wbwindow.Response.prototype){var originalRedirect=this.$wbwindow.Response.prototype.redirect;this.$wbwindow.Response.prototype.redirect=function redirect(url,status){var rwURL=wombat.rewriteUrl(url,true,null,wombat.$wbwindow.document);return originalRedirect.call(this,rwURL,status)}}if(this.$wbwindow.EventSource&&this.$wbwindow.EventSource.prototype){var origEventSource=this.$wbwindow.EventSource;this.$wbwindow.EventSource=function(EventSource_){return function EventSource(url,configuration){wombat.domConstructorErrorChecker(this,"EventSource",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteUrl(url)),new EventSource_(rwURL,configuration)}}(this.$wbwindow.EventSource),this.$wbwindow.EventSource.prototype=origEventSource.prototype,Object.defineProperty(this.$wbwindow.EventSource.prototype,"constructor",{value:this.$wbwindow.EventSource}),addToStringTagToClass(this.$wbwindow.EventSource,"EventSource")}if(this.$wbwindow.WebSocket&&this.$wbwindow.WebSocket.prototype){var origWebSocket=this.$wbwindow.WebSocket;this.$wbwindow.WebSocket=function(WebSocket_){return function WebSocket(url,configuration){wombat.domConstructorErrorChecker(this,"WebSocket",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteWSURL(url)),new WebSocket_(rwURL,configuration)}}(this.$wbwindow.WebSocket),this.$wbwindow.WebSocket.prototype=origWebSocket.prototype,Object.defineProperty(this.$wbwindow.WebSocket.prototype,"constructor",{value:this.$wbwindow.WebSocket}),addToStringTagToClass(this.$wbwindow.WebSocket,"WebSocket")}},Wombat.prototype.initElementGetSetAttributeOverride=function(){if(!this.wb_opts.skip_setAttribute&&this.$wbwindow.Element&&this.$wbwindow.Element.prototype){var wombat=this,ElementProto=this.$wbwindow.Element.prototype;if(ElementProto.setAttribute){var orig_setAttribute=ElementProto.setAttribute;ElementProto._orig_setAttribute=orig_setAttribute,ElementProto.setAttribute=function setAttribute(name,value){var rwValue=value;if(name&&typeof rwValue==="string"){var lowername=name.toLowerCase();if(this.tagName==="LINK"&&lowername==="href"&&rwValue.indexOf("data:text/css")===0)rwValue=wombat.rewriteInlineStyle(value);else if(lowername==="style")rwValue=wombat.rewriteStyle(value);else if(lowername==="srcset")rwValue=wombat.rewriteSrcset(value,this);else{var shouldRW=wombat.shouldRewriteAttr(this.tagName,lowername);shouldRW&&(wombat.removeWBOSRC(this),!this._no_rewrite&&(rwValue=wombat.rewriteUrl(value,false,wombat.rwModForElement(this,lowername))))}}return orig_setAttribute.call(this,name,rwValue)}}if(ElementProto.getAttribute){var orig_getAttribute=ElementProto.getAttribute;this.wb_getAttribute=orig_getAttribute,ElementProto.getAttribute=function getAttribute(name){var result=orig_getAttribute.call(this,name);if(result===null)return result;var lowerName=name;if(name&&(lowerName=name.toLowerCase()),wombat.shouldRewriteAttr(this.tagName,lowerName)){var maybeWBOSRC=wombat.retrieveWBOSRC(this);return maybeWBOSRC?maybeWBOSRC:wombat.extractOriginalURL(result)}return wombat.startsWith(lowerName,"data-")&&wombat.startsWithOneOf(result,wombat.wb_prefixes)?wombat.extractOriginalURL(result):result}}}},Wombat.prototype.initSvgImageOverrides=function(){if(this.$wbwindow.SVGImageElement){var svgImgProto=this.$wbwindow.SVGImageElement.prototype,orig_getAttr=svgImgProto.getAttribute,orig_getAttrNS=svgImgProto.getAttributeNS,orig_setAttr=svgImgProto.setAttribute,orig_setAttrNS=svgImgProto.setAttributeNS,wombat=this;svgImgProto.getAttribute=function getAttribute(name){var value=orig_getAttr.call(this,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.getAttributeNS=function getAttributeNS(ns,name){var value=orig_getAttrNS.call(this,ns,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.setAttribute=function setAttribute(name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttr.call(this,name,rwValue)},svgImgProto.setAttributeNS=function setAttributeNS(ns,name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttrNS.call(this,ns,name,rwValue)}}},Wombat.prototype.initCreateElementNSFix=function(){if(this.$wbwindow.document.createElementNS&&this.$wbwindow.Document.prototype.createElementNS){var orig_createElementNS=this.$wbwindow.document.createElementNS,wombat=this,createElementNS=function createElementNS(namespaceURI,qualifiedName){return orig_createElementNS.call(wombat.proxyToObj(this),wombat.extractOriginalURL(namespaceURI),qualifiedName)};this.$wbwindow.Document.prototype.createElementNS=createElementNS,this.$wbwindow.document.createElementNS=createElementNS}},Wombat.prototype.initInsertAdjacentElementHTMLOverrides=function(){var Element=this.$wbwindow.Element;if(Element&&Element.prototype){var elementProto=Element.prototype,rewriteFn=this.rewriteInsertAdjHTMLOrElemArgs;if(elementProto.insertAdjacentHTML){var origInsertAdjacentHTML=elementProto.insertAdjacentHTML;elementProto.insertAdjacentHTML=function insertAdjacentHTML(position,text){return rewriteFn(this,origInsertAdjacentHTML,position,text,true)}}if(elementProto.insertAdjacentElement){var origIAdjElem=elementProto.insertAdjacentElement;elementProto.insertAdjacentElement=function insertAdjacentElement(position,element){return rewriteFn(this,origIAdjElem,position,element,false)}}}},Wombat.prototype.initDomOverride=function(){var Node=this.$wbwindow.Node;if(Node&&Node.prototype){var rewriteFn=this.rewriteNodeFuncArgs;if(Node.prototype.appendChild){var originalAppendChild=Node.prototype.appendChild;Node.prototype.appendChild=function appendChild(newNode,oldNode){return rewriteFn(this,originalAppendChild,newNode,oldNode)}}if(Node.prototype.insertBefore){var originalInsertBefore=Node.prototype.insertBefore;Node.prototype.insertBefore=function insertBefore(newNode,oldNode){return rewriteFn(this,originalInsertBefore,newNode,oldNode)}}if(Node.prototype.replaceChild){var originalReplaceChild=Node.prototype.replaceChild;Node.prototype.replaceChild=function replaceChild(newNode,oldNode){return rewriteFn(this,originalReplaceChild,newNode,oldNode)}}this.overridePropToProxy(Node.prototype,"ownerDocument"),this.overridePropToProxy(this.$wbwindow.HTMLHtmlElement.prototype,"parentNode"),this.overridePropToProxy(this.$wbwindow.Event.prototype,"target")}this.$wbwindow.Element&&this.$wbwindow.Element.prototype&&(this.overrideParentNodeAppendPrepend(this.$wbwindow.Element),this.overrideChildNodeInterface(this.$wbwindow.Element,false)),this.$wbwindow.DocumentFragment&&this.$wbwindow.DocumentFragment.prototype&&this.overrideParentNodeAppendPrepend(this.$wbwindow.DocumentFragment)},Wombat.prototype.initDocOverrides=function($document){if(Object.defineProperty){this.overrideReferrer($document),this.defGetterProp($document,"origin",function origin(){return this.WB_wombat_location.origin}),this.defGetterProp(this.$wbwindow,"origin",function origin(){return this.WB_wombat_location.origin});var wombat=this,domain_setter=function domain(val){var loc=this.WB_wombat_location;loc&&wombat.endsWith(loc.hostname,val)&&(this.__wb_domain=val)},domain_getter=function domain(){return this.__wb_domain||this.WB_wombat_location.hostname};this.defProp($document,"domain",domain_setter,domain_getter)}},Wombat.prototype.initDocWriteOpenCloseOverride=function(){if(this.$wbwindow.DOMParser){var DocumentProto=this.$wbwindow.Document.prototype,$wbDocument=this.$wbwindow.document,docWriteWritelnRWFn=this.rewriteDocWriteWriteln,orig_doc_write=$wbDocument.write,new_write=function write(){return docWriteWritelnRWFn(this,orig_doc_write,arguments)};$wbDocument.write=new_write,DocumentProto.write=new_write;var orig_doc_writeln=$wbDocument.writeln,new_writeln=function writeln(){return docWriteWritelnRWFn(this,orig_doc_writeln,arguments)};$wbDocument.writeln=new_writeln,DocumentProto.writeln=new_writeln;var wombat=this,orig_doc_open=$wbDocument.open,new_open=function open(){var res,thisObj=wombat.proxyToObj(this);if(arguments.length===3){var rwUrl=wombat.rewriteUrl(arguments[0],false,"mp_");res=orig_doc_open.call(thisObj,rwUrl,arguments[1],arguments[2]),wombat.initNewWindowWombat(res,arguments[0])}else res=orig_doc_open.call(thisObj),wombat.initNewWindowWombat(thisObj.defaultView);return res};$wbDocument.open=new_open,DocumentProto.open=new_open;var originalClose=$wbDocument.close,newClose=function close(){var thisObj=wombat.proxyToObj(this);return wombat.initNewWindowWombat(thisObj.defaultView),originalClose.__WB_orig_apply?originalClose.__WB_orig_apply(thisObj,arguments):originalClose.apply(thisObj,arguments)};$wbDocument.close=newClose,DocumentProto.close=newClose;var oBodyGetter=this.getOrigGetter(DocumentProto,"body"),oBodySetter=this.getOrigSetter(DocumentProto,"body");oBodyGetter&&oBodySetter&&this.defProp(DocumentProto,"body",function body(newBody){return newBody&&(newBody instanceof HTMLBodyElement||newBody instanceof HTMLFrameSetElement)&&wombat.rewriteElemComplete(newBody),oBodySetter.call(wombat.proxyToObj(this),newBody)},oBodyGetter)}},Wombat.prototype.initIframeWombat=function(iframe){var win;win=iframe._get_contentWindow?iframe._get_contentWindow.call(iframe):iframe.contentWindow;try{if(!win||win===this.$wbwindow||win._skip_wombat||win._wb_wombat)return}catch(e){return}var src=iframe.src;this.initNewWindowWombat(win,src)},Wombat.prototype.initNewWindowWombat=function(win,src){var fullWombat=false;if(win&&!win._wb_wombat){if((!src||src===""||this.startsWithOneOf(src,["about:blank","javascript:"]))&&(fullWombat=true),!fullWombat&&this.wb_info.isSW){var origURL=this.extractOriginalURL(src);(origURL==="about:blank"||origURL.startsWith("srcdoc:")||origURL.startsWith("blob:"))&&(fullWombat=true)}if(fullWombat){var newInfo={};Object.assign(newInfo,this.wb_info);var wombat=new Wombat(win,newInfo);win._wb_wombat=wombat.wombatInit()}else this.initProtoPmOrigin(win),this.initPostMessageOverride(win),this.initMessageEventOverride(win),this.initCheckThisFunc(win)}},Wombat.prototype.initTimeoutIntervalOverrides=function(){var rewriteFn=this.rewriteSetTimeoutInterval;if(this.$wbwindow.setTimeout&&!this.$wbwindow.setTimeout.__$wbpatched$__){var originalSetTimeout=this.$wbwindow.setTimeout;this.$wbwindow.setTimeout=function setTimeout(){return rewriteFn(this,originalSetTimeout,arguments)},this.$wbwindow.setTimeout.__$wbpatched$__=true}if(this.$wbwindow.setInterval&&!this.$wbwindow.setInterval.__$wbpatched$__){var originalSetInterval=this.$wbwindow.setInterval;this.$wbwindow.setInterval=function setInterval(){return rewriteFn(this,originalSetInterval,arguments)},this.$wbwindow.setInterval.__$wbpatched$__=true}},Wombat.prototype.initWorkerOverrides=function(){var wombat=this;if(this.$wbwindow.Worker&&!this.$wbwindow.Worker._wb_worker_overriden){var orig_worker=this.$wbwindow.Worker;this.$wbwindow.Worker=function(Worker_){return function Worker(url,options){return wombat.domConstructorErrorChecker(this,"Worker",arguments),new Worker_(wombat.rewriteWorker(url),options)}}(orig_worker),this.$wbwindow.Worker.prototype=orig_worker.prototype,Object.defineProperty(this.$wbwindow.Worker.prototype,"constructor",{value:this.$wbwindow.Worker}),this.$wbwindow.Worker._wb_worker_overriden=true}if(this.$wbwindow.SharedWorker&&!this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden){var oSharedWorker=this.$wbwindow.SharedWorker;this.$wbwindow.SharedWorker=function(SharedWorker_){return function SharedWorker(url,options){return wombat.domConstructorErrorChecker(this,"SharedWorker",arguments),new SharedWorker_(wombat.rewriteWorker(url),options)}}(oSharedWorker),this.$wbwindow.SharedWorker.prototype=oSharedWorker.prototype,Object.defineProperty(this.$wbwindow.SharedWorker.prototype,"constructor",{value:this.$wbwindow.SharedWorker}),this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden=true}if(this.$wbwindow.ServiceWorkerContainer&&this.$wbwindow.ServiceWorkerContainer.prototype&&this.$wbwindow.ServiceWorkerContainer.prototype.register){var orig_register=this.$wbwindow.ServiceWorkerContainer.prototype.register;this.$wbwindow.ServiceWorkerContainer.prototype.register=function register(scriptURL,options){var newScriptURL=new URL(scriptURL,wombat.$wbwindow.document.baseURI).href,mod=wombat.getPageUnderModifier();return options&&options.scope?options.scope=wombat.rewriteUrl(options.scope,false,mod):options={scope:wombat.rewriteUrl("/",false,mod)},orig_register.call(this,wombat.rewriteUrl(newScriptURL,false,"sw_"),options)}}if(this.$wbwindow.Worklet&&this.$wbwindow.Worklet.prototype&&this.$wbwindow.Worklet.prototype.addModule&&!this.$wbwindow.Worklet.__wb_workerlet_overriden){var oAddModule=this.$wbwindow.Worklet.prototype.addModule;this.$wbwindow.Worklet.prototype.addModule=function addModule(moduleURL,options){var rwModuleURL=wombat.rewriteUrl(moduleURL,false,"js_");return oAddModule.call(this,rwModuleURL,options)},this.$wbwindow.Worklet.__wb_workerlet_overriden=true}},Wombat.prototype.initLocOverride=function(loc,oSetter,oGetter){if(Object.defineProperty)for(var prop,i=0;i=0&&props.splice(foundInx,1);return props}})}catch(e){console.log(e)}},Wombat.prototype.initHashChange=function(){if(this.$wbwindow.__WB_top_frame){var wombat=this,receive_hash_change=function receive_hash_change(event){if(event.data&&event.data.from_top){var message=event.data.message;message.wb_type&&(message.wb_type!=="outer_hashchange"||wombat.$wbwindow.location.hash==message.hash||(wombat.$wbwindow.location.hash=message.hash))}},send_hash_change=function send_hash_change(){var message={wb_type:"hashchange",hash:wombat.$wbwindow.location.hash};wombat.sendTopMessage(message)};this.$wbwindow.addEventListener("message",receive_hash_change),this.$wbwindow.addEventListener("hashchange",send_hash_change)}},Wombat.prototype.initPostMessageOverride=function($wbwindow){if($wbwindow.postMessage&&!$wbwindow.__orig_postMessage){var orig=$wbwindow.postMessage,wombat=this;$wbwindow.__orig_postMessage=orig;var postmessage_rewritten=function postMessage(message,targetOrigin,transfer,from_top){var from,src_id,this_obj=wombat.proxyToObj(this);if(this_obj.__WB_source&&this_obj.__WB_source.WB_wombat_location){var source=this_obj.__WB_source;if(from=source.WB_wombat_location.origin,this_obj.__WB_win_id||(this_obj.__WB_win_id={},this_obj.__WB_counter=0),!source.__WB_id){var id=this_obj.__WB_counter;source.__WB_id=id+source.WB_wombat_location.href,this_obj.__WB_counter+=1}this_obj.__WB_win_id[source.__WB_id]=source,src_id=source.__WB_id,this_obj.__WB_source=undefined}else from=window.WB_wombat_location.origin;var to_origin=targetOrigin;to_origin===this_obj.location.origin&&(to_origin=from);var new_message={from:from,to_origin:to_origin,src_id:src_id,message:message,from_top:from_top};if(targetOrigin!=="*"){if(this_obj.location.origin==="null"||this_obj.location.origin==="")return;targetOrigin=this_obj.location.origin}return orig.call(this_obj,new_message,targetOrigin,transfer)};$wbwindow.postMessage=postmessage_rewritten,$wbwindow.Window.prototype.postMessage=postmessage_rewritten;var eventTarget=null;eventTarget=$wbwindow.EventTarget&&$wbwindow.EventTarget.prototype?$wbwindow.EventTarget.prototype:$wbwindow;var _oAddEventListener=eventTarget.addEventListener;eventTarget.addEventListener=function addEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.add_or_get(listener,function(){return wrapEventListener(listener,obj,wombat)}):type==="storage"?wombat.storage_listeners.add_or_get(listener,function(){return wrapSameOriginEventListener(listener,obj)}):rwListener=listener,rwListener)return _oAddEventListener.call(obj,type,rwListener,useCapture)};var _oRemoveEventListener=eventTarget.removeEventListener;eventTarget.removeEventListener=function removeEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.remove(listener):type==="storage"?wombat.storage_listeners.remove(listener):rwListener=listener,rwListener)return _oRemoveEventListener.call(obj,type,rwListener,useCapture)};var override_on_prop=function(onevent,wrapperFN){var orig_setter=wombat.getOrigSetter($wbwindow,onevent),setter=function(value){this["__orig_"+onevent]=value;var obj=wombat.proxyToObj(this),listener=value?wrapperFN(value,obj,wombat):value;return orig_setter.call(obj,listener)},getter=function(){return this["__orig_"+onevent]};wombat.defProp($wbwindow,onevent,setter,getter)};override_on_prop("onmessage",wrapEventListener),override_on_prop("onstorage",wrapSameOriginEventListener)}},Wombat.prototype.initMessageEventOverride=function($wbwindow){!$wbwindow.MessageEvent||$wbwindow.MessageEvent.prototype.__extended||(this.addEventOverride("target"),this.addEventOverride("srcElement"),this.addEventOverride("currentTarget"),this.addEventOverride("eventPhase"),this.addEventOverride("path"),this.overridePropToProxy($wbwindow.MessageEvent.prototype,"source"),$wbwindow.MessageEvent.prototype.__extended=true)},Wombat.prototype.initUIEventsOverrides=function(){this.overrideAnUIEvent("UIEvent"),this.overrideAnUIEvent("MouseEvent"),this.overrideAnUIEvent("TouchEvent"),this.overrideAnUIEvent("FocusEvent"),this.overrideAnUIEvent("KeyboardEvent"),this.overrideAnUIEvent("WheelEvent"),this.overrideAnUIEvent("InputEvent"),this.overrideAnUIEvent("CompositionEvent")},Wombat.prototype.initOpenOverride=function(){var orig=this.$wbwindow.open;this.$wbwindow.Window.prototype.open&&(orig=this.$wbwindow.Window.prototype.open);var wombat=this,open_rewritten=function open(strUrl,strWindowName,strWindowFeatures){var rwStrUrl=wombat.rewriteUrl(strUrl,false,""),res=orig.call(wombat.proxyToObj(this),rwStrUrl,strWindowName,strWindowFeatures);return wombat.initNewWindowWombat(res,strUrl),res};this.$wbwindow.open=open_rewritten,this.$wbwindow.Window.prototype.open&&(this.$wbwindow.Window.prototype.open=open_rewritten);for(var i=0;i + + +Chapter 1.3: Textures, TPages and CLUTs + + + + +
+

Chapter 1.3: Textures, TPages and CLUTs

+
+

+This tutorial will teach you how to draw graphics with textures, from +converting texture data to loading it onto VRAM, and finally drawing said +texture. This is yet another essential part in PS1 graphics programming, +as not having any textures would not always make for a pretty looking game. +

+

+This chapter will also cover some development tools to be used for +preparing texture data, such as timtool and img2tim. +

+

+Compatible with PSn00bSDK: Yes +

+ +

Tutorial Index

+ + +

Textures

+

+A texture in the context of graphics programming, is basically a bitmap +of pixels that can either be drawn to the screen as a 2D image or +mapped onto polygons to add textures on 3D models. +

+

+On the PS1 hardware, textures are naturally stored in VRAM alongside the +display and drawing buffers described in previous tutorials. Textures are +generally positioned outside of said visual areas so to not get overwritten +by graphics frames drawn by the GPU. Once texture images have been loaded, +it can then be drawn by textured sprite and/or polygon primitives. +

+

+The PS1 supports texture color depths of 4, 8 and 16 bits per pixel. 4-bit +and 8-bit textures are usually accompanied with CLUT or Color Lookup Table. +A CLUT is essentially the color palette of its associated texture image. +

+

+Because the VRAM addresses pixels in 16-bit words, 4-bit and 8-bit textures +are stored at quarter and half of the actual width of the texture respectively +as shown in the image below. +

+
+ +4-bit, 8-bit and 16-bit texture images as they appear logically in VRAM +
+

+This also means that 8-bit textures must have a width that is of a multiple +of 2 whilst 4-bit textures must be of a multiple of 4 to ensure that the pixel +data is word-aligned in VRAM. Failing to do so will often yield artifacts, or +corrupted texture images when trying to upload odd-width images into VRAM. +

+

+Texture images are usually handled on the PS1 in the TIM file format, which +is a very simple image format designed specifically for the PS1 as it can +hold X and Y coordinates for either image and CLUT data which are used as +target coordinates when uploading the texture to VRAM. TIM files are typically +created from regular image files (bmp, pcx, jpeg or bmp) with tools such as +timtool included in the PsyQ/Programmer Tool SDK, +img2tim which is a free +command-line driven TIM converter and finally, +timedit which is essentially +a free albeit slightly dodgy equivalent of timtool. +

+

+For beginners it may be best to use timedit as its the only free tool +(or at the very least one that I'm aware of) that features a graphical preview +of the TIM image layout in VRAM or stick to timtool if you're using the +PsyQ/Programmers Tool SDK. +

+ +

Color Look-up Tables (CLUT)

+

+A CLUT is simply a 16x1 or 256x1 16-bit pixel image that normally accompanies +4-bit and 8-bit texture images respectively. If you haven't guessed by now, a +CLUT is essentially the color palette of the image it accompanies, where each +pixel of the CLUT represents a 16-bit color entry for the texture image +starting from the left (ie. the first pixel from the left represents the color +for index 0 while the very last pixel of the CLUT represents the color for +index 15 or index 255). CLUTs are normally used with 4-bit and 8-bit texture +images, though it can also be used for procedurally generated textures such as +animated plasma effects. +

+

+The TIM file format could support multiple CLUTs on a single TIM file, +as the CLUT headers in the file format allow for more than 1 pixel high +CLUTs. This is beyond the scope of this part of the tutorial series however. +

+ +

Texture Addressing on the PS1

+

+You may have learned from the last tutorial that the PS1 accesses the VRAM +as a two-dimensional image rather than a more conventional linear framebuffer. +Well, this also applies on how textures in VRAM are 'selected' for drawing with +textured primitives. +

+

+The PS1 addresses the VRAM in pages, in which the VRAM is divided into a +grid of 16x2 cells of 64x256 16-bit pixels each. The actual texture page +coordinates are still X,Y but with a 64x256 granularity instead. The image +below visually illustrates the cells of each texture page. +

+
+ +Texture page grid of the VRAM +
+

+Texture pages only applies to the selection of the area at which textures would +be read from by textured graphics primitives, as VRAM coordinates of display +and drawing environments do not adhere to this texture page granularity at all. +Setting a texture page serves as an anchor point at which textured primitives +would source texture data from starting from the top-left corner of the texture +page, with the U,V coordinates of the primitive serving as an offset relative to +the current texture page allowing small portions of a texture page to be read +and drawn, such as animation frames of one particular character in a large +sprite sheet. +

+
+ +An example of using U,V coordinates to select a particular sprite (in this +case at 48,32) in a sprite sheet +
+

+Of course this would mean that the texture images would have to be arranged +such that it is placed on the top-right corner of a texture page boundary for +the U,V coordinates to make the most sense. However, some simple offset +translation based on the texture image's position relative to the page boundary +should get around that issue and is going to be covered in this chapter. +

+

+One thing to consider about texture coordinates that may initially confuse +beginners is that while the width of a 4-bit and 8-bit texture on VRAM varies +from the actual width of the image itself, the texture coordinates does not +need to be adjusted to take that into account as long as the correct color +depth is specified in the texture page value. In this case, if you want to +draw a texture at 96,24 from a 256x256 4-bit texture sheet, you just simply +specify a U,V coordinate of 96,24 in your primitive as if the texture were +still 16-bit. +

+
+ +Texture coordinates are always true to the actual size of the +texture image +
+

+And because the U,V coordinates only have a range of 0-255, it is not possible +to draw a texture image larger than 256x256 pixels regardless of color depth. +To draw larger textures one has to draw multiple primitives to span the entire +size of a larger image, trying to draw a SPRT primitive with a size greater +than 256x256 will only result in a wrap around. +

+ +

Defining and Selecting a Texture Page

+

+A texture page value is easily defined using the getTPage() macro. +A function version of this macro is present in the PsyQ/Programmers Tool SDK +as GetTPage(), but this is not available in PSn00bSDK as it would be +considered redundant. +

+
tpage = getTPage( tp, abr, x, y );
+
+

+tp specifies the color depth for the texture page in the range of +0 to 2 (0:4-bit, 1:8-bit, 2:16-bit). abr specifies the blend operator +for both non-textured and textured semi-transparent primitives which can be +ignored for now and lastly, x,y specifies the X,Y coordinates of the +VRAM in 16-bit pixel units. Keep in mind that the coordinates will be rounded +down to the next lowest texture page. +

+

+Now that a texture page value has been defined, there are a number of ways to +set it to the GPU. One is through the tpage field of the DRAWENV +struct which specifies the initial texture page value whenever the drawing +environment is applied. Another is to use a DR_TPAGE primitive which +can be defined with the setDrawTPage() macro which follows the same +syntax as getTPage(). DR_TPAGE is needed for SPRT +primitives which lack a tpage field, though it is also useful for non-textured +primitives to set the blend operator for semi-transparent primitives. +

+

+Because the PS1 usually processes primitives that have been sorted last first, +DR_TPAGE primitives must be sorted after SPRT or other similar +primitives have been sorted. +

+

+The following table describes the bit fields of a texture page value which may +come in handy for the more crafty programmers. +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Bits15-141312111098-76-543-0
DescriptionReservedY-flip*X-flip*Texture disable*Draw on displayed areaDither enableTexture color depthBlend operatorTexture page Y (n*256)Texutre page X (n*64)
*Does not work on really early units, not recommended +to use to maintain compatibility
+
+

+Whilst on the topic of texture page selection, when working with 4-bit and +8-bit texture images you will also need to use getClut() to define a +CLUT value and is required for 4-bit and 8-bit texture images to draw properly. +Obviously this is not required for 16-bit texture images. +

+
clut = getClut( x, y );
+
+

+x,y specifies the X,Y coordinate of the CLUT within the VRAM. +However, keep in mind that the X axis must be a multiple to 16 pixels for a +CLUT to be defined correctly as otherwise it will be rounded down to the +lowest CLUT value. +

+

+As for selecting the CLUT, all textured primitives usually have a clut +field in the struct in which the CLUT value can be set to. There is also a +setClut() macro for setting CLUT coordinates to a primitive directly. +

+

+Usually, the coordinates for texture page and CLUT values are typically derived +from the X,Y coordinates of the TIM image file to take into account TIM images +that are not placed on the top-left corner of the texture page boundary. +

+

+The following describes the bit fields of a CLUT value. +

+
+ + + + + + + + + + + + + +
Bits1514-65-0
DescriptionReservedY coordinateX coordinate (n*16)
+
+ +

Creating a TIM Texture Image

+

+There are numerous ways to convert image files into the TIM format, the most +common is by using timtool from the official PsyQ/Programmers Tool SDKs, +or at least those who chose to use that SDK. Another is to use my free but old +command-line converter called img2tim, which follows the same command +line syntax as bmp2tim but supports many more image formats supported +by the FreeImage library and has better conversion options related to +transparencies. +

+

+Perhaps the most preferable tool to use for those who've stuck to using +PSn00bSDK is timedit, as not only is it free but also features a +similar graphical interface to timtool, on top of extra features +useful for managing a large amount of TIM images, such as grouping. +

+

+Whichever tool you use, convert the image below into a 8-bit TIM file with +image coordinates of 640,0 and CLUT coordinates of 0,480. You will have to +convert it into a 4-bit or 8-bit TIM as this tutorial is also going to cover +how to work with CLUTs. +

+
+ +
+

+When converting the image, always remember that texture image and CLUT +coordinates in the tools are always absolute coordinates to the VRAM +regardless of color depth. And in case you're wondering, the PS1 cannot +use 24-bit TIM images, as the GPU can only draw graphics in 16-bit color +depth but can display 24-bit images by entering 24-bit color mode, but +this is beyond the scope of this chapter. +

+ +

Including TIM Image Data

+

+In the meantime, the easiest way to include a binary file into your program +is to include it as an object file by means of a very simple assembler file, +and access that file as an array by defining its symbol name with extern. +Its not ideal for larger projects, but this will do the job for testing purposes. +

+

PsyQ/Programmers Tool SDK

+

+Save the following as my_image.asm. +

+
    opt	m+,l.,c+
+
+    section data            ; Store the array in the data section
+
+    global tim_my_image     ; Define label as global
+tim_my_image:
+    incbin 'my_image.tim'   ; Include file data (your TIM)
+
+

+The name of the data is defined by the label, in this case tim_my_image. +Make sure the label is also defined by a global directive so the linker can find +it when linked against your C program. When writing the assembler file, make sure +line indentations are created with real TAB characters as ASMPSX will throw an +error otherwise. +

+

+Assemble the file using ASMPSX to turn it into an object file. +

+
asmpsx /l my_image.asm,my_image.obj
+
+

Then link it with your program by simply specifying the object file.

+
ccpsx -O2 -Xm -Xo$80010000 my_image.obj program.c -o program.cpe
+
+

PSn00bSDK

+

+In PSn00bSDK, it works in much the same principle as in the PsyQ SDK, +but this one will be written in GNU assembler syntax. Save the assembler +file with a .s file extension. +

+
.section .data
+
+.global tim_my_image
+.type tim_my_image, @object
+tim_my_image:
+    .incbin "my_image.tim"
+
+

+It is recommended to use the makefile from one of the example programs included +with PSn00bSDK, as it has parameters for building with assembler files already +defined and should automatically pick your assembler file as long as it has +a .s file extension. +

+

Accessing the Array

+

+On both SDKs, the binary file can be accessed as an array by simply defining +it with an extern. Make sure the name matches with the label name defined +in the assembler file. +

+
extern int tim_my_image[];
+
+

+If you get mismatching type errors when compiling, you can simply cast it with a +different pointer type with (u_long*) or (u_int*) depending on +the compiler warnings you're getting. u_int is typically used in PSn00bSDK +instead of u_long as modern GCC interprets u_long as a 64-bit integer +whereas it is a 32-bit integer in PsyQ. +

+ +

Parsing and Uploading the TIM to VRAM

+

+To parse a TIM image file, use OpenTIM() to set the TIM file data for +parsing and ReadTIM() to retrieve header information of the TIM file to +a TIM_IMAGE struct. The TIM_IMAGE would contain not only a pointer +to the X,Y and size coordinates of either texture image and CLUT but also +pointers to the image and CLUT data within the TIM file. +

+

+In PSn00bSDK, GetTimInfo() function instead. It still returns the same +TIM_IMAGE struct. +

+

+The TIM image coordinates are in the *prect field and CLUT in +the *crect field, both are of type RECT. The actual texture image data +is at *paddr and the CLUT data at *caddr. The color depth of +the TIM file are in bits 0-3 of the mode field and CLUT presence is +determined by testing bit 4. +

+

+Uploading either pixel or CLUT data is done using LoadImage(), followed +by a call to DrawSync() to wait for texture upload to complete. Whilst +you might be able to get away with not waiting for the texture upload to +finish processing, it is recommended to wait for upload completion as +unpredictable results may occur otherwise. +

+

+The TIM image upload function should go like this. +

+
void LoadTexture(u_long *tim, TIM_IMAGE *tparam) {
+
+    // Read TIM information (PsyQ)
+    OpenTIM(tim);
+    ReadTIM(tparam);
+
+    // Read TIM information (PSn00bSDK)
+    //GetTimInfo(tim, tparam);
+
+    // Upload pixel data to framebuffer
+    LoadImage(tparam->prect, (u_long*)tparam->paddr);
+    DrawSync(0);
+
+    // Upload CLUT to framebuffer if present
+    if( tparam->mode & 0x8 ) {
+
+        LoadImage(tparam->crect, (u_long*)tparam->caddr);
+        DrawSync(0);
+
+    }
+
+}
+
+

+In PSn00bSDK, you may additionally replace u_long to u_int. They are actually +the same in PsyQ/Programmers Tool whereas in modern GCC/PSn00bSDK u_long +usually defines a 64-bit unsigned integer. +

+

+Now that you have a TIM upload function, the TIM loading sequence should go like so. +

+
extern int tim_my_image[];
+
+// To keep a copy of the TIM coordinates for later
+int tim_mode;
+RECT tim_prect,tim_crect;
+
+..
+
+void LoadStuff(void) {
+
+    // This can be defined locally, if you don't need the TIM coordinates
+    TIM_IMAGE my_image;
+
+    // Load the TIM
+    LoadTexture((u_long*)tim_my_image, &my_image);
+    
+    // Copy the TIM coordinates
+    tim_prect   = *my_image.prect;
+    tim_crect   = *my_image.crect;
+    tim_mode    = my_image.mode;
+    
+}
+
+

+Reason you may want to keep a copy of the TIM coordinates separate from the +TIM_IMAGE variable is that once you've figured out how to load files +from CD and load the TIM file to a dynamically allocated buffer, relying on +the TIM_IMAGE struct may not be a good idea as the *prect and +*crect fields point to the TIM file data directly, and will most +likely become undefined data once you've finished uploading that TIM to +VRAM and deallocated its buffer in later parts of your program. +

+ +

Drawing the TIM

+

+Now that a texture image has been loaded. The next thing to do is to set +it's texture page to the drawing environment. Since there's only one texture +to deal with, the texture page can be set to the DRAWENV struct. +

+
draw[0].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+draw[1].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+
+

+To deal with TIMs that are not page aligned, the U,V offset relative to +the TIM's rounded down texture page can be determined with the following +and should work for texture images of any color depth. +

+
int tim_uoffs,tim_voffs;
+
+tim_uoffs = (tim_prect.x%64)<<(2-(tim_mode&0x3));
+tim_voffs = (tim_prect.y&0xff);
+
+

+Whilst this would allow texture images to not require being aligned to the +top-left corner of the texture page boundary, texture images cannot cross +between the vertical texture page bounds and for 4-bit texture images, +should not cross the horizontal texture page bounds as otherwise trying to +draw the texture image with a SPRT will result in wrapping in +relation to the texture page. +

+

+Now to actually draw the texture image. This can be done with a SPRT +primitive which simply draws a textured sprite of a specified size. It doesn't +do fancy things such as rotation and scaling but its a useful primitive for +drawing simple sprites. +

+
SPRT *sprt;
+
+...
+
+sprt = (SPRT*)nextpri;
+
+setSprt(sprt);              // Initialize the primitive (important)
+setXY0(sprt, 48, 48);       // Position the sprite at (48,48)
+setWH(sprt, 64, 64);        // Set sprite size to 64x64 pixels
+setUV0(sprt,                // Set UV coordinates from TIM offsets
+    tim_uoffs, 
+    tim_voffs);
+setClut(sprt,               // Set CLUT coordinates from TIM to sprite
+    tim_crect.x,
+    tim_crect.y);
+setRGB0(sprt,               // Set color of sprite, 128 is neutral
+    128, 128, 128);
+
+addPrim(ot[db], sprt);      // Sort primitive to OT
+nextpri += sizeof(SPRT);    // Advance next primitive
+
+

+When dealing with multiple texture images that tend to reside on different +texture pages, you will have to sort a DR_TPAGE primitive right after +the SPRT primitive that requires a particular texture page value set, +as the PS1 processes primitives that are sorted last first. +

+

+Whilst not required in this chapter, this is how you sort a DR_TPAGE +for when you start playing around with more texture images. +

+
DR_TPAGE *tpage;
+
+...
+
+tpage = (DR_TPAGE*)nextpri;
+
+setDrawTPage(tpage, 0, 1,       // Set TPage primitive
+    getTPage(my_image.mode&0x3, 0, 
+    my_image.prect->x, my_image.prect->y));
+
+addPrim(ot[db], tpage);         // Sort primitive to OT
+
+nextpri += sizeof(DR_TPAGE);    // Advance next primitive address
+
+

+A very simple optimization practice you may want to consider when you start +getting into larger projects is that you only need to sort one DR_TPAGE +primitive if all the sprites sorted prior share a common texture page. +

+ +

Sample Code

+

+Working from code in the last tutorial, here's what the code should +look like: +

+
#include <sys/types.h>	// This provides typedefs needed by libgte.h and libgpu.h
+#include <stdio.h>	// Not necessary but include it anyway
+#include <libetc.h>	// Includes some functions that controls the display
+#include <libgte.h>	// GTE header, not really used but libgpu.h depends on it
+#include <libgpu.h>	// GPU library header
+
+#define OTLEN 8         // Ordering table length (recommended to set as a define
+                        // so it can be changed easily)
+
+DISPENV disp[2];        // Display/drawing buffer parameters
+DRAWENV draw[2];
+int db = 0;
+
+// PSn00bSDK requires having all u_long types replaced with
+// u_int, as u_long in modern GCC that PSn00bSDK uses defines it as a 64-bit integer.
+
+u_long ot[2][OTLEN];    // Ordering table length
+char pribuff[2][32768]; // Primitive buffer
+char *nextpri;          // Next primitive pointer
+
+int tim_mode;           // TIM image parameters
+RECT tim_prect,tim_crect;
+int tim_uoffs,tim_voffs;
+
+void display() {
+    
+    DrawSync(0);                // Wait for any graphics processing to finish
+    
+    VSync(0);                   // Wait for vertical retrace
+
+    PutDispEnv(&disp[db]);      // Apply the DISPENV/DRAWENVs
+    PutDrawEnv(&draw[db]);
+
+    SetDispMask(1);             // Enable the display
+
+    DrawOTag(ot[db]+OTLEN-1);   // Draw the ordering table
+    
+    db = !db;                   // Swap buffers on every pass (alternates between 1 and 0)
+    nextpri = pribuff[db];      // Reset next primitive pointer
+    
+}
+
+// Texture upload function
+void LoadTexture(u_long *tim, TIM_IMAGE *tparam) {
+
+    // Read TIM parameters (PsyQ)
+    OpenTIM(tim);
+    ReadTIM(tparam);
+
+    // Read TIM parameters (PSn00bSDK)
+    //GetTimInfo(tim, tparam);
+
+    // Upload pixel data to framebuffer
+    LoadImage(tparam->prect, (u_long*)tparam->paddr);
+    DrawSync(0);
+
+    // Upload CLUT to framebuffer if present
+    if( tparam->mode & 0x8 ) {
+
+        LoadImage(tparam->crect, (u_long*)tparam->caddr);
+        DrawSync(0);
+
+    }
+
+}
+
+void loadstuff(void) {
+
+    TIM_IMAGE my_image;         // TIM image parameters
+
+    extern u_long tim_my_image[];
+
+    // Load the texture
+    LoadTexture(tim_my_image, &my_image);
+
+    // Copy the TIM coordinates
+    tim_prect   = *my_image.prect;
+    tim_crect   = *my_image.crect;
+    tim_mode    = my_image.mode;
+
+    // Calculate U,V offset for TIMs that are not page aligned
+    tim_uoffs = (tim_prect.x%64)<<(2-(tim_mode&0x3));
+    tim_voffs = (tim_prect.y&0xff);
+
+}
+
+// To make main look tidy, init stuff has to be moved here
+void init(void) {
+	
+    // Reset graphics
+    ResetGraph(0);
+
+    // First buffer
+    SetDefDispEnv(&disp[0], 0, 0, 320, 240);
+    SetDefDrawEnv(&draw[0], 0, 240, 320, 240);
+    // Second buffer
+    SetDefDispEnv(&disp[1], 0, 240, 320, 240);
+    SetDefDrawEnv(&draw[1], 0, 0, 320, 240);
+
+    draw[0].isbg = 1;               // Enable clear
+    setRGB0(&draw[0], 63, 0, 127);  // Set clear color (dark purple)
+    draw[1].isbg = 1;
+    setRGB0(&draw[1], 63, 0, 127);
+
+    nextpri = pribuff[0];           // Set initial primitive pointer address
+ 
+    // load textures and possibly other stuff
+    loadstuff();
+
+    // set tpage of lone texture as initial tpage
+    draw[0].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+    draw[1].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+
+    // apply initial drawing environment
+    PutDrawEnv(&draw[!db]);
+ 
+}
+
+int main() {
+    
+    TILE *tile;                         // Pointer for TILE
+    SPRT *sprt;                         // Pointer for SPRT
+
+    // Init stuff
+    init();
+	
+    while(1) {
+
+        ClearOTagR(ot[db], OTLEN);      // Clear ordering table
+    
+        // Sort textured sprite
+        
+        sprt = (SPRT*)nextpri;
+
+        setSprt(sprt);                  // Initialize the primitive (very important)
+        setXY0(sprt, 48, 48);           // Position the sprite at (48,48)
+        setWH(sprt, 64, 64);            // Set size to 64x64 pixels
+        setUV0(sprt,                    // Set UV coordinates
+            tim_uoffs, 
+            tim_voffs);
+        setClut(sprt,                   // Set CLUT coordinates to sprite
+            tim_crect.x,
+            tim_crect.y);
+        setRGB0(sprt,                   // Set primitive color
+            128, 128, 128);
+        addPrim(ot[db], sprt);          // Sort primitive to OT
+
+        nextpri += sizeof(SPRT);        // Advance next primitive address
+        
+        
+        // Sort untextured tile primitive from the last tutorial
+        
+        tile = (TILE*)nextpri;          // Cast next primitive
+
+        setTile(tile);                  // Initialize the primitive (very important)
+        setXY0(tile, 32, 32);           // Set primitive (x,y) position
+        setWH(tile, 64, 64);            // Set primitive size
+        setRGB0(tile, 255, 255, 0);     // Set color yellow
+        addPrim(ot[db], tile);          // Add primitive to the ordering table
+        
+        nextpri += sizeof(TILE);        // Advance the next primitive pointer
+        
+    
+        // Update the display
+        display();
+        
+    }
+    
+    return 0;
+}
+
+

+Compile the example, run it and you should get a yellow square cascaded by +a sprite with your texture image. +

+
+ +
+

+You may notice that the untextured rectangle is drawn before the textured +sprite even though the textured sprite was sorted first and the untextured +rectangle last. That's because of the way how primitives are appended to +the ordering table in addPrim(), where the primitive being sorted +is linked right after a primitive that has been sorted to it previously, +and the PS1 hardware parses the ordering table from the top of the array +to the bottom with a reverse ordering table (created with ClearOTagR()). +

+
+ +How primitives are appended to a preoccupied ordering table entry +
+

+Primitive 1 would be the SPRT primitive, Primitive 2 would be the +DR_TPAGE primitive (if there was) and lastly, Primitive 3 would be +the untextured TILE primitive. You may think that a reverse +order ordering table makes no sense at first, but this technique is +integral to the PS1's ability to do on-the-fly sorting of polygons when +rendering 3D graphics. +

+ +

Tips, Tricks and Improvements

+

Pre-calculate tpage, CLUT and UV offset in a struct

+

+Using getTPage(), getClut() and setClut() macros on +every sprite sorted isn't exactly the most efficient way of doing things +especially when drawing lots of sprites, not to mention makes your code +look cluttered when managing several TIM images at once. This can be +addressed by defining a struct that contains all the parameters you +need precomputed for drawing a sprite. +

+
typedef struct _SPRITE {
+    u_short tpage;  // Tpage value
+    u_short clut;   // CLUT value
+    u_char  u,v;    // UV offset (useful for non page aligned TIMs)
+    u_char  w,h;    // Size (primitives can only draw 256x256 anyway)
+    CVECTOR col;
+} SPRITE;
+
+...
+
+// Sets parameters to a MYSPRITE using coordinates from TIM_INFO
+
+void GetSprite(TIM_IMAGE *tim, SPRITE *sprite) {
+
+    // Get tpage value
+    sprite->tpage = getTPage(tim->mode&0x3, 0, 
+        tim->prect->x, tim->prect->y);
+        
+    // Get CLUT value
+    if( tim->mode & 0x8 ) {
+        sprite->clut = getClut(tim->crect->x, tim->crect->y);
+    }
+    
+    // Set sprite size
+    sprite->w = tim->prect->w<<(2-tim->mode&0x3);
+    sprite->h = tim->prect->h;
+    
+    // Set UV offset
+    sprite->u = (tim->prect->x&0x3f)<<(2-tim->mode&0x3);
+    sprite->v = tim->prect->y&0xff;
+    
+    // Set neutral color
+    sprite->col.r = 128;
+    sprite->col.g = 128;
+    sprite->col.b = 128;
+    
+}
+
+

And to draw the sprite, you can write a simple function for it:

+
char *SortSprite(int x, int y, u_long *ot, char *pri, SPRITE *sprite) {
+
+    SPRT *sprt;
+    DR_TPAGE *tpage;
+    
+    sprt = (SPRT*)pri;                  // initialize the sprite
+    setSprt(sprt);
+
+    setXY0(sprt, x, y);                 // Set position
+    setWH(sprt, sprite->w, sprite->h);  // Set size
+    setUV0(sprt, sprite->u, sprite->v); // Set UV coordinate of sprite
+    setRGB0(sprt,                       // Set the color
+        sprite->col.r, 
+        sprite->col.g, 
+        sprite->col.b);
+    sprt->clut = sprite->clut;          // Set the CLUT value
+    
+    addPrim(ot, sprt);                  // Sort the primitive and advance
+    pri += sizeof(SPRT);
+
+    tpage = (DR_TPAGE*)pri;             // Sort the texture page value
+    setDrawTPage(tpage, 0, 1, sprite->tpage);
+    addPrim(ot, tpage);
+
+    return pri+sizeof(DR_TPAGE);        // Return new primitive pointer
+                                        // (set to nextpri)
+
+}
+
+

+This method will also work for sprite sheets by simply modifying the +GetSprite() function mentioned earlier such that you can specify an U,V +offset relative to the TIM's U,V offset and the size of the sprite. +

+ +

Use Sprite Sheets

+

+Instead of creating a single TIM image for each and every animation frame +of your character sprite, it is best to compile such small images as one +large image. This is called a sprite sheet and allows all sprite frames +to share a common palette if you use 4-bit or 8-bit color depth. If all +sprite frames have the same size, arrange the sprites in a grid where +each cell is the same size as the sprites. This way you can compute the +U,V coordinate of the sprites in the grid easily. +

+ +

Lower color depth textures are faster

+

+A 4-bit texture is faster to draw than a 8-bit texture, and a 8-bit +texture is faster to draw than a 16-bit texture, as the GPU has to read +less data from VRAM when drawing textures of lower color depth. Find a +balance that achieves best possible performance without degrading the +quality of your sprites or texture images too much. +

+ +

SPRT_8 and SPRT_16 are faster than SPRT

+

+The fixed size sprite primitives are a bit faster than SPRT +primitives, making them best suited for particle sprites of a fixed size +or when drawing tiles of a 2D map. +

+ +

Minimize tpage primitive changes

+

+This technique is most applicable when drawing tile maps. If your tile +sheet fits in a single texture page (less than 256x256), you only have +to sort a single DR_TPAGE primitive for all the tiles. This may +also help the GPU maintain texture cache coherency, as well as minimizing +redundant primitive packets.

+

Textures don't need to be powers of two

+

+Texutures of any size can be used by the GPU, though it is recommended to +make the width of 8-bit TIMs a multiples of 2 and multiples of 4 for 4-bit +TIMs as mentioned earlier in this chapter. +

+ +

Conclusion

+

+This concludes Chapter 1.3. of Lameguy64's PSX Tutorial series. If you've +sifted though this chapter well enough, you should have figured out how to +handle TIM images, uploading them to VRAM and drawing them. +

+

A few things you may want to experiment with for further learning:

+
    +
  • Try loading more TIMs and drawing them as individual sprites. Make sure +the TIM image and CLUT do not overlap one another in the TIM editing tool +you're using.
  • +
+ +
+ + + + + + +
PreviousBack to IndexNext
+ + + + \ No newline at end of file diff --git a/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/ordertable-multiprims.svg b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/ordertable-multiprims.svg new file mode 100644 index 0000000..980bef1 --- /dev/null +++ b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/ordertable-multiprims.svg @@ -0,0 +1,705 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + Primitive 2 + + OT[0]=0xffffffff (end) + Start of table + + + OT[1] + + OT[2] + + OT[3] + + OT[4] + + OT[5] + + OT[6] + + OT[7] + + + + + + + + + + + + + + Primitive 1 + Primitive 3 + + diff --git a/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/page-texcoord-relation.svg b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/page-texcoord-relation.svg new file mode 100644 index 0000000..3e83f8a --- /dev/null +++ b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/page-texcoord-relation.svg @@ -0,0 +1,268 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + Texture PageBoundary + (48,32) + + + (0,0) + + diff --git a/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/style.css b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/style.css new file mode 100644 index 0000000..50d70a8 --- /dev/null +++ b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/style.css @@ -0,0 +1,49 @@ +body { + max-width: 900px; + margin: auto; + padding: 8px; + font-family: sans-serif; + font-size: 14px; + //color: white; + //background: black; +} + +h { + background-color: #e0e0e0; + padding: 2px; +} + +h2 { + border-bottom: 1px solid; + //padding-left: 8px; +} + +img { + display: block; +} + +.footer-table { + font-size: 14px; +} + +.bordered-table { + border-collapse: collapse; + font-size: 11px; +} + +.bordered-table th, .bordered-table td { + border: 1px solid; + padding: 4px; +} + +hr { + border: none; + border-bottom: 1px solid black; +} + +pre { + padding: 8px; + font-size: 12px; + color: black; + background-color: LightGray; +} \ No newline at end of file diff --git a/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/vram-texture-coordinates.svg b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/vram-texture-coordinates.svg new file mode 100644 index 0000000..b24e4f1 --- /dev/null +++ b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/vram-texture-coordinates.svg @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + (0,0) + (1023,511) + + + + + + + + + + + + + + + + + (0,0) + (255,255) + 16-bit + + (0,0) + (255,255) + 8-bit + + 4-bit + (0,0) + (255,255) + + + diff --git a/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/vram-texture-pages.svg b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/vram-texture-pages.svg new file mode 100644 index 0000000..ad4330d --- /dev/null +++ b/Docs/Chapter 1.3 Textures, TPages and CLUTs_files/vram-texture-pages.svg @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + (0,0) + (1023,511) + + + + + + + + + + + + + + + + + + diff --git a/Docs/Chapter 1.4 Controllers.htm b/Docs/Chapter 1.4 Controllers.htm new file mode 100644 index 0000000..ccd3d77 --- /dev/null +++ b/Docs/Chapter 1.4 Controllers.htm @@ -0,0 +1,672 @@ + + + + + + + + + + + +Chapter 1.4: Controllers + + + + + +
+
The Wayback Machine - +http://web.archive.org/web/20220827033533/http://lameguy64.net/tutorials/pstutorials/chapter1/4-controllers.html
+ + + +

Chapter 1.4: Controllers

+ +

One of the most important aspects when writing software for a game +console is taking input from a controller, as otherwise what's the point of +a game where the player can't take control of it? Unless its some demoscene +stuff you intend to write for the console.

+ +

This chapter goes over how to initialize and handle controller input in +your PS1 program. This is something that hasn't really been explained very +well in many tutorials that I've seen in my personal experience, so I'll try +my best to explain how controller input is handled on the PS1 in great +detail.

+ +

Compatible with PSn00bSDK: Yes

+ +

Tutorial Index

+ + +

Methods to Obtain Controller Input

+ +

There are a number of ways to obtain input from the controller on the +PS1; direct hardware access (complicated), through BIOS +InitPAD()/StartPAD() (simpler, but has some limitations), though +PadInit()/PadRead() (even simpler, but only works with digital +pads), and through various controller related libraries such as +libtap, libgun and libpad provided in the PsyQ/Programmers' Tool SDK.

+ +

The most common method people tend to use is PadInit() and +PadRead(), and whilst it is the most simplest to use it is not +exactly the most ideal method for a proper game project. For one it only +works with digital pads and Dual-shock pads with analog turned off. Secondly, +there's no way to determine if a pad is disconnected or not and rumble +features on Dual-shock pads cannot be accessed with it. It is, however, +ideal for prototype test programs.

+ +

Another method which is a little more ideal is to use InitPAD() +and StartPAD() which are provided by the BIOS ROM of the console. This +method is not only capable of determining if a controller is attached or not +but it also works with a wider variety of controllers including multi-taps. +The only controllers that it doesn't really support properly are both the +Namco and Konami lightguns as they need to be polled in a specific way for +it to work, which is not supported by these functions. Lightguns are beyond +the scope for this chapter anyway. It still cannot access rumble features +of Dual-analog and Dual-shock controllers however.

+ +

For compatibility with PSn00bSDK, only the BIOS InitPAD() and +StartPAD() functions will be covered in this chapter. In the future, +I will look into making a follow-up that uses libpad or a similar equivalent +library for PSn00bSDK that would also cover accessing rumble features of +Dual-analog and Dual-shock pads.

+ + +

Initializing the Pad Subsystem

+ +

The first step is to define an array of two 34 byte elements. This array +will be used as a buffer for storing incoming controller data which requires +at least about 34 bytes per controller port. It may sound a bit excessive for +storing input data, but this is necessary in case a multitap is connected to +one of the ports, as a multitap will return up to 34 bytes from all four +controllers connected to the tap.

+ +
u_char padbuff[2][34];
+
+ +

Next is to call InitPAD() to define the pad array to the BIOS +controller subsystem. libpad still uses the same buffer format that +InitPAD() would return data to, so upgrading from the BIOS pad +routines to libpad should be pretty trivial.

+ +
InitPAD( padbuff[0], 34, padbuff[1], 34 );
+
+ +

Before you start controller polling, it may be a good idea to set the +first two elements of both arrays with 0xff, so that your program won't +process faulty input when the array is parsed with zero filled data. You'll +figure out why later in this chapter.

+ +
padbuff[0][0] = padbuff[0][1] = 0xff;
+padbuff[1][0] = padbuff[1][1] = 0xff;
+
+ +

Then start controller polling with StartPAD().

+ +
StartPAD();
+
+ +

After calling StartPAD() and you have a tty console in your +development setup (or the message window in no$psx), you may see the +message "VSync: Timeout" pop-up when your program reaches a +VSync call. This happens because StartPAD() annoyingly +re-enables automatic VSync interrupt acknowledge in the PS1's kernel, +preventing the VSync() function from detecting a VSync interrupt +from occuring. Fortunately, it has a timeout, of which it will disable +the automatic VSync acknowledge so it can detect VSync interrupts again. +This can happen on both Programmer's Tool and PSn00bSDK.

+ +

In PSn00bSDK, you can avoid this message by calling +ChangeClearPAD(1) after StartPAD(), but this function is only +really exposed in PSn00bSDK.

+ +

It is generally unnecessary to stop controller polling using +StopPAD(), plus it may cause Dual-shock controllers to reset their +controller mode back to digital mode. If you're using BIOS memory card +functions later down the line, stopping pads will also stop memory cards +as well. Using StopPAD() is only really required when transferring +execution to a child PS-EXE or another PS-EXE altogether.

+ + +

Parsing Controller Data

+ +

After calling StartPAD(), the padbuff array will be filled +with controller data in every frame. For NTSC systems this is 60 times a +second and 50 for PAL systems. Therefore, the controller ports are polled +automatically, so the only thing to do next is to parse the input data +stored in the arrays.

+ +

Because the PS1's BIOS controller handler depends on VSync interrupts to +poll the controller ports, you will not be able to receive further inputs +when interrupts are disabled, but this isn't really something to worry about +yet at this point in this tutorial series.

+ +

Now, onto the pad buffer data. The first byte stored in the padbuff +array denotes the status of the port. A value of zero indicates that a +device (ie. controller) is connected to the port and the rest of the array +contains valid controller data.

+
if( padbuff[0][0] == 0 )
+{
+    // controller on port 1 connected
+}
+
+

The following byte is the controller ID, denoting both the controller +type and the number of bytes the controller has sent to the console. The +controller type is stored in the upper 4 bits of the ID byte while the data +byte is at the lower 4 bits.

+ +
padtype = padbuff[0][1]>>4;    // get the controller type
+padlen  = padbuff[0][1]&0xF;   // get the data length (normally not needed)
+
+ +

The following table lists controller type values that are relevant to this +chapter. Lightgun IDs are omitted as you won't be able to poll them properly +anyway.

+ +
+ + + + + + + + + + +
ControllerType Value
Mouse0x1
Namco NegCon0x2
Digital pad or Dual-shock in digital mode (no light)0x4
Analog Stick or Dual-analog in green mode0x5
Dual-shock in analog mode or Dual-analog in red mode0x7
Namco JogCon0xE
+
+ +

The two bytes following the ID byte are usually the controller's digital +button states as a 16-bit integer, with each bit representing the state +of one button. Oddly, the bit states are inverted, where a bit value of 1 +means released and a value of 0 means pressed.

+ +

The following table lists which bit of the 16-bit word corresponds to each +button of a standard digital pad or Dual-shock controller. Both controllers +share the same bit positions.

+ +
+ + + + + + + + + + + + + + + + + + +
BitButton
0Select
1L3 (Dual-shock only)
2R3 (Dual-shock only)
3Start
4Up
5Right
6Down
7Left
8L2
9R2
10L1
11R1
12Triangle
13Circle
14Cross
15Square
+
+ +

It may be a good idea to define a list of constants for each button so +you can specify which bit to test with more coherent names. In PSn00bSDK, +these are already defined in psxpad.h.

+ +
#define PAD_SELECT      1
+#define PAD_L3          2
+#define PAD_R3          4
+#define PAD_START       8
+#define PAD_UP          16
+#define PAD_RIGHT       32
+#define PAD_DOWN        64
+#define PAD_LEFT        128
+#define PAD_L2          256
+#define PAD_R2          512
+#define PAD_L1          1024
+#define PAD_R1          2048
+#define PAD_TRIANGLE    4096
+#define PAD_CIRCLE      8192
+#define PAD_CROSS       16384
+#define PAD_SQUARE      32768
+
+ +

To test if a button is pressed, simply mask the 16-bit integer against the +bit value of the button you want to test using an AND (&) operator. Because +the pressed state of a button is zero, you'll have to follow it up with a +NOT (!) operator as well.

+ +
u_short button;
+
+...
+
+button = *((u_short*)(padbuff[0]+2));
+
+// is cross pressed?
+if( !( button & PAD_CROSS ) )
+{
+    // do something when cross is pressed
+}
+
+ +

If you only need to support regular controllers (digital, analog stick, +Dual-analog, Dual-shock), a simplle struct like this should suffice for +all the four controller types and with analog input on controllers with +such features. In PSn00bSDK, the following struct is already defined in +psxpad.h.

+ +
typedef struct _PADTYPE
+{
+    unsigned char	stat;
+    unsigned char	len:4;
+    unsigned char	type:4;
+    unsigned short	btn;
+    unsigned char	rs_x,rs_y;
+    unsigned char	ls_x,ls_y;
+} PADTYPE;
+
+ +

If the connected controller doesn't feature any analog inputs, the +elements following btn relating to analog sticks can be ignored.

+ +

When parsing analog stick inputs, remember that the coordinates when the +stick is at its center position is not always 128, due to deadzones and +other factors that affect the potentiometers. Its recommended to implement +a simple threshold to make sure your code does not register false inputs +(at least to the player) when the stick is placed at its center position.

+ +

For more information about controller input data, you may want to check +the +Controllers Chapter in nocash's PSX specs document.

+ + +

Implementation

+ +

As an exercise for this chapter, we're going to make the textured sprite +from the last chapter move using the controller. Begin by adding the button +definitions, structs and padbuff array described in this chapter near +the beginning of the source file, but must be placed after the +#include directives. When using PSn00bSDK, you can simply include +psxpad.h instead, which already has those defined.

+ +

Next, place the InitPAD() and StartPAD() calls at the end +of your init() function, so pads get initialized alongside the +graphics. If you're using PSn00bSDK, you can add ChangeClearPAD(1) +after StartPAD() to avoid the VSync: Timeout message from +cropping up in your tty terminal.

+ +

Next, define two variables named pos_x and pos_y of type +int at the start of the main() function, preferably before +the line that defines the TILE variable. These will be for storing +the X,Y coordinates for the textured sprite. Following that, define a +variable pad of type PADTYPE, this will be used for reading +controller inputs more easily.

+ +

Before the while loop, set both pos_x and pos_y to 48 to +make sure they don't contain a random undefined value. Within the loop, add +the following code that parses the controller and performs actions according +to controller inputs.

+ +
pad = (PADTYPE*)padbuff[0];
+
+// Only parse inputs when a controller is connected
+if( pad->stat == 0 )
+{
+    // Only parse when a digital pad, 
+    // dual-analog and dual-shock is connected
+    if( ( pad->type == 0x4 ) || 
+        ( pad->type == 0x5 ) || 
+        ( pad->type == 0x7 ) )
+    {
+        if( !(pad->btn&PAD;_UP) )            // test UP
+        {
+            pos_y--;
+        }
+        else if( !(pad->btn&PAD;_UP) )       // test DOWN
+        {
+            pos_y++;
+        }
+        if( !(pad->btn&PAD;_LEFT) )          // test LEFT
+        {
+            pos_x--;
+        }
+        else if( !(pad->btn&PAD;_RIGHT) )    // test RIGHT
+        {
+            pos_x++;
+        }
+    }
+}
+
+ +

Now, go to the bit of code that sorts the textured sprite, and modify +the setXY0 macro call to use X,Y coordinates from pos_x and +pos_y.

+ +
setXY0(sprt, pos_x, pos_y);
+
+ +

If you followed the instructions properly, the source code should look +like this:

+ +
#include <sys/types.h>	// This provides typedefs needed by libgte.h and libgpu.h
+#include <stdio.h>	// Not necessary but include it anyway
+#include <libetc.h>	// Includes some functions that controls the display
+#include <libgte.h>	// GTE header, not really used but libgpu.h depends on it
+#include <libgpu.h>	// GPU library header
+#include <libapi.h>     // API header, has InitPAD() and StartPAD() defs
+
+#define OTLEN 8         // Ordering table length (recommended to set as a define
+                        // so it can be changed easily)
+
+DISPENV disp[2];        // Display/drawing buffer parameters
+DRAWENV draw[2];
+int db = 0;
+
+// PSn00bSDK requires having all u_long types replaced with
+// u_int, as u_long in modern GCC that PSn00bSDK uses defines it as a 64-bit integer.
+
+u_long ot[2][OTLEN];    // Ordering table length
+char pribuff[2][32768]; // Primitive buffer
+char *nextpri;          // Next primitive pointer
+
+int tim_mode;           // TIM image parameters
+RECT tim_prect,tim_crect;
+int tim_uoffs,tim_voffs;
+
+// Pad stuff (omit when using PSn00bSDK)
+#define PAD_SELECT      1
+#define PAD_L3          2
+#define PAD_R3          4
+#define PAD_START       8
+#define PAD_UP          16
+#define PAD_RIGHT       32
+#define PAD_DOWN        64
+#define PAD_LEFT        128
+#define PAD_L2          256
+#define PAD_R2          512
+#define PAD_L1          1024
+#define PAD_R1          2048
+#define PAD_TRIANGLE    4096
+#define PAD_CIRCLE      8192
+#define PAD_CROSS       16384
+#define PAD_SQUARE      32768
+
+typedef struct _PADTYPE
+{
+    unsigned char	stat;
+    unsigned char	len:4;
+    unsigned char	type:4;
+    unsigned short	btn;
+    unsigned char	rs_x,rs_y;
+    unsigned char	ls_x,ls_y;
+} PADTYPE;
+
+// pad buffer arrays
+u_char padbuff[2][34];
+
+void display() {
+    
+    DrawSync(0);                // Wait for any graphics processing to finish
+    
+    VSync(0);                   // Wait for vertical retrace
+
+    PutDispEnv(&disp;[db]);      // Apply the DISPENV/DRAWENVs
+    PutDrawEnv(&draw;[db]);
+
+    SetDispMask(1);             // Enable the display
+
+    DrawOTag(ot[db]+OTLEN-1);   // Draw the ordering table
+    
+    db = !db;                   // Swap buffers on every pass (alternates between 1 and 0)
+    nextpri = pribuff[db];      // Reset next primitive pointer
+    
+}
+
+// Texture upload function
+void LoadTexture(u_long *tim, TIM_IMAGE *tparam) {
+
+    // Read TIM parameters (PsyQ)
+    OpenTIM(tim);
+    ReadTIM(tparam);
+
+    // Read TIM parameters (PSn00bSDK)
+    //GetTimInfo(tim, tparam);
+
+    // Upload pixel data to framebuffer
+    LoadImage(tparam->prect, (u_long*)tparam->paddr);
+    DrawSync(0);
+
+    // Upload CLUT to framebuffer if present
+    if( tparam->mode & 0x8 ) {
+
+        LoadImage(tparam->crect, (u_long*)tparam->caddr);
+        DrawSync(0);
+
+    }
+
+}
+
+void loadstuff(void) {
+
+    TIM_IMAGE my_image;         // TIM image parameters
+
+    extern u_long tim_my_image[];
+
+    // Load the texture
+    LoadTexture(tim_my_image, &my;_image);
+
+    // Copy the TIM coordinates
+    tim_prect   = *my_image.prect;
+    tim_crect   = *my_image.crect;
+    tim_mode    = my_image.mode;
+
+    // Calculate U,V offset for TIMs that are not page aligned
+    tim_uoffs = (tim_prect.x%64)<<(2-(tim_mode&0x3));
+    tim_voffs = (tim_prect.y&0xff);
+
+}
+
+// To make main look tidy, init stuff has to be moved here
+void init(void) {
+	
+    // Reset graphics
+    ResetGraph(0);
+
+    // First buffer
+    SetDefDispEnv(&disp;[0], 0, 0, 320, 240);
+    SetDefDrawEnv(&draw;[0], 0, 240, 320, 240);
+    // Second buffer
+    SetDefDispEnv(&disp;[1], 0, 240, 320, 240);
+    SetDefDrawEnv(&draw;[1], 0, 0, 320, 240);
+
+    draw[0].isbg = 1;               // Enable clear
+    setRGB0(&draw;[0], 63, 0, 127);  // Set clear color (dark purple)
+    draw[1].isbg = 1;
+    setRGB0(&draw;[1], 63, 0, 127);
+
+    nextpri = pribuff[0];           // Set initial primitive pointer address
+ 
+    // load textures and possibly other stuff
+    loadstuff();
+
+    // set tpage of lone texture as initial tpage
+    draw[0].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+    draw[1].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+
+    // apply initial drawing environment
+    PutDrawEnv(&draw;[!db]);
+    
+    // Initialize the pads
+    InitPAD( padbuff[0], 34, padbuff[1], 34 );
+    
+    // Begin polling
+    StartPAD();
+    
+    // To avoid VSync Timeout error, may not be defined in PsyQ
+    ChangeClearPAD( 1 );
+ 
+}
+
+int main() {
+    
+    int pos_x,pos_y;
+    PADTYPE *pad;
+    
+    TILE *tile;                         // Pointer for TILE
+    SPRT *sprt;                         // Pointer for SPRT
+
+    // Init stuff
+    init();
+	
+    pos_x = pos_y = 48;
+    
+    while(1) {
+    
+        // Parse controller input
+        pad = (PADTYPE*)padbuff[0];
+
+        // Only parse inputs when a controller is connected
+        if( pad->stat == 0 )
+        {
+            // Only parse when a digital pad, 
+            // dual-analog and dual-shock is connected
+            if( ( pad->type == 0x4 ) || 
+                ( pad->type == 0x5 ) || 
+                ( pad->type == 0x7 ) )
+            {
+                if( !(pad->btn&PAD;_UP) )            // test UP
+                {
+                    pos_y--;
+                }
+                else if( !(pad->btn&PAD;_DOWN) )       // test DOWN
+                {
+                    pos_y++;
+                }
+                if( !(pad->btn&PAD;_LEFT) )          // test LEFT
+                {
+                    pos_x--;
+                }
+                else if( !(pad->btn&PAD;_RIGHT) )    // test RIGHT
+                {
+                    pos_x++;
+                }
+            }
+        }
+
+        ClearOTagR(ot[db], OTLEN);      // Clear ordering table
+    
+        // Sort textured sprite
+        
+        sprt = (SPRT*)nextpri;
+
+        setSprt(sprt);                  // Initialize the primitive (very important)
+        setXY0(sprt, pos_x, pos_y);           // Position the sprite at (48,48)
+        setWH(sprt, 64, 64);            // Set size to 64x64 pixels
+        setUV0(sprt,                    // Set UV coordinates
+            tim_uoffs, 
+            tim_voffs);
+        setClut(sprt,                   // Set CLUT coordinates to sprite
+            tim_crect.x,
+            tim_crect.y);
+        setRGB0(sprt,                   // Set primitive color
+            128, 128, 128);
+        addPrim(ot[db], sprt);          // Sort primitive to OT
+
+        nextpri += sizeof(SPRT);        // Advance next primitive address
+        
+        
+        // Sort untextured tile primitive from the last tutorial
+        
+        tile = (TILE*)nextpri;          // Cast next primitive
+
+        setTile(tile);                  // Initialize the primitive (very important)
+        setXY0(tile, 32, 32);           // Set primitive (x,y) position
+        setWH(tile, 64, 64);            // Set primitive size
+        setRGB0(tile, 255, 255, 0);     // Set color yellow
+        addPrim(ot[db], tile);          // Add primitive to the ordering table
+        
+        nextpri += sizeof(TILE);        // Advance the next primitive pointer
+        
+    
+        // Update the display
+        display();
+        
+    }
+    
+    return 0;
+}
+
+ +

Compile, execute and you should get a textured sprite that you can move +around with the directional pad of the controller.

+ +
+ +
+ + +

Conclusion

+ +

Hopefully, this chapter should teach you about getting controller input +more than using PadInit() and PadStart(). The only downside +with this method is that you can't control the vibration motors of either +Dual-analog or Dual-shock controllers, or enabling analog mode on Dual-shock +controllers in software. Hopefully, this will be covered in a future chapter +of this tutorial series.

+ +

In the next chapter, we'll be looking into handling analog inputs and a +glimpse of fixed-point integer math for performing fractional calculations +without using floats.

+ +
+ + + + + + +
PreviousBack to IndexNext
+ + + + + + \ No newline at end of file diff --git a/Docs/Chapter 1.4 Controllers_files/analytics.js b/Docs/Chapter 1.4 Controllers_files/analytics.js new file mode 100644 index 0000000..10400f0 --- /dev/null +++ b/Docs/Chapter 1.4 Controllers_files/analytics.js @@ -0,0 +1,474 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0 +/* eslint-disable no-var, semi, prefer-arrow-callback, prefer-template */ + +/** + * Collection of methods for sending analytics events to Archive.org's analytics server. + * + * These events are used for internal stats and sent (in anonymized form) to Google Analytics. + * + * @see analytics.md + * + * @type {Object} + */ +window.archive_analytics = (function defineArchiveAnalytics() { + // keep orignal Date object so as not to be affected by wayback's + // hijacking global Date object + var Date = window.Date; + var ARCHIVE_ANALYTICS_VERSION = 2; + var DEFAULT_SERVICE = 'ao_2'; + var NO_SAMPLING_SERVICE = 'ao_no_sampling'; // sends every event instead of a percentage + + var startTime = new Date(); + + /** + * @return {Boolean} + */ + function isPerformanceTimingApiSupported() { + return 'performance' in window && 'timing' in window.performance; + } + + /** + * Determines how many milliseconds elapsed between the browser starting to parse the DOM and + * the current time. + * + * Uses the Performance API or a fallback value if it's not available. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API + * + * @return {Number} + */ + function getLoadTime() { + var start; + + if (isPerformanceTimingApiSupported()) + start = window.performance.timing.domLoading; + else + start = startTime.getTime(); + + return new Date().getTime() - start; + } + + /** + * Determines how many milliseconds elapsed between the user navigating to the page and + * the current time. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API + * + * @return {Number|null} null if the browser doesn't support the Performance API + */ + function getNavToDoneTime() { + if (!isPerformanceTimingApiSupported()) + return null; + + return new Date().getTime() - window.performance.timing.navigationStart; + } + + /** + * Performs an arithmetic calculation on a string with a number and unit, while maintaining + * the unit. + * + * @param {String} original value to modify, with a unit + * @param {Function} doOperation accepts one Number parameter, returns a Number + * @returns {String} + */ + function computeWithUnit(original, doOperation) { + var number = parseFloat(original, 10); + var unit = original.replace(/(\d*\.\d+)|\d+/, ''); + + return doOperation(number) + unit; + } + + /** + * Computes the default font size of the browser. + * + * @returns {String|null} computed font-size with units (typically pixels), null if it cannot be computed + */ + function getDefaultFontSize() { + var fontSizeStr; + + if (!('getComputedStyle' in window)) + return null; + + var style = window.getComputedStyle(document.documentElement); + if (!style) + return null; + + fontSizeStr = style.fontSize; + + // Don't modify the value if tracking book reader. + if (document.querySelector('#BookReader')) + return fontSizeStr; + + return computeWithUnit(fontSizeStr, function reverseBootstrapFontSize(number) { + // Undo the 62.5% size applied in the Bootstrap CSS. + return number * 1.6; + }); + } + + /** + * Get the URL parameters for a given Location + * @param {Location} + * @return {Object} The URL parameters + */ + function getParams(location) { + if (!location) location = window.location; + var vars; + var i; + var pair; + var params = {}; + var query = location.search; + if (!query) return params; + vars = query.substring(1).split('&'); + for (i = 0; i < vars.length; i++) { + pair = vars[i].split('='); + params[pair[0]] = decodeURIComponent(pair[1]); + } + return params; + } + + function getMetaProp(name) { + var metaTag = document.querySelector('meta[property=' + name + ']'); + return metaTag ? metaTag.getAttribute('content') || null : null; + } + + var ArchiveAnalytics = { + /** + * @type {String|null} + */ + service: getMetaProp('service'), + mediaType: getMetaProp('mediatype'), + primaryCollection: getMetaProp('primary_collection'), + + /** + * Key-value pairs to send in pageviews (you can read this after a pageview to see what was + * sent). + * + * @type {Object} + */ + values: {}, + + /** + * Sends an analytics ping, preferably using navigator.sendBeacon() + * @param {Object} values + * @param {Function} [onload_callback] (deprecated) callback to invoke once ping to analytics server is done + * @param {Boolean} [augment_for_ao_site] (deprecated) if true, add some archive.org site-specific values + */ + send_ping: function send_ping(values, onload_callback, augment_for_ao_site) { + if (typeof window.navigator !== 'undefined' && typeof window.navigator.sendBeacon !== 'undefined') + this.send_ping_via_beacon(values); + else + this.send_ping_via_image(values); + }, + + /** + * Sends a ping via Beacon API + * NOTE: Assumes window.navigator.sendBeacon exists + * @param {Object} values Tracking parameters to pass + */ + send_ping_via_beacon: function send_ping_via_beacon(values) { + var url = this.generate_tracking_url(values || {}); + window.navigator.sendBeacon(url); + }, + + /** + * Sends a ping via Image object + * @param {Object} values Tracking parameters to pass + */ + send_ping_via_image: function send_ping_via_image(values) { + var url = this.generate_tracking_url(values || {}); + var loadtime_img = new Image(1, 1); + loadtime_img.src = url; + loadtime_img.alt = ''; + }, + + /** + * Construct complete tracking URL containing payload + * @param {Object} params Tracking parameters to pass + * @return {String} URL to use for tracking call + */ + generate_tracking_url: function generate_tracking_url(params) { + var baseUrl = '//analytics.archive.org/0.gif'; + var keys; + var outputParams = params; + var outputParamsArray = []; + + outputParams.service = outputParams.service || this.service || DEFAULT_SERVICE; + + // Build array of querystring parameters + keys = Object.keys(outputParams); + keys.forEach(function keyIteration(key) { + outputParamsArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(outputParams[key])); + }); + outputParamsArray.push('version=' + ARCHIVE_ANALYTICS_VERSION); + outputParamsArray.push('count=' + (keys.length + 2)); // Include `version` and `count` in count + + return baseUrl + '?' + outputParamsArray.join('&'); + }, + + /** + * @param {int} page Page number + */ + send_scroll_fetch_event: function send_scroll_fetch_event(page) { + var additionalValues = { ev: page }; + var loadTime = getLoadTime(); + var navToDoneTime = getNavToDoneTime(); + if (loadTime) additionalValues.loadtime = loadTime; + if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime; + this.send_event('page_action', 'scroll_fetch', location.pathname, additionalValues); + }, + + send_scroll_fetch_base_event: function send_scroll_fetch_base_event() { + var additionalValues = {}; + var loadTime = getLoadTime(); + var navToDoneTime = getNavToDoneTime(); + if (loadTime) additionalValues.loadtime = loadTime; + if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime; + this.send_event('page_action', 'scroll_fetch_base', location.pathname, additionalValues); + }, + + /** + * @param {Object} [options] + * @param {String} [options.mediaType] + * @param {String} [options.mediaLanguage] + * @param {String} [options.page] The path portion of the page URL + */ + send_pageview: function send_pageview(options) { + var settings = options || {}; + + var defaultFontSize; + var loadTime = getLoadTime(); + var mediaType = settings.mediaType; + var primaryCollection = settings.primaryCollection; + var page = settings.page; + var navToDoneTime = getNavToDoneTime(); + + /** + * @return {String} + */ + function get_locale() { + if (navigator) { + if (navigator.language) + return navigator.language; + + else if (navigator.browserLanguage) + return navigator.browserLanguage; + + else if (navigator.systemLanguage) + return navigator.systemLanguage; + + else if (navigator.userLanguage) + return navigator.userLanguage; + } + return ''; + } + + defaultFontSize = getDefaultFontSize(); + + // Set field values + this.values.kind = 'pageview'; + this.values.timediff = (new Date().getTimezoneOffset()/60)*(-1); // *timezone* diff from UTC + this.values.locale = get_locale(); + this.values.referrer = (document.referrer == '' ? '-' : document.referrer); + + if (loadTime) + this.values.loadtime = loadTime; + + if (navToDoneTime) + this.values.nav_to_done_ms = navToDoneTime; + + if (settings.trackingId) { + this.values.ga_tid = settings.trackingId; + } + + /* START CUSTOM DIMENSIONS */ + if (defaultFontSize) + this.values.ga_cd1 = defaultFontSize; + + if ('devicePixelRatio' in window) + this.values.ga_cd2 = window.devicePixelRatio; + + if (mediaType) + this.values.ga_cd3 = mediaType; + + if (settings.mediaLanguage) { + this.values.ga_cd4 = settings.mediaLanguage; + } + + if (primaryCollection) { + this.values.ga_cd5 = primaryCollection; + } + /* END CUSTOM DIMENSIONS */ + + if (page) + this.values.page = page; + + this.send_ping(this.values); + }, + + /** + * Sends a tracking "Event". + * @param {string} category + * @param {string} action + * @param {string} label + * @param {Object} additionalEventParams + */ + send_event: function send_event( + category, + action, + label, + additionalEventParams + ) { + if (!label) label = window.location.pathname; + if (!additionalEventParams) additionalEventParams = {}; + if (additionalEventParams.mediaLanguage) { + additionalEventParams.ga_cd4 = additionalEventParams.mediaLanguage; + delete additionalEventParams.mediaLanguage; + } + var eventParams = Object.assign( + { + kind: 'event', + ec: category, + ea: action, + el: label, + cache_bust: Math.random(), + }, + additionalEventParams + ); + this.send_ping(eventParams); + }, + + /** + * Sends every event instead of a small percentage. + * + * Use this sparingly as it can generate a lot of events. + * + * @param {string} category + * @param {string} action + * @param {string} label + * @param {Object} additionalEventParams + */ + send_event_no_sampling: function send_event_no_sampling( + category, + action, + label, + additionalEventParams + ) { + var extraParams = additionalEventParams || {}; + extraParams.service = NO_SAMPLING_SERVICE; + this.send_event(category, action, label, extraParams); + }, + + /** + * @param {Object} options see this.send_pageview options + */ + send_pageview_on_load: function send_pageview_on_load(options) { + var self = this; + window.addEventListener('load', function send_pageview_with_options() { + self.send_pageview(options); + }); + }, + + /** + * Handles tracking events passed in URL. + * Assumes category and action values are separated by a "|" character. + * NOTE: Uses the unsampled analytics property. Watch out for future high click links! + * @param {Location} + */ + process_url_events: function process_url_events(location) { + var eventValues; + var actionValue; + var eventValue = getParams(location).iax; + if (!eventValue) return; + eventValues = eventValue.split('|'); + actionValue = eventValues.length >= 1 ? eventValues[1] : ''; + this.send_event_no_sampling( + eventValues[0], + actionValue, + window.location.pathname + ); + }, + + /** + * Attaches handlers for event tracking. + * + * To enable click tracking for a link, add a `data-event-click-tracking` + * attribute containing the Google Analytics Event Category and Action, separated + * by a vertical pipe (|). + * e.g. `` + * + * To enable form submit tracking, add a `data-event-form-tracking` attribute + * to the `form` tag. + * e.g. `` + * + * Additional tracking options can be added via a `data-event-tracking-options` + * parameter. This parameter, if included, should be a JSON string of the parameters. + * Valid parameters are: + * - service {string}: Corresponds to the Google Analytics property data values flow into + */ + set_up_event_tracking: function set_up_event_tracking() { + var self = this; + var clickTrackingAttributeName = 'event-click-tracking'; + var formTrackingAttributeName = 'event-form-tracking'; + var trackingOptionsAttributeName = 'event-tracking-options'; + + function handleAction(event, attributeName) { + var selector = '[data-' + attributeName + ']'; + var eventTarget = event.target; + if (!eventTarget) return; + var target = eventTarget.closest(selector); + if (!target) return; + var categoryAction; + var categoryActionParts; + var options; + categoryAction = target.dataset[toCamelCase(attributeName)]; + if (!categoryAction) return; + categoryActionParts = categoryAction.split('|'); + options = target.dataset[toCamelCase(trackingOptionsAttributeName)]; + options = options ? JSON.parse(options) : {}; + self.send_event( + categoryActionParts[0], + categoryActionParts[1], + categoryActionParts[2] || window.location.pathname, + options.service ? { service: options.service } : {} + ); + } + + function toCamelCase(str) { + return str.replace(/\W+(.)/g, function (match, chr) { + return chr.toUpperCase(); + }); + }; + + document.addEventListener('click', function(e) { + handleAction(e, clickTrackingAttributeName); + }); + + document.addEventListener('submit', function(e) { + handleAction(e, formTrackingAttributeName); + }); + }, + + /** + * @returns {Object[]} + */ + get_data_packets: function get_data_packets() { + return [this.values]; + }, + + /** + * Creates a tracking image for tracking JS compatibility. + * + * @param {string} type The type value for track_js_case in query params for 0.gif + */ + create_tracking_image: function create_tracking_image(type) { + this.send_ping_via_image({ + cache_bust: Math.random(), + kind: 'track_js', + track_js_case: type, + }); + } + }; + + return ArchiveAnalytics; +}()); +// @license-end diff --git a/Docs/Chapter 1.4 Controllers_files/banner-styles.css b/Docs/Chapter 1.4 Controllers_files/banner-styles.css new file mode 100644 index 0000000..8d39621 --- /dev/null +++ b/Docs/Chapter 1.4 Controllers_files/banner-styles.css @@ -0,0 +1,500 @@ +@import 'record.css'; /* for SPN1 */ + +#wm-ipp-base { + height:65px;/* initial height just in case js code fails */ + padding:0; + margin:0; + border:none; + background:none transparent; +} +#wm-ipp { + z-index: 2147483647; +} +#wm-ipp, #wm-ipp * { + font-family:Lucida Grande, Helvetica, Arial, sans-serif; + font-size:12px; + line-height:1.2; + letter-spacing:0; + width:auto; + height:auto; + max-width:none; + max-height:none; + min-width:0 !important; + min-height:0; + outline:none; + float:none; + text-align:left; + border:none; + color: #000; + text-indent: 0; + position: initial; + background: none; +} +#wm-ipp div, #wm-ipp canvas { + display: block; +} +#wm-ipp div, #wm-ipp tr, #wm-ipp td, #wm-ipp a, #wm-ipp form { + padding:0; + margin:0; + border:none; + border-radius:0; + background-color:transparent; + background-image:none; + /*z-index:2147483640;*/ + height:auto; +} +#wm-ipp table { + border:none; + border-collapse:collapse; + margin:0; + padding:0; + width:auto; + font-size:inherit; +} +#wm-ipp form input { + padding:1px !important; + height:auto; + display:inline; + margin:0; + color: #000; + background: none #fff; + border: 1px solid #666; +} +#wm-ipp form input[type=submit] { + padding:0 8px !important; + margin:1px 0 1px 5px !important; + width:auto !important; + border: 1px solid #000 !important; + background: #fff !important; + color: #000 !important; +} +#wm-ipp a { + display: inline; +} +#wm-ipp a:hover{ + text-decoration:underline; +} +#wm-ipp a.wm-btn:hover { + text-decoration:none; + color:#ff0 !important; +} +#wm-ipp a.wm-btn:hover span { + color:#ff0 !important; +} +#wm-ipp #wm-ipp-inside { + margin: 0 6px; + border:5px solid #000; + border-top:none; + background-color:rgba(255,255,255,0.9); + -moz-box-shadow:1px 1px 4px #333; + -webkit-box-shadow:1px 1px 4px #333; + box-shadow:1px 1px 4px #333; + border-radius:0 0 8px 8px; +} +/* selectors are intentionally verbose to ensure priority */ +#wm-ipp #wm-logo { + padding:0 10px; + vertical-align:middle; + min-width:100px; + flex: 0 0 100px; +} +#wm-ipp .c { + padding-left: 4px; +} +#wm-ipp .c .u { + margin-top: 4px !important; +} +#wm-ipp .n { + padding:0 0 0 5px !important; + vertical-align: bottom; +} +#wm-ipp .n a { + text-decoration:none; + color:#33f; + font-weight:bold; +} +#wm-ipp .n .b { + padding:0 6px 0 0 !important; + text-align:right !important; + overflow:visible; + white-space:nowrap; + color:#99a; + vertical-align:middle; +} +#wm-ipp .n .y .b { + padding:0 6px 2px 0 !important; +} +#wm-ipp .n .c { + background:#000; + color:#ff0; + font-weight:bold; + padding:0 !important; + text-align:center; +} +#wm-ipp.hi .n td.c { + color:#ec008c; +} +#wm-ipp .n td.f { + padding:0 0 0 6px !important; + text-align:left !important; + overflow:visible; + white-space:nowrap; + color:#99a; + vertical-align:middle; +} +#wm-ipp .n tr.m td { + text-transform:uppercase; + white-space:nowrap; + padding:2px 0; +} +#wm-ipp .c .s { + padding:0 5px 0 0 !important; + vertical-align:bottom; +} +#wm-ipp #wm-nav-captures { + white-space: nowrap; +} +#wm-ipp .c .s a.t { + color:#33f; + font-weight:bold; + line-height: 1.8; +} +#wm-ipp .c .s div.r { + color: #666; + font-size:9px; + white-space:nowrap; +} +#wm-ipp .c .k { + padding-bottom:1px; +} +#wm-ipp .c .s { + padding:0 5px 2px 0 !important; +} +#wm-ipp td#displayMonthEl { + padding: 2px 0 !important; +} +#wm-ipp td#displayYearEl { + padding: 0 0 2px 0 !important; +} + +div#wm-ipp-sparkline { + position:relative;/* for positioning markers */ + white-space:nowrap; + background-color:#fff; + cursor:pointer; + line-height:0.9; +} +#sparklineImgId, #wm-sparkline-canvas { + position:relative; + z-index:9012; + max-width:none; +} +#wm-ipp-sparkline div.yt { + position:absolute; + z-index:9010 !important; + background-color:#ff0 !important; + top: 0; +} +#wm-ipp-sparkline div.mt { + position:absolute; + z-index:9013 !important; + background-color:#ec008c !important; + top: 0; +} +#wm-ipp .r { + margin-left: 4px; +} +#wm-ipp .r a { + color:#33f; + border:none; + position:relative; + background-color:transparent; + background-repeat:no-repeat !important; + background-position:100% 100% !important; + text-decoration: none; +} +#wm-ipp #wm-capinfo { + /* prevents notice div background from sticking into round corners of + #wm-ipp-inside */ + border-radius: 0 0 4px 4px; +} +#wm-ipp #wm-capinfo .c-logo { + display:block; + float:left; + margin-right:3px; + width:90px; + min-height:90px; + max-height: 290px; + border-radius:45px; + overflow:hidden; + background-position:50%; + background-size:auto 90px; + box-shadow: 0 0 2px 2px rgba(208,208,208,128) inset; +} +#wm-ipp #wm-capinfo .c-logo span { + display:inline-block; +} +#wm-ipp #wm-capinfo .c-logo img { + height:90px; + position:relative; + left:-50%; +} +#wm-ipp #wm-capinfo .wm-title { + font-size:130%; +} +#wm-ipp #wm-capinfo a.wm-selector { + display:inline-block; + color: #aaa; + text-decoration:none !important; + padding: 2px 8px; +} +#wm-ipp #wm-capinfo a.wm-selector.selected { + background-color:#666; +} +#wm-ipp #wm-capinfo a.wm-selector:hover { + color: #fff; +} +#wm-ipp #wm-capinfo.notice-only #wm-capinfo-collected-by, +#wm-ipp #wm-capinfo.notice-only #wm-capinfo-timestamps { + display: none; +} +#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content { + background-color:#ff0; + padding:5px; + font-size:14px; + text-align:center; +} +#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content * { + font-size:14px; + text-align:center; +} +#wm-ipp #wm-expand { + right: 1px; + bottom: -1px; + color: #ffffff; + background-color: #666 !important; + padding:0 5px 0 3px !important; + border-radius: 3px 3px 0 0 !important; +} +#wm-ipp #wm-expand span { + color: #ffffff; +} +#wm-ipp #wm-expand #wm-expand-icon { + display: inline-block; + transition: transform 0.5s; + transform-origin: 50% 45%; +} +#wm-ipp #wm-expand.wm-open #wm-expand-icon { + transform: rotate(180deg); +} +#wm-ipp #wmtb { + text-align:right; +} +#wm-ipp #wmtb #wmtbURL { + width: calc(100% - 45px); +} +#wm-ipp #wm-graph-anchor { + border-right:1px solid #ccc; +} +/* time coherence */ +html.wb-highlight { + box-shadow: inset 0 0 0 3px #a50e3a !important; +} +.wb-highlight { + outline: 3px solid #a50e3a !important; +} +#wm-ipp-print { + display:none !important; +} +@media print { +#wm-ipp-base { + display:none !important; +} +#wm-ipp-print { + display:block !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +} +@media (max-width:414px) { + #wm-ipp .xxs { + display:none !important; + } +} +@media (min-width:1055px) { +#wm-ipp #wm-graph-anchor { + display:block !important; +} +} +@media (max-width:1054px) { +#wm-ipp #wm-graph-anchor { + display:none !important; +} +} +@media (max-width:1163px) { +#wm-logo { + display:none !important; +} +} + +#wm-btns { + white-space: nowrap; + margin-top: -2px; +} + +#wm-btns #wm-save-snapshot-open { + margin-right: 7px; + top: -6px; +} + +#wm-btns #wm-sign-in { + box-sizing: content-box; + display: none; + margin-right: 7px; + top: -8px; + + /* + round border around sign in button + */ + border: 2px #000 solid; + border-radius: 14px; + padding-right: 2px; + padding-bottom: 2px; + width: 11px; + height: 11px; +} + +#wm-btns #wm-sign-in>.iconochive-person { + font-size: 12.5px; +} + +#wm-save-snapshot-open > .iconochive-web { + color:#000; + font-size:160%; +} + +#wm-ipp #wm-share { + display: flex; + align-items: flex-end; + justify-content: space-between; +} + +#wm-share > #wm-screenshot { + display: inline-block; + margin-right: 3px; + visibility: hidden; +} + +#wm-screenshot > .iconochive-image { + color:#000; + font-size:160%; +} + +#wm-share > #wm-video { + display: inline-block; + margin-right: 3px; + visibility: hidden; +} + +#wm-video > .iconochive-movies { + color: #000; + display: inline-block; + font-size: 150%; + margin-bottom: 2px; +} + +#wm-btns #wm-save-snapshot-in-progress { + display: none; + font-size:160%; + opacity: 0.5; + position: relative; + margin-right: 7px; + top: -5px; +} + +#wm-btns #wm-save-snapshot-success { + display: none; + color: green; + position: relative; + top: -7px; +} + +#wm-btns #wm-save-snapshot-fail { + display: none; + color: red; + position: relative; + top: -7px; +} + +.wm-icon-screen-shot { + background: url("../images/web-screenshot.svg") no-repeat !important; + background-size: contain !important; + width: 22px !important; + height: 19px !important; + + display: inline-block; +} +#donato { + /* transition effect is disable so as to simplify height adjustment */ + /*transition: height 0.5s;*/ + height: 0; + margin: 0; + padding: 0; + border-bottom: 1px solid #999 !important; +} +body.wm-modal { + height: auto !important; + overflow: hidden !important; +} +#donato #donato-base { + width: 100%; + height: 100%; + /*bottom: 0;*/ + margin: 0; + padding: 0; + position: absolute; + z-index: 2147483639; +} +body.wm-modal #donato #donato-base { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 2147483640; +} + +.wb-autocomplete-suggestions { + font-family: Lucida Grande, Helvetica, Arial, sans-serif; + font-size: 12px; + text-align: left; + cursor: default; + border: 1px solid #ccc; + border-top: 0; + background: #fff; + box-shadow: -1px 1px 3px rgba(0,0,0,.1); + position: absolute; + display: none; + z-index: 2147483647; + max-height: 254px; + overflow: hidden; + overflow-y: auto; + box-sizing: border-box; +} +.wb-autocomplete-suggestion { + position: relative; + padding: 0 .6em; + line-height: 23px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 1.02em; + color: #333; +} +.wb-autocomplete-suggestion b { + font-weight: bold; +} +.wb-autocomplete-suggestion.selected { + background: #f0f0f0; +} diff --git a/Docs/Chapter 1.4 Controllers_files/bundle-playback.js b/Docs/Chapter 1.4 Controllers_files/bundle-playback.js new file mode 100644 index 0000000..390894d --- /dev/null +++ b/Docs/Chapter 1.4 Controllers_files/bundle-playback.js @@ -0,0 +1,3 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=9)}([function(e,t,n){"use strict";function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){return!t||"object"!==o(t)&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}function s(e){var t="function"==typeof Map?new Map:void 0;return(s=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,o)}function o(){return a(e,arguments,u(this).constructor)}return o.prototype=Object.create(e.prototype,{constructor:{value:o,enumerable:!1,writable:!0,configurable:!0}}),l(o,e)})(e)}function a(e,t,n){return(a=c()?Reflect.construct:function(e,t,n){var o=[null];o.push.apply(o,t);var i=new(Function.bind.apply(e,o));return n&&l(i,n.prototype),i}).apply(null,arguments)}function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function l(e,t){return(l=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}if(n.d(t,"a",(function(){return f})),n.d(t,"d",(function(){return p})),n.d(t,"c",(function(){return d})),n.d(t,"b",(function(){return v})),"undefiend"!=typeof window.XMLHttpRequest){var h=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&l(e,t)}(o,e);var t,n=(t=o,function(){var e,n=u(t);if(c()){var o=u(this).constructor;e=Reflect.construct(n,arguments,o)}else e=n.apply(this,arguments);return r(this,e)});function o(){return i(this,o),n.apply(this,arguments)}return o}(s(XMLHttpRequest));Object.defineProperty(h.prototype,"responseURL",Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype,"responseURL"))}function f(e,t,n,o,i){var r;if((r=window.XMLHttpRequest?new h:new ActiveXObject("Microsoft.XMLHTTP")).onreadystatechange=function(){4==this.readyState&&n(r)},r.open(e,t,!0),o)for(var s in o)o.hasOwnProperty(s)&&r.setRequestHeader(s,o[s]);r.withCredentials=!0,r.send(i)}function p(e){return void 0!==e&&e&&e.constructor===Array}function d(e){return document.cookie.search(e)>=0}function m(e,t){var n=window["HTML".concat(e,"Element")];if(void 0!==n){var o=Object.getOwnPropertyDescriptor(n.prototype,t);void 0!==o&&Object.defineProperty(n.prototype,"_wm_".concat(t),o)}}function v(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"src",n="_wm_".concat(t);return n in e.__proto__?e[n]:e[t]}m("Image","src"),m("Media","src"),m("Embed","src"),m("IFrame","src"),m("Script","src"),m("Link","href"),m("Anchor","href")},function(e,t,n){"use strict";n.d(t,"c",(function(){return s})),n.d(t,"b",(function(){return a})),n.d(t,"a",(function(){return c}));var o=["January","February","March","April","May","June","July","August","September","October","November","December"],i=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],r={Y:function(e){return e.getUTCFullYear()},m:function(e){return e.getUTCMonth()+1},b:function(e){return i[e.getUTCMonth()]},B:function(e){return o[e.getUTCMonth()]},d:function(e){return e.getUTCDate()},H:function(e){return("0"+e.getUTCHours()).slice(-2)},M:function(e){return("0"+e.getUTCMinutes()).slice(-2)},S:function(e){return("0"+e.getUTCSeconds()).slice(-2)},"%":function(){return"%"}};function s(e){var t=function(e){return"number"==typeof e&&(e=e.toString()),[e.slice(-14,-10),e.slice(-10,-8),e.slice(-8,-6),e.slice(-6,-4),e.slice(-4,-2),e.slice(-2)]}(e);return new Date(Date.UTC(t[0],t[1]-1,t[2],t[3],t[4],t[5]))}function a(e){return i[e]}function c(e,t){return t.replace(/%./g,(function(t){var n=r[t[1]];return n?n(s(e)):t}))}},function(e,t,n){"use strict";n.d(t,"b",(function(){return s})),n.d(t,"a",(function(){return a}));var o=n(0);function i(e,t){for(var n=0;n=400?i.failure&&i.failure(e):i.success&&i.success(e)}),{"Content-Type":"application/json"},r.stringify({url:e,snapshot:t,tags:n||[]})),!1}var a=function(){function e(t,n,i){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.el=t,this.url=n,this.timestamp=i,t.onclick=this.save.bind(this),document.addEventListener("DOMContentLoaded",(function(){Object(o.c)("logged-in-user")&&Object(o.c)("logged-in-sig")?r.userIsLoggedIn():r.userIsNotLoggedIn()}))}var t,n,r;return t=e,(n=[{key:"save",value:function(e){this.start(),s(this.url,this.timestamp,[],{failure:this.failure.bind(this),success:this.success.bind(this)})}},{key:"start",value:function(){this.hide(["wm-save-snapshot-fail","wm-save-snapshot-open","wm-save-snapshot-success"]),this.show(["wm-save-snapshot-in-progress"])}},{key:"failure",value:function(e){401==e.status?this.userNotLoggedIn(e):(this.hide(["wm-save-snapshot-in-progress","wm-save-snapshot-success"]),this.show(["wm-save-snapshot-fail","wm-save-snapshot-open"]),console.log("You have got an error."),console.log("If you think something wrong here please send it to support."),console.log('Response: "'+e.responseText+'"'),console.log('status: "'+e.status+'"'))}},{key:"success",value:function(e){this.hide(["wm-save-snapshot-fail","wm-save-snapshot-in-progress"]),this.show(["wm-save-snapshot-open","wm-save-snapshot-success"])}},{key:"userIsLoggedIn",value:function(){this.show("wm-save-snapshot-open"),this.hide("wm-sign-in")}},{key:"userIsNotLoggedIn",value:function(){this.hide(["wm-save-snapshot-open","wm-save-snapshot-in-progress"]),this.show("wm-sign-in")}},{key:"show",value:function(e){this.setDisplayStyle(e,"inline-block")}},{key:"hide",value:function(e){this.setDisplayStyle(e,"none")}},{key:"setDisplayStyle",value:function(e,t){var n=this;(Object(o.d)(e)?e:[e]).forEach((function(e){var o=n.el.getRootNode().getElementById(e);o&&(o.style.display=t)}))}}])&&i(t.prototype,n),r&&i(t,r),e}()},,,,,,,function(e,t,n){"use strict";var o;n.r(t);var i,r={createElementNS:document.createElementNS};var s=!0;function a(e){!function(e,t,n){if(n){var o=new Date;o.setTime(o.getTime()+24*n*60*60*1e3);var i="; expires="+o.toGMTString()}else i="";document.cookie=e+"="+t+i+"; path=/"}(e,"",-1)}var c=n(0),l=n(1),u=window.Date;function h(e,t){return(e=e.toString()).length>=t?e:"00000000".substring(0,t-e.length)+e}function f(e){for(var t=0,n=0;n3}(e)){var o=[];for(n=0;n=e.length?{done:!0}:{done:!1,value:e[t++]}},e:function(e){throw e},f:n}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i,r=!0,s=!1;return{s:function(){o=e[Symbol.iterator]()},n:function(){var e=o.next();return r=e.done,e},e:function(e){s=!0,i=e},f:function(){try{r||null==o.return||o.return()}finally{if(s)throw i}}}}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n2&&void 0!==arguments[2]?arguments[2]:"src",r=window.location.origin,s=b(window,e),l=p(s);try{for(l.s();!(o=l.n()).done;){var u=o.value;if(!n||n(u)){var h=Object(c.b)(u,i);h&&!h.startsWith(t)&&h.startsWith(r)&&(h.startsWith("data:")||a.push(h))}}}catch(e){l.e(e)}finally{l.f()}}u("img"),u("frame"),u("iframe",(function(e){return"playback"!==e.id})),u("script"),u("link",(function(e){return"stylesheet"===e.rel}),"href");var h=a.filter((function(e,t,n){return n.indexOf(e)===t}));h.length>0?(s=0,h.map((function(e){e.match("^https?://")&&(s++,Object(c.a)("HEAD",e,(function(e){if(200==e.status){var t=e.getResponseHeader("Memento-Datetime");if(null==t)console.log("%s: no Memento-Datetime",u);else{var n=document.createElement("span"),a=function(e,t){var n=new Date(e).getTime()-t,o="";n<0?(o+="-",n=Math.abs(n)):o+="+";var i=!1;if(n<1e3)return{delta:n,text:"",highlight:i};var r=n,s=Math.floor(n/1e3/60/60/24/30/12);n-=1e3*s*60*60*24*30*12;var a=Math.floor(n/1e3/60/60/24/30);n-=1e3*a*60*60*24*30;var c=Math.floor(n/1e3/60/60/24);n-=1e3*c*60*60*24;var l=Math.floor(n/1e3/60/60);n-=1e3*l*60*60;var u=Math.floor(n/1e3/60);n-=1e3*u*60;var h=Math.floor(n/1e3),f=[];s>1?(f.push(s+" years"),i=!0):1==s&&(f.push(s+" year"),i=!0);a>1?(f.push(a+" months"),i=!0):1==a&&(f.push(a+" month"),i=!0);c>1?f.push(c+" days"):1==c&&f.push(c+" day");l>1?f.push(l+" hours"):1==l&&f.push(l+" hour");u>1?f.push(u+" minutes"):1==u&&f.push(u+" minute");h>1?f.push(h+" seconds"):1==h&&f.push(h+" second");f.length>2&&(f=f.slice(0,2));return{delta:r,text:o+f.join(" "),highlight:i}}(t,r),c=a.highlight?"color:red;":"";n.innerHTML=" "+a.text,n.title=t,n.setAttribute("style",c);var l=e.getResponseHeader("Content-Type"),u=e.responseURL.replace(window.location.origin,""),h=document.createElement("a");h.innerHTML=u.split("/").splice(3).join("/"),h._wm_href=u,h.title=l,h.onmouseover=y,h.onmouseout=w;var f=document.createElement("div");f.setAttribute("data-delta",a.delta),f.appendChild(h),f.append(n),o.appendChild(f);var p=Array.prototype.slice.call(o.childNodes,0);p.sort((function(e,t){return t.getAttribute("data-delta")-e.getAttribute("data-delta")})),o.innerHTML="";for(var d=0,m=p.length;d0)for(var n=0;n0)for(var n=0;n0?this.sc.scrollTop=i+this.sc.suggestionHeight+o-this.sc.maxHeight:i<0&&(this.sc.scrollTop=i+o)}}},{key:"blurHandler",value:function(){var e=this;try{var t=this.root.querySelector(".wb-autocomplete-suggestions:hover")}catch(e){t=null}t?this.input!==document.activeElement&&setTimeout((function(){return e.focus()}),20):(this.last_val=this.input.value,this.sc.style.display="none",setTimeout((function(){return e.sc.style.display="none"}),350))}},{key:"suggest",value:function(e){var t=this.input.value;if(this.cache[t]=e,e.length&&t.length>=this.minChars){for(var n="",o=0;o40)&&13!=n&&27!=n){var o=this.input.value;if(o.length>=this.minChars){if(o!=this.last_val){if(this.last_val=o,clearTimeout(this.timer),this.cache){if(o in this.cache)return void this.suggest(this.cache[o]);for(var i=1;i'+e.replace(n,"$1")+""}},{key:"onSelect",value:function(e,t,n){}}]),e}(),j=function(){function e(t,n){S(this,e);var o=t.getRootNode();if(o.querySelector){var i="object"==_(t)?[t]:o.querySelectorAll(t);this.elems=i.map((function(e){return new E(e,n)}))}}return T(e,[{key:"destroy",value:function(){for(;this.elems.length>0;)this.elems.pop().unload()}}]),e}(),R=n(2),A=window.JSON,N=window.open,U=window.Date,I=document,D=document,P=function(e){return D.getElementById(e)};var q,F="/static/";function B(e){L.classList.contains("wm-closed")?(e&&O.classList.add("notice-only"),L.classList.replace("wm-closed","wm-open"),O.style.display="block",m(q,F,P)):(L.classList.replace("wm-open","wm-closed"),O.style.display="none",O.classList.remove("notice-only"))}function W(e,t){var n=P(e);n&&(n.style.visibility=t?"visible":"hidden")}function X(e,t){Object(c.d)(e)||(e=[e]);for(var n=0;n0&&r<60,r)}))}window.__wm={init:function(e){!function(){var e=document.cookie.split(";");if(e.length>40)for(var t=0;t1?t-1:0),o=1;o0;)x.appendChild(C.children[0]);if(d)for(var H=0;H'+((""+n).replace(/\B(?=(\d{3})+$)/g,",")+" ")+(n>1?"captures":"capture")+"",f=l.a(i,"%d %b %Y");s!=i&&(f+=" - "+l.a(s,"%d %b %Y")),h+='
'+f+"
",t.innerHTML=h}(o),function(e,t,n,o,i,r,s){var a=o.getContext("2d");if(a){a.fillStyle="#FFF";var c=(new u).getUTCFullYear(),l=t/(c-i+1),h=f(e.years),p=h[0],d=n/h[1];if(r>=i){var m=T(r);a.fillStyle="#FFFFA5",a.fillRect(m,0,l,n)}for(var v=i;v<=c;v++){m=T(v);a.beginPath(),a.moveTo(m,0),a.lineTo(m,n),a.lineWidth=1,a.strokeStyle="#CCC",a.stroke()}s=parseInt(s)-1;for(var g=(l-1)/12,y=0;y0){var M=Math.ceil(S*d);a.fillStyle=v==r&&_==s?"#EC008C":"#000",a.fillRect(Math.round(b),Math.ceil(n-M),Math.ceil(g),Math.round(M))}b+=g}}}function T(e){return Math.ceil((e-i)*l)+.5}}(o,e,t,Z,a,_,S)}}))}else{var te=new Image;te.src="/__wb/sparkline?url="+encodeURIComponent(r)+"&width="+e+"&height="+t+"&selected_year="+_+"&selected_month="+S+(i&&"&collection="+i||""),te.alt="sparkline",te.width=e,te.height=t,te.id="sparklineImgId",te.border="0",Q.parentNode.replaceChild(te,Q)}function ne(e){for(var t=[],n=e.length,o=0;o. + */ +(function(){function FuncMap(){this._map=[]}function ensureNumber(maybeNumber){try{switch(typeof maybeNumber){case"number":case"bigint":return maybeNumber;}var converted=Number(maybeNumber);return isNaN(converted)?null:converted}catch(e){}return null}function addToStringTagToClass(clazz,tag){typeof self.Symbol!=="undefined"&&typeof self.Symbol.toStringTag!=="undefined"&&Object.defineProperty(clazz.prototype,self.Symbol.toStringTag,{value:tag,enumerable:false})}function autobind(clazz){for(var prop,propValue,proto=clazz.__proto__||clazz.constructor.prototype||clazz.prototype,clazzProps=Object.getOwnPropertyNames(proto),len=clazzProps.length,i=0;i=0){var fnMapping=this._map.splice(idx,1);return fnMapping[0][1]}return null},FuncMap.prototype.map=function(param){for(var i=0;i0&&afw.preserveMedia(media)})},AutoFetcher.prototype.terminate=function(){this.worker.terminate()},AutoFetcher.prototype.justFetch=function(urls){this.worker.postMessage({type:"fetch-all",values:urls})},AutoFetcher.prototype.fetchAsPage=function(url,originalUrl,title){if(url){var headers={"X-Wombat-History-Page":originalUrl};if(title){var encodedTitle=encodeURIComponent(title.trim());title&&(headers["X-Wombat-History-Title"]=encodedTitle)}var fetchData={url:url,options:{headers:headers,cache:"no-store"}};this.justFetch([fetchData])}},AutoFetcher.prototype.postMessage=function(msg,deferred){if(deferred){var afWorker=this;return void Promise.resolve().then(function(){afWorker.worker.postMessage(msg)})}this.worker.postMessage(msg)},AutoFetcher.prototype.preserveSrcset=function(srcset,mod){this.postMessage({type:"values",srcset:{value:srcset,mod:mod,presplit:true}},true)},AutoFetcher.prototype.preserveDataSrcset=function(elem){this.postMessage({type:"values",srcset:{value:elem.dataset.srcset,mod:this.rwMod(elem),presplit:false}},true)},AutoFetcher.prototype.preserveMedia=function(media){this.postMessage({type:"values",media:media},true)},AutoFetcher.prototype.getSrcset=function(elem){return this.wombat.wb_getAttribute?this.wombat.wb_getAttribute.call(elem,"srcset"):elem.getAttribute("srcset")},AutoFetcher.prototype.rwMod=function(elem){switch(elem.tagName){case"SOURCE":return elem.parentElement&&elem.parentElement.tagName==="PICTURE"?"im_":"oe_";case"IMG":return"im_";}return"oe_"},AutoFetcher.prototype.extractFromLocalDoc=function(){var afw=this;Promise.resolve().then(function(){for(var msg={type:"values",context:{docBaseURI:document.baseURI}},media=[],i=0,sheets=document.styleSheets;i=0)||scriptType.indexOf("text/template")>=0)},Wombat.prototype.skipWrapScriptTextBasedOnText=function(text){if(!text||text.indexOf(this.WB_ASSIGN_FUNC)>=0||text.indexOf("<")===0)return true;for(var override_props=["window","self","document","location","top","parent","frames","opener"],i=0;i=0)return false;return true},Wombat.prototype.nodeHasChildren=function(node){if(!node)return false;if(typeof node.hasChildNodes==="function")return node.hasChildNodes();var kids=node.children||node.childNodes;return!!kids&&kids.length>0},Wombat.prototype.rwModForElement=function(elem,attrName){if(!elem)return undefined;var mod="mp_";if(!(elem.tagName==="LINK"&&attrName==="href")){var maybeMod=this.tagToMod[elem.tagName];maybeMod!=null&&(mod=maybeMod[attrName])}else if(elem.rel){var relV=elem.rel.trim().toLowerCase(),asV=this.wb_getAttribute.call(elem,"as");if(asV&&this.linkTagMods.linkRelToAs[relV]!=null){var asMods=this.linkTagMods.linkRelToAs[relV];mod=asMods[asV.toLowerCase()]}else this.linkTagMods[relV]!=null&&(mod=this.linkTagMods[relV])}return mod},Wombat.prototype.removeWBOSRC=function(elem){elem.tagName!=="SCRIPT"||elem.__$removedWBOSRC$__||(elem.hasAttribute("__wb_orig_src")&&elem.removeAttribute("__wb_orig_src"),elem.__$removedWBOSRC$__=true)},Wombat.prototype.retrieveWBOSRC=function(elem){if(elem.tagName==="SCRIPT"&&!elem.__$removedWBOSRC$__){var maybeWBOSRC;return maybeWBOSRC=this.wb_getAttribute?this.wb_getAttribute.call(elem,"__wb_orig_src"):elem.getAttribute("__wb_orig_src"),maybeWBOSRC==null&&(elem.__$removedWBOSRC$__=true),maybeWBOSRC}return undefined},Wombat.prototype.wrapScriptTextJsProxy=function(scriptText){return"var _____WB$wombat$assign$function_____ = function(name) {return (self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init(name)) || self[name]; };\nif (!self.__WB_pmw) { self.__WB_pmw = function(obj) { this.__WB_source = obj; return this; } }\n{\nlet window = _____WB$wombat$assign$function_____(\"window\");\nlet self = _____WB$wombat$assign$function_____(\"self\");\nlet document = _____WB$wombat$assign$function_____(\"document\");\nlet location = _____WB$wombat$assign$function_____(\"location\");\nlet top = _____WB$wombat$assign$function_____(\"top\");\nlet parent = _____WB$wombat$assign$function_____(\"parent\");\nlet frames = _____WB$wombat$assign$function_____(\"frames\");\nlet opener = _____WB$wombat$assign$function_____(\"opener\");\n"+scriptText.replace(this.DotPostMessageRe,".__WB_pmw(self.window)$1")+"\n\n}"},Wombat.prototype.watchElem=function(elem,func){if(!this.$wbwindow.MutationObserver)return false;var m=new this.$wbwindow.MutationObserver(function(records,observer){for(var r,i=0;i"},Wombat.prototype.getFinalUrl=function(useRel,mod,url){var prefix=useRel?this.wb_rel_prefix:this.wb_abs_prefix;return mod==null&&(mod=this.wb_info.mod),this.wb_info.is_live||(prefix+=this.wb_info.wombat_ts),prefix+=mod,prefix[prefix.length-1]!=="/"&&(prefix+="/"),prefix+url},Wombat.prototype.resolveRelUrl=function(url,doc){var docObj=doc||this.$wbwindow.document,parser=this.makeParser(docObj.baseURI,docObj),hash=parser.href.lastIndexOf("#"),href=hash>=0?parser.href.substring(0,hash):parser.href,lastslash=href.lastIndexOf("/");return parser.href=lastslash>=0&&lastslash!==href.length-1?href.substring(0,lastslash+1)+url:href+url,parser.href},Wombat.prototype.extractOriginalURL=function(rewrittenUrl){if(!rewrittenUrl)return"";if(this.wb_is_proxy)return rewrittenUrl;var rwURLString=rewrittenUrl.toString(),url=rwURLString;if(this.startsWithOneOf(url,this.IGNORE_PREFIXES))return url;var start;start=this.startsWith(url,this.wb_abs_prefix)?this.wb_abs_prefix.length:this.wb_rel_prefix&&this.startsWith(url,this.wb_rel_prefix)?this.wb_rel_prefix.length:this.wb_rel_prefix?1:0;var index=url.indexOf("/http",start);return index<0&&(index=url.indexOf("///",start)),index<0&&(index=url.indexOf("/blob:",start)),index<0&&(index=url.indexOf("/about:blank",start)),index>=0?url=url.substr(index+1):(index=url.indexOf(this.wb_replay_prefix),index>=0&&(url=url.substr(index+this.wb_replay_prefix.length)),url.length>4&&url.charAt(2)==="_"&&url.charAt(3)==="/"&&(url=url.substr(4)),url!==rwURLString&&!this.startsWithOneOf(url,this.VALID_PREFIXES)&&!this.startsWith(url,"blob:")&&(url=this.wb_orig_scheme+url)),rwURLString.charAt(0)==="/"&&rwURLString.charAt(1)!=="/"&&this.startsWith(url,this.wb_orig_origin)&&(url=url.substr(this.wb_orig_origin.length)),this.startsWith(url,this.REL_PREFIX)?this.wb_info.wombat_scheme+":"+url:url},Wombat.prototype.makeParser=function(maybeRewrittenURL,doc){var originalURL=this.extractOriginalURL(maybeRewrittenURL),docElem=doc;return doc||(this.$wbwindow.location.href==="about:blank"&&this.$wbwindow.opener?docElem=this.$wbwindow.opener.document:docElem=this.$wbwindow.document),this._makeURLParser(originalURL,docElem)},Wombat.prototype._makeURLParser=function(url,docElem){try{return new this.$wbwindow.URL(url,docElem.baseURI)}catch(e){}var p=docElem.createElement("a");return p._no_rewrite=true,p.href=url,p},Wombat.prototype.defProp=function(obj,prop,setFunc,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;var descriptor={configurable:true,enumerable:enumerable||false,get:getFunc};setFunc&&(descriptor.set=setFunc);try{return Object.defineProperty(obj,prop,descriptor),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.defGetterProp=function(obj,prop,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;try{return Object.defineProperty(obj,prop,{configurable:true,enumerable:enumerable||false,get:getFunc}),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.getOrigGetter=function(obj,prop){var orig_getter;if(obj.__lookupGetter__&&(orig_getter=obj.__lookupGetter__(prop)),!orig_getter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_getter=props.get)}return orig_getter},Wombat.prototype.getOrigSetter=function(obj,prop){var orig_setter;if(obj.__lookupSetter__&&(orig_setter=obj.__lookupSetter__(prop)),!orig_setter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_setter=props.set)}return orig_setter},Wombat.prototype.getAllOwnProps=function(obj){for(var ownProps=[],props=Object.getOwnPropertyNames(obj),i=0;i "+final_href),actualLocation.href=final_href}}},Wombat.prototype.checkLocationChange=function(wombatLoc,isTop){var locType=typeof wombatLoc,actual_location=isTop?this.$wbwindow.__WB_replay_top.location:this.$wbwindow.location;locType==="string"?this.updateLocation(wombatLoc,actual_location.href,actual_location):locType==="object"&&this.updateLocation(wombatLoc.href,wombatLoc._orig_href,actual_location)},Wombat.prototype.checkAllLocations=function(){return!this.wb_wombat_updating&&void(this.wb_wombat_updating=true,this.checkLocationChange(this.$wbwindow.WB_wombat_location,false),this.$wbwindow.WB_wombat_location!=this.$wbwindow.__WB_replay_top.WB_wombat_location&&this.checkLocationChange(this.$wbwindow.__WB_replay_top.WB_wombat_location,true),this.wb_wombat_updating=false)},Wombat.prototype.proxyToObj=function(source){if(source)try{var proxyRealObj=source.__WBProxyRealObj__;if(proxyRealObj)return proxyRealObj}catch(e){}return source},Wombat.prototype.objToProxy=function(obj){if(obj)try{var maybeWbProxy=obj._WB_wombat_obj_proxy;if(maybeWbProxy)return maybeWbProxy}catch(e){}return obj},Wombat.prototype.defaultProxyGet=function(obj,prop,ownProps,fnCache){switch(prop){case"__WBProxyRealObj__":return obj;case"location":case"WB_wombat_location":return obj.WB_wombat_location;case"_WB_wombat_obj_proxy":return obj._WB_wombat_obj_proxy;case"__WB_pmw":case"WB_wombat_eval":case this.WB_ASSIGN_FUNC:case this.WB_CHECK_THIS_FUNC:return obj[prop];case"origin":return obj.WB_wombat_location.origin;case"constructor":if(obj.constructor===Window)return obj.constructor;}var retVal=obj[prop],type=typeof retVal;if(type==="function"&&ownProps.indexOf(prop)!==-1){switch(prop){case"requestAnimationFrame":case"cancelAnimationFrame":{if(!this.isNativeFunction(retVal))return retVal;break}}var cachedFN=fnCache[prop];return cachedFN&&cachedFN.original===retVal||(cachedFN={original:retVal,boundFn:retVal.bind(obj)},fnCache[prop]=cachedFN),cachedFN.boundFn}return type==="object"&&retVal&&retVal._WB_wombat_obj_proxy?(retVal instanceof Window&&this.initNewWindowWombat(retVal),retVal._WB_wombat_obj_proxy):retVal},Wombat.prototype.setLoc=function(loc,originalURL){var parser=this.makeParser(originalURL,loc.ownerDocument);loc._orig_href=originalURL,loc._parser=parser;var href=parser.href;loc._hash=parser.hash,loc._href=href,loc._host=parser.host,loc._hostname=parser.hostname,loc._origin=parser.origin?parser.host?parser.origin:"null":parser.protocol+"//"+parser.hostname+(parser.port?":"+parser.port:""),loc._pathname=parser.pathname,loc._port=parser.port,loc._protocol=parser.protocol,loc._search=parser.search,Object.defineProperty||(loc.href=href,loc.hash=parser.hash,loc.host=loc._host,loc.hostname=loc._hostname,loc.origin=loc._origin,loc.pathname=loc._pathname,loc.port=loc._port,loc.protocol=loc._protocol,loc.search=loc._search)},Wombat.prototype.makeGetLocProp=function(prop,origGetter){var wombat=this;return function newGetLocProp(){if(this._no_rewrite)return origGetter.call(this,prop);var curr_orig_href=origGetter.call(this,"href");return prop==="href"?wombat.extractOriginalURL(curr_orig_href):prop==="ancestorOrigins"?[]:(this._orig_href!==curr_orig_href&&wombat.setLoc(this,curr_orig_href),this["_"+prop])}},Wombat.prototype.makeSetLocProp=function(prop,origSetter,origGetter){var wombat=this;return function newSetLocProp(value){if(this._no_rewrite)return origSetter.call(this,prop,value);if(this["_"+prop]!==value){if(this["_"+prop]=value,!this._parser){var href=origGetter.call(this);this._parser=wombat.makeParser(href,this.ownerDocument)}var rel=false;prop==="href"&&typeof value==="string"&&value&&(value[0]==="."?value=wombat.resolveRelUrl(value,this.ownerDocument):value[0]==="/"&&(value.length<=1||value[1]!=="/")&&(rel=true,value=WB_wombat_location.origin+value));try{this._parser[prop]=value}catch(e){console.log("Error setting "+prop+" = "+value)}prop==="hash"?(value=this._parser[prop],origSetter.call(this,"hash",value)):(rel=rel||value===this._parser.pathname,value=wombat.rewriteUrl(this._parser.href,rel),origSetter.call(this,"href",value))}}},Wombat.prototype.styleReplacer=function(match,n1,n2,n3,offset,string){return n1+this.rewriteUrl(n2)+n3},Wombat.prototype.domConstructorErrorChecker=function(thisObj,what,args,numRequiredArgs){var errorMsg,needArgs=typeof numRequiredArgs==="number"?numRequiredArgs:1;if(thisObj instanceof Window?errorMsg="Failed to construct '"+what+"': Please use the 'new' operator, this DOM object constructor cannot be called as a function.":args&&args.length=0)return url;if(url.indexOf(this.wb_rel_prefix)===0&&url.indexOf("http")>1){var scheme_sep=url.indexOf(":/");return scheme_sep>0&&url[scheme_sep+2]!=="/"?url.substring(0,scheme_sep+2)+"/"+url.substring(scheme_sep+2):url}return this.getFinalUrl(true,mod,this.wb_orig_origin+url)}url.charAt(0)==="."&&(url=this.resolveRelUrl(url,doc));var prefix=this.startsWithOneOf(url.toLowerCase(),this.VALID_PREFIXES);if(prefix){var orig_host=this.$wbwindow.__WB_replay_top.location.host,orig_protocol=this.$wbwindow.__WB_replay_top.location.protocol,prefix_host=prefix+orig_host+"/";if(this.startsWith(url,prefix_host)){if(this.startsWith(url,this.wb_replay_prefix))return url;var curr_scheme=orig_protocol+"//",path=url.substring(prefix_host.length),rebuild=false;return path.indexOf(this.wb_rel_prefix)<0&&url.indexOf("/static/")<0&&(path=this.getFinalUrl(true,mod,WB_wombat_location.origin+"/"+path),rebuild=true),prefix!==curr_scheme&&prefix!==this.REL_PREFIX&&(rebuild=true),rebuild&&(url=useRel?"":curr_scheme+orig_host,path&&path[0]!=="/"&&(url+="/"),url+=path),url}return this.getFinalUrl(useRel,mod,url)}return prefix=this.startsWithOneOf(url,this.BAD_PREFIXES),prefix?this.getFinalUrl(useRel,mod,this.extractOriginalURL(url)):this.isHostUrl(url)&&!this.startsWith(url,originalLoc.host+"/")?this.getFinalUrl(useRel,mod,this.wb_orig_scheme+url):url},Wombat.prototype.rewriteUrl=function(url,useRel,mod,doc){var rewritten=this.rewriteUrl_(url,useRel,mod,doc);return this.debug_rw&&(url===rewritten?console.log("NOT REWRITTEN "+url):console.log("REWRITE: "+url+" -> "+rewritten)),rewritten},Wombat.prototype.performAttributeRewrite=function(elem,name,value,absUrlOnly){switch(name){case"innerHTML":case"outerHTML":return this.rewriteHtml(value);case"filter":return this.rewriteInlineStyle(value);case"style":return this.rewriteStyle(value);case"srcset":return this.rewriteSrcset(value,elem);}if(absUrlOnly&&!this.startsWithOneOf(value,this.VALID_PREFIXES))return value;var mod=this.rwModForElement(elem,name);return this.wbUseAFWorker&&this.WBAutoFetchWorker&&this.isSavedDataSrcSrcset(elem)&&this.WBAutoFetchWorker.preserveDataSrcset(elem),this.rewriteUrl(value,false,mod,elem.ownerDocument)},Wombat.prototype.rewriteAttr=function(elem,name,absUrlOnly){var changed=false;if(!elem||!elem.getAttribute||elem._no_rewrite||elem["_"+name])return changed;var value=this.wb_getAttribute.call(elem,name);if(!value||this.startsWith(value,"javascript:"))return changed;var new_value=this.performAttributeRewrite(elem,name,value,absUrlOnly);return new_value!==value&&(this.removeWBOSRC(elem),this.wb_setAttribute.call(elem,name,new_value),changed=true),changed},Wombat.prototype.noExceptRewriteStyle=function(style){try{return this.rewriteStyle(style)}catch(e){return style}},Wombat.prototype.rewriteStyle=function(style){if(!style)return style;var value=style;return typeof style==="object"&&(value=style.toString()),typeof value==="string"?value.replace(this.STYLE_REGEX,this.styleReplacer).replace(this.IMPORT_REGEX,this.styleReplacer).replace(this.no_wombatRe,""):value},Wombat.prototype.rewriteSrcset=function(value,elem){if(!value)return"";for(var split=value.split(this.srcsetRe),values=[],mod=this.rwModForElement(elem,"srcset"),i=0;i=0){var JS="javascript:";new_value="javascript:window.parent._wb_wombat.initNewWindowWombat(window);"+value.substr(11)}return new_value||(new_value=this.rewriteUrl(value,false,this.rwModForElement(elem,attrName))),new_value!==value&&(this.wb_setAttribute.call(elem,attrName,new_value),true)},Wombat.prototype.rewriteScript=function(elem){if(elem.hasAttribute("src")||!elem.textContent||!this.$wbwindow.Proxy)return this.rewriteAttr(elem,"src");if(this.skipWrapScriptBasedOnType(elem.type))return false;var text=elem.textContent.trim();return!this.skipWrapScriptTextBasedOnText(text)&&(elem.textContent=this.wrapScriptTextJsProxy(text),true)},Wombat.prototype.rewriteSVGElem=function(elem){var changed=this.rewriteAttr(elem,"filter");return changed=this.rewriteAttr(elem,"style")||changed,changed=this.rewriteAttr(elem,"xlink:href")||changed,changed=this.rewriteAttr(elem,"href")||changed,changed=this.rewriteAttr(elem,"src")||changed,changed},Wombat.prototype.rewriteElem=function(elem){var changed=false;if(!elem)return changed;if(elem instanceof SVGElement)changed=this.rewriteSVGElem(elem);else switch(elem.tagName){case"META":var maybeCSP=this.wb_getAttribute.call(elem,"http-equiv");maybeCSP&&maybeCSP.toLowerCase()==="content-security-policy"&&(this.wb_setAttribute.call(elem,"http-equiv","_"+maybeCSP),changed=true);break;case"STYLE":var new_content=this.rewriteStyle(elem.textContent);elem.textContent!==new_content&&(elem.textContent=new_content,changed=true,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.sheet!=null&&this.WBAutoFetchWorker.deferredSheetExtraction(elem.sheet));break;case"LINK":changed=this.rewriteAttr(elem,"href"),this.wbUseAFWorker&&elem.rel==="stylesheet"&&this._addEventListener(elem,"load",this.utilFns.wbSheetMediaQChecker);break;case"IMG":changed=this.rewriteAttr(elem,"src"),changed=this.rewriteAttr(elem,"srcset")||changed,changed=this.rewriteAttr(elem,"style")||changed,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.dataset.srcset&&this.WBAutoFetchWorker.preserveDataSrcset(elem);break;case"OBJECT":if(this.wb_info.isSW&&elem.parentElement&&elem.getAttribute("type")==="application/pdf"){for(var iframe=this.$wbwindow.document.createElement("IFRAME"),i=0;i0;)for(var child,children=rewriteQ.shift(),i=0;i"+rwString+"","text/html");if(!inner_doc||!this.nodeHasChildren(inner_doc.head)||!inner_doc.head.children[0].content)return rwString;var template=inner_doc.head.children[0];if(template._no_rewrite=true,this.recurseRewriteElem(template.content)){var new_html=template.innerHTML;if(checkEndTag){var first_elem=template.content.children&&template.content.children[0];if(first_elem){var end_tag="";this.endsWith(new_html,end_tag)&&!this.endsWith(rwString.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(rwString[0]!=="<"||rwString[rwString.length-1]!==">")return this.write_buff+=rwString,undefined}return new_html}return rwString},Wombat.prototype.rewriteHtmlFull=function(string,checkEndTag){var inner_doc=new DOMParser().parseFromString(string,"text/html");if(!inner_doc)return string;for(var changed=false,i=0;i=0)inner_doc.documentElement._no_rewrite=true,new_html=this.reconstructDocType(inner_doc.doctype)+inner_doc.documentElement.outerHTML;else{inner_doc.head._no_rewrite=true,inner_doc.body._no_rewrite=true;var headHasKids=this.nodeHasChildren(inner_doc.head),bodyHasKids=this.nodeHasChildren(inner_doc.body);if(new_html=(headHasKids?inner_doc.head.outerHTML:"")+(bodyHasKids?inner_doc.body.outerHTML:""),checkEndTag)if(inner_doc.all.length>3){var end_tag="";this.endsWith(new_html,end_tag)&&!this.endsWith(string.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(string[0]!=="<"||string[string.length-1]!==">")return void(this.write_buff+=string);new_html=this.reconstructDocType(inner_doc.doctype)+new_html}return new_html}return string},Wombat.prototype.rewriteInlineStyle=function(orig){var decoded;try{decoded=decodeURIComponent(orig)}catch(e){decoded=orig}if(decoded!==orig){var parts=this.rewriteStyle(decoded).split(",",2);return parts[0]+","+encodeURIComponent(parts[1])}return this.rewriteStyle(orig)},Wombat.prototype.rewriteCookie=function(cookie){var wombat=this,rwCookie=cookie.replace(this.wb_abs_prefix,"").replace(this.wb_rel_prefix,"");return rwCookie=rwCookie.replace(this.cookie_domain_regex,function(m,m1){var message={domain:m1,cookie:rwCookie,wb_type:"cookie"};return wombat.sendTopMessage(message,true),wombat.$wbwindow.location.hostname.indexOf(".")>=0&&!wombat.IP_RX.test(wombat.$wbwindow.location.hostname)?"Domain=."+wombat.$wbwindow.location.hostname:""}).replace(this.cookie_path_regex,function(m,m1){var rewritten=wombat.rewriteUrl(m1);return rewritten.indexOf(wombat.wb_curr_host)===0&&(rewritten=rewritten.substring(wombat.wb_curr_host.length)),"Path="+rewritten}),wombat.$wbwindow.location.protocol!=="https:"&&(rwCookie=rwCookie.replace("secure","")),rwCookie.replace(",|",",")},Wombat.prototype.rewriteWorker=function(workerUrl){if(!workerUrl)return workerUrl;var isBlob=workerUrl.indexOf("blob:")===0,isJS=workerUrl.indexOf("javascript:")===0;if(!isBlob&&!isJS){if(!this.startsWithOneOf(workerUrl,this.VALID_PREFIXES)&&!this.startsWith(workerUrl,"/")&&!this.startsWithOneOf(workerUrl,this.BAD_PREFIXES)){var rurl=this.resolveRelUrl(workerUrl,this.$wbwindow.document);return this.rewriteUrl(rurl,false,"wkr_",this.$wbwindow.document)}return this.rewriteUrl(workerUrl,false,"wkr_",this.$wbwindow.document)}var workerCode=isJS?workerUrl.replace("javascript:",""):null;if(isBlob){var x=new XMLHttpRequest;this.utilFns.XHRopen.call(x,"GET",workerUrl,false),this.utilFns.XHRsend.call(x),workerCode=x.responseText.replace(this.workerBlobRe,"").replace(this.rmCheckThisInjectRe,"this")}if(this.wb_info.static_prefix||this.wb_info.ww_rw_script){var originalURL=this.$wbwindow.document.baseURI,ww_rw=this.wb_info.ww_rw_script||this.wb_info.static_prefix+"wombatWorkers.js",rw="(function() { self.importScripts('"+ww_rw+"'); new WBWombat({'prefix': '"+this.wb_abs_prefix+"', 'prefixMod': '"+this.wb_abs_prefix+"wkrf_/', 'originalURL': '"+originalURL+"'}); })();";workerCode=rw+workerCode}var blob=new Blob([workerCode],{type:"application/javascript"});return URL.createObjectURL(blob)},Wombat.prototype.rewriteTextNodeFn=function(fnThis,originalFn,argsObj){var args,deproxiedThis=this.proxyToObj(fnThis);if(argsObj.length>0&&deproxiedThis.parentElement&&deproxiedThis.parentElement.tagName==="STYLE"){args=new Array(argsObj.length);var dataIndex=argsObj.length-1;dataIndex===2?(args[0]=argsObj[0],args[1]=argsObj[1]):dataIndex===1&&(args[0]=argsObj[0]),args[dataIndex]=this.rewriteStyle(argsObj[dataIndex])}else args=argsObj;return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(deproxiedThis,args):originalFn.apply(deproxiedThis,args)},Wombat.prototype.rewriteDocWriteWriteln=function(fnThis,originalFn,argsObj){var string,thisObj=this.proxyToObj(fnThis),argLen=argsObj.length;if(argLen===0)return originalFn.call(thisObj);string=argLen===1?argsObj[0]:Array.prototype.join.call(argsObj,"");var new_buff=this.rewriteHtml(string,true),res=originalFn.call(thisObj,new_buff);return this.initNewWindowWombat(thisObj.defaultView),res},Wombat.prototype.rewriteChildNodeFn=function(fnThis,originalFn,argsObj){var thisObj=this.proxyToObj(fnThis);if(argsObj.length===0)return originalFn.call(thisObj);var newArgs=this.rewriteElementsInArguments(argsObj);return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(thisObj,newArgs):originalFn.apply(thisObj,newArgs)},Wombat.prototype.rewriteInsertAdjHTMLOrElemArgs=function(fnThis,originalFn,position,textOrElem,rwHTML){var fnThisObj=this.proxyToObj(fnThis);return fnThisObj._no_rewrite?originalFn.call(fnThisObj,position,textOrElem):rwHTML?originalFn.call(fnThisObj,position,this.rewriteHtml(textOrElem)):(this.rewriteElemComplete(textOrElem),originalFn.call(fnThisObj,position,textOrElem))},Wombat.prototype.rewriteSetTimeoutInterval=function(fnThis,originalFn,argsObj){var rw=this.isString(argsObj[0]),args=rw?new Array(argsObj.length):argsObj;if(rw){args[0]=this.$wbwindow.Proxy?this.wrapScriptTextJsProxy(argsObj[0]):argsObj[0].replace(/\blocation\b/g,"WB_wombat_$&");for(var i=1;i0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parse"),this.$wbwindow.CSSStyleValue.parseAll&&this.$wbwindow.CSSStyleValue.parseAll.toString().indexOf("[native code]")>0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parseAll")}if(this.$wbwindow.CSSKeywordValue&&this.$wbwindow.CSSKeywordValue.prototype){var oCSSKV=this.$wbwindow.CSSKeywordValue;this.$wbwindow.CSSKeywordValue=function(CSSKeywordValue_){return function CSSKeywordValue(cssValue){return wombat.domConstructorErrorChecker(this,"CSSKeywordValue",arguments),new CSSKeywordValue_(wombat.rewriteStyle(cssValue))}}(this.$wbwindow.CSSKeywordValue),this.$wbwindow.CSSKeywordValue.prototype=oCSSKV.prototype,Object.defineProperty(this.$wbwindow.CSSKeywordValue.prototype,"constructor",{value:this.$wbwindow.CSSKeywordValue}),addToStringTagToClass(this.$wbwindow.CSSKeywordValue,"CSSKeywordValue")}if(this.$wbwindow.StylePropertyMap&&this.$wbwindow.StylePropertyMap.prototype){var originalSet=this.$wbwindow.StylePropertyMap.prototype.set;this.$wbwindow.StylePropertyMap.prototype.set=function set(){if(arguments.length<=1)return originalSet.__WB_orig_apply?originalSet.__WB_orig_apply(this,arguments):originalSet.apply(this,arguments);var newArgs=new Array(arguments.length);newArgs[0]=arguments[0];for(var i=1;i")&&(array[0]=wombat.rewriteHtml(array[0]),options.type="text/html"),new Blob_(array,options)}}(this.$wbwindow.Blob),this.$wbwindow.Blob.prototype=orig_blob.prototype}},Wombat.prototype.initDocTitleOverride=function(){var orig_get_title=this.getOrigGetter(this.$wbwindow.document,"title"),orig_set_title=this.getOrigSetter(this.$wbwindow.document,"title"),wombat=this,set_title=function title(value){var res=orig_set_title.call(this,value),message={wb_type:"title",title:value};return wombat.sendTopMessage(message),res};this.defProp(this.$wbwindow.document,"title",set_title,orig_get_title)},Wombat.prototype.initFontFaceOverride=function(){if(this.$wbwindow.FontFace){var wombat=this,origFontFace=this.$wbwindow.FontFace;this.$wbwindow.FontFace=function(FontFace_){return function FontFace(family,source,descriptors){wombat.domConstructorErrorChecker(this,"FontFace",arguments,2);var rwSource=source;return source!=null&&(typeof source==="string"?rwSource=wombat.rewriteInlineStyle(source):rwSource=wombat.rewriteInlineStyle(source.toString())),new FontFace_(family,rwSource,descriptors)}}(this.$wbwindow.FontFace),this.$wbwindow.FontFace.prototype=origFontFace.prototype,Object.defineProperty(this.$wbwindow.FontFace.prototype,"constructor",{value:this.$wbwindow.FontFace}),addToStringTagToClass(this.$wbwindow.FontFace,"FontFace")}},Wombat.prototype.initFixedRatio=function(){try{this.$wbwindow.devicePixelRatio=1}catch(e){}if(Object.defineProperty)try{Object.defineProperty(this.$wbwindow,"devicePixelRatio",{value:1,writable:false})}catch(e){}},Wombat.prototype.initPaths=function(wbinfo){wbinfo.wombat_opts=wbinfo.wombat_opts||{},Object.assign(this.wb_info,wbinfo),this.wb_opts=wbinfo.wombat_opts,this.wb_replay_prefix=wbinfo.prefix,this.wb_is_proxy=wbinfo.proxy_magic||!this.wb_replay_prefix,this.wb_info.top_host=this.wb_info.top_host||"*",this.wb_curr_host=this.$wbwindow.location.protocol+"//"+this.$wbwindow.location.host,this.wb_info.wombat_opts=this.wb_info.wombat_opts||{},this.wb_orig_scheme=wbinfo.wombat_scheme+"://",this.wb_orig_origin=this.wb_orig_scheme+wbinfo.wombat_host,this.wb_abs_prefix=this.wb_replay_prefix,this.wb_capture_date_part=!wbinfo.is_live&&wbinfo.wombat_ts?"/"+wbinfo.wombat_ts+"/":"",this.initBadPrefixes(this.wb_replay_prefix),this.initCookiePreset()},Wombat.prototype.initSeededRandom=function(seed){this.$wbwindow.Math.seed=parseInt(seed);var wombat=this;this.$wbwindow.Math.random=function random(){return wombat.$wbwindow.Math.seed=(wombat.$wbwindow.Math.seed*9301+49297)%233280,wombat.$wbwindow.Math.seed/233280}},Wombat.prototype.initHistoryOverrides=function(){this.overrideHistoryFunc("pushState"),this.overrideHistoryFunc("replaceState");var wombat=this;this.$wbwindow.addEventListener("popstate",function(event){wombat.sendHistoryUpdate(wombat.$wbwindow.WB_wombat_location.href,wombat.$wbwindow.document.title)})},Wombat.prototype.initCookiePreset=function(){if(this.wb_info.presetCookie)for(var splitCookies=this.wb_info.presetCookie.split(";"),i=0;i0?"&":"?")+value.toString(),value=null):contentType==="application/json"||contentType==="text/plain"?(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+jsonToQueryString(value),value=null):wombat.startsWith(contentType,"multipart/form-data")&&(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+mfdToQueryString(value,contentType))}if(this.__WB_xhr_open_arguments.length>2&&(this.__WB_xhr_open_arguments[2]=true),this._no_rewrite||(this.__WB_xhr_open_arguments[1]=wombat.rewriteUrl(this.__WB_xhr_open_arguments[1])),origOpen.apply(this,this.__WB_xhr_open_arguments),!wombat.startsWith(this.__WB_xhr_open_arguments[1],"data:")){for(const[name,value]of this.__WB_xhr_headers.entries())origSetRequestHeader.call(this,name,value);origSetRequestHeader.call(this,"X-Pywb-Requested-With","XMLHttpRequest")}origSend.call(this,value)}}if(this.$wbwindow.fetch){var orig_fetch=this.$wbwindow.fetch;this.$wbwindow.fetch=function fetch(input,init_opts){var rwInput=input,inputType=typeof input;if(inputType==="string")rwInput=wombat.rewriteUrl(input);else if(inputType==="object"&&input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(rwInput=new Request(new_url,init_opts))}else inputType==="object"&&input.href&&(rwInput=wombat.rewriteUrl(input.href));if(init_opts||(init_opts={}),init_opts.credentials===undefined)try{init_opts.credentials="include"}catch(e){}return orig_fetch.call(wombat.proxyToObj(this),rwInput,init_opts)}}if(this.$wbwindow.Request&&this.$wbwindow.Request.prototype){var orig_request=this.$wbwindow.Request;this.$wbwindow.Request=function(Request_){return function Request(input,init_opts){wombat.domConstructorErrorChecker(this,"Request",arguments);var newInitOpts=init_opts||{},newInput=input,inputType=typeof input;switch(inputType){case"string":newInput=wombat.rewriteUrl(input);break;case"object":if(newInput=input,input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(newInput=new Request_(new_url,input))}else input.href&&(newInput=wombat.rewriteUrl(input.toString(),true));}return newInitOpts.credentials="include",new Request_(newInput,newInitOpts)}}(this.$wbwindow.Request),this.$wbwindow.Request.prototype=orig_request.prototype,Object.defineProperty(this.$wbwindow.Request.prototype,"constructor",{value:this.$wbwindow.Request})}if(this.$wbwindow.Response&&this.$wbwindow.Response.prototype){var originalRedirect=this.$wbwindow.Response.prototype.redirect;this.$wbwindow.Response.prototype.redirect=function redirect(url,status){var rwURL=wombat.rewriteUrl(url,true,null,wombat.$wbwindow.document);return originalRedirect.call(this,rwURL,status)}}if(this.$wbwindow.EventSource&&this.$wbwindow.EventSource.prototype){var origEventSource=this.$wbwindow.EventSource;this.$wbwindow.EventSource=function(EventSource_){return function EventSource(url,configuration){wombat.domConstructorErrorChecker(this,"EventSource",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteUrl(url)),new EventSource_(rwURL,configuration)}}(this.$wbwindow.EventSource),this.$wbwindow.EventSource.prototype=origEventSource.prototype,Object.defineProperty(this.$wbwindow.EventSource.prototype,"constructor",{value:this.$wbwindow.EventSource}),addToStringTagToClass(this.$wbwindow.EventSource,"EventSource")}if(this.$wbwindow.WebSocket&&this.$wbwindow.WebSocket.prototype){var origWebSocket=this.$wbwindow.WebSocket;this.$wbwindow.WebSocket=function(WebSocket_){return function WebSocket(url,configuration){wombat.domConstructorErrorChecker(this,"WebSocket",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteWSURL(url)),new WebSocket_(rwURL,configuration)}}(this.$wbwindow.WebSocket),this.$wbwindow.WebSocket.prototype=origWebSocket.prototype,Object.defineProperty(this.$wbwindow.WebSocket.prototype,"constructor",{value:this.$wbwindow.WebSocket}),addToStringTagToClass(this.$wbwindow.WebSocket,"WebSocket")}},Wombat.prototype.initElementGetSetAttributeOverride=function(){if(!this.wb_opts.skip_setAttribute&&this.$wbwindow.Element&&this.$wbwindow.Element.prototype){var wombat=this,ElementProto=this.$wbwindow.Element.prototype;if(ElementProto.setAttribute){var orig_setAttribute=ElementProto.setAttribute;ElementProto._orig_setAttribute=orig_setAttribute,ElementProto.setAttribute=function setAttribute(name,value){var rwValue=value;if(name&&typeof rwValue==="string"){var lowername=name.toLowerCase();if(this.tagName==="LINK"&&lowername==="href"&&rwValue.indexOf("data:text/css")===0)rwValue=wombat.rewriteInlineStyle(value);else if(lowername==="style")rwValue=wombat.rewriteStyle(value);else if(lowername==="srcset")rwValue=wombat.rewriteSrcset(value,this);else{var shouldRW=wombat.shouldRewriteAttr(this.tagName,lowername);shouldRW&&(wombat.removeWBOSRC(this),!this._no_rewrite&&(rwValue=wombat.rewriteUrl(value,false,wombat.rwModForElement(this,lowername))))}}return orig_setAttribute.call(this,name,rwValue)}}if(ElementProto.getAttribute){var orig_getAttribute=ElementProto.getAttribute;this.wb_getAttribute=orig_getAttribute,ElementProto.getAttribute=function getAttribute(name){var result=orig_getAttribute.call(this,name);if(result===null)return result;var lowerName=name;if(name&&(lowerName=name.toLowerCase()),wombat.shouldRewriteAttr(this.tagName,lowerName)){var maybeWBOSRC=wombat.retrieveWBOSRC(this);return maybeWBOSRC?maybeWBOSRC:wombat.extractOriginalURL(result)}return wombat.startsWith(lowerName,"data-")&&wombat.startsWithOneOf(result,wombat.wb_prefixes)?wombat.extractOriginalURL(result):result}}}},Wombat.prototype.initSvgImageOverrides=function(){if(this.$wbwindow.SVGImageElement){var svgImgProto=this.$wbwindow.SVGImageElement.prototype,orig_getAttr=svgImgProto.getAttribute,orig_getAttrNS=svgImgProto.getAttributeNS,orig_setAttr=svgImgProto.setAttribute,orig_setAttrNS=svgImgProto.setAttributeNS,wombat=this;svgImgProto.getAttribute=function getAttribute(name){var value=orig_getAttr.call(this,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.getAttributeNS=function getAttributeNS(ns,name){var value=orig_getAttrNS.call(this,ns,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.setAttribute=function setAttribute(name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttr.call(this,name,rwValue)},svgImgProto.setAttributeNS=function setAttributeNS(ns,name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttrNS.call(this,ns,name,rwValue)}}},Wombat.prototype.initCreateElementNSFix=function(){if(this.$wbwindow.document.createElementNS&&this.$wbwindow.Document.prototype.createElementNS){var orig_createElementNS=this.$wbwindow.document.createElementNS,wombat=this,createElementNS=function createElementNS(namespaceURI,qualifiedName){return orig_createElementNS.call(wombat.proxyToObj(this),wombat.extractOriginalURL(namespaceURI),qualifiedName)};this.$wbwindow.Document.prototype.createElementNS=createElementNS,this.$wbwindow.document.createElementNS=createElementNS}},Wombat.prototype.initInsertAdjacentElementHTMLOverrides=function(){var Element=this.$wbwindow.Element;if(Element&&Element.prototype){var elementProto=Element.prototype,rewriteFn=this.rewriteInsertAdjHTMLOrElemArgs;if(elementProto.insertAdjacentHTML){var origInsertAdjacentHTML=elementProto.insertAdjacentHTML;elementProto.insertAdjacentHTML=function insertAdjacentHTML(position,text){return rewriteFn(this,origInsertAdjacentHTML,position,text,true)}}if(elementProto.insertAdjacentElement){var origIAdjElem=elementProto.insertAdjacentElement;elementProto.insertAdjacentElement=function insertAdjacentElement(position,element){return rewriteFn(this,origIAdjElem,position,element,false)}}}},Wombat.prototype.initDomOverride=function(){var Node=this.$wbwindow.Node;if(Node&&Node.prototype){var rewriteFn=this.rewriteNodeFuncArgs;if(Node.prototype.appendChild){var originalAppendChild=Node.prototype.appendChild;Node.prototype.appendChild=function appendChild(newNode,oldNode){return rewriteFn(this,originalAppendChild,newNode,oldNode)}}if(Node.prototype.insertBefore){var originalInsertBefore=Node.prototype.insertBefore;Node.prototype.insertBefore=function insertBefore(newNode,oldNode){return rewriteFn(this,originalInsertBefore,newNode,oldNode)}}if(Node.prototype.replaceChild){var originalReplaceChild=Node.prototype.replaceChild;Node.prototype.replaceChild=function replaceChild(newNode,oldNode){return rewriteFn(this,originalReplaceChild,newNode,oldNode)}}this.overridePropToProxy(Node.prototype,"ownerDocument"),this.overridePropToProxy(this.$wbwindow.HTMLHtmlElement.prototype,"parentNode"),this.overridePropToProxy(this.$wbwindow.Event.prototype,"target")}this.$wbwindow.Element&&this.$wbwindow.Element.prototype&&(this.overrideParentNodeAppendPrepend(this.$wbwindow.Element),this.overrideChildNodeInterface(this.$wbwindow.Element,false)),this.$wbwindow.DocumentFragment&&this.$wbwindow.DocumentFragment.prototype&&this.overrideParentNodeAppendPrepend(this.$wbwindow.DocumentFragment)},Wombat.prototype.initDocOverrides=function($document){if(Object.defineProperty){this.overrideReferrer($document),this.defGetterProp($document,"origin",function origin(){return this.WB_wombat_location.origin}),this.defGetterProp(this.$wbwindow,"origin",function origin(){return this.WB_wombat_location.origin});var wombat=this,domain_setter=function domain(val){var loc=this.WB_wombat_location;loc&&wombat.endsWith(loc.hostname,val)&&(this.__wb_domain=val)},domain_getter=function domain(){return this.__wb_domain||this.WB_wombat_location.hostname};this.defProp($document,"domain",domain_setter,domain_getter)}},Wombat.prototype.initDocWriteOpenCloseOverride=function(){if(this.$wbwindow.DOMParser){var DocumentProto=this.$wbwindow.Document.prototype,$wbDocument=this.$wbwindow.document,docWriteWritelnRWFn=this.rewriteDocWriteWriteln,orig_doc_write=$wbDocument.write,new_write=function write(){return docWriteWritelnRWFn(this,orig_doc_write,arguments)};$wbDocument.write=new_write,DocumentProto.write=new_write;var orig_doc_writeln=$wbDocument.writeln,new_writeln=function writeln(){return docWriteWritelnRWFn(this,orig_doc_writeln,arguments)};$wbDocument.writeln=new_writeln,DocumentProto.writeln=new_writeln;var wombat=this,orig_doc_open=$wbDocument.open,new_open=function open(){var res,thisObj=wombat.proxyToObj(this);if(arguments.length===3){var rwUrl=wombat.rewriteUrl(arguments[0],false,"mp_");res=orig_doc_open.call(thisObj,rwUrl,arguments[1],arguments[2]),wombat.initNewWindowWombat(res,arguments[0])}else res=orig_doc_open.call(thisObj),wombat.initNewWindowWombat(thisObj.defaultView);return res};$wbDocument.open=new_open,DocumentProto.open=new_open;var originalClose=$wbDocument.close,newClose=function close(){var thisObj=wombat.proxyToObj(this);return wombat.initNewWindowWombat(thisObj.defaultView),originalClose.__WB_orig_apply?originalClose.__WB_orig_apply(thisObj,arguments):originalClose.apply(thisObj,arguments)};$wbDocument.close=newClose,DocumentProto.close=newClose;var oBodyGetter=this.getOrigGetter(DocumentProto,"body"),oBodySetter=this.getOrigSetter(DocumentProto,"body");oBodyGetter&&oBodySetter&&this.defProp(DocumentProto,"body",function body(newBody){return newBody&&(newBody instanceof HTMLBodyElement||newBody instanceof HTMLFrameSetElement)&&wombat.rewriteElemComplete(newBody),oBodySetter.call(wombat.proxyToObj(this),newBody)},oBodyGetter)}},Wombat.prototype.initIframeWombat=function(iframe){var win;win=iframe._get_contentWindow?iframe._get_contentWindow.call(iframe):iframe.contentWindow;try{if(!win||win===this.$wbwindow||win._skip_wombat||win._wb_wombat)return}catch(e){return}var src=iframe.src;this.initNewWindowWombat(win,src)},Wombat.prototype.initNewWindowWombat=function(win,src){var fullWombat=false;if(win&&!win._wb_wombat){if((!src||src===""||this.startsWithOneOf(src,["about:blank","javascript:"]))&&(fullWombat=true),!fullWombat&&this.wb_info.isSW){var origURL=this.extractOriginalURL(src);(origURL==="about:blank"||origURL.startsWith("srcdoc:")||origURL.startsWith("blob:"))&&(fullWombat=true)}if(fullWombat){var newInfo={};Object.assign(newInfo,this.wb_info);var wombat=new Wombat(win,newInfo);win._wb_wombat=wombat.wombatInit()}else this.initProtoPmOrigin(win),this.initPostMessageOverride(win),this.initMessageEventOverride(win),this.initCheckThisFunc(win)}},Wombat.prototype.initTimeoutIntervalOverrides=function(){var rewriteFn=this.rewriteSetTimeoutInterval;if(this.$wbwindow.setTimeout&&!this.$wbwindow.setTimeout.__$wbpatched$__){var originalSetTimeout=this.$wbwindow.setTimeout;this.$wbwindow.setTimeout=function setTimeout(){return rewriteFn(this,originalSetTimeout,arguments)},this.$wbwindow.setTimeout.__$wbpatched$__=true}if(this.$wbwindow.setInterval&&!this.$wbwindow.setInterval.__$wbpatched$__){var originalSetInterval=this.$wbwindow.setInterval;this.$wbwindow.setInterval=function setInterval(){return rewriteFn(this,originalSetInterval,arguments)},this.$wbwindow.setInterval.__$wbpatched$__=true}},Wombat.prototype.initWorkerOverrides=function(){var wombat=this;if(this.$wbwindow.Worker&&!this.$wbwindow.Worker._wb_worker_overriden){var orig_worker=this.$wbwindow.Worker;this.$wbwindow.Worker=function(Worker_){return function Worker(url,options){return wombat.domConstructorErrorChecker(this,"Worker",arguments),new Worker_(wombat.rewriteWorker(url),options)}}(orig_worker),this.$wbwindow.Worker.prototype=orig_worker.prototype,Object.defineProperty(this.$wbwindow.Worker.prototype,"constructor",{value:this.$wbwindow.Worker}),this.$wbwindow.Worker._wb_worker_overriden=true}if(this.$wbwindow.SharedWorker&&!this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden){var oSharedWorker=this.$wbwindow.SharedWorker;this.$wbwindow.SharedWorker=function(SharedWorker_){return function SharedWorker(url,options){return wombat.domConstructorErrorChecker(this,"SharedWorker",arguments),new SharedWorker_(wombat.rewriteWorker(url),options)}}(oSharedWorker),this.$wbwindow.SharedWorker.prototype=oSharedWorker.prototype,Object.defineProperty(this.$wbwindow.SharedWorker.prototype,"constructor",{value:this.$wbwindow.SharedWorker}),this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden=true}if(this.$wbwindow.ServiceWorkerContainer&&this.$wbwindow.ServiceWorkerContainer.prototype&&this.$wbwindow.ServiceWorkerContainer.prototype.register){var orig_register=this.$wbwindow.ServiceWorkerContainer.prototype.register;this.$wbwindow.ServiceWorkerContainer.prototype.register=function register(scriptURL,options){var newScriptURL=new URL(scriptURL,wombat.$wbwindow.document.baseURI).href,mod=wombat.getPageUnderModifier();return options&&options.scope?options.scope=wombat.rewriteUrl(options.scope,false,mod):options={scope:wombat.rewriteUrl("/",false,mod)},orig_register.call(this,wombat.rewriteUrl(newScriptURL,false,"sw_"),options)}}if(this.$wbwindow.Worklet&&this.$wbwindow.Worklet.prototype&&this.$wbwindow.Worklet.prototype.addModule&&!this.$wbwindow.Worklet.__wb_workerlet_overriden){var oAddModule=this.$wbwindow.Worklet.prototype.addModule;this.$wbwindow.Worklet.prototype.addModule=function addModule(moduleURL,options){var rwModuleURL=wombat.rewriteUrl(moduleURL,false,"js_");return oAddModule.call(this,rwModuleURL,options)},this.$wbwindow.Worklet.__wb_workerlet_overriden=true}},Wombat.prototype.initLocOverride=function(loc,oSetter,oGetter){if(Object.defineProperty)for(var prop,i=0;i=0&&props.splice(foundInx,1);return props}})}catch(e){console.log(e)}},Wombat.prototype.initHashChange=function(){if(this.$wbwindow.__WB_top_frame){var wombat=this,receive_hash_change=function receive_hash_change(event){if(event.data&&event.data.from_top){var message=event.data.message;message.wb_type&&(message.wb_type!=="outer_hashchange"||wombat.$wbwindow.location.hash==message.hash||(wombat.$wbwindow.location.hash=message.hash))}},send_hash_change=function send_hash_change(){var message={wb_type:"hashchange",hash:wombat.$wbwindow.location.hash};wombat.sendTopMessage(message)};this.$wbwindow.addEventListener("message",receive_hash_change),this.$wbwindow.addEventListener("hashchange",send_hash_change)}},Wombat.prototype.initPostMessageOverride=function($wbwindow){if($wbwindow.postMessage&&!$wbwindow.__orig_postMessage){var orig=$wbwindow.postMessage,wombat=this;$wbwindow.__orig_postMessage=orig;var postmessage_rewritten=function postMessage(message,targetOrigin,transfer,from_top){var from,src_id,this_obj=wombat.proxyToObj(this);if(this_obj.__WB_source&&this_obj.__WB_source.WB_wombat_location){var source=this_obj.__WB_source;if(from=source.WB_wombat_location.origin,this_obj.__WB_win_id||(this_obj.__WB_win_id={},this_obj.__WB_counter=0),!source.__WB_id){var id=this_obj.__WB_counter;source.__WB_id=id+source.WB_wombat_location.href,this_obj.__WB_counter+=1}this_obj.__WB_win_id[source.__WB_id]=source,src_id=source.__WB_id,this_obj.__WB_source=undefined}else from=window.WB_wombat_location.origin;var to_origin=targetOrigin;to_origin===this_obj.location.origin&&(to_origin=from);var new_message={from:from,to_origin:to_origin,src_id:src_id,message:message,from_top:from_top};if(targetOrigin!=="*"){if(this_obj.location.origin==="null"||this_obj.location.origin==="")return;targetOrigin=this_obj.location.origin}return orig.call(this_obj,new_message,targetOrigin,transfer)};$wbwindow.postMessage=postmessage_rewritten,$wbwindow.Window.prototype.postMessage=postmessage_rewritten;var eventTarget=null;eventTarget=$wbwindow.EventTarget&&$wbwindow.EventTarget.prototype?$wbwindow.EventTarget.prototype:$wbwindow;var _oAddEventListener=eventTarget.addEventListener;eventTarget.addEventListener=function addEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.add_or_get(listener,function(){return wrapEventListener(listener,obj,wombat)}):type==="storage"?wombat.storage_listeners.add_or_get(listener,function(){return wrapSameOriginEventListener(listener,obj)}):rwListener=listener,rwListener)return _oAddEventListener.call(obj,type,rwListener,useCapture)};var _oRemoveEventListener=eventTarget.removeEventListener;eventTarget.removeEventListener=function removeEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.remove(listener):type==="storage"?wombat.storage_listeners.remove(listener):rwListener=listener,rwListener)return _oRemoveEventListener.call(obj,type,rwListener,useCapture)};var override_on_prop=function(onevent,wrapperFN){var orig_setter=wombat.getOrigSetter($wbwindow,onevent),setter=function(value){this["__orig_"+onevent]=value;var obj=wombat.proxyToObj(this),listener=value?wrapperFN(value,obj,wombat):value;return orig_setter.call(obj,listener)},getter=function(){return this["__orig_"+onevent]};wombat.defProp($wbwindow,onevent,setter,getter)};override_on_prop("onmessage",wrapEventListener),override_on_prop("onstorage",wrapSameOriginEventListener)}},Wombat.prototype.initMessageEventOverride=function($wbwindow){!$wbwindow.MessageEvent||$wbwindow.MessageEvent.prototype.__extended||(this.addEventOverride("target"),this.addEventOverride("srcElement"),this.addEventOverride("currentTarget"),this.addEventOverride("eventPhase"),this.addEventOverride("path"),this.overridePropToProxy($wbwindow.MessageEvent.prototype,"source"),$wbwindow.MessageEvent.prototype.__extended=true)},Wombat.prototype.initUIEventsOverrides=function(){this.overrideAnUIEvent("UIEvent"),this.overrideAnUIEvent("MouseEvent"),this.overrideAnUIEvent("TouchEvent"),this.overrideAnUIEvent("FocusEvent"),this.overrideAnUIEvent("KeyboardEvent"),this.overrideAnUIEvent("WheelEvent"),this.overrideAnUIEvent("InputEvent"),this.overrideAnUIEvent("CompositionEvent")},Wombat.prototype.initOpenOverride=function(){var orig=this.$wbwindow.open;this.$wbwindow.Window.prototype.open&&(orig=this.$wbwindow.Window.prototype.open);var wombat=this,open_rewritten=function open(strUrl,strWindowName,strWindowFeatures){var rwStrUrl=wombat.rewriteUrl(strUrl,false,""),res=orig.call(wombat.proxyToObj(this),rwStrUrl,strWindowName,strWindowFeatures);return wombat.initNewWindowWombat(res,strUrl),res};this.$wbwindow.open=open_rewritten,this.$wbwindow.Window.prototype.open&&(this.$wbwindow.Window.prototype.open=open_rewritten);for(var i=0;i + + +Chapter 1.5: Fixed point Math + + + + +

Chapter 1.5: Fixed Point Math

+ +

If you've been trying to write some games on a modern system (ie. your +PC), you probably already know about using floats to implement momentum and +jumping physics in a 2D platformer, or have something move towards a direction +specified in degrees rather than X,Y velocity values to have said object +move at an angle, like a ship in an Asteroids style game or a car in a +top-down racer... On the PlayStation, these principles still apply. But +there's a problem; the PlayStation does not have a hardware floating point +unit.

+ +

Whilst you can still use floats on the PlayStation as the compiler will +resort to software emulation to perform such operations, which you might +be able to get away with, it's not exactly the most ideal method as software +emulation of floats is quite slow, and will become an issue if used for +collision detection or processing movement of a hundred projectile +entities... The alternative that's better suited for a system without a +floating point unit is to use a integer based fractional number system +commonly known as fixed point math.

+ +

As the name suggests, fixed point math is a trick for storing fractional +numbers with fixed points, in this case an integer scale of 4096 will have +a range between zero to 4095 representing a fractional value, with 4096 +representing a integer value of 1. Fixed point math is used heavily in games +made for systems that lacked a floating point unit (5th generation and older +consoles), or where integer math is considerably faster than floating point +math and not all systems feature a floating point unit +(ie. IBM compatible PCs).

+ +

This chapter covers the general idea and basics of performing fixed point +integer arithmetic. This is a highly essential chapter when getting into +PlayStation homebrew development, as learning it will increase the range of +games you can write for the console immensely. If you've already learned some +binary encoding of numeric values this chapter might be a bit easier to +understand.

+ +

Compatible with PSn00bSDK: Yes

+ +

Tutorial Index

+ + +

Principle of Fixed Point Math

+ +

The basic principle of fixed point integer math is instead of representing +a real number of 1.25 as a floating point number, you instead represent +the number as an integer value from a scale value, in this case 4096 which is +defined as ONE in the GTE library headers. So a real value of 1.25 +will look like 5120 when converted to fixed point math with a scale of 4096. +Converting a floating point number to a fixed point number is achieved by +simply multiplying the real number against the scale value and rounding off +the fractions, turning the result into a plain integer value. The following +table shows some sample floating point values represented in fixed point.

+ +
+ + + + + + + + + +
Floating point representationFixed point representation
1.255120
0.753072
0.2819
21.28487179
1.0 = 4096
+
+ +

Using a scale value of 4096 is most ideal for PlayStation projects not +only because it provides a good balance between precision and the maximum +decimal value it can store, alongside allowing fixed point values to be +rounded and divided quickly with simple bit shift operations and AND masks, +but also because the Geometry Transformation Engine (GTE) also uses the +same scale value for some of its registers. Any scale value can be used to +gain more precision or to increase the decimal range limit. Generally +a power of two scale value is recommended for performance reasons.

+ +
+ + + + + + + + + + + + + + + +
Bits313029282726252423222120191817161514131211109876543210
DescriptionSignDecimal (19 bits)Fractional (12 bits)
+
+ +

The table above illustrates a fixed point value with a scale value +of 4096 in a signed 32-bit integer. As there's only 19 bits available for +the decimal portion the maximum range for the decimal value is -524288 to +524287.

+ + +

Fixed Point Arithmetic Basics

+ +

Simple mathematical operations in fixed point such as addition and +subtraction is not that much different to performing the same operations +with integers, only values have to be based around the scale value.

+
fixed_value += 4096;    // += 1.0
+fixed_value -= 4096;    // -= 1.0
+fixed_value += 2048;    // += 0.5
+fixed_value -= 6144;    // -= 1.5
+
+ +

For decimal multiplication and divide operations, the value for the +dividend or multiplicand stays as-is.

+
fixed_value *= 5;       // *= 5;
+fixed_value /= 10;      // /= 10;
+
+ +

The equation for performing fractional multiplications is a little +tricky, but still not too complicated.

+
multiplicand    = 2048;                             // multiplicand = 0.5;
+fixed_value     = (fixed_value*multiplicand)>>12;   // fixed_value *= multiplicand;
+
+multiplicand    = 6144;                             // multiplicand = 1.5;
+fixed_value     = (fixed_value*multiplicand)>>12;   // fixed_value *= multiplicand;
+
+ +

For fractional divisions, the value will need to be multiplied by the +scale value and then divided by the dividend. Keep in mind that division by +zero would still occur if the dividend is zero. +

dividend        = 2048;                             // dividend = 0.5;
+fixed_value     = (fixed_value*4096)/dividend;      // fixed_value /= dividend;
+
+dividend        = 6144;                             // dividend = 1.5;
+fixed_value     = (fixed_value*4096)/dividend;      // fixed_value /= dividend;
+
+ + +

Implementing Tank Controls with Fixed Point

+ +

As a little exercise, this part of the chapter will go through using fixed +point math to implement tank controls. The basic jist of this is that instead +of moving the player position directly by which direction of the D-pad is +pressed, instead, left and right will adjust the angle the player is facing +while up and down moves the player forward and backward perpendicular to +where the player is facing. To do this requires using sine and cosine values +and conveniently, the GTE libraries of both SDKs feature integer based sine +and cosine functions which is exactly what's needed for this exercise.

+ +

In the official PsyQ or Programmer's Tool SDKs there are two integer based +sine and cosine functions which are rsin()/rcos() and +csin()/ccos(). The differences between the two is that +rsin()/rcos() takes up less code but is slower, whereas +csin()/ccos() takes up more code but is faster. PSn00bSDK on +the other hand only has one set of functions called isin()/icos(), +which are based on a Taylor series implementation which is both small and fast. +PSn00bSDK provides definitions for rsin()/rcos() and +csin()/ccos() that simply points to isin()/icos() +for compatibility with PsyQ / Programmer's Tool projects and sample code.

+ +

These functions also take degrees in a fixed point range of 0 to 4096 +where 4096 equals to 360 degrees, so a value of 45 degrees will be +specified as 512. The value returned is also in a fixed point notation +with a scale of 4096, where 4096 equals to 1.0.

+ +

Working from code from the controllers chapter, rewrite the code that +defines the initial values of the player position to begin at the center +of the screen. Because the player coordinates will be in fixed point, the +screen center coordinate must be multiplied by ONE.

+ +
pos_x = ONE*(disp[0].disp.w>>1);
+pos_y = ONE*(disp[0].disp.h>>1);
+
+ +

Then, define a new variable for the player's angle and set the initial +value to zero.

+ +
int angle;
+
+angle = 0;
+
+ +

The player will be represented as a rotating triangle in this chaper, so +a simple array of vector coordinates will define the shape of the triangle. +The triangle will be rotated with, you guessed it, fixed point math as well. +

+ +
SVECTOR player_tri[] =
+{
+    {   0, -20,  0  },
+    {  10,  20,  0  },
+    { -10,  20,  0  }
+};
+
+ +

Then ditch the code for sorting TILE and SPRT primitives +and replace it with the following routine that sorts a rotating triangle.

+
POLY_F3 *tri;
+
+...
+
+// Rotate the triangle coordinates based on the player's angle
+// as well as apply the position
+for( i=0; i<3; i++ )
+{
+    v[i].vx = (((player_tri[i].vx*ccos( angle ))
+        -(player_tri[i].vy*csin( angle )))>>12)+(pos_x>>12);
+    v[i].vy = (((player_tri[i].vy*ccos( angle ))
+        +(player_tri[i].vx*csin( angle )))>>12)+(pos_y>>12);
+}
+
+// Sort the player triangle
+tri = (POLY_F3*)nextpri;
+setPolyF3( tri );
+setRGB0( tri, 255, 255, 0 );
+setXY3( tri,
+    v[0].vx, v[0].vy,
+    v[1].vx, v[1].vy,
+    v[2].vx, v[2].vy );
+    
+addPrim( ot[db], tri );
+nextpri += sizeof(POLY_F3);
+
+ +

And then replace the input code with the following. If you've written +something that played like Asteroids in the past, this may look pretty +familiar.

+
if( !(pad->btn&PAD_UP) )            // test UP
+{
+    pos_x += csin( angle );
+    pos_y -= ccos( angle );
+}
+else if( !(pad->btn&PAD_DOWN) )     // test DOWN
+{
+    pos_x -= csin( angle );
+    pos_y += ccos( angle );
+}
+if( !(pad->btn&PAD_LEFT) )          // test LEFT
+{
+    // Turns counter-clockwise
+    angle -= 16;
+}
+else if( !(pad->btn&PAD_RIGHT) )    // test RIGHT
+{
+    // Turns clockwise
+    angle += 16;
+}
+
+ +

Next is to finally add some text drawing so the player coordinates can be +displayed on screen, so you can see how fixed point numbers work while +interacting with the sample program more easily. This can be done with the +debug font functions provided by libetc or psxetc in both +PsyQ / Programmer's Tool and PSn00bSDK respectively. These font functions are +intended for debugging purposes, so they're not fit for drawing things like +player status with custom fonts and lower case letters.

+ +

Start by loading the debug font texture to VRAM using FntLoad(), +then, initialize a font window using FntOpen(). Ideally, you should +put these calls inside the init() function.

+ +
// Load the font texture on the upper-right corner of the VRAM
+FntLoad( 960, 0 );
+
+// Define a font window of 100 characters covering the whole screen
+FntOpen( 0, 8, 320, 224, 0, 100 );
+
+ +

Text can be printed using FntPrint() which works more or less like +printf(), only text output is directed to the specified font window. +The first argument specifies which font window the text should go to, the +value of which is obtained through the return value of FntOpen(). +Specifying -1 directs text to the last opened font window. The debug font +routines can only draw uppercase text, and text color and font cannot be +customized.

+ +

In PsyQ / Programmers tool the first argument can be omitted, but modern +GNU GCC does not support this convention anymore.

+ +
// Print player coordinates
+FntPrint( -1, "POS_X=%d (%d.%d)\n", pos_x, (pos_x>>12), (pos_x&0xfff) );
+FntPrint( -1, "POS_Y=%d (%d.%d)\n", pos_y, (pos_y>>12), (pos_y&0xfff) );
+FntPrint( -1, "ANGLE=%d\n", angle );
+
+ +

Then to make the text actually appear, call FntFlush() to draw the +characters 'printed' by FntPrint() and flush the character buffer. +This should be called immediately before the display() call in the +sample code.

+ +
// Draw and flush the character buffer
+FntFlush( -1 );
+
+ +

The finished code should look like the following (the texture stuff +remains for future samples):

+
#include <sys/types.h>	// This provides typedefs needed by libgte.h and libgpu.h
+#include <stdio.h>	// Not necessary but include it anyway
+#include <psxetc.h>	// Includes some functions that controls the display
+#include <psxgte.h>	// GTE header, not really used but libgpu.h depends on it
+#include <psxgpu.h>	// GPU library header
+#include <psxapi.h>
+
+#define OTLEN 8         // Ordering table length (recommended to set as a define
+                        // so it can be changed easily)
+
+DISPENV disp[2];        // Display/drawing buffer parameters
+DRAWENV draw[2];
+int db = 0;
+
+// PSn00bSDK requires having all u_long types replaced with
+// u_int, as u_long in modern GCC that PSn00bSDK uses defines it as a 64-bit integer.
+
+u_int ot[2][OTLEN];    // Ordering table length
+char pribuff[2][32768]; // Primitive buffer
+char *nextpri;          // Next primitive pointer
+
+int tim_mode;           // TIM image parameters
+RECT tim_prect,tim_crect;
+int tim_uoffs,tim_voffs;
+
+// Pad stuff
+#define PAD_SELECT      1
+#define PAD_L3          2
+#define PAD_R3          4
+#define PAD_START       8
+#define PAD_UP          16
+#define PAD_RIGHT       32
+#define PAD_DOWN        64
+#define PAD_LEFT        128
+#define PAD_L2          256
+#define PAD_R2          512
+#define PAD_L1          1024
+#define PAD_R1          2048
+#define PAD_TRIANGLE    4096
+#define PAD_CIRCLE      8192
+#define PAD_CROSS       16384
+#define PAD_SQUARE      32768
+
+typedef struct _PADTYPE
+{
+    unsigned char	stat;
+    unsigned char	len:4;
+    unsigned char	type:4;
+    unsigned short	btn;
+    unsigned char	rs_x,rs_y;
+    unsigned char	ls_x,ls_y;
+} PADTYPE;
+
+u_char padbuff[2][34];
+
+// For the player triangle
+SVECTOR player_tri[] = {
+    {   0, -20,  0  },
+    {  10,  20,  0  },
+    { -10,  20,  0  }
+};
+
+void display() {
+    
+    DrawSync(0);                // Wait for any graphics processing to finish
+    
+    VSync(0);                   // Wait for vertical retrace
+
+    PutDispEnv(&disp[db]);      // Apply the DISPENV/DRAWENVs
+    PutDrawEnv(&draw[db]);
+
+    SetDispMask(1);             // Enable the display
+
+    DrawOTag(ot[db]+OTLEN-1);   // Draw the ordering table
+    
+    db = !db;                   // Swap buffers on every pass (alternates between 1 and 0)
+    nextpri = pribuff[db];      // Reset next primitive pointer
+    
+}
+
+// Texture upload function
+void LoadTexture(u_int *tim, TIM_IMAGE *tparam) {
+
+    // Read TIM parameters (PsyQ)
+    //OpenTIM(tim);
+    //ReadTIM(tparam);
+
+    // Read TIM parameters (PSn00bSDK)
+    GetTimInfo(tim, tparam);
+
+    // Upload pixel data to framebuffer
+    LoadImage(tparam->prect, tparam->paddr);
+    DrawSync(0);
+
+    // Upload CLUT to framebuffer if present
+    if( tparam->mode & 0x8 ) {
+
+        LoadImage(tparam->crect, tparam->caddr);
+        DrawSync(0);
+
+    }
+
+}
+
+void loadstuff(void) {
+
+    TIM_IMAGE my_image;         // TIM image parameters
+
+    extern u_int tim_my_image[];
+
+    // Load the texture
+    LoadTexture(tim_my_image, &my_image);
+
+    // Copy the TIM coordinates
+    tim_prect   = *my_image.prect;
+    tim_crect   = *my_image.crect;
+    tim_mode    = my_image.mode;
+
+    // Calculate U,V offset for TIMs that are not page aligned
+    tim_uoffs = (tim_prect.x%64)<<(2-(tim_mode&0x3));
+    tim_voffs = (tim_prect.y&0xff);
+
+}
+
+// To make main look tidy, init stuff has to be moved here
+void init(void) {
+    
+    // Reset graphics
+    ResetGraph(0);
+
+    // First buffer
+    SetDefDispEnv(&disp[0], 0, 0, 320, 240);
+    SetDefDrawEnv(&draw[0], 0, 240, 320, 240);
+    // Second buffer
+    SetDefDispEnv(&disp[1], 0, 240, 320, 240);
+    SetDefDrawEnv(&draw[1], 0, 0, 320, 240);
+
+    draw[0].isbg = 1;               // Enable clear
+    setRGB0(&draw[0], 63, 0, 127);  // Set clear color (dark purple)
+    draw[1].isbg = 1;
+    setRGB0(&draw[1], 63, 0, 127);
+
+    nextpri = pribuff[0];           // Set initial primitive pointer address
+ 
+    // load textures and possibly other stuff
+    loadstuff();
+
+    // set tpage of lone texture as initial tpage
+    draw[0].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+    draw[1].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+
+    // apply initial drawing environment
+    PutDrawEnv(&draw[!db]);
+    
+    // Initialize the pads
+    InitPAD( padbuff[0], 34, padbuff[1], 34 );
+    
+    // Begin polling
+    StartPAD();
+    
+    // To avoid VSync Timeout error, may not be defined in PsyQ
+    ChangeClearPAD( 1 );
+    
+    // Load the font texture on the upper-right corner of the VRAM
+    FntLoad( 960, 0 );
+
+    // Define a font window of 100 characters covering the whole screen
+    FntOpen( 0, 8, 320, 224, 0, 100 );
+ 
+}
+
+int main() {
+    
+    int i;
+    int pos_x,pos_y,angle;
+    PADTYPE *pad;
+    POLY_F3 *tri;
+    SVECTOR v[3];
+    
+    TILE *tile;                         // Pointer for TILE
+    SPRT *sprt;                         // Pointer for SPRT
+
+    // Init stuff
+    init();
+    
+    pos_x = ONE*(disp[0].disp.w>>1);
+    pos_y = ONE*(disp[0].disp.h>>1);
+    angle = 0;
+    
+    while(1) {
+    
+        // Parse controller input
+        pad = (PADTYPE*)padbuff[0];
+
+        // Only parse inputs when a controller is connected
+        if( pad->stat == 0 )
+        {
+            // Only parse when a digital pad, 
+            // dual-analog and dual-shock is connected
+            if( ( pad->type == 0x4 ) || 
+                ( pad->type == 0x5 ) || 
+                ( pad->type == 0x7 ) )
+            {
+                if( !(pad->btn&PAD_UP) )            // test UP
+                {
+                    pos_x += csin( angle );
+                    pos_y -= ccos( angle );
+                }
+                else if( !(pad->btn&PAD_DOWN) )     // test DOWN
+                {
+                    pos_x -= csin( angle );
+                    pos_y += ccos( angle );
+                }
+                if( !(pad->btn&PAD_LEFT) )          // test LEFT
+                {
+                    angle -= 16;
+                }
+                else if( !(pad->btn&PAD_RIGHT) )    // test RIGHT
+                {
+                    angle += 16;
+                }
+            }
+        }
+
+        ClearOTagR(ot[db], OTLEN);      // Clear ordering table
+        
+        // Rotate the triangle coordinates based on the player's angle
+        // as well as apply the position
+        for( i=0; i<3; i++ )
+        {
+            v[i].vx = (((player_tri[i].vx*icos( angle ))
+                -(player_tri[i].vy*csin( angle )))>>12)+(pos_x>>12);
+            v[i].vy = (((player_tri[i].vy*icos( angle ))
+                +(player_tri[i].vx*csin( angle )))>>12)+(pos_y>>12);
+        }
+        
+        // Sort the player triangle
+        tri = (POLY_F3*)nextpri;
+        setPolyF3( tri );
+        setRGB0( tri, 255, 255, 0 );
+        setXY3( tri,
+            v[0].vx, v[0].vy,
+            v[1].vx, v[1].vy,
+            v[2].vx, v[2].vy );
+        addPrim( ot[db], tri );
+        nextpri += sizeof(POLY_F3);
+        
+        // Print player coordinates
+        FntPrint( -1, "POS_X=%d (%d.%d)\n", pos_x, (pos_x>>12), (pos_x&0xfff) );
+        FntPrint( -1, "POS_Y=%d (%d.%d)\n", pos_y, (pos_y>>12), (pos_y&0xfff) );
+        FntPrint( -1, "ANGLE=%d\n", angle );
+
+        // Draw and flush the character buffer
+        FntFlush( -1 );
+        
+        // Update the display
+        display();
+        
+    }
+    
+    return 0;
+}
+
+ +

Compile and execute the code and you should get a triangle that controls +more or less like a tank. Press the left and right directional buttons to +turn the triangle while pressing up and down moves the triangle forwards and +backward perpendicular to where the triangle is pointing.

+ +
+ +
+ + +

Asteroids Style Physics with Fixed Point

+ +

Now to make the tank controls sample a little more interesting by making +it control like an Asteroids or Spacewar game. Basically, instead of +accumulating the player coordinates directly with values based on the +player's angle you accumulate them to a velocity vector, which in turn +accumulates on the player's coordinates. This way, simple momentum based +physics are archieved.

+ +

This can be done by implementing some changes to the last sample. Start +by adding variables in main() that'll be used to store the player's +velocity coordinates.

+ +
int vel_x,vel_y;
+
+...
+
+// just to make sure they don't contain garbage
+vel_x = 0;
+vel_y = 0;
+
+ +

Next, modify some of the pad code to accumulate player movement +towards the velocity variables instead of the player's coordinates. +Put some bit shifts that divides the sin/cos values by 8 otherwise +the 'ship' will accelerate too quickly.

+ +
if( !(pad->btn&PAD_UP) )            // test UP
+{
+    vel_x += csin( angle )>>3;
+    vel_y -= ccos( angle )>>3;
+}
+else if( !(pad->btn&PAD_DOWN) )     // test DOWN
+{
+    vel_x -= csin( angle )>>3;
+    vel_y += ccos( angle )>>3;
+}
+
+ +

Immediately after the pad code add a few lines that accumulates the +velocity coordinates to the player's coordinates.

+ +
// accumulate player coordinates by its velocity
+pos_x += vel_x;
+pos_y += vel_y;
+
+ +

And to keep the player from going off-screen, place the following lines +immediately after the above code to wrap the player's coordinates when they +go off-screen.

+ +
// wrap player coordinates from going off-screen
+if( (pos_x>>12) < 0 )
+{
+    pos_x += (320<<12);
+}
+if( (pos_x>>12) > 320 )
+{
+    pos_x -= (320<<12);
+}
+if( (pos_y>>12) < 0 )
+{
+    pos_y += (240<<12);
+}
+if( (pos_y>>12) > 240 )
+{
+    pos_y -= (240<<12);
+}
+
+ +

Then place some additional FntPrint() calls that displays the +values from the velocity variables. You'll also need to increase the +character buffer size of the FntOpen() call (the last argument) +from 100 to 150.

+ +
FntPrint( -1, "VEL_X=%d (%d.%d)\n", vel_x, (vel_x>>12), (vel_x&0xfff) );
+FntPrint( -1, "VEL_Y=%d (%d.%d)\n", vel_y, (vel_y>>12), (vel_y&0xfff) );
+
+ +

Compile the sample demo and the triangle should behave a lot like an +Asteroids ship.

+ +
+ +
+ +

If you want velocity to slowly diminish overtime, add the following +lines just before the velocity vectors are accumulated to the player +coordinates. It also acts as a velocity constraint as well.

+ +
// equivalent to multiplying each axis by 0.9765625
+vel_x = (vel_x*4000)>>12;
+vel_y = (vel_y*4000)>>12;
+
+ + +

Bonus: Fixed Point Sprite Rotation

+ +

Since it is something new programmers would find incredibly handy and to +show off some of what the 32-bit console can do, especially for PSn00bSDK folk +as it does not have an equivalent to libgs, here's a fixed point based sprite +rotation and scaling routine that draws a rotated and scaled sprite with a +single quad. The math for rotation is more or less the same as how the player +triangle is rotated.

+ +
void sortRotSprite( int x, int y, int pw, int ph, int angle, int scale )
+{
+    POLY_FT4 *quad;
+    SVECTOR	s[4];
+    SVECTOR	v[4];
+
+    int i,cx,cy;
+
+    // calculate the pivot point (center) of the sprite
+    cx = pw>>1;
+    cy = ph>>1;
+
+    // increment by 0.5 on the bottom and right coords so scaling
+    // would increment a bit smoother
+    s[0].vx = -(((pw*scale)>>12)-cx);
+    s[0].vy = -(((ph*scale)>>12)-cy);
+
+    s[1].vx = (((pw*scale)+2048)>>12)-cx;
+    s[1].vy = s[0].vy;
+
+    s[2].vx = -(((pw*scale)>>12)-cx);
+    s[2].vy = (((ph*scale)+2048)>>12)-cy;
+
+    s[3].vx = (((pw*scale)+2048)>>12)-cx;
+    s[3].vy = s[2].vy;
+    
+    // a simple but pretty effective optimization trick
+    cx = ccos( angle );
+    cy = csin( angle );
+    
+    // calculate rotated sprite coordinates
+    for( i=0; i<4; i++ )
+    {
+        v[i].vx = (((s[i].vx*cx)
+            -(s[i].vy*cy))>>12)+x;
+        v[i].vy = (((s[i].vy*cx)
+            +(s[i].vx*cy))>>12)+y;
+    }
+
+    // initialize the quad primitive for the sprite
+    quad = (POLY_FT4*)nextpri;
+    setPolyFT4( quad );
+
+    // set CLUT and tpage to the primitive
+    setTPage( quad, tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+    setClut( quad, tim_crect.x, tim_crect.y );
+
+    // set color, screen coordinates and texture coordinates of primitive
+    setRGB0( quad, 128, 128, 128 );
+    setXY4( quad,
+        v[0].vx, v[0].vy,
+        v[1].vx, v[1].vy,
+        v[2].vx, v[2].vy,
+        v[3].vx, v[3].vy );
+    setUVWH( quad, tim_uoffs, tim_voffs, pw, ph );
+
+    // add it to the ordering table
+    addPrim( ot[db], quad );
+    nextpri += sizeof(POLY_FT4);
+
+}
+
+ +

By this point, it should be easy to figure out how to use this function +in the sample above. The angle and scale arguments are of a +fixed point notation, where 4096 is 360 degrees on the former and 4096 is +1.0 on the latter.

+ +
+ +
+ +

Limitations of Fixed Point Math

+ +

One limitation that becomes apparent when performing 32-bit fixed point +math arithmetic is when performing fractional multiplication operations with +large fixed point values due to the C compiler usually always performing +multiplication and bit shifting operations with 32-bit registers, as +multiplying a large fixed point value by a large fixed point multiplicand +will likely exceed the capacity of a 32-bit word, yielding a faulty +result.

+ +

The MIPS R3000 CPU of the PlayStation can actually return a 64-bit +multiplication result, but the C compiler doesn't really make use of this +trait when performing multiplication operations with 32-bit integers. So the +only way to effectively take advantage of this is to use some in-line +assembly code. Should be pretty self explanatory on how and where it should +be used.

+ +
/* in-line assembly macro for performing multiplication        */
+/* operations with 12-bit fractions. Uses what is effectively  */
+/* 64-bit maths with both hi and lo result registers to avoid  */
+/* overflow bugs when using 32-bit maths.                      */
+/*                                                             */
+/* Performs r2 = ( r0 * r1 )>>12 with 64-bit arithmetic        */
+/*                                                             */
+#define mult12( r0, r1, r2 ) __asm__ volatile ( \
+    "mult   %1, %0;"        /* multiply values (small * large is faster) */\ 
+    "mflo   $t0;"           /* retrieve the 64-bit result in two regs    */\
+    "mfhi   $t1;"                                                          \
+    "srl    $t0, 12;"       /* equivalent to dividing LO by 4096         */\
+    "and    $t1, 0x0fff;"   /* mask HI result to fit in upper 12 bits of */\
+    "sll    $t1, 20;"       /* LO result, then shift bits to position    */\
+    "or	    $t0, $t1;"      /* combine the bits                          */\
+    "sw	    $t0, ( %2 );"   /* store the result to r2                    */\
+    :																	   \
+    : "r"( r0 ), "r"( r1 ), "r"( r2 ) \
+    : "lo", "hi", "t0", "t1", "memory" )
+
+ +

Though naturally, fixed point math can't really be as precise as floating +point math in certain operations. You can try increasing precision by +increasing the base value, alloting more bits for the fractional part of a +fixed point value, but there will still be something rough about it.

+ + +

Conclusion and Further Reading

+ +

This chapter only really covers some pretty basic but highly essential math +operations with fixed point math, but it should be enough to get you started +in learning the practice. Interestingly fixed point math is still somewhat +talked about around the modern Internet, so you may want to look around for +those for further reading if need-be.

+ +
+ + + + + + +
PreviousBack to IndexNext
+ + + + \ No newline at end of file diff --git a/Docs/Chapter 1.5 Fixed point Math_files/style.css b/Docs/Chapter 1.5 Fixed point Math_files/style.css new file mode 100644 index 0000000..50d70a8 --- /dev/null +++ b/Docs/Chapter 1.5 Fixed point Math_files/style.css @@ -0,0 +1,49 @@ +body { + max-width: 900px; + margin: auto; + padding: 8px; + font-family: sans-serif; + font-size: 14px; + //color: white; + //background: black; +} + +h { + background-color: #e0e0e0; + padding: 2px; +} + +h2 { + border-bottom: 1px solid; + //padding-left: 8px; +} + +img { + display: block; +} + +.footer-table { + font-size: 14px; +} + +.bordered-table { + border-collapse: collapse; + font-size: 11px; +} + +.bordered-table th, .bordered-table td { + border: 1px solid; + padding: 4px; +} + +hr { + border: none; + border-bottom: 1px solid black; +} + +pre { + padding: 8px; + font-size: 12px; + color: black; + background-color: LightGray; +} \ No newline at end of file diff --git a/Docs/Chapter 1.6 Using the CD-ROM.htm b/Docs/Chapter 1.6 Using the CD-ROM.htm new file mode 100644 index 0000000..f71f269 --- /dev/null +++ b/Docs/Chapter 1.6 Using the CD-ROM.htm @@ -0,0 +1,807 @@ + + + +Chapter 1.6: Using the CD-ROM + + + + +

Chapter 1.6: Using the CD-ROM

+ +

So you've managed to make it this far in this tutorial series and have +wrapped your head around uploading textures to VRAM, drawing sprite +primitives with the GPU, handling controller input and rotating sprites +using fixed-point integer math, you should be good to go at making a simple +game with the knowledge you've gained so far. However, there's one more thing +to learn that's very important to know in this endeavor before going forward +and that is using the CD-ROM.

+ +

The most obvious use of the CD-ROM drive on the PlayStation is, well, to +load data files from of course. Such files range from texture images, to +level data and even your program executable that the console loads during +the boot process. You can only go so far by including resources into your +PS-EXE as the console only has 2 megabytes worth of RAM, and more +importantly, they take up memory space that cannot be reclaimed even when +they are no longer needed (ie. texture images).

+ +

This chapter goes over the basic usage and operation of the CD-ROM library +for reading files from the CD-ROM.

+ +

Compatible with PSn00bSDK: Yes

+ +

Tutorial Index

+ + +

Workings of the CD-ROM

+ +

Before going into the know-abouts on using the CD-ROM we shall first +look into a brief explanation of how the CD-ROM format works, as +understanding some of the inner workings of the format will aid you +greatly in understanding the CD-ROM drive as you develop software for +the PlayStation.

+ +

CD-ROM Structure

+ +

A CD-ROM typically consists of a Table-of-Contents or TOC +and a huge spiral track that spans the entire surface of the disc containing +sectors of binary data running along the track. The TOC contains information +about the location of tracks written on the disc, their pregap and their +type of track, a track in this case being the track number to your favorite +song in a music CD. The TOC would also contain information of the next +writable location in multi-session discs, which would in turn contain +another TOC of the next session but this only really applies to CD-R +discs.

+ +

PlayStation game discs typically contain only one track, with the exception +of games, usually early titles, using Red Book CD Audio tracks for music +immediately after the first data track of the disc.

+ +

CD-ROM Sectors

+ +

A CD-ROM sector is a small data block of 2352 bytes but this only +applies to plain CD audio tracks, as the usable sector size is reduced +to 2048 bytes for data storage (ie. files). The remaining space of the +CD-ROM sector is taken up by sync and address bytes used to allow for +precise logical block addressing of the disc, alongside error detection +and correcting codes to help maintain data integrity of each data sector. +This sector format is known as Mode1, with a similar format used by the +PlayStation being Mode2/Form1 from the CD-ROM XA specification as used +by the Philips CD-i.

+ +

As far as the PlayStation is concerned, the console only reads +the data part of the sector on data reads, with the sync/address +and EDC/ECC correction handled transparently by the hardware +unless instructed to ignore EDC/ECC correction on data reads or +to read whole sectors including their EDC/ECC data.

+ +

CD-ROM XA Extensions

+ +

As the PlayStation inherits the CD-ROM XA features from the CD-i as Sony +co-developed the Green Book standard with Philips, the console supports +playing back of special ADPCOM compressed audio tracks, most commonly known +as XA audio.

+ +

XA audio are basically special audio tracks compressed using ADPCM, with +a maximum sample rate of 37.8KHz mono or stereo. Unlike regular Red Book CD +Audio tracks XA audio tracks are interleaved with eight different audio +tracks at once (the interleave can be doubled, tripled and quadrupled by +using mono and/or 18.9KHz sample rate) and is played at double the speed of +plain CD Audio. However, the hardware only supports playing one audio +channel at a time.

+ +

This has some advantages, for one the drive does not need to change speed +when going from audio playback to data reading which can help improve load +times. Secondly, XA audio tracks take up less space overall especially when +the XA audio data is laid out properly (ie. all audio channels are of roughly +the same length to one another). Thirdly, XA audio data can be interleaved +with data sectors, and is used for video playback. The Data/XA Audio interleave +also explains why some files on a PlayStation game disc (usually .STR video +files) cannot be accessed directly under most computer operating systems, +usually resulting in read errors.

+ +

However, XA audio has one downside and that is it tends to skip during +normal playback more than when playing back regular CD Audio tracks.

+ +

There is one more feature of XA audio that's seldom used and that is +channel switching during playback. This would be useful for adding dynamic +music to a game by switching to different channels of different tangents +of a level theme depending on the area the player is currently at. One game +that makes use of this feature is Klonoa: Door to Phantomile.

+ +

The 650MB Limit

+ +

Even though the PlayStation can read 700MB discs it cannot read more +than 650MB of the disc as the console's CD-ROM controller does not take +larger discs into account, as the console was designed before such discs +ever existed and the original CD-ROM specification specifies 650MB as the +standard capacity.

+ +

Ways of Accessing the CD-ROM

+ +

There are two possible methods for accessing the CD-ROM of the PlayStation +that will work on both PsyQ/Programmer's Tool and PSn00bSDK. The first method +and one that is seldom used is the CD-ROM functions included in the console's +BIOS, which consists of calling _96_init() to initialize the BIOS +CD-ROM subsystem and using the BIOS file I/O functions such as open() +and read() with the cdrom: device name. Whilst this method is +technically the most efficient option as it does not introduce any code +overhead to your PlayStation project as the functions already reside in the +console itself, the BIOS CD-ROM functions are rather limited and in a good +number of cases, unstable.

+ +

For one, calling _96_init() clears all but the DMA channel for the +CD-ROM controller, breaking the GPU library unless you call the function before +ResetGraph(). The BIOS functions are also quite slow and does not +support asynchronous reads, even though the hardware itself clearly supports +such operations. It does not even support playing CD Audio nor XA audio tracks +directly, so a different method is often most desired.

+ +

The other, more preferable method is to use the CD-ROM library called +libcd in PsyQ/Programmer's Tool and psxcd in PSn00bSDK +respectively. Not only is it faster and more stable than the BIOS CD-ROM +functions at the cost of some overhead, but it also offers greater control +of the CD-ROM hardware as well. Playing CD Audio and XA audio included.

+ +

Using the CD-ROM Library

+ +

Using the CD-ROM library is quite simple in principle. The first thing +to be done is to initialize the CD-ROM library by calling the function +CdInit(). This function not only initializes the CD-ROM hardware +but also the CD-ROM library itself.

+ +

Initializing the CD-ROM

+ +

After calling ResetGraph() in your init routine you can then call +CdInit() to initialize the CD-ROM library.

+ +
int CdInit(void)
+
+ +

The behavior of this function is very identical in both SDKs, but the +PsyQ/Programmers Tool implementation insists that a CD must be inserted +otherwise the function will output a lot of error messages to stdout and +fail to initialize. PSn00bSDK's implementation does not throw errors +when no disc is inserted, as it is written with utility style programs +that don't always need to read the disc in mind.

+ +

After calling this function, all CD-ROM functions exposed by the library +should be usable.

+ +

Locating a File

+ +

Now that the CD-ROM library is initialized the next step is to locate a +file on the disc to be read. Unlike the BIOS CD-ROM functions, the CD-ROM +library does not have a concept of file handles and instead, files are +read directly from their logical position on the disc. Conveniently, no +files on a CD-ROM are fragmented so file read operations from the CD-ROM +are quite simple and pretty straight forward.

+ +

Locating a file on the disc is done by using CdSearchFile().

+ +
CdlFILE* CdSearchFile(const char *filename, CdlFILE *loc)
+
+ +

This function will search the CD-ROM file system for the file specified +by filename and stores information about the file, such as it's +logical position and file size, to a CdlFILE struct specified by +loc. This information is all we need to read a file from the disc. +The file name must contain an absolute path from the root directory of +the disc as the CD-ROM library has no concept of current directories. The +file path must additionally end with a file version identifier, usually +;1.

+ +

The return value is just a pointer to the struct specified by loc, +but NULL is returned if the file name specified does not exist.

+ +

Reading a File

+ +

Now that the file we want to read has been located, the next logical step +is to read it's contents. This is done by using two CD-ROM functions; +CdControl() and CdRead().

+ +

CdControl() is kind of a multi-purpose function as it is used +to issue raw commands directly to the console's CD-ROM controller and +can be used to make the controller do all kinds of things including +CD audio playback, but this will be covered in greater detail at a later +chapter.

+ +

For this instance, we'll be using CdControl() to issue a +CdlSetloc command to the CD-ROM controller with the location of the +file we just located.

+ +
int CdControl(u_char com, u_char *param, u_char *result);
+
+ +

com specifies the command to be issued (CdlSetloc in this +case) while param should be a pointer to the loc element in +the CdlFILE struct we obtained from the CdSearchFile() call +earlier. result can be ignored in this instance so NULL can be +specified for that argument. result is where return data from the +CD-ROM controller would be stored to if a small buffer is specified.

+ +

The reason we must issue a CdlSetloc command is it sets the +target location for a read, seek or play operation. The CD-ROM hardware +does not yet move it's optical pickup when CdlSetloc is issued +alone, seeking only occurs when either of the aforementioned disc +operations have been issued and that a target location set by issuing +CdlSetloc was set prior.

+ +

And finally, we can now start a read operation by calling the +CdRead() function.

+ +
int CdRead(int sectors, unsigned int *buf, int mode)
+
+ +

As the name says it all, this function starts a data read operation from +the disc starting from the logical position specified by a previously issued +CdlSetloc command. sectors specifies the number of sectors to +read from the disc, the exact number can be determined by taking the +size field in the CdlFILE struct, adding it by 2047 then +divide by 2048. More will be explained why it had to be computed that way +later. The sectors read are then stored to buf, which +must point to a buffer large enough to contain all the sectors to be read. +mode specifies the CD-ROM mode to use for the read operation, usually +CdlModeSpeed is needed for all file read operations so data is read +at full speed.

+ +

If you haven't guessed from the brief explanation of the operation of +CD-ROMs earlier in this chapter, each sector is 2048 bytes long. And because +CdRead() only reads in sector units you can only read data in +multiples of 2048 bytes. The logical position of files stored on the CD-ROM +are also addressed in multiples of 2048 bytes.

+ +

The BIOS CD-ROM functions are also limited to reading files in units of +2048 bytes and file seeks are limited to multiples of 2048 bytes as well. +Because of this, it is much faster to just load the entirety of a file into +memory and parse the file from there, especially as performing several small +seeks and read operations would be quite slow, even moreso as the +PlayStation's CD-ROM hardware doesn't have very fast access times.

+ +

Once CdRead() has been called and the PlayStation's optical pickup +seeks to the file's location and begins reading data, the memory buffer +specified for buf should start filling with data from the file being +read. However, the CdRead() function finishes almost immediately and +the buffer may not yet contain the file you're reading yet, so another step +is required to make sure the buffer is filled with all the contents of the +file you want to load.

+ +
int CdReadSync(int mode, unsigned char *result)
+
+ +

This function will wait until the CdRead() operation is actually +completed if mode is set to zero. If it is non-zero the function +returns the number of sectors remaining in the read operation. This is +useful if you want to have a animated loading screen or some such. +If a buffer is specified in result the most recent CD-ROM status +is written to that buffer, but it can be left NULL if not needed.

+ +

The asynchronous nature of the CdRead() function may be utilized +for implemented animated loading screens and such.

+ +

Finished Routine

+ +

With all that taken in, a simple CD-ROM read routine should like this.

+ +
CdlFILE filePos;
+int     numsecs;
+char    *buff;
+
+/* locate the file on the CD */
+if( CdSearchFile( &filePos, "\\TEXTURE.TIM;1" ) == NULL )
+{
+    /* print error message if file not found */
+    printf( "File was not found." );
+}
+else
+{
+    /* calculate number of sectors to read for the file */
+    numsecs = (filePos.size+2047)/2048;
+    
+    /* allocate buffer for the file */
+    buff = (char*)malloc( 2048*numsecs );
+    
+    /* set read target to the file */
+    CdControl( CdlSetloc, (u_char*)&filePos.loc, 0 );
+    
+    /* start read operation */
+    CdRead( numsecs, (u_long*)buff, CdlModeSpeed );
+    
+    /* wait until the read operation is complete */
+    CdReadSync( 0, 0 );
+}
+
+ +

You may notice that the file size has to be incremented by 2047 +then divided by 2048. This is to make sure that the sector count calculated +would cover the entirety of a file that is not a perfect multiple of 2048 +bytes. If the file to be read were like 1536 bytes, the whole file would be +read as one whole sector with the excess bytes being nothing more but +padding.

+ +

You also have to remember that data reads from the CD are still multiples +of 2048 bytes, so the read buffer must be allocated in multiples of 2048 bytes +to avoid buffer overflow bugs, which would in turn trash the memory heap.

+ +

Implementation

+ +

To keep things simple we'll just implement it on top of the exisitng +sample program from the last chapter. Basically instead of reading the +sample texture from an array we'll load the texture file from the CD-ROM. +This can be quickly implemented by implementing our CD-ROM file read routine +as a nice little function.

+ +
char *loadfile( const char *filename )
+{
+    CdlFILE filePos;
+    int     numsecs;
+    char    *buff;
+    
+    buff = NULL;
+    
+    /* locate the file on the CD */
+    if( CdSearchFile( filename, &filePos ) == NULL )
+    {
+        /* print error message if file not found */
+        printf( "%s not found.", filename );
+    }
+    else
+    {
+        /* calculate number of sectors to read for the file */
+        numsecs = (filePos.size+2047)/2048;
+        
+        /* allocate buffer for the file (replace with malloc3() for PsyQ) */
+        buff = (char*)malloc( 2048*numsecs );
+        
+        /* set read target to the file */
+        CdControl( CdlSetloc, (u_char*)&filePos.loc, 0 );
+        
+        /* start read operation */
+        CdRead( numsecs, (u_long*)buff, CdlModeSpeed );
+        
+        /* wait until the read operation is complete */
+        CdReadSync( 0, 0 );
+    }
+    
+    return( buff );
+    
+} /* loadfile */
+
+ +

The sample program from the last chapter will be used to demonstrate the +usage of the function, to load the texture image from CD rather than loading +from an array. The ever growing sample program should be like this. Tweak +the code as you see fit to make it work with the SDK you're using.

+ +
#include <sys/types.h>	// This provides typedefs needed by libgte.h and libgpu.h
+#include <stdio.h>	// Not necessary but include it anyway
+#include <psxetc.h>	// Includes some functions that controls the display
+#include <psxgte.h>	// GTE header, not really used but libgpu.h depends on it
+#include <psxgpu.h>	// GPU library header
+#include <psxapi.h>
+
+#define OTLEN 8         // Ordering table length (recommended to set as a define
+                        // so it can be changed easily)
+
+DISPENV disp[2];        // Display/drawing buffer parameters
+DRAWENV draw[2];
+int db = 0;
+
+// PSn00bSDK requires having all u_long types replaced with
+// u_int, as u_long in modern GCC that PSn00bSDK uses defines it as a 64-bit integer.
+
+u_int ot[2][OTLEN];    // Ordering table length
+char pribuff[2][32768]; // Primitive buffer
+char *nextpri;          // Next primitive pointer
+
+int tim_mode;           // TIM image parameters
+RECT tim_prect,tim_crect;
+int tim_uoffs,tim_voffs;
+
+// Pad stuff
+#define PAD_SELECT      1
+#define PAD_L3          2
+#define PAD_R3          4
+#define PAD_START       8
+#define PAD_UP          16
+#define PAD_RIGHT       32
+#define PAD_DOWN        64
+#define PAD_LEFT        128
+#define PAD_L2          256
+#define PAD_R2          512
+#define PAD_L1          1024
+#define PAD_R1          2048
+#define PAD_TRIANGLE    4096
+#define PAD_CIRCLE      8192
+#define PAD_CROSS       16384
+#define PAD_SQUARE      32768
+
+typedef struct _PADTYPE
+{
+    unsigned char	stat;
+    unsigned char	len:4;
+    unsigned char	type:4;
+    unsigned short	btn;
+    unsigned char	rs_x,rs_y;
+    unsigned char	ls_x,ls_y;
+} PADTYPE;
+
+u_char padbuff[2][34];
+
+// For the player triangle
+SVECTOR player_tri[] = {
+    {   0, -20,  0  },
+    {  10,  20,  0  },
+    { -10,  20,  0  }
+};
+
+void display() {
+    
+    DrawSync(0);                // Wait for any graphics processing to finish
+    
+    VSync(0);                   // Wait for vertical retrace
+
+    PutDispEnv(&disp[db]);      // Apply the DISPENV/DRAWENVs
+    PutDrawEnv(&draw[db]);
+
+    SetDispMask(1);             // Enable the display
+
+    DrawOTag(ot[db]+OTLEN-1);   // Draw the ordering table
+    
+    db = !db;                   // Swap buffers on every pass (alternates between 1 and 0)
+    nextpri = pribuff[db];      // Reset next primitive pointer
+    
+}
+
+// CD loading function
+char *loadfile( const char *filename )
+{
+    CdlFILE filePos;
+    int     numsecs;
+    char    *buff;
+    
+    buff = NULL;
+    
+    /* locate the file on the CD */
+    if( CdSearchFile( filename, &filePos ) == NULL )
+    {
+        /* print error message if file not found */
+        printf( "%s not found.", filename );
+    }
+    else
+    {
+        /* calculate number of sectors to read for the file */
+        numsecs = (filePos.size+2047)/2048;
+        
+        /* allocate buffer for the file (replace with malloc3() for PsyQ) */
+        buff = (char*)malloc( 2048*numsecs );
+        
+        /* set read target to the file */
+        CdControl( CdlSetloc, (u_char*)&filePos.loc, 0 );
+        
+        /* start read operation */
+        CdRead( numsecs, (u_long*)buff, CdlModeSpeed );
+        
+        /* wait until the read operation is complete */
+        CdReadSync( 0, 0 );
+    }
+    
+    return( buff );
+    
+} /* loadfile */
+
+// Texture upload function
+void LoadTexture(u_int *tim, TIM_IMAGE *tparam) {
+
+    // Read TIM parameters (PsyQ)
+    //OpenTIM(tim);
+    //ReadTIM(tparam);
+
+    // Read TIM parameters (PSn00bSDK)
+    GetTimInfo(tim, tparam);
+
+    // Upload pixel data to framebuffer
+    LoadImage(tparam->prect, tparam->paddr);
+    DrawSync(0);
+
+    // Upload CLUT to framebuffer if present
+    if( tparam->mode & 0x8 ) {
+
+        LoadImage(tparam->crect, tparam->caddr);
+        DrawSync(0);
+
+    }
+
+}
+
+void loadstuff(void) {
+
+    TIM_IMAGE   my_image;       // TIM image parameters
+    u_int       *filebuff;      // Pointer for the file loaded from the disc
+
+    if( filebuff = (u_int*)loadfile( "\\TEXTURE.TIM;1" ) )
+    {    
+        // On successful file read, load the texture to VRAM
+        LoadTexture(tim_my_image, &my_image);
+
+        // Copy the TIM coordinates
+        tim_prect   = *my_image.prect;
+        tim_crect   = *my_image.crect;
+        tim_mode    = my_image.mode;
+
+        // Calculate U,V offset for TIMs that are not page aligned
+        tim_uoffs = (tim_prect.x%64)<<(2-(tim_mode&0x3));
+        tim_voffs = (tim_prect.y&0xff);
+        
+        // Free the file buffer
+        free( filebuff );
+    }
+    else
+    {
+        // Output error text that the image failed to load
+        printf( "Error: TEXTURE.TIM file not found.\n" );
+    }
+}
+
+// To make main look tidy, init stuff has to be moved here
+void init(void) {
+    
+    // Reset graphics
+    ResetGraph(0);
+    
+    // Initialize the CD-ROM library
+    CdInit();
+
+    // First buffer
+    SetDefDispEnv(&disp[0], 0, 0, 320, 240);
+    SetDefDrawEnv(&draw[0], 0, 240, 320, 240);
+    // Second buffer
+    SetDefDispEnv(&disp[1], 0, 240, 320, 240);
+    SetDefDrawEnv(&draw[1], 0, 0, 320, 240);
+
+    draw[0].isbg = 1;               // Enable clear
+    setRGB0(&draw[0], 63, 0, 127);  // Set clear color (dark purple)
+    draw[1].isbg = 1;
+    setRGB0(&draw[1], 63, 0, 127);
+
+    nextpri = pribuff[0];           // Set initial primitive pointer address
+ 
+    // load textures and possibly other stuff
+    loadstuff();
+
+    // set tpage of lone texture as initial tpage
+    draw[0].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+    draw[1].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
+
+    // apply initial drawing environment
+    PutDrawEnv(&draw[!db]);
+    
+    // Initialize the pads
+    InitPAD( padbuff[0], 34, padbuff[1], 34 );
+    
+    // Begin polling
+    StartPAD();
+    
+    // To avoid VSync Timeout error, may not be defined in PsyQ
+    ChangeClearPAD( 1 );
+    
+    // Load the font texture on the upper-right corner of the VRAM
+    FntLoad( 960, 0 );
+
+    // Define a font window of 100 characters covering the whole screen
+    FntOpen( 0, 8, 320, 224, 0, 100 );
+ 
+}
+
+int main() {
+    
+    int i;
+    int pos_x,pos_y,angle;
+    PADTYPE *pad;
+    POLY_F3 *tri;
+    SVECTOR v[3];
+    
+    TILE *tile;                         // Pointer for TILE
+    SPRT *sprt;                         // Pointer for SPRT
+
+    // Init stuff
+    init();
+    
+    pos_x = ONE*(disp[0].disp.w>>1);
+    pos_y = ONE*(disp[0].disp.h>>1);
+    angle = 0;
+    
+    while(1) {
+    
+        // Parse controller input
+        pad = (PADTYPE*)padbuff[0];
+
+        // Only parse inputs when a controller is connected
+        if( pad->stat == 0 )
+        {
+            // Only parse when a digital pad, 
+            // dual-analog and dual-shock is connected
+            if( ( pad->type == 0x4 ) || 
+                ( pad->type == 0x5 ) || 
+                ( pad->type == 0x7 ) )
+            {
+                if( !(pad->btn&PAD_UP) )            // test UP
+                {
+                    pos_x += csin( angle );
+                    pos_y -= ccos( angle );
+                }
+                else if( !(pad->btn&PAD_DOWN) )     // test DOWN
+                {
+                    pos_x -= csin( angle );
+                    pos_y += ccos( angle );
+                }
+                if( !(pad->btn&PAD_LEFT) )          // test LEFT
+                {
+                    angle -= 16;
+                }
+                else if( !(pad->btn&PAD_RIGHT) )    // test RIGHT
+                {
+                    angle += 16;
+                }
+            }
+        }
+
+        ClearOTagR(ot[db], OTLEN);      // Clear ordering table
+        
+        // Rotate the triangle coordinates based on the player's angle
+        // as well as apply the position
+        for( i=0; i<3; i++ )
+        {
+            v[i].vx = (((player_tri[i].vx*icos( angle ))
+                -(player_tri[i].vy*csin( angle )))>>12)+(pos_x>>12);
+            v[i].vy = (((player_tri[i].vy*icos( angle ))
+                +(player_tri[i].vx*csin( angle )))>>12)+(pos_y>>12);
+        }
+        
+        // Sort the player triangle
+        tri = (POLY_F3*)nextpri;
+        setPolyF3( tri );
+        setRGB0( tri, 255, 255, 0 );
+        setXY3( tri,
+            v[0].vx, v[0].vy,
+            v[1].vx, v[1].vy,
+            v[2].vx, v[2].vy );
+        addPrim( ot[db], tri );
+        nextpri += sizeof(POLY_F3);
+        
+        // Print player coordinates
+        FntPrint( -1, "POS_X=%d (%d.%d)\n", pos_x, (pos_x>>12), (pos_x&0xfff) );
+        FntPrint( -1, "POS_Y=%d (%d.%d)\n", pos_y, (pos_y>>12), (pos_y&0xfff) );
+        FntPrint( -1, "ANGLE=%d\n", angle );
+
+        // Draw and flush the character buffer
+        FntFlush( -1 );
+        
+        // Update the display
+        display();
+        
+    }
+    
+    return 0;
+}
+
+ +

Creating the CD Image

+ +

Once you've got the code above compiled it won't do jack for now as it +doesn't yet have a CD to read the texture file from. This is where disc +image creation comes to play in this chapter.

+ +

In the old days, the only preferred way to create PlayStation CD images +was with the old, MS-DOS based BUILDCD tool from the old leaked PsyQ SDK +dump. This tool is quite difficult to use on modern computers these days +largely due to being a 16-bit MS-DOS program, and while Dosbox can be used +to get it going on a modern machine it is not exactly ideal, not to mention +is very slow.

+ +

To make image creation even more tedious with the old BUILDCD method +the resulting image file is of a special format intended for early CD +burners and special disc cutters, so another tool called STRIPISO is +used to convert the image file to that of a usable file format. However, +the conversion process does not convert the Table-of-Contents data +produced by BUILDCD as well, so having multiple tracks in the disc image +was not possible with this method.

+ +

While other, more conventional means of disc image creation like +MKISOFS would work, it is not exactly ideal for projects that use XA +audio or streaming data which require support for mixed Mode2/Form1 +and Mode2/Form2 not supported by MKISOFS so you'd be stuck with having +to use BUILDCD in the old days. Well... That is until +MKPSXISO by +yours truly came about, solving this disc image creation dilemma in the +PlayStation homebrew scene once and for all. Said tool was so useful that +it got used quite extensively around ROM hacking and game translation +groups... Or so I heard.

+ +

Creating a MKPSXISO Script

+ +

Kind of like BUILDCD, MKPSXISO uses a simple script system to define +the contents and layout of a CD image. However, it uses a XML based +format similar to that you'd find in HTML webpage files, of which, in +of itself is quite simple especially if you use a good text editor +such as Notepad++ or nedit.

+ +

The following is a link to a sample XML script for this tutorial. +You must save the link as a file as otherwise your browser's going to +treat it as a web page. The file also contains comments that explains +the usage of the XML file.

+ +MKPSXISO Sample Script + +

The script file assumes that the above C program is compiled as +cdread.exe.

+ +

Creating the SYSTEM.CNF File

+ +

Before the ISO image can be created, a SYSTEM.CNF file must be +created and is a standard requirement for PlayStation game discs to include. +The SYSTEM.CNF file is a simple text file that defines a few +parameters. The most important being the BOOT variable.

+ +
BOOT=cdrom:\cdread.exe;1
+TCB=4
+EVENT=10
+STACK=801FFFF0
+
+ +

The parameters that immediately follow it TCB, EVENT and +STACK are special parameters that change the way how the kernel +is set up for the game. The parameters in the sample script are typical +values used in most games, so they can be left alone.

+ +

Save the text file as SYSTEM.TXT as the script provided will +rename it as SYSTEM.CNF as the disc image gets created. This is +done so that editing the CNF file is made easier when need-be, such as +changing the executable name.

+ +

Creating the disc image

+ +

Building the disc image with MKPSXISO is just a matter of simply +invoking it from the command line with the XML script as the argument. + +

mkpsxiso mkpsxiso-sample.xml
+
+ +

If things go accordingly you should end up with a BIN/CUE image pair +named cdtutorial. Run the disc image in your preferred emulator +and the program from the last tutorial should work now, only there's now +a slight delay from loading data from the disc.

+ +
+ +
+ +

Conclusion

+ +

Hopefully this chapter has cleared up a lot of things in regards to +reading data from the CD, something I feel is still not explained very +well. But then I haven't really kept up with the PlayStation homebrew +scene in a very long while. In the future, stuff like CD Audio and XA +audio playback will be covered.

+ +

And this concludes the basics chapter of this tutorial series, and +if you've managed to reach this far after reading the first few chapters +you should be able to do quite a bit with 2D stuff on the console now... +Provided you managed to absorb it all.

+ +
+ + + + + + +
PreviousBack to Index
+ + + \ No newline at end of file diff --git a/Docs/Chapter 1.6 Using the CD-ROM_files/style.css b/Docs/Chapter 1.6 Using the CD-ROM_files/style.css new file mode 100644 index 0000000..50d70a8 --- /dev/null +++ b/Docs/Chapter 1.6 Using the CD-ROM_files/style.css @@ -0,0 +1,49 @@ +body { + max-width: 900px; + margin: auto; + padding: 8px; + font-family: sans-serif; + font-size: 14px; + //color: white; + //background: black; +} + +h { + background-color: #e0e0e0; + padding: 2px; +} + +h2 { + border-bottom: 1px solid; + //padding-left: 8px; +} + +img { + display: block; +} + +.footer-table { + font-size: 14px; +} + +.bordered-table { + border-collapse: collapse; + font-size: 11px; +} + +.bordered-table th, .bordered-table td { + border: 1px solid; + padding: 4px; +} + +hr { + border: none; + border-bottom: 1px solid black; +} + +pre { + padding: 8px; + font-size: 12px; + color: black; + background-color: LightGray; +} \ No newline at end of file diff --git a/Docs/Chapter 2.2 Reading a file from CD-ROM.htm b/Docs/Chapter 2.2 Reading a file from CD-ROM.htm new file mode 100644 index 0000000..f7db53b --- /dev/null +++ b/Docs/Chapter 2.2 Reading a file from CD-ROM.htm @@ -0,0 +1,508 @@ + + + + + + + + + + + +Chapter 2.2: Reading a file from CD-ROM + + + + + +
+
The Wayback Machine - +http://web.archive.org/web/20220827033533/http://lameguy64.net/tutorials/pstutorials/chapter2-old/2-readfile.html
+ + + +
+

Chapter 2.2: Reading a file from CD-ROM

+
+

This chapter details how to read a file from the CD-ROM, loading the contents +into system RAM using the CD-ROM library.

+

PSn00bSDK Compatible: Yes

+ +

Tutorial Index

+ + +

Initializing the CD-ROM

+

Initializing the CD-ROM library is accomplished by simply calling +CdInit(). This function must be called after calling ResetGraph() +and before any CD library related function. If you intend to use the SPU you +must call SpuInit() immediately before calling CdInit(). Once the +CD-ROM library is initialized any other CD-ROM related function can be used.

+

SDK Difference

+

In PsyQ/Programmers' Tool, CdInit() will throw a lot of CD retry +messages when no disc is inserted and may lock up. In PSn00bSDK the function +carries on like normal when no disc is inserted.

+ +

Locating a file in the CD-ROM

+

The CD-ROM library does not have a concept of file handles. Instead, files +are read by locating the start position of a file through the ISO9660 file +system, seeking to it and issuing a read operation until a set number of +sectors have been read. This method of loading files from disc should not be +a problem under any circumstances as files stored in a ISO9660 file system are +never fragmented. In fact, this method of reading files may have some +advantages.

+

Locating a file on the disc is done through the CdSearchFile() +function, which parses through the disc's file system to locate the file +and returns a CdlFILE which describes information about the file found +including it's position in the disc.

+
CdlFILE file;
+
+// Search for the file
+if( !CdSearchFile( &file;, "\\MYDIR\\MYFILE.DAT;1" ) )
+{
+    // Return value is NULL, file is not found
+    printf( "File not found.\n" );
+    return 0;
+}
+
+// When file found
+printf( "File found!\n" );
+
+

The file name must be specified as a complete path from root and must use +backlash characters as directory separators (use \\ in C code). The file name +must also end with a file version number which is always ';1'. If the file is +found, it will return the pointer to the specified CdlFILE struct, +otherwise it would simply return NULL.

+

The CdlFILE struct contains a copy of the file name, file size and +most importantly, the location.

+ +

Reading File Contents

+

Now for the interesting stuff. Reading sectors is accomplished by using +CdRead(). But before you start a read operation, you must first set +the location of where to start reading from. This can be done using +CdControl() and the CdlSetloc command. Use this to set the +target location of the file.

+
char *buffer;
+
+// Allocate a buffer for the file
+buffer = (char*)malloc( 2048*((file.size+2047)/2048) );
+
+// Set seek target (seek actually happens on CdRead())
+CdControl( CdSetloc, &file.loc;, 0 );
+
+// Read sectors
+CdRead( (file.size+2047)/2048, (unsigned int*)buffer, CdlModeSpeed );
+
+// Wait until read has completed
+CdReadSync( 0, 0 );
+
+

You may have noticed by now that read sizes in this chapter are described in +sector units rather than byte units. That's because the CD-ROM subsystem can +only read in sector units and the CD-ROM library is optimized for reading in +sector units rather than buffering sectors to allow for byte by byte reading +for performance reasons.

+

Data sectors are typically 2048 bytes in size so buffers where data read +from the CD would be loaded to must be multiples of 2048 bytes. The arithmetic +to snap byte sizes to the nearest sector unit is described in the pseudo code +described above.

+

Once a read operation has been issued, you must wait until reading has +completed using CdReadSync(). You can alternatively poll the status +asynchronously by setting the mode parameter to 1 to perform animations +while waiting for a long read to complete.

+

If you intend to read only a part of a file instead of a whole file in a +single read operation, be aware that you can't simply call CdRead() +again to read the following sectors. Instead, you must retrieve the current +location using CdlGetloc, set it as the seek target with CdlSetloc +then call CdRead().

+ + +

Simple File Read Function

+

The examples described above can be consolidated into a single function, +which can be useful for quickly implementing a file read function for testing +purposes:

+
u_long *load_file(const char* filename)
+{
+    CdlFILE	file;
+    u_long	*buffer;
+    
+    
+    printf( "Reading file %s... ", filename );
+
+    // Search for the file
+    if( !CdSearchFile( &file;, (char*)filename ) )
+    {
+        // Return value is NULL, file is not found
+        printf( "Not found!\n" );
+        return NULL;
+    }
+    
+    // Allocate a buffer for the file
+    buffer = (u_long*)malloc( 2048*((file.size+2047)/2048) );
+
+    // Set seek target (seek actually happens on CdRead())
+    CdControl( CdlSetloc, (u_char*)&file.pos;, 0 );
+
+    // Read sectors
+    CdRead( (file.size+2047)/2048, buffer, CdlModeSpeed );
+
+    // Wait until read has completed
+    CdReadSync( 0, 0 );
+    
+    printf( "Done.\n" );
+
+    return buffer;
+}
+
+

If you want to do asynchronous loading to do background animations, you'll +have to use the CD functions more directly.

+ + +

Image Loader Example

+

For completedness, this example demonstrates loading a high resolution +24-bit TIM from CD and displaying it into the framebuffer in 24-bit color mode. +Take any image file you wish to use, scale it down to 640x480 and convert it +into a 24-bit TIM with the load position set to (0,0). Name that TIM file +myimage.tim for this example.

+

PsyQ/Programmers' Tool Version

+
#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <libgte.h>
+#include <libgpu.h>
+#include <libetc.h>
+#include <libcd.h>
+
+// Display environment struct
+DISPENV disp;
+
+u_long *load_file(const char* filename)
+{
+    CdlFILE file;
+    u_long  *buffer;
+    
+    
+    printf( "Reading file %s... ", filename );
+
+    // Search for the file
+    if( !CdSearchFile( &file;, (char*)filename ) )
+    {
+        // Return value is NULL, file is not found
+        printf( "Not found!\n" );
+        return NULL;
+    }
+    
+    // Allocate a buffer for the file
+    buffer = (u_long*)malloc( 2048*((file.size+2047)/2048) );
+
+    // Set seek target (seek actually happens on CdRead())
+    CdControl( CdlSetloc, (u_char*)&file.pos;, 0 );
+
+    // Read sectors
+    CdRead( (file.size+2047)/2048, buffer, CdlModeSpeed );
+
+    // Wait until read has completed
+    CdReadSync( 0, 0 );
+    
+    printf( "Done.\n" );
+
+    return buffer;
+}
+
+void display_picture()
+{
+    u_long      *image;
+    TIM_IMAGE   tim;
+
+    // Load the image file
+    if( !(image = load_file( "\\MYIMAGE.TIM;1" )) )
+    {
+        printf( "Could not load image file.\n " );
+        return;
+    }
+
+    // Read TIM header
+    OpenTIM( image );
+    ReadTIM( &tim; );
+
+    // Load image to VRAM
+    LoadImage( tim.prect, tim.paddr );
+    DrawSync( 0 );
+
+    // Enable video display
+    VSync( 0 );
+    SetDispMask( 1 );
+}
+
+void init()
+{
+    // Reset GPU (also installs IRQ handlers which are mandatory)
+    ResetGraph(0);
+
+    // Init CD-ROM library
+    CdInit();
+
+    // Set DISPENV for 640x480 24-bit color mode
+    SetDefDispEnv( &disp;, 0, 0, 640, 480 );
+    disp.isrgb24 = 1;   // Enables 24-bit (cannot be used for GPU graphics)
+    disp.isinter = 1;   // Enable interlace so hi-res will display properly
+
+    // Set display environment
+    PutDispEnv( &disp; );
+}
+
+int main(int argc, const char *argv[])
+{
+    // Init stuff
+    init();
+
+    // Load and display picture
+    display_picture();
+
+    // Loop for safe idling
+    while( 1 )
+    {
+        VSync( 0 );
+    }
+
+    return 0;
+}
+
+ +

PSn00bSDK Version

+
#include <stdio.h>
+#include <malloc.h>
+#include <psxgpu.h>
+#include <psxcd.h>
+
+// Display environment struct
+DISPENV disp;
+
+
+unsigned int *load_file(const char* filename)
+{
+    CdlFILE	file;
+    unsigned int *buffer;
+    
+    
+    printf( "Reading file %s... ", filename );
+
+    // Search for the file
+    if( !CdSearchFile( &file;, (char*)filename ) )
+    {
+        // Return value is NULL, file is not found
+        printf( "Not found!\n" );
+        return NULL;
+    }
+    
+    // Allocate a buffer for the file
+    buffer = (unsigned int*)malloc( 2048*((file.size+2047)/2048) );
+
+    // Set seek target (seek actually happens on CdRead())
+    CdControl( CdlSetloc, (unsigned char*)&file.pos;, 0 );
+
+    // Read sectors
+    CdRead( (file.size+2047)/2048, buffer, CdlModeSpeed );
+
+    // Wait until read has completed
+    CdReadSync( 0, 0 );
+    
+    printf( "Done.\n" );
+
+    return buffer;
+}
+
+void display_picture()
+{
+    unsigned int    *image;
+    TIM_IMAGE       tim;
+
+    // Load the image file
+    if( !(image = load_file( "\\MYIMAGE.TIM;1" )) )
+    {
+        printf( "Could not load image file.\n " );
+        return;
+    }
+
+    // Read TIM header
+    GetTimInfo( image, &tim; );
+
+    // Load image to VRAM
+    LoadImage( tim.prect, tim.paddr );
+    DrawSync( 0 );
+	
+    // Enable video display
+    VSync( 0 );
+    SetDispMask( 1 );
+}
+
+void init()
+{
+    // Reset GPU (also installs IRQ handlers which are mandatory)
+    ResetGraph(0);
+
+    // Init CD-ROM library
+    CdInit( 0 );
+
+    // Set DISPENV for 640x480 24-bit color mode
+    SetDefDispEnv( &disp;, 0, 0, 640, 480 );
+    disp.isrgb24 = 1;   // Enables 24-bit (cannot be used for GPU graphics)
+    disp.isinter = 1;   // Enable interlace so hi-res will display properly
+
+    // Set display environment
+    PutDispEnv( &disp; );
+}
+
+int main(int argc, const char *argv[])
+{
+    // Init stuff
+    init();
+
+    // Load and display picture
+    display_picture();
+    
+    // Loop for safe idling
+    while( 1 )
+    {
+        VSync( 0 );
+    }
+
+    return 0;
+}
+
+

Most notable differences between the PsyQ/Programmers' Tool and PSn00bSDK +versions is the latter uses type unsigned int instead of u_long +or unsigned long and for good reason. Modern compilers such as GCC +7.4.0 tend to assume long to be a 64-bit integer and may cause problems when +using unsigned long. There's no OpenTIM() and ReadTIM() +equivalent. Instead, there's GetTimInfo() but this may change in the +future.

+

Make sure you've compiled the example as readfile.exe for coherency with +the rest of this chapter.

+ +

Building the ISO image

+

In the past, the only way to create proper ISO images for PS1 games is to +use a tool called BUILDCD included in leaked copies of the PsyQ SDK. The +biggest problem of using BUILDCD is it generates an image file of a special +format not supported by any burner program, so another tool has to be used +(stripiso) to convert the image file it generates into a usable ISO image. +Not only that, BUILDCD is a 16-bit DOS executable and is difficult and very +slow to use on a modern system.

+

Instead of using BUILDCD, use +MKPSXISO instead. It +offers about the same functionality as BUILDCD but better, and is built for +modern Windows and Linux platforms.

+

MKPSXISO Project XML

+
<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- Defines the ISO project -->
+<iso_project image_name="readfile.iso">
+
+    <!-- Defines the data track for this tutorial -->
+    <track type="data">
+
+        <!-- Specifies identifier strings such as volume label -->
+        <!-- System and application identifiers must be PLAYSTATION -->
+        <identifiers
+            system      ="PLAYSTATION"
+            application ="PLAYSTATION"
+            volume      ="PSXTUTORIAL"
+            volume_set  ="PSXTUTORIAL"
+            publisher   ="MEIDOTEK"
+        />
+
+        <!-- Defines the directory tree of the data track -->
+        <directory_tree>
+
+            <!-- Specify files in the directory tree -->
+            <file name="system.cnf"      type="data" source="system.cnf"/>
+            <file name="readfile.exe"    type="data" source="readfile.exe"/>
+            <file name="myimage.tim"     type="data" source="myimage.tim"/>
+
+            <!-- Place dummy sectors at the end -->
+            <dummy sectors="1024"/>
+
+        </directory_tree>
+
+    </track>
+
+</iso_project>
+
+

While on the subject of ISO image creation, you'll also need to create a +special SYSTEM.CNF file in order to make your disc image bootable. A +SYSTEM.CNF file is simply a text file that specifies the file name of the +executable, stack address, number of task and event blocks. The latter three +wouldn't be discussed in this chapter and the typical values are generally +good enough.

+

SYSTEM.CNF Contents

+
BOOT=cdrom:\readfile.exe;1
+TCB=4
+EVENT=10
+STACK=801FFFF0
+
+

Once your MKPSXISO project and SYSTEM.CNF files are set, execute MKPSXISO +like so to create the ISO image:

+
mkpsxiso readfile.xml
+
+

Run the ISO image in an emulator and you should see your 24-bit TIM image +displayed. It may take awhile for the image to appear as the 24-bit TIM image +is a pretty big file to load for the PS1.

+

+ +

Conclusion

+

This chapter should cover about everything you need to know to load a file +from CD-ROM. The next chapter will cover common ways to optimize CD-ROM +accesses to speed up load times in your homebrew.

+ +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/Docs/Chapter 2.2 Reading a file from CD-ROM_files/analytics.js b/Docs/Chapter 2.2 Reading a file from CD-ROM_files/analytics.js new file mode 100644 index 0000000..10400f0 --- /dev/null +++ b/Docs/Chapter 2.2 Reading a file from CD-ROM_files/analytics.js @@ -0,0 +1,474 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3.0 +/* eslint-disable no-var, semi, prefer-arrow-callback, prefer-template */ + +/** + * Collection of methods for sending analytics events to Archive.org's analytics server. + * + * These events are used for internal stats and sent (in anonymized form) to Google Analytics. + * + * @see analytics.md + * + * @type {Object} + */ +window.archive_analytics = (function defineArchiveAnalytics() { + // keep orignal Date object so as not to be affected by wayback's + // hijacking global Date object + var Date = window.Date; + var ARCHIVE_ANALYTICS_VERSION = 2; + var DEFAULT_SERVICE = 'ao_2'; + var NO_SAMPLING_SERVICE = 'ao_no_sampling'; // sends every event instead of a percentage + + var startTime = new Date(); + + /** + * @return {Boolean} + */ + function isPerformanceTimingApiSupported() { + return 'performance' in window && 'timing' in window.performance; + } + + /** + * Determines how many milliseconds elapsed between the browser starting to parse the DOM and + * the current time. + * + * Uses the Performance API or a fallback value if it's not available. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API + * + * @return {Number} + */ + function getLoadTime() { + var start; + + if (isPerformanceTimingApiSupported()) + start = window.performance.timing.domLoading; + else + start = startTime.getTime(); + + return new Date().getTime() - start; + } + + /** + * Determines how many milliseconds elapsed between the user navigating to the page and + * the current time. + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance_API + * + * @return {Number|null} null if the browser doesn't support the Performance API + */ + function getNavToDoneTime() { + if (!isPerformanceTimingApiSupported()) + return null; + + return new Date().getTime() - window.performance.timing.navigationStart; + } + + /** + * Performs an arithmetic calculation on a string with a number and unit, while maintaining + * the unit. + * + * @param {String} original value to modify, with a unit + * @param {Function} doOperation accepts one Number parameter, returns a Number + * @returns {String} + */ + function computeWithUnit(original, doOperation) { + var number = parseFloat(original, 10); + var unit = original.replace(/(\d*\.\d+)|\d+/, ''); + + return doOperation(number) + unit; + } + + /** + * Computes the default font size of the browser. + * + * @returns {String|null} computed font-size with units (typically pixels), null if it cannot be computed + */ + function getDefaultFontSize() { + var fontSizeStr; + + if (!('getComputedStyle' in window)) + return null; + + var style = window.getComputedStyle(document.documentElement); + if (!style) + return null; + + fontSizeStr = style.fontSize; + + // Don't modify the value if tracking book reader. + if (document.querySelector('#BookReader')) + return fontSizeStr; + + return computeWithUnit(fontSizeStr, function reverseBootstrapFontSize(number) { + // Undo the 62.5% size applied in the Bootstrap CSS. + return number * 1.6; + }); + } + + /** + * Get the URL parameters for a given Location + * @param {Location} + * @return {Object} The URL parameters + */ + function getParams(location) { + if (!location) location = window.location; + var vars; + var i; + var pair; + var params = {}; + var query = location.search; + if (!query) return params; + vars = query.substring(1).split('&'); + for (i = 0; i < vars.length; i++) { + pair = vars[i].split('='); + params[pair[0]] = decodeURIComponent(pair[1]); + } + return params; + } + + function getMetaProp(name) { + var metaTag = document.querySelector('meta[property=' + name + ']'); + return metaTag ? metaTag.getAttribute('content') || null : null; + } + + var ArchiveAnalytics = { + /** + * @type {String|null} + */ + service: getMetaProp('service'), + mediaType: getMetaProp('mediatype'), + primaryCollection: getMetaProp('primary_collection'), + + /** + * Key-value pairs to send in pageviews (you can read this after a pageview to see what was + * sent). + * + * @type {Object} + */ + values: {}, + + /** + * Sends an analytics ping, preferably using navigator.sendBeacon() + * @param {Object} values + * @param {Function} [onload_callback] (deprecated) callback to invoke once ping to analytics server is done + * @param {Boolean} [augment_for_ao_site] (deprecated) if true, add some archive.org site-specific values + */ + send_ping: function send_ping(values, onload_callback, augment_for_ao_site) { + if (typeof window.navigator !== 'undefined' && typeof window.navigator.sendBeacon !== 'undefined') + this.send_ping_via_beacon(values); + else + this.send_ping_via_image(values); + }, + + /** + * Sends a ping via Beacon API + * NOTE: Assumes window.navigator.sendBeacon exists + * @param {Object} values Tracking parameters to pass + */ + send_ping_via_beacon: function send_ping_via_beacon(values) { + var url = this.generate_tracking_url(values || {}); + window.navigator.sendBeacon(url); + }, + + /** + * Sends a ping via Image object + * @param {Object} values Tracking parameters to pass + */ + send_ping_via_image: function send_ping_via_image(values) { + var url = this.generate_tracking_url(values || {}); + var loadtime_img = new Image(1, 1); + loadtime_img.src = url; + loadtime_img.alt = ''; + }, + + /** + * Construct complete tracking URL containing payload + * @param {Object} params Tracking parameters to pass + * @return {String} URL to use for tracking call + */ + generate_tracking_url: function generate_tracking_url(params) { + var baseUrl = '//analytics.archive.org/0.gif'; + var keys; + var outputParams = params; + var outputParamsArray = []; + + outputParams.service = outputParams.service || this.service || DEFAULT_SERVICE; + + // Build array of querystring parameters + keys = Object.keys(outputParams); + keys.forEach(function keyIteration(key) { + outputParamsArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(outputParams[key])); + }); + outputParamsArray.push('version=' + ARCHIVE_ANALYTICS_VERSION); + outputParamsArray.push('count=' + (keys.length + 2)); // Include `version` and `count` in count + + return baseUrl + '?' + outputParamsArray.join('&'); + }, + + /** + * @param {int} page Page number + */ + send_scroll_fetch_event: function send_scroll_fetch_event(page) { + var additionalValues = { ev: page }; + var loadTime = getLoadTime(); + var navToDoneTime = getNavToDoneTime(); + if (loadTime) additionalValues.loadtime = loadTime; + if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime; + this.send_event('page_action', 'scroll_fetch', location.pathname, additionalValues); + }, + + send_scroll_fetch_base_event: function send_scroll_fetch_base_event() { + var additionalValues = {}; + var loadTime = getLoadTime(); + var navToDoneTime = getNavToDoneTime(); + if (loadTime) additionalValues.loadtime = loadTime; + if (navToDoneTime) additionalValues.nav_to_done_ms = navToDoneTime; + this.send_event('page_action', 'scroll_fetch_base', location.pathname, additionalValues); + }, + + /** + * @param {Object} [options] + * @param {String} [options.mediaType] + * @param {String} [options.mediaLanguage] + * @param {String} [options.page] The path portion of the page URL + */ + send_pageview: function send_pageview(options) { + var settings = options || {}; + + var defaultFontSize; + var loadTime = getLoadTime(); + var mediaType = settings.mediaType; + var primaryCollection = settings.primaryCollection; + var page = settings.page; + var navToDoneTime = getNavToDoneTime(); + + /** + * @return {String} + */ + function get_locale() { + if (navigator) { + if (navigator.language) + return navigator.language; + + else if (navigator.browserLanguage) + return navigator.browserLanguage; + + else if (navigator.systemLanguage) + return navigator.systemLanguage; + + else if (navigator.userLanguage) + return navigator.userLanguage; + } + return ''; + } + + defaultFontSize = getDefaultFontSize(); + + // Set field values + this.values.kind = 'pageview'; + this.values.timediff = (new Date().getTimezoneOffset()/60)*(-1); // *timezone* diff from UTC + this.values.locale = get_locale(); + this.values.referrer = (document.referrer == '' ? '-' : document.referrer); + + if (loadTime) + this.values.loadtime = loadTime; + + if (navToDoneTime) + this.values.nav_to_done_ms = navToDoneTime; + + if (settings.trackingId) { + this.values.ga_tid = settings.trackingId; + } + + /* START CUSTOM DIMENSIONS */ + if (defaultFontSize) + this.values.ga_cd1 = defaultFontSize; + + if ('devicePixelRatio' in window) + this.values.ga_cd2 = window.devicePixelRatio; + + if (mediaType) + this.values.ga_cd3 = mediaType; + + if (settings.mediaLanguage) { + this.values.ga_cd4 = settings.mediaLanguage; + } + + if (primaryCollection) { + this.values.ga_cd5 = primaryCollection; + } + /* END CUSTOM DIMENSIONS */ + + if (page) + this.values.page = page; + + this.send_ping(this.values); + }, + + /** + * Sends a tracking "Event". + * @param {string} category + * @param {string} action + * @param {string} label + * @param {Object} additionalEventParams + */ + send_event: function send_event( + category, + action, + label, + additionalEventParams + ) { + if (!label) label = window.location.pathname; + if (!additionalEventParams) additionalEventParams = {}; + if (additionalEventParams.mediaLanguage) { + additionalEventParams.ga_cd4 = additionalEventParams.mediaLanguage; + delete additionalEventParams.mediaLanguage; + } + var eventParams = Object.assign( + { + kind: 'event', + ec: category, + ea: action, + el: label, + cache_bust: Math.random(), + }, + additionalEventParams + ); + this.send_ping(eventParams); + }, + + /** + * Sends every event instead of a small percentage. + * + * Use this sparingly as it can generate a lot of events. + * + * @param {string} category + * @param {string} action + * @param {string} label + * @param {Object} additionalEventParams + */ + send_event_no_sampling: function send_event_no_sampling( + category, + action, + label, + additionalEventParams + ) { + var extraParams = additionalEventParams || {}; + extraParams.service = NO_SAMPLING_SERVICE; + this.send_event(category, action, label, extraParams); + }, + + /** + * @param {Object} options see this.send_pageview options + */ + send_pageview_on_load: function send_pageview_on_load(options) { + var self = this; + window.addEventListener('load', function send_pageview_with_options() { + self.send_pageview(options); + }); + }, + + /** + * Handles tracking events passed in URL. + * Assumes category and action values are separated by a "|" character. + * NOTE: Uses the unsampled analytics property. Watch out for future high click links! + * @param {Location} + */ + process_url_events: function process_url_events(location) { + var eventValues; + var actionValue; + var eventValue = getParams(location).iax; + if (!eventValue) return; + eventValues = eventValue.split('|'); + actionValue = eventValues.length >= 1 ? eventValues[1] : ''; + this.send_event_no_sampling( + eventValues[0], + actionValue, + window.location.pathname + ); + }, + + /** + * Attaches handlers for event tracking. + * + * To enable click tracking for a link, add a `data-event-click-tracking` + * attribute containing the Google Analytics Event Category and Action, separated + * by a vertical pipe (|). + * e.g. `` + * + * To enable form submit tracking, add a `data-event-form-tracking` attribute + * to the `form` tag. + * e.g. `` + * + * Additional tracking options can be added via a `data-event-tracking-options` + * parameter. This parameter, if included, should be a JSON string of the parameters. + * Valid parameters are: + * - service {string}: Corresponds to the Google Analytics property data values flow into + */ + set_up_event_tracking: function set_up_event_tracking() { + var self = this; + var clickTrackingAttributeName = 'event-click-tracking'; + var formTrackingAttributeName = 'event-form-tracking'; + var trackingOptionsAttributeName = 'event-tracking-options'; + + function handleAction(event, attributeName) { + var selector = '[data-' + attributeName + ']'; + var eventTarget = event.target; + if (!eventTarget) return; + var target = eventTarget.closest(selector); + if (!target) return; + var categoryAction; + var categoryActionParts; + var options; + categoryAction = target.dataset[toCamelCase(attributeName)]; + if (!categoryAction) return; + categoryActionParts = categoryAction.split('|'); + options = target.dataset[toCamelCase(trackingOptionsAttributeName)]; + options = options ? JSON.parse(options) : {}; + self.send_event( + categoryActionParts[0], + categoryActionParts[1], + categoryActionParts[2] || window.location.pathname, + options.service ? { service: options.service } : {} + ); + } + + function toCamelCase(str) { + return str.replace(/\W+(.)/g, function (match, chr) { + return chr.toUpperCase(); + }); + }; + + document.addEventListener('click', function(e) { + handleAction(e, clickTrackingAttributeName); + }); + + document.addEventListener('submit', function(e) { + handleAction(e, formTrackingAttributeName); + }); + }, + + /** + * @returns {Object[]} + */ + get_data_packets: function get_data_packets() { + return [this.values]; + }, + + /** + * Creates a tracking image for tracking JS compatibility. + * + * @param {string} type The type value for track_js_case in query params for 0.gif + */ + create_tracking_image: function create_tracking_image(type) { + this.send_ping_via_image({ + cache_bust: Math.random(), + kind: 'track_js', + track_js_case: type, + }); + } + }; + + return ArchiveAnalytics; +}()); +// @license-end diff --git a/Docs/Chapter 2.2 Reading a file from CD-ROM_files/banner-styles.css b/Docs/Chapter 2.2 Reading a file from CD-ROM_files/banner-styles.css new file mode 100644 index 0000000..8d39621 --- /dev/null +++ b/Docs/Chapter 2.2 Reading a file from CD-ROM_files/banner-styles.css @@ -0,0 +1,500 @@ +@import 'record.css'; /* for SPN1 */ + +#wm-ipp-base { + height:65px;/* initial height just in case js code fails */ + padding:0; + margin:0; + border:none; + background:none transparent; +} +#wm-ipp { + z-index: 2147483647; +} +#wm-ipp, #wm-ipp * { + font-family:Lucida Grande, Helvetica, Arial, sans-serif; + font-size:12px; + line-height:1.2; + letter-spacing:0; + width:auto; + height:auto; + max-width:none; + max-height:none; + min-width:0 !important; + min-height:0; + outline:none; + float:none; + text-align:left; + border:none; + color: #000; + text-indent: 0; + position: initial; + background: none; +} +#wm-ipp div, #wm-ipp canvas { + display: block; +} +#wm-ipp div, #wm-ipp tr, #wm-ipp td, #wm-ipp a, #wm-ipp form { + padding:0; + margin:0; + border:none; + border-radius:0; + background-color:transparent; + background-image:none; + /*z-index:2147483640;*/ + height:auto; +} +#wm-ipp table { + border:none; + border-collapse:collapse; + margin:0; + padding:0; + width:auto; + font-size:inherit; +} +#wm-ipp form input { + padding:1px !important; + height:auto; + display:inline; + margin:0; + color: #000; + background: none #fff; + border: 1px solid #666; +} +#wm-ipp form input[type=submit] { + padding:0 8px !important; + margin:1px 0 1px 5px !important; + width:auto !important; + border: 1px solid #000 !important; + background: #fff !important; + color: #000 !important; +} +#wm-ipp a { + display: inline; +} +#wm-ipp a:hover{ + text-decoration:underline; +} +#wm-ipp a.wm-btn:hover { + text-decoration:none; + color:#ff0 !important; +} +#wm-ipp a.wm-btn:hover span { + color:#ff0 !important; +} +#wm-ipp #wm-ipp-inside { + margin: 0 6px; + border:5px solid #000; + border-top:none; + background-color:rgba(255,255,255,0.9); + -moz-box-shadow:1px 1px 4px #333; + -webkit-box-shadow:1px 1px 4px #333; + box-shadow:1px 1px 4px #333; + border-radius:0 0 8px 8px; +} +/* selectors are intentionally verbose to ensure priority */ +#wm-ipp #wm-logo { + padding:0 10px; + vertical-align:middle; + min-width:100px; + flex: 0 0 100px; +} +#wm-ipp .c { + padding-left: 4px; +} +#wm-ipp .c .u { + margin-top: 4px !important; +} +#wm-ipp .n { + padding:0 0 0 5px !important; + vertical-align: bottom; +} +#wm-ipp .n a { + text-decoration:none; + color:#33f; + font-weight:bold; +} +#wm-ipp .n .b { + padding:0 6px 0 0 !important; + text-align:right !important; + overflow:visible; + white-space:nowrap; + color:#99a; + vertical-align:middle; +} +#wm-ipp .n .y .b { + padding:0 6px 2px 0 !important; +} +#wm-ipp .n .c { + background:#000; + color:#ff0; + font-weight:bold; + padding:0 !important; + text-align:center; +} +#wm-ipp.hi .n td.c { + color:#ec008c; +} +#wm-ipp .n td.f { + padding:0 0 0 6px !important; + text-align:left !important; + overflow:visible; + white-space:nowrap; + color:#99a; + vertical-align:middle; +} +#wm-ipp .n tr.m td { + text-transform:uppercase; + white-space:nowrap; + padding:2px 0; +} +#wm-ipp .c .s { + padding:0 5px 0 0 !important; + vertical-align:bottom; +} +#wm-ipp #wm-nav-captures { + white-space: nowrap; +} +#wm-ipp .c .s a.t { + color:#33f; + font-weight:bold; + line-height: 1.8; +} +#wm-ipp .c .s div.r { + color: #666; + font-size:9px; + white-space:nowrap; +} +#wm-ipp .c .k { + padding-bottom:1px; +} +#wm-ipp .c .s { + padding:0 5px 2px 0 !important; +} +#wm-ipp td#displayMonthEl { + padding: 2px 0 !important; +} +#wm-ipp td#displayYearEl { + padding: 0 0 2px 0 !important; +} + +div#wm-ipp-sparkline { + position:relative;/* for positioning markers */ + white-space:nowrap; + background-color:#fff; + cursor:pointer; + line-height:0.9; +} +#sparklineImgId, #wm-sparkline-canvas { + position:relative; + z-index:9012; + max-width:none; +} +#wm-ipp-sparkline div.yt { + position:absolute; + z-index:9010 !important; + background-color:#ff0 !important; + top: 0; +} +#wm-ipp-sparkline div.mt { + position:absolute; + z-index:9013 !important; + background-color:#ec008c !important; + top: 0; +} +#wm-ipp .r { + margin-left: 4px; +} +#wm-ipp .r a { + color:#33f; + border:none; + position:relative; + background-color:transparent; + background-repeat:no-repeat !important; + background-position:100% 100% !important; + text-decoration: none; +} +#wm-ipp #wm-capinfo { + /* prevents notice div background from sticking into round corners of + #wm-ipp-inside */ + border-radius: 0 0 4px 4px; +} +#wm-ipp #wm-capinfo .c-logo { + display:block; + float:left; + margin-right:3px; + width:90px; + min-height:90px; + max-height: 290px; + border-radius:45px; + overflow:hidden; + background-position:50%; + background-size:auto 90px; + box-shadow: 0 0 2px 2px rgba(208,208,208,128) inset; +} +#wm-ipp #wm-capinfo .c-logo span { + display:inline-block; +} +#wm-ipp #wm-capinfo .c-logo img { + height:90px; + position:relative; + left:-50%; +} +#wm-ipp #wm-capinfo .wm-title { + font-size:130%; +} +#wm-ipp #wm-capinfo a.wm-selector { + display:inline-block; + color: #aaa; + text-decoration:none !important; + padding: 2px 8px; +} +#wm-ipp #wm-capinfo a.wm-selector.selected { + background-color:#666; +} +#wm-ipp #wm-capinfo a.wm-selector:hover { + color: #fff; +} +#wm-ipp #wm-capinfo.notice-only #wm-capinfo-collected-by, +#wm-ipp #wm-capinfo.notice-only #wm-capinfo-timestamps { + display: none; +} +#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content { + background-color:#ff0; + padding:5px; + font-size:14px; + text-align:center; +} +#wm-ipp #wm-capinfo #wm-capinfo-notice .wm-capinfo-content * { + font-size:14px; + text-align:center; +} +#wm-ipp #wm-expand { + right: 1px; + bottom: -1px; + color: #ffffff; + background-color: #666 !important; + padding:0 5px 0 3px !important; + border-radius: 3px 3px 0 0 !important; +} +#wm-ipp #wm-expand span { + color: #ffffff; +} +#wm-ipp #wm-expand #wm-expand-icon { + display: inline-block; + transition: transform 0.5s; + transform-origin: 50% 45%; +} +#wm-ipp #wm-expand.wm-open #wm-expand-icon { + transform: rotate(180deg); +} +#wm-ipp #wmtb { + text-align:right; +} +#wm-ipp #wmtb #wmtbURL { + width: calc(100% - 45px); +} +#wm-ipp #wm-graph-anchor { + border-right:1px solid #ccc; +} +/* time coherence */ +html.wb-highlight { + box-shadow: inset 0 0 0 3px #a50e3a !important; +} +.wb-highlight { + outline: 3px solid #a50e3a !important; +} +#wm-ipp-print { + display:none !important; +} +@media print { +#wm-ipp-base { + display:none !important; +} +#wm-ipp-print { + display:block !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +} +@media (max-width:414px) { + #wm-ipp .xxs { + display:none !important; + } +} +@media (min-width:1055px) { +#wm-ipp #wm-graph-anchor { + display:block !important; +} +} +@media (max-width:1054px) { +#wm-ipp #wm-graph-anchor { + display:none !important; +} +} +@media (max-width:1163px) { +#wm-logo { + display:none !important; +} +} + +#wm-btns { + white-space: nowrap; + margin-top: -2px; +} + +#wm-btns #wm-save-snapshot-open { + margin-right: 7px; + top: -6px; +} + +#wm-btns #wm-sign-in { + box-sizing: content-box; + display: none; + margin-right: 7px; + top: -8px; + + /* + round border around sign in button + */ + border: 2px #000 solid; + border-radius: 14px; + padding-right: 2px; + padding-bottom: 2px; + width: 11px; + height: 11px; +} + +#wm-btns #wm-sign-in>.iconochive-person { + font-size: 12.5px; +} + +#wm-save-snapshot-open > .iconochive-web { + color:#000; + font-size:160%; +} + +#wm-ipp #wm-share { + display: flex; + align-items: flex-end; + justify-content: space-between; +} + +#wm-share > #wm-screenshot { + display: inline-block; + margin-right: 3px; + visibility: hidden; +} + +#wm-screenshot > .iconochive-image { + color:#000; + font-size:160%; +} + +#wm-share > #wm-video { + display: inline-block; + margin-right: 3px; + visibility: hidden; +} + +#wm-video > .iconochive-movies { + color: #000; + display: inline-block; + font-size: 150%; + margin-bottom: 2px; +} + +#wm-btns #wm-save-snapshot-in-progress { + display: none; + font-size:160%; + opacity: 0.5; + position: relative; + margin-right: 7px; + top: -5px; +} + +#wm-btns #wm-save-snapshot-success { + display: none; + color: green; + position: relative; + top: -7px; +} + +#wm-btns #wm-save-snapshot-fail { + display: none; + color: red; + position: relative; + top: -7px; +} + +.wm-icon-screen-shot { + background: url("../images/web-screenshot.svg") no-repeat !important; + background-size: contain !important; + width: 22px !important; + height: 19px !important; + + display: inline-block; +} +#donato { + /* transition effect is disable so as to simplify height adjustment */ + /*transition: height 0.5s;*/ + height: 0; + margin: 0; + padding: 0; + border-bottom: 1px solid #999 !important; +} +body.wm-modal { + height: auto !important; + overflow: hidden !important; +} +#donato #donato-base { + width: 100%; + height: 100%; + /*bottom: 0;*/ + margin: 0; + padding: 0; + position: absolute; + z-index: 2147483639; +} +body.wm-modal #donato #donato-base { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 2147483640; +} + +.wb-autocomplete-suggestions { + font-family: Lucida Grande, Helvetica, Arial, sans-serif; + font-size: 12px; + text-align: left; + cursor: default; + border: 1px solid #ccc; + border-top: 0; + background: #fff; + box-shadow: -1px 1px 3px rgba(0,0,0,.1); + position: absolute; + display: none; + z-index: 2147483647; + max-height: 254px; + overflow: hidden; + overflow-y: auto; + box-sizing: border-box; +} +.wb-autocomplete-suggestion { + position: relative; + padding: 0 .6em; + line-height: 23px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-size: 1.02em; + color: #333; +} +.wb-autocomplete-suggestion b { + font-weight: bold; +} +.wb-autocomplete-suggestion.selected { + background: #f0f0f0; +} diff --git a/Docs/Chapter 2.2 Reading a file from CD-ROM_files/bundle-playback.js b/Docs/Chapter 2.2 Reading a file from CD-ROM_files/bundle-playback.js new file mode 100644 index 0000000..390894d --- /dev/null +++ b/Docs/Chapter 2.2 Reading a file from CD-ROM_files/bundle-playback.js @@ -0,0 +1,3 @@ +// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0 +!function(e){var t={};function n(o){if(t[o])return t[o].exports;var i=t[o]={i:o,l:!1,exports:{}};return e[o].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=e,n.c=t,n.d=function(e,t,o){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(n.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)n.d(o,i,function(t){return e[t]}.bind(null,i));return o},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=9)}([function(e,t,n){"use strict";function o(e){return(o="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){return!t||"object"!==o(t)&&"function"!=typeof t?function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e):t}function s(e){var t="function"==typeof Map?new Map:void 0;return(s=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,o)}function o(){return a(e,arguments,u(this).constructor)}return o.prototype=Object.create(e.prototype,{constructor:{value:o,enumerable:!1,writable:!0,configurable:!0}}),l(o,e)})(e)}function a(e,t,n){return(a=c()?Reflect.construct:function(e,t,n){var o=[null];o.push.apply(o,t);var i=new(Function.bind.apply(e,o));return n&&l(i,n.prototype),i}).apply(null,arguments)}function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function l(e,t){return(l=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}if(n.d(t,"a",(function(){return f})),n.d(t,"d",(function(){return p})),n.d(t,"c",(function(){return d})),n.d(t,"b",(function(){return v})),"undefiend"!=typeof window.XMLHttpRequest){var h=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&l(e,t)}(o,e);var t,n=(t=o,function(){var e,n=u(t);if(c()){var o=u(this).constructor;e=Reflect.construct(n,arguments,o)}else e=n.apply(this,arguments);return r(this,e)});function o(){return i(this,o),n.apply(this,arguments)}return o}(s(XMLHttpRequest));Object.defineProperty(h.prototype,"responseURL",Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype,"responseURL"))}function f(e,t,n,o,i){var r;if((r=window.XMLHttpRequest?new h:new ActiveXObject("Microsoft.XMLHTTP")).onreadystatechange=function(){4==this.readyState&&n(r)},r.open(e,t,!0),o)for(var s in o)o.hasOwnProperty(s)&&r.setRequestHeader(s,o[s]);r.withCredentials=!0,r.send(i)}function p(e){return void 0!==e&&e&&e.constructor===Array}function d(e){return document.cookie.search(e)>=0}function m(e,t){var n=window["HTML".concat(e,"Element")];if(void 0!==n){var o=Object.getOwnPropertyDescriptor(n.prototype,t);void 0!==o&&Object.defineProperty(n.prototype,"_wm_".concat(t),o)}}function v(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"src",n="_wm_".concat(t);return n in e.__proto__?e[n]:e[t]}m("Image","src"),m("Media","src"),m("Embed","src"),m("IFrame","src"),m("Script","src"),m("Link","href"),m("Anchor","href")},function(e,t,n){"use strict";n.d(t,"c",(function(){return s})),n.d(t,"b",(function(){return a})),n.d(t,"a",(function(){return c}));var o=["January","February","March","April","May","June","July","August","September","October","November","December"],i=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],r={Y:function(e){return e.getUTCFullYear()},m:function(e){return e.getUTCMonth()+1},b:function(e){return i[e.getUTCMonth()]},B:function(e){return o[e.getUTCMonth()]},d:function(e){return e.getUTCDate()},H:function(e){return("0"+e.getUTCHours()).slice(-2)},M:function(e){return("0"+e.getUTCMinutes()).slice(-2)},S:function(e){return("0"+e.getUTCSeconds()).slice(-2)},"%":function(){return"%"}};function s(e){var t=function(e){return"number"==typeof e&&(e=e.toString()),[e.slice(-14,-10),e.slice(-10,-8),e.slice(-8,-6),e.slice(-6,-4),e.slice(-4,-2),e.slice(-2)]}(e);return new Date(Date.UTC(t[0],t[1]-1,t[2],t[3],t[4],t[5]))}function a(e){return i[e]}function c(e,t){return t.replace(/%./g,(function(t){var n=r[t[1]];return n?n(s(e)):t}))}},function(e,t,n){"use strict";n.d(t,"b",(function(){return s})),n.d(t,"a",(function(){return a}));var o=n(0);function i(e,t){for(var n=0;n=400?i.failure&&i.failure(e):i.success&&i.success(e)}),{"Content-Type":"application/json"},r.stringify({url:e,snapshot:t,tags:n||[]})),!1}var a=function(){function e(t,n,i){var r=this;!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.el=t,this.url=n,this.timestamp=i,t.onclick=this.save.bind(this),document.addEventListener("DOMContentLoaded",(function(){Object(o.c)("logged-in-user")&&Object(o.c)("logged-in-sig")?r.userIsLoggedIn():r.userIsNotLoggedIn()}))}var t,n,r;return t=e,(n=[{key:"save",value:function(e){this.start(),s(this.url,this.timestamp,[],{failure:this.failure.bind(this),success:this.success.bind(this)})}},{key:"start",value:function(){this.hide(["wm-save-snapshot-fail","wm-save-snapshot-open","wm-save-snapshot-success"]),this.show(["wm-save-snapshot-in-progress"])}},{key:"failure",value:function(e){401==e.status?this.userNotLoggedIn(e):(this.hide(["wm-save-snapshot-in-progress","wm-save-snapshot-success"]),this.show(["wm-save-snapshot-fail","wm-save-snapshot-open"]),console.log("You have got an error."),console.log("If you think something wrong here please send it to support."),console.log('Response: "'+e.responseText+'"'),console.log('status: "'+e.status+'"'))}},{key:"success",value:function(e){this.hide(["wm-save-snapshot-fail","wm-save-snapshot-in-progress"]),this.show(["wm-save-snapshot-open","wm-save-snapshot-success"])}},{key:"userIsLoggedIn",value:function(){this.show("wm-save-snapshot-open"),this.hide("wm-sign-in")}},{key:"userIsNotLoggedIn",value:function(){this.hide(["wm-save-snapshot-open","wm-save-snapshot-in-progress"]),this.show("wm-sign-in")}},{key:"show",value:function(e){this.setDisplayStyle(e,"inline-block")}},{key:"hide",value:function(e){this.setDisplayStyle(e,"none")}},{key:"setDisplayStyle",value:function(e,t){var n=this;(Object(o.d)(e)?e:[e]).forEach((function(e){var o=n.el.getRootNode().getElementById(e);o&&(o.style.display=t)}))}}])&&i(t.prototype,n),r&&i(t,r),e}()},,,,,,,function(e,t,n){"use strict";var o;n.r(t);var i,r={createElementNS:document.createElementNS};var s=!0;function a(e){!function(e,t,n){if(n){var o=new Date;o.setTime(o.getTime()+24*n*60*60*1e3);var i="; expires="+o.toGMTString()}else i="";document.cookie=e+"="+t+i+"; path=/"}(e,"",-1)}var c=n(0),l=n(1),u=window.Date;function h(e,t){return(e=e.toString()).length>=t?e:"00000000".substring(0,t-e.length)+e}function f(e){for(var t=0,n=0;n3}(e)){var o=[];for(n=0;n=e.length?{done:!0}:{done:!1,value:e[t++]}},e:function(e){throw e},f:n}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i,r=!0,s=!1;return{s:function(){o=e[Symbol.iterator]()},n:function(){var e=o.next();return r=e.done,e},e:function(e){s=!0,i=e},f:function(){try{r||null==o.return||o.return()}finally{if(s)throw i}}}}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=new Array(t);n2&&void 0!==arguments[2]?arguments[2]:"src",r=window.location.origin,s=b(window,e),l=p(s);try{for(l.s();!(o=l.n()).done;){var u=o.value;if(!n||n(u)){var h=Object(c.b)(u,i);h&&!h.startsWith(t)&&h.startsWith(r)&&(h.startsWith("data:")||a.push(h))}}}catch(e){l.e(e)}finally{l.f()}}u("img"),u("frame"),u("iframe",(function(e){return"playback"!==e.id})),u("script"),u("link",(function(e){return"stylesheet"===e.rel}),"href");var h=a.filter((function(e,t,n){return n.indexOf(e)===t}));h.length>0?(s=0,h.map((function(e){e.match("^https?://")&&(s++,Object(c.a)("HEAD",e,(function(e){if(200==e.status){var t=e.getResponseHeader("Memento-Datetime");if(null==t)console.log("%s: no Memento-Datetime",u);else{var n=document.createElement("span"),a=function(e,t){var n=new Date(e).getTime()-t,o="";n<0?(o+="-",n=Math.abs(n)):o+="+";var i=!1;if(n<1e3)return{delta:n,text:"",highlight:i};var r=n,s=Math.floor(n/1e3/60/60/24/30/12);n-=1e3*s*60*60*24*30*12;var a=Math.floor(n/1e3/60/60/24/30);n-=1e3*a*60*60*24*30;var c=Math.floor(n/1e3/60/60/24);n-=1e3*c*60*60*24;var l=Math.floor(n/1e3/60/60);n-=1e3*l*60*60;var u=Math.floor(n/1e3/60);n-=1e3*u*60;var h=Math.floor(n/1e3),f=[];s>1?(f.push(s+" years"),i=!0):1==s&&(f.push(s+" year"),i=!0);a>1?(f.push(a+" months"),i=!0):1==a&&(f.push(a+" month"),i=!0);c>1?f.push(c+" days"):1==c&&f.push(c+" day");l>1?f.push(l+" hours"):1==l&&f.push(l+" hour");u>1?f.push(u+" minutes"):1==u&&f.push(u+" minute");h>1?f.push(h+" seconds"):1==h&&f.push(h+" second");f.length>2&&(f=f.slice(0,2));return{delta:r,text:o+f.join(" "),highlight:i}}(t,r),c=a.highlight?"color:red;":"";n.innerHTML=" "+a.text,n.title=t,n.setAttribute("style",c);var l=e.getResponseHeader("Content-Type"),u=e.responseURL.replace(window.location.origin,""),h=document.createElement("a");h.innerHTML=u.split("/").splice(3).join("/"),h._wm_href=u,h.title=l,h.onmouseover=y,h.onmouseout=w;var f=document.createElement("div");f.setAttribute("data-delta",a.delta),f.appendChild(h),f.append(n),o.appendChild(f);var p=Array.prototype.slice.call(o.childNodes,0);p.sort((function(e,t){return t.getAttribute("data-delta")-e.getAttribute("data-delta")})),o.innerHTML="";for(var d=0,m=p.length;d0)for(var n=0;n0)for(var n=0;n0?this.sc.scrollTop=i+this.sc.suggestionHeight+o-this.sc.maxHeight:i<0&&(this.sc.scrollTop=i+o)}}},{key:"blurHandler",value:function(){var e=this;try{var t=this.root.querySelector(".wb-autocomplete-suggestions:hover")}catch(e){t=null}t?this.input!==document.activeElement&&setTimeout((function(){return e.focus()}),20):(this.last_val=this.input.value,this.sc.style.display="none",setTimeout((function(){return e.sc.style.display="none"}),350))}},{key:"suggest",value:function(e){var t=this.input.value;if(this.cache[t]=e,e.length&&t.length>=this.minChars){for(var n="",o=0;o40)&&13!=n&&27!=n){var o=this.input.value;if(o.length>=this.minChars){if(o!=this.last_val){if(this.last_val=o,clearTimeout(this.timer),this.cache){if(o in this.cache)return void this.suggest(this.cache[o]);for(var i=1;i'+e.replace(n,"$1")+""}},{key:"onSelect",value:function(e,t,n){}}]),e}(),j=function(){function e(t,n){S(this,e);var o=t.getRootNode();if(o.querySelector){var i="object"==_(t)?[t]:o.querySelectorAll(t);this.elems=i.map((function(e){return new E(e,n)}))}}return T(e,[{key:"destroy",value:function(){for(;this.elems.length>0;)this.elems.pop().unload()}}]),e}(),R=n(2),A=window.JSON,N=window.open,U=window.Date,I=document,D=document,P=function(e){return D.getElementById(e)};var q,F="/static/";function B(e){L.classList.contains("wm-closed")?(e&&O.classList.add("notice-only"),L.classList.replace("wm-closed","wm-open"),O.style.display="block",m(q,F,P)):(L.classList.replace("wm-open","wm-closed"),O.style.display="none",O.classList.remove("notice-only"))}function W(e,t){var n=P(e);n&&(n.style.visibility=t?"visible":"hidden")}function X(e,t){Object(c.d)(e)||(e=[e]);for(var n=0;n0&&r<60,r)}))}window.__wm={init:function(e){!function(){var e=document.cookie.split(";");if(e.length>40)for(var t=0;t1?t-1:0),o=1;o0;)x.appendChild(C.children[0]);if(d)for(var H=0;H'+((""+n).replace(/\B(?=(\d{3})+$)/g,",")+" ")+(n>1?"captures":"capture")+"",f=l.a(i,"%d %b %Y");s!=i&&(f+=" - "+l.a(s,"%d %b %Y")),h+='
'+f+"
",t.innerHTML=h}(o),function(e,t,n,o,i,r,s){var a=o.getContext("2d");if(a){a.fillStyle="#FFF";var c=(new u).getUTCFullYear(),l=t/(c-i+1),h=f(e.years),p=h[0],d=n/h[1];if(r>=i){var m=T(r);a.fillStyle="#FFFFA5",a.fillRect(m,0,l,n)}for(var v=i;v<=c;v++){m=T(v);a.beginPath(),a.moveTo(m,0),a.lineTo(m,n),a.lineWidth=1,a.strokeStyle="#CCC",a.stroke()}s=parseInt(s)-1;for(var g=(l-1)/12,y=0;y0){var M=Math.ceil(S*d);a.fillStyle=v==r&&_==s?"#EC008C":"#000",a.fillRect(Math.round(b),Math.ceil(n-M),Math.ceil(g),Math.round(M))}b+=g}}}function T(e){return Math.ceil((e-i)*l)+.5}}(o,e,t,Z,a,_,S)}}))}else{var te=new Image;te.src="/__wb/sparkline?url="+encodeURIComponent(r)+"&width="+e+"&height="+t+"&selected_year="+_+"&selected_month="+S+(i&&"&collection="+i||""),te.alt="sparkline",te.width=e,te.height=t,te.id="sparklineImgId",te.border="0",Q.parentNode.replaceChild(te,Q)}function ne(e){for(var t=[],n=e.length,o=0;o. + */ +(function(){function FuncMap(){this._map=[]}function ensureNumber(maybeNumber){try{switch(typeof maybeNumber){case"number":case"bigint":return maybeNumber;}var converted=Number(maybeNumber);return isNaN(converted)?null:converted}catch(e){}return null}function addToStringTagToClass(clazz,tag){typeof self.Symbol!=="undefined"&&typeof self.Symbol.toStringTag!=="undefined"&&Object.defineProperty(clazz.prototype,self.Symbol.toStringTag,{value:tag,enumerable:false})}function autobind(clazz){for(var prop,propValue,proto=clazz.__proto__||clazz.constructor.prototype||clazz.prototype,clazzProps=Object.getOwnPropertyNames(proto),len=clazzProps.length,i=0;i=0){var fnMapping=this._map.splice(idx,1);return fnMapping[0][1]}return null},FuncMap.prototype.map=function(param){for(var i=0;i0&&afw.preserveMedia(media)})},AutoFetcher.prototype.terminate=function(){this.worker.terminate()},AutoFetcher.prototype.justFetch=function(urls){this.worker.postMessage({type:"fetch-all",values:urls})},AutoFetcher.prototype.fetchAsPage=function(url,originalUrl,title){if(url){var headers={"X-Wombat-History-Page":originalUrl};if(title){var encodedTitle=encodeURIComponent(title.trim());title&&(headers["X-Wombat-History-Title"]=encodedTitle)}var fetchData={url:url,options:{headers:headers,cache:"no-store"}};this.justFetch([fetchData])}},AutoFetcher.prototype.postMessage=function(msg,deferred){if(deferred){var afWorker=this;return void Promise.resolve().then(function(){afWorker.worker.postMessage(msg)})}this.worker.postMessage(msg)},AutoFetcher.prototype.preserveSrcset=function(srcset,mod){this.postMessage({type:"values",srcset:{value:srcset,mod:mod,presplit:true}},true)},AutoFetcher.prototype.preserveDataSrcset=function(elem){this.postMessage({type:"values",srcset:{value:elem.dataset.srcset,mod:this.rwMod(elem),presplit:false}},true)},AutoFetcher.prototype.preserveMedia=function(media){this.postMessage({type:"values",media:media},true)},AutoFetcher.prototype.getSrcset=function(elem){return this.wombat.wb_getAttribute?this.wombat.wb_getAttribute.call(elem,"srcset"):elem.getAttribute("srcset")},AutoFetcher.prototype.rwMod=function(elem){switch(elem.tagName){case"SOURCE":return elem.parentElement&&elem.parentElement.tagName==="PICTURE"?"im_":"oe_";case"IMG":return"im_";}return"oe_"},AutoFetcher.prototype.extractFromLocalDoc=function(){var afw=this;Promise.resolve().then(function(){for(var msg={type:"values",context:{docBaseURI:document.baseURI}},media=[],i=0,sheets=document.styleSheets;i=0)||scriptType.indexOf("text/template")>=0)},Wombat.prototype.skipWrapScriptTextBasedOnText=function(text){if(!text||text.indexOf(this.WB_ASSIGN_FUNC)>=0||text.indexOf("<")===0)return true;for(var override_props=["window","self","document","location","top","parent","frames","opener"],i=0;i=0)return false;return true},Wombat.prototype.nodeHasChildren=function(node){if(!node)return false;if(typeof node.hasChildNodes==="function")return node.hasChildNodes();var kids=node.children||node.childNodes;return!!kids&&kids.length>0},Wombat.prototype.rwModForElement=function(elem,attrName){if(!elem)return undefined;var mod="mp_";if(!(elem.tagName==="LINK"&&attrName==="href")){var maybeMod=this.tagToMod[elem.tagName];maybeMod!=null&&(mod=maybeMod[attrName])}else if(elem.rel){var relV=elem.rel.trim().toLowerCase(),asV=this.wb_getAttribute.call(elem,"as");if(asV&&this.linkTagMods.linkRelToAs[relV]!=null){var asMods=this.linkTagMods.linkRelToAs[relV];mod=asMods[asV.toLowerCase()]}else this.linkTagMods[relV]!=null&&(mod=this.linkTagMods[relV])}return mod},Wombat.prototype.removeWBOSRC=function(elem){elem.tagName!=="SCRIPT"||elem.__$removedWBOSRC$__||(elem.hasAttribute("__wb_orig_src")&&elem.removeAttribute("__wb_orig_src"),elem.__$removedWBOSRC$__=true)},Wombat.prototype.retrieveWBOSRC=function(elem){if(elem.tagName==="SCRIPT"&&!elem.__$removedWBOSRC$__){var maybeWBOSRC;return maybeWBOSRC=this.wb_getAttribute?this.wb_getAttribute.call(elem,"__wb_orig_src"):elem.getAttribute("__wb_orig_src"),maybeWBOSRC==null&&(elem.__$removedWBOSRC$__=true),maybeWBOSRC}return undefined},Wombat.prototype.wrapScriptTextJsProxy=function(scriptText){return"var _____WB$wombat$assign$function_____ = function(name) {return (self._wb_wombat && self._wb_wombat.local_init && self._wb_wombat.local_init(name)) || self[name]; };\nif (!self.__WB_pmw) { self.__WB_pmw = function(obj) { this.__WB_source = obj; return this; } }\n{\nlet window = _____WB$wombat$assign$function_____(\"window\");\nlet self = _____WB$wombat$assign$function_____(\"self\");\nlet document = _____WB$wombat$assign$function_____(\"document\");\nlet location = _____WB$wombat$assign$function_____(\"location\");\nlet top = _____WB$wombat$assign$function_____(\"top\");\nlet parent = _____WB$wombat$assign$function_____(\"parent\");\nlet frames = _____WB$wombat$assign$function_____(\"frames\");\nlet opener = _____WB$wombat$assign$function_____(\"opener\");\n"+scriptText.replace(this.DotPostMessageRe,".__WB_pmw(self.window)$1")+"\n\n}"},Wombat.prototype.watchElem=function(elem,func){if(!this.$wbwindow.MutationObserver)return false;var m=new this.$wbwindow.MutationObserver(function(records,observer){for(var r,i=0;i"},Wombat.prototype.getFinalUrl=function(useRel,mod,url){var prefix=useRel?this.wb_rel_prefix:this.wb_abs_prefix;return mod==null&&(mod=this.wb_info.mod),this.wb_info.is_live||(prefix+=this.wb_info.wombat_ts),prefix+=mod,prefix[prefix.length-1]!=="/"&&(prefix+="/"),prefix+url},Wombat.prototype.resolveRelUrl=function(url,doc){var docObj=doc||this.$wbwindow.document,parser=this.makeParser(docObj.baseURI,docObj),hash=parser.href.lastIndexOf("#"),href=hash>=0?parser.href.substring(0,hash):parser.href,lastslash=href.lastIndexOf("/");return parser.href=lastslash>=0&&lastslash!==href.length-1?href.substring(0,lastslash+1)+url:href+url,parser.href},Wombat.prototype.extractOriginalURL=function(rewrittenUrl){if(!rewrittenUrl)return"";if(this.wb_is_proxy)return rewrittenUrl;var rwURLString=rewrittenUrl.toString(),url=rwURLString;if(this.startsWithOneOf(url,this.IGNORE_PREFIXES))return url;var start;start=this.startsWith(url,this.wb_abs_prefix)?this.wb_abs_prefix.length:this.wb_rel_prefix&&this.startsWith(url,this.wb_rel_prefix)?this.wb_rel_prefix.length:this.wb_rel_prefix?1:0;var index=url.indexOf("/http",start);return index<0&&(index=url.indexOf("///",start)),index<0&&(index=url.indexOf("/blob:",start)),index<0&&(index=url.indexOf("/about:blank",start)),index>=0?url=url.substr(index+1):(index=url.indexOf(this.wb_replay_prefix),index>=0&&(url=url.substr(index+this.wb_replay_prefix.length)),url.length>4&&url.charAt(2)==="_"&&url.charAt(3)==="/"&&(url=url.substr(4)),url!==rwURLString&&!this.startsWithOneOf(url,this.VALID_PREFIXES)&&!this.startsWith(url,"blob:")&&(url=this.wb_orig_scheme+url)),rwURLString.charAt(0)==="/"&&rwURLString.charAt(1)!=="/"&&this.startsWith(url,this.wb_orig_origin)&&(url=url.substr(this.wb_orig_origin.length)),this.startsWith(url,this.REL_PREFIX)?this.wb_info.wombat_scheme+":"+url:url},Wombat.prototype.makeParser=function(maybeRewrittenURL,doc){var originalURL=this.extractOriginalURL(maybeRewrittenURL),docElem=doc;return doc||(this.$wbwindow.location.href==="about:blank"&&this.$wbwindow.opener?docElem=this.$wbwindow.opener.document:docElem=this.$wbwindow.document),this._makeURLParser(originalURL,docElem)},Wombat.prototype._makeURLParser=function(url,docElem){try{return new this.$wbwindow.URL(url,docElem.baseURI)}catch(e){}var p=docElem.createElement("a");return p._no_rewrite=true,p.href=url,p},Wombat.prototype.defProp=function(obj,prop,setFunc,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;var descriptor={configurable:true,enumerable:enumerable||false,get:getFunc};setFunc&&(descriptor.set=setFunc);try{return Object.defineProperty(obj,prop,descriptor),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.defGetterProp=function(obj,prop,getFunc,enumerable){var existingDescriptor=Object.getOwnPropertyDescriptor(obj,prop);if(existingDescriptor&&!existingDescriptor.configurable)return false;if(!getFunc)return false;try{return Object.defineProperty(obj,prop,{configurable:true,enumerable:enumerable||false,get:getFunc}),true}catch(e){return console.warn("Failed to redefine property %s",prop,e.message),false}},Wombat.prototype.getOrigGetter=function(obj,prop){var orig_getter;if(obj.__lookupGetter__&&(orig_getter=obj.__lookupGetter__(prop)),!orig_getter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_getter=props.get)}return orig_getter},Wombat.prototype.getOrigSetter=function(obj,prop){var orig_setter;if(obj.__lookupSetter__&&(orig_setter=obj.__lookupSetter__(prop)),!orig_setter&&Object.getOwnPropertyDescriptor){var props=Object.getOwnPropertyDescriptor(obj,prop);props&&(orig_setter=props.set)}return orig_setter},Wombat.prototype.getAllOwnProps=function(obj){for(var ownProps=[],props=Object.getOwnPropertyNames(obj),i=0;i "+final_href),actualLocation.href=final_href}}},Wombat.prototype.checkLocationChange=function(wombatLoc,isTop){var locType=typeof wombatLoc,actual_location=isTop?this.$wbwindow.__WB_replay_top.location:this.$wbwindow.location;locType==="string"?this.updateLocation(wombatLoc,actual_location.href,actual_location):locType==="object"&&this.updateLocation(wombatLoc.href,wombatLoc._orig_href,actual_location)},Wombat.prototype.checkAllLocations=function(){return!this.wb_wombat_updating&&void(this.wb_wombat_updating=true,this.checkLocationChange(this.$wbwindow.WB_wombat_location,false),this.$wbwindow.WB_wombat_location!=this.$wbwindow.__WB_replay_top.WB_wombat_location&&this.checkLocationChange(this.$wbwindow.__WB_replay_top.WB_wombat_location,true),this.wb_wombat_updating=false)},Wombat.prototype.proxyToObj=function(source){if(source)try{var proxyRealObj=source.__WBProxyRealObj__;if(proxyRealObj)return proxyRealObj}catch(e){}return source},Wombat.prototype.objToProxy=function(obj){if(obj)try{var maybeWbProxy=obj._WB_wombat_obj_proxy;if(maybeWbProxy)return maybeWbProxy}catch(e){}return obj},Wombat.prototype.defaultProxyGet=function(obj,prop,ownProps,fnCache){switch(prop){case"__WBProxyRealObj__":return obj;case"location":case"WB_wombat_location":return obj.WB_wombat_location;case"_WB_wombat_obj_proxy":return obj._WB_wombat_obj_proxy;case"__WB_pmw":case"WB_wombat_eval":case this.WB_ASSIGN_FUNC:case this.WB_CHECK_THIS_FUNC:return obj[prop];case"origin":return obj.WB_wombat_location.origin;case"constructor":if(obj.constructor===Window)return obj.constructor;}var retVal=obj[prop],type=typeof retVal;if(type==="function"&&ownProps.indexOf(prop)!==-1){switch(prop){case"requestAnimationFrame":case"cancelAnimationFrame":{if(!this.isNativeFunction(retVal))return retVal;break}}var cachedFN=fnCache[prop];return cachedFN&&cachedFN.original===retVal||(cachedFN={original:retVal,boundFn:retVal.bind(obj)},fnCache[prop]=cachedFN),cachedFN.boundFn}return type==="object"&&retVal&&retVal._WB_wombat_obj_proxy?(retVal instanceof Window&&this.initNewWindowWombat(retVal),retVal._WB_wombat_obj_proxy):retVal},Wombat.prototype.setLoc=function(loc,originalURL){var parser=this.makeParser(originalURL,loc.ownerDocument);loc._orig_href=originalURL,loc._parser=parser;var href=parser.href;loc._hash=parser.hash,loc._href=href,loc._host=parser.host,loc._hostname=parser.hostname,loc._origin=parser.origin?parser.host?parser.origin:"null":parser.protocol+"//"+parser.hostname+(parser.port?":"+parser.port:""),loc._pathname=parser.pathname,loc._port=parser.port,loc._protocol=parser.protocol,loc._search=parser.search,Object.defineProperty||(loc.href=href,loc.hash=parser.hash,loc.host=loc._host,loc.hostname=loc._hostname,loc.origin=loc._origin,loc.pathname=loc._pathname,loc.port=loc._port,loc.protocol=loc._protocol,loc.search=loc._search)},Wombat.prototype.makeGetLocProp=function(prop,origGetter){var wombat=this;return function newGetLocProp(){if(this._no_rewrite)return origGetter.call(this,prop);var curr_orig_href=origGetter.call(this,"href");return prop==="href"?wombat.extractOriginalURL(curr_orig_href):prop==="ancestorOrigins"?[]:(this._orig_href!==curr_orig_href&&wombat.setLoc(this,curr_orig_href),this["_"+prop])}},Wombat.prototype.makeSetLocProp=function(prop,origSetter,origGetter){var wombat=this;return function newSetLocProp(value){if(this._no_rewrite)return origSetter.call(this,prop,value);if(this["_"+prop]!==value){if(this["_"+prop]=value,!this._parser){var href=origGetter.call(this);this._parser=wombat.makeParser(href,this.ownerDocument)}var rel=false;prop==="href"&&typeof value==="string"&&value&&(value[0]==="."?value=wombat.resolveRelUrl(value,this.ownerDocument):value[0]==="/"&&(value.length<=1||value[1]!=="/")&&(rel=true,value=WB_wombat_location.origin+value));try{this._parser[prop]=value}catch(e){console.log("Error setting "+prop+" = "+value)}prop==="hash"?(value=this._parser[prop],origSetter.call(this,"hash",value)):(rel=rel||value===this._parser.pathname,value=wombat.rewriteUrl(this._parser.href,rel),origSetter.call(this,"href",value))}}},Wombat.prototype.styleReplacer=function(match,n1,n2,n3,offset,string){return n1+this.rewriteUrl(n2)+n3},Wombat.prototype.domConstructorErrorChecker=function(thisObj,what,args,numRequiredArgs){var errorMsg,needArgs=typeof numRequiredArgs==="number"?numRequiredArgs:1;if(thisObj instanceof Window?errorMsg="Failed to construct '"+what+"': Please use the 'new' operator, this DOM object constructor cannot be called as a function.":args&&args.length=0)return url;if(url.indexOf(this.wb_rel_prefix)===0&&url.indexOf("http")>1){var scheme_sep=url.indexOf(":/");return scheme_sep>0&&url[scheme_sep+2]!=="/"?url.substring(0,scheme_sep+2)+"/"+url.substring(scheme_sep+2):url}return this.getFinalUrl(true,mod,this.wb_orig_origin+url)}url.charAt(0)==="."&&(url=this.resolveRelUrl(url,doc));var prefix=this.startsWithOneOf(url.toLowerCase(),this.VALID_PREFIXES);if(prefix){var orig_host=this.$wbwindow.__WB_replay_top.location.host,orig_protocol=this.$wbwindow.__WB_replay_top.location.protocol,prefix_host=prefix+orig_host+"/";if(this.startsWith(url,prefix_host)){if(this.startsWith(url,this.wb_replay_prefix))return url;var curr_scheme=orig_protocol+"//",path=url.substring(prefix_host.length),rebuild=false;return path.indexOf(this.wb_rel_prefix)<0&&url.indexOf("/static/")<0&&(path=this.getFinalUrl(true,mod,WB_wombat_location.origin+"/"+path),rebuild=true),prefix!==curr_scheme&&prefix!==this.REL_PREFIX&&(rebuild=true),rebuild&&(url=useRel?"":curr_scheme+orig_host,path&&path[0]!=="/"&&(url+="/"),url+=path),url}return this.getFinalUrl(useRel,mod,url)}return prefix=this.startsWithOneOf(url,this.BAD_PREFIXES),prefix?this.getFinalUrl(useRel,mod,this.extractOriginalURL(url)):this.isHostUrl(url)&&!this.startsWith(url,originalLoc.host+"/")?this.getFinalUrl(useRel,mod,this.wb_orig_scheme+url):url},Wombat.prototype.rewriteUrl=function(url,useRel,mod,doc){var rewritten=this.rewriteUrl_(url,useRel,mod,doc);return this.debug_rw&&(url===rewritten?console.log("NOT REWRITTEN "+url):console.log("REWRITE: "+url+" -> "+rewritten)),rewritten},Wombat.prototype.performAttributeRewrite=function(elem,name,value,absUrlOnly){switch(name){case"innerHTML":case"outerHTML":return this.rewriteHtml(value);case"filter":return this.rewriteInlineStyle(value);case"style":return this.rewriteStyle(value);case"srcset":return this.rewriteSrcset(value,elem);}if(absUrlOnly&&!this.startsWithOneOf(value,this.VALID_PREFIXES))return value;var mod=this.rwModForElement(elem,name);return this.wbUseAFWorker&&this.WBAutoFetchWorker&&this.isSavedDataSrcSrcset(elem)&&this.WBAutoFetchWorker.preserveDataSrcset(elem),this.rewriteUrl(value,false,mod,elem.ownerDocument)},Wombat.prototype.rewriteAttr=function(elem,name,absUrlOnly){var changed=false;if(!elem||!elem.getAttribute||elem._no_rewrite||elem["_"+name])return changed;var value=this.wb_getAttribute.call(elem,name);if(!value||this.startsWith(value,"javascript:"))return changed;var new_value=this.performAttributeRewrite(elem,name,value,absUrlOnly);return new_value!==value&&(this.removeWBOSRC(elem),this.wb_setAttribute.call(elem,name,new_value),changed=true),changed},Wombat.prototype.noExceptRewriteStyle=function(style){try{return this.rewriteStyle(style)}catch(e){return style}},Wombat.prototype.rewriteStyle=function(style){if(!style)return style;var value=style;return typeof style==="object"&&(value=style.toString()),typeof value==="string"?value.replace(this.STYLE_REGEX,this.styleReplacer).replace(this.IMPORT_REGEX,this.styleReplacer).replace(this.no_wombatRe,""):value},Wombat.prototype.rewriteSrcset=function(value,elem){if(!value)return"";for(var split=value.split(this.srcsetRe),values=[],mod=this.rwModForElement(elem,"srcset"),i=0;i=0){var JS="javascript:";new_value="javascript:window.parent._wb_wombat.initNewWindowWombat(window);"+value.substr(11)}return new_value||(new_value=this.rewriteUrl(value,false,this.rwModForElement(elem,attrName))),new_value!==value&&(this.wb_setAttribute.call(elem,attrName,new_value),true)},Wombat.prototype.rewriteScript=function(elem){if(elem.hasAttribute("src")||!elem.textContent||!this.$wbwindow.Proxy)return this.rewriteAttr(elem,"src");if(this.skipWrapScriptBasedOnType(elem.type))return false;var text=elem.textContent.trim();return!this.skipWrapScriptTextBasedOnText(text)&&(elem.textContent=this.wrapScriptTextJsProxy(text),true)},Wombat.prototype.rewriteSVGElem=function(elem){var changed=this.rewriteAttr(elem,"filter");return changed=this.rewriteAttr(elem,"style")||changed,changed=this.rewriteAttr(elem,"xlink:href")||changed,changed=this.rewriteAttr(elem,"href")||changed,changed=this.rewriteAttr(elem,"src")||changed,changed},Wombat.prototype.rewriteElem=function(elem){var changed=false;if(!elem)return changed;if(elem instanceof SVGElement)changed=this.rewriteSVGElem(elem);else switch(elem.tagName){case"META":var maybeCSP=this.wb_getAttribute.call(elem,"http-equiv");maybeCSP&&maybeCSP.toLowerCase()==="content-security-policy"&&(this.wb_setAttribute.call(elem,"http-equiv","_"+maybeCSP),changed=true);break;case"STYLE":var new_content=this.rewriteStyle(elem.textContent);elem.textContent!==new_content&&(elem.textContent=new_content,changed=true,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.sheet!=null&&this.WBAutoFetchWorker.deferredSheetExtraction(elem.sheet));break;case"LINK":changed=this.rewriteAttr(elem,"href"),this.wbUseAFWorker&&elem.rel==="stylesheet"&&this._addEventListener(elem,"load",this.utilFns.wbSheetMediaQChecker);break;case"IMG":changed=this.rewriteAttr(elem,"src"),changed=this.rewriteAttr(elem,"srcset")||changed,changed=this.rewriteAttr(elem,"style")||changed,this.wbUseAFWorker&&this.WBAutoFetchWorker&&elem.dataset.srcset&&this.WBAutoFetchWorker.preserveDataSrcset(elem);break;case"OBJECT":if(this.wb_info.isSW&&elem.parentElement&&elem.getAttribute("type")==="application/pdf"){for(var iframe=this.$wbwindow.document.createElement("IFRAME"),i=0;i0;)for(var child,children=rewriteQ.shift(),i=0;i"+rwString+"","text/html");if(!inner_doc||!this.nodeHasChildren(inner_doc.head)||!inner_doc.head.children[0].content)return rwString;var template=inner_doc.head.children[0];if(template._no_rewrite=true,this.recurseRewriteElem(template.content)){var new_html=template.innerHTML;if(checkEndTag){var first_elem=template.content.children&&template.content.children[0];if(first_elem){var end_tag="";this.endsWith(new_html,end_tag)&&!this.endsWith(rwString.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(rwString[0]!=="<"||rwString[rwString.length-1]!==">")return this.write_buff+=rwString,undefined}return new_html}return rwString},Wombat.prototype.rewriteHtmlFull=function(string,checkEndTag){var inner_doc=new DOMParser().parseFromString(string,"text/html");if(!inner_doc)return string;for(var changed=false,i=0;i=0)inner_doc.documentElement._no_rewrite=true,new_html=this.reconstructDocType(inner_doc.doctype)+inner_doc.documentElement.outerHTML;else{inner_doc.head._no_rewrite=true,inner_doc.body._no_rewrite=true;var headHasKids=this.nodeHasChildren(inner_doc.head),bodyHasKids=this.nodeHasChildren(inner_doc.body);if(new_html=(headHasKids?inner_doc.head.outerHTML:"")+(bodyHasKids?inner_doc.body.outerHTML:""),checkEndTag)if(inner_doc.all.length>3){var end_tag="";this.endsWith(new_html,end_tag)&&!this.endsWith(string.toLowerCase(),end_tag)&&(new_html=new_html.substring(0,new_html.length-end_tag.length))}else if(string[0]!=="<"||string[string.length-1]!==">")return void(this.write_buff+=string);new_html=this.reconstructDocType(inner_doc.doctype)+new_html}return new_html}return string},Wombat.prototype.rewriteInlineStyle=function(orig){var decoded;try{decoded=decodeURIComponent(orig)}catch(e){decoded=orig}if(decoded!==orig){var parts=this.rewriteStyle(decoded).split(",",2);return parts[0]+","+encodeURIComponent(parts[1])}return this.rewriteStyle(orig)},Wombat.prototype.rewriteCookie=function(cookie){var wombat=this,rwCookie=cookie.replace(this.wb_abs_prefix,"").replace(this.wb_rel_prefix,"");return rwCookie=rwCookie.replace(this.cookie_domain_regex,function(m,m1){var message={domain:m1,cookie:rwCookie,wb_type:"cookie"};return wombat.sendTopMessage(message,true),wombat.$wbwindow.location.hostname.indexOf(".")>=0&&!wombat.IP_RX.test(wombat.$wbwindow.location.hostname)?"Domain=."+wombat.$wbwindow.location.hostname:""}).replace(this.cookie_path_regex,function(m,m1){var rewritten=wombat.rewriteUrl(m1);return rewritten.indexOf(wombat.wb_curr_host)===0&&(rewritten=rewritten.substring(wombat.wb_curr_host.length)),"Path="+rewritten}),wombat.$wbwindow.location.protocol!=="https:"&&(rwCookie=rwCookie.replace("secure","")),rwCookie.replace(",|",",")},Wombat.prototype.rewriteWorker=function(workerUrl){if(!workerUrl)return workerUrl;var isBlob=workerUrl.indexOf("blob:")===0,isJS=workerUrl.indexOf("javascript:")===0;if(!isBlob&&!isJS){if(!this.startsWithOneOf(workerUrl,this.VALID_PREFIXES)&&!this.startsWith(workerUrl,"/")&&!this.startsWithOneOf(workerUrl,this.BAD_PREFIXES)){var rurl=this.resolveRelUrl(workerUrl,this.$wbwindow.document);return this.rewriteUrl(rurl,false,"wkr_",this.$wbwindow.document)}return this.rewriteUrl(workerUrl,false,"wkr_",this.$wbwindow.document)}var workerCode=isJS?workerUrl.replace("javascript:",""):null;if(isBlob){var x=new XMLHttpRequest;this.utilFns.XHRopen.call(x,"GET",workerUrl,false),this.utilFns.XHRsend.call(x),workerCode=x.responseText.replace(this.workerBlobRe,"").replace(this.rmCheckThisInjectRe,"this")}if(this.wb_info.static_prefix||this.wb_info.ww_rw_script){var originalURL=this.$wbwindow.document.baseURI,ww_rw=this.wb_info.ww_rw_script||this.wb_info.static_prefix+"wombatWorkers.js",rw="(function() { self.importScripts('"+ww_rw+"'); new WBWombat({'prefix': '"+this.wb_abs_prefix+"', 'prefixMod': '"+this.wb_abs_prefix+"wkrf_/', 'originalURL': '"+originalURL+"'}); })();";workerCode=rw+workerCode}var blob=new Blob([workerCode],{type:"application/javascript"});return URL.createObjectURL(blob)},Wombat.prototype.rewriteTextNodeFn=function(fnThis,originalFn,argsObj){var args,deproxiedThis=this.proxyToObj(fnThis);if(argsObj.length>0&&deproxiedThis.parentElement&&deproxiedThis.parentElement.tagName==="STYLE"){args=new Array(argsObj.length);var dataIndex=argsObj.length-1;dataIndex===2?(args[0]=argsObj[0],args[1]=argsObj[1]):dataIndex===1&&(args[0]=argsObj[0]),args[dataIndex]=this.rewriteStyle(argsObj[dataIndex])}else args=argsObj;return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(deproxiedThis,args):originalFn.apply(deproxiedThis,args)},Wombat.prototype.rewriteDocWriteWriteln=function(fnThis,originalFn,argsObj){var string,thisObj=this.proxyToObj(fnThis),argLen=argsObj.length;if(argLen===0)return originalFn.call(thisObj);string=argLen===1?argsObj[0]:Array.prototype.join.call(argsObj,"");var new_buff=this.rewriteHtml(string,true),res=originalFn.call(thisObj,new_buff);return this.initNewWindowWombat(thisObj.defaultView),res},Wombat.prototype.rewriteChildNodeFn=function(fnThis,originalFn,argsObj){var thisObj=this.proxyToObj(fnThis);if(argsObj.length===0)return originalFn.call(thisObj);var newArgs=this.rewriteElementsInArguments(argsObj);return originalFn.__WB_orig_apply?originalFn.__WB_orig_apply(thisObj,newArgs):originalFn.apply(thisObj,newArgs)},Wombat.prototype.rewriteInsertAdjHTMLOrElemArgs=function(fnThis,originalFn,position,textOrElem,rwHTML){var fnThisObj=this.proxyToObj(fnThis);return fnThisObj._no_rewrite?originalFn.call(fnThisObj,position,textOrElem):rwHTML?originalFn.call(fnThisObj,position,this.rewriteHtml(textOrElem)):(this.rewriteElemComplete(textOrElem),originalFn.call(fnThisObj,position,textOrElem))},Wombat.prototype.rewriteSetTimeoutInterval=function(fnThis,originalFn,argsObj){var rw=this.isString(argsObj[0]),args=rw?new Array(argsObj.length):argsObj;if(rw){args[0]=this.$wbwindow.Proxy?this.wrapScriptTextJsProxy(argsObj[0]):argsObj[0].replace(/\blocation\b/g,"WB_wombat_$&");for(var i=1;i0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parse"),this.$wbwindow.CSSStyleValue.parseAll&&this.$wbwindow.CSSStyleValue.parseAll.toString().indexOf("[native code]")>0&&cssStyleValueOverride(this.$wbwindow.CSSStyleValue,"parseAll")}if(this.$wbwindow.CSSKeywordValue&&this.$wbwindow.CSSKeywordValue.prototype){var oCSSKV=this.$wbwindow.CSSKeywordValue;this.$wbwindow.CSSKeywordValue=function(CSSKeywordValue_){return function CSSKeywordValue(cssValue){return wombat.domConstructorErrorChecker(this,"CSSKeywordValue",arguments),new CSSKeywordValue_(wombat.rewriteStyle(cssValue))}}(this.$wbwindow.CSSKeywordValue),this.$wbwindow.CSSKeywordValue.prototype=oCSSKV.prototype,Object.defineProperty(this.$wbwindow.CSSKeywordValue.prototype,"constructor",{value:this.$wbwindow.CSSKeywordValue}),addToStringTagToClass(this.$wbwindow.CSSKeywordValue,"CSSKeywordValue")}if(this.$wbwindow.StylePropertyMap&&this.$wbwindow.StylePropertyMap.prototype){var originalSet=this.$wbwindow.StylePropertyMap.prototype.set;this.$wbwindow.StylePropertyMap.prototype.set=function set(){if(arguments.length<=1)return originalSet.__WB_orig_apply?originalSet.__WB_orig_apply(this,arguments):originalSet.apply(this,arguments);var newArgs=new Array(arguments.length);newArgs[0]=arguments[0];for(var i=1;i")&&(array[0]=wombat.rewriteHtml(array[0]),options.type="text/html"),new Blob_(array,options)}}(this.$wbwindow.Blob),this.$wbwindow.Blob.prototype=orig_blob.prototype}},Wombat.prototype.initDocTitleOverride=function(){var orig_get_title=this.getOrigGetter(this.$wbwindow.document,"title"),orig_set_title=this.getOrigSetter(this.$wbwindow.document,"title"),wombat=this,set_title=function title(value){var res=orig_set_title.call(this,value),message={wb_type:"title",title:value};return wombat.sendTopMessage(message),res};this.defProp(this.$wbwindow.document,"title",set_title,orig_get_title)},Wombat.prototype.initFontFaceOverride=function(){if(this.$wbwindow.FontFace){var wombat=this,origFontFace=this.$wbwindow.FontFace;this.$wbwindow.FontFace=function(FontFace_){return function FontFace(family,source,descriptors){wombat.domConstructorErrorChecker(this,"FontFace",arguments,2);var rwSource=source;return source!=null&&(typeof source==="string"?rwSource=wombat.rewriteInlineStyle(source):rwSource=wombat.rewriteInlineStyle(source.toString())),new FontFace_(family,rwSource,descriptors)}}(this.$wbwindow.FontFace),this.$wbwindow.FontFace.prototype=origFontFace.prototype,Object.defineProperty(this.$wbwindow.FontFace.prototype,"constructor",{value:this.$wbwindow.FontFace}),addToStringTagToClass(this.$wbwindow.FontFace,"FontFace")}},Wombat.prototype.initFixedRatio=function(){try{this.$wbwindow.devicePixelRatio=1}catch(e){}if(Object.defineProperty)try{Object.defineProperty(this.$wbwindow,"devicePixelRatio",{value:1,writable:false})}catch(e){}},Wombat.prototype.initPaths=function(wbinfo){wbinfo.wombat_opts=wbinfo.wombat_opts||{},Object.assign(this.wb_info,wbinfo),this.wb_opts=wbinfo.wombat_opts,this.wb_replay_prefix=wbinfo.prefix,this.wb_is_proxy=wbinfo.proxy_magic||!this.wb_replay_prefix,this.wb_info.top_host=this.wb_info.top_host||"*",this.wb_curr_host=this.$wbwindow.location.protocol+"//"+this.$wbwindow.location.host,this.wb_info.wombat_opts=this.wb_info.wombat_opts||{},this.wb_orig_scheme=wbinfo.wombat_scheme+"://",this.wb_orig_origin=this.wb_orig_scheme+wbinfo.wombat_host,this.wb_abs_prefix=this.wb_replay_prefix,this.wb_capture_date_part=!wbinfo.is_live&&wbinfo.wombat_ts?"/"+wbinfo.wombat_ts+"/":"",this.initBadPrefixes(this.wb_replay_prefix),this.initCookiePreset()},Wombat.prototype.initSeededRandom=function(seed){this.$wbwindow.Math.seed=parseInt(seed);var wombat=this;this.$wbwindow.Math.random=function random(){return wombat.$wbwindow.Math.seed=(wombat.$wbwindow.Math.seed*9301+49297)%233280,wombat.$wbwindow.Math.seed/233280}},Wombat.prototype.initHistoryOverrides=function(){this.overrideHistoryFunc("pushState"),this.overrideHistoryFunc("replaceState");var wombat=this;this.$wbwindow.addEventListener("popstate",function(event){wombat.sendHistoryUpdate(wombat.$wbwindow.WB_wombat_location.href,wombat.$wbwindow.document.title)})},Wombat.prototype.initCookiePreset=function(){if(this.wb_info.presetCookie)for(var splitCookies=this.wb_info.presetCookie.split(";"),i=0;i0?"&":"?")+value.toString(),value=null):contentType==="application/json"||contentType==="text/plain"?(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+jsonToQueryString(value),value=null):wombat.startsWith(contentType,"multipart/form-data")&&(this.__WB_xhr_open_arguments[0]="GET",this.__WB_xhr_open_arguments[1]+=(this.__WB_xhr_open_arguments[1].indexOf("?")>0?"&":"?")+mfdToQueryString(value,contentType))}if(this.__WB_xhr_open_arguments.length>2&&(this.__WB_xhr_open_arguments[2]=true),this._no_rewrite||(this.__WB_xhr_open_arguments[1]=wombat.rewriteUrl(this.__WB_xhr_open_arguments[1])),origOpen.apply(this,this.__WB_xhr_open_arguments),!wombat.startsWith(this.__WB_xhr_open_arguments[1],"data:")){for(const[name,value]of this.__WB_xhr_headers.entries())origSetRequestHeader.call(this,name,value);origSetRequestHeader.call(this,"X-Pywb-Requested-With","XMLHttpRequest")}origSend.call(this,value)}}if(this.$wbwindow.fetch){var orig_fetch=this.$wbwindow.fetch;this.$wbwindow.fetch=function fetch(input,init_opts){var rwInput=input,inputType=typeof input;if(inputType==="string")rwInput=wombat.rewriteUrl(input);else if(inputType==="object"&&input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(rwInput=new Request(new_url,init_opts))}else inputType==="object"&&input.href&&(rwInput=wombat.rewriteUrl(input.href));if(init_opts||(init_opts={}),init_opts.credentials===undefined)try{init_opts.credentials="include"}catch(e){}return orig_fetch.call(wombat.proxyToObj(this),rwInput,init_opts)}}if(this.$wbwindow.Request&&this.$wbwindow.Request.prototype){var orig_request=this.$wbwindow.Request;this.$wbwindow.Request=function(Request_){return function Request(input,init_opts){wombat.domConstructorErrorChecker(this,"Request",arguments);var newInitOpts=init_opts||{},newInput=input,inputType=typeof input;switch(inputType){case"string":newInput=wombat.rewriteUrl(input);break;case"object":if(newInput=input,input.url){var new_url=wombat.rewriteUrl(input.url);new_url!==input.url&&(newInput=new Request_(new_url,input))}else input.href&&(newInput=wombat.rewriteUrl(input.toString(),true));}return newInitOpts.credentials="include",new Request_(newInput,newInitOpts)}}(this.$wbwindow.Request),this.$wbwindow.Request.prototype=orig_request.prototype,Object.defineProperty(this.$wbwindow.Request.prototype,"constructor",{value:this.$wbwindow.Request})}if(this.$wbwindow.Response&&this.$wbwindow.Response.prototype){var originalRedirect=this.$wbwindow.Response.prototype.redirect;this.$wbwindow.Response.prototype.redirect=function redirect(url,status){var rwURL=wombat.rewriteUrl(url,true,null,wombat.$wbwindow.document);return originalRedirect.call(this,rwURL,status)}}if(this.$wbwindow.EventSource&&this.$wbwindow.EventSource.prototype){var origEventSource=this.$wbwindow.EventSource;this.$wbwindow.EventSource=function(EventSource_){return function EventSource(url,configuration){wombat.domConstructorErrorChecker(this,"EventSource",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteUrl(url)),new EventSource_(rwURL,configuration)}}(this.$wbwindow.EventSource),this.$wbwindow.EventSource.prototype=origEventSource.prototype,Object.defineProperty(this.$wbwindow.EventSource.prototype,"constructor",{value:this.$wbwindow.EventSource}),addToStringTagToClass(this.$wbwindow.EventSource,"EventSource")}if(this.$wbwindow.WebSocket&&this.$wbwindow.WebSocket.prototype){var origWebSocket=this.$wbwindow.WebSocket;this.$wbwindow.WebSocket=function(WebSocket_){return function WebSocket(url,configuration){wombat.domConstructorErrorChecker(this,"WebSocket",arguments);var rwURL=url;return url!=null&&(rwURL=wombat.rewriteWSURL(url)),new WebSocket_(rwURL,configuration)}}(this.$wbwindow.WebSocket),this.$wbwindow.WebSocket.prototype=origWebSocket.prototype,Object.defineProperty(this.$wbwindow.WebSocket.prototype,"constructor",{value:this.$wbwindow.WebSocket}),addToStringTagToClass(this.$wbwindow.WebSocket,"WebSocket")}},Wombat.prototype.initElementGetSetAttributeOverride=function(){if(!this.wb_opts.skip_setAttribute&&this.$wbwindow.Element&&this.$wbwindow.Element.prototype){var wombat=this,ElementProto=this.$wbwindow.Element.prototype;if(ElementProto.setAttribute){var orig_setAttribute=ElementProto.setAttribute;ElementProto._orig_setAttribute=orig_setAttribute,ElementProto.setAttribute=function setAttribute(name,value){var rwValue=value;if(name&&typeof rwValue==="string"){var lowername=name.toLowerCase();if(this.tagName==="LINK"&&lowername==="href"&&rwValue.indexOf("data:text/css")===0)rwValue=wombat.rewriteInlineStyle(value);else if(lowername==="style")rwValue=wombat.rewriteStyle(value);else if(lowername==="srcset")rwValue=wombat.rewriteSrcset(value,this);else{var shouldRW=wombat.shouldRewriteAttr(this.tagName,lowername);shouldRW&&(wombat.removeWBOSRC(this),!this._no_rewrite&&(rwValue=wombat.rewriteUrl(value,false,wombat.rwModForElement(this,lowername))))}}return orig_setAttribute.call(this,name,rwValue)}}if(ElementProto.getAttribute){var orig_getAttribute=ElementProto.getAttribute;this.wb_getAttribute=orig_getAttribute,ElementProto.getAttribute=function getAttribute(name){var result=orig_getAttribute.call(this,name);if(result===null)return result;var lowerName=name;if(name&&(lowerName=name.toLowerCase()),wombat.shouldRewriteAttr(this.tagName,lowerName)){var maybeWBOSRC=wombat.retrieveWBOSRC(this);return maybeWBOSRC?maybeWBOSRC:wombat.extractOriginalURL(result)}return wombat.startsWith(lowerName,"data-")&&wombat.startsWithOneOf(result,wombat.wb_prefixes)?wombat.extractOriginalURL(result):result}}}},Wombat.prototype.initSvgImageOverrides=function(){if(this.$wbwindow.SVGImageElement){var svgImgProto=this.$wbwindow.SVGImageElement.prototype,orig_getAttr=svgImgProto.getAttribute,orig_getAttrNS=svgImgProto.getAttributeNS,orig_setAttr=svgImgProto.setAttribute,orig_setAttrNS=svgImgProto.setAttributeNS,wombat=this;svgImgProto.getAttribute=function getAttribute(name){var value=orig_getAttr.call(this,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.getAttributeNS=function getAttributeNS(ns,name){var value=orig_getAttrNS.call(this,ns,name);return name.indexOf("xlink:href")>=0||name==="href"?wombat.extractOriginalURL(value):value},svgImgProto.setAttribute=function setAttribute(name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttr.call(this,name,rwValue)},svgImgProto.setAttributeNS=function setAttributeNS(ns,name,value){var rwValue=value;return(name.indexOf("xlink:href")>=0||name==="href")&&(rwValue=wombat.rewriteUrl(value)),orig_setAttrNS.call(this,ns,name,rwValue)}}},Wombat.prototype.initCreateElementNSFix=function(){if(this.$wbwindow.document.createElementNS&&this.$wbwindow.Document.prototype.createElementNS){var orig_createElementNS=this.$wbwindow.document.createElementNS,wombat=this,createElementNS=function createElementNS(namespaceURI,qualifiedName){return orig_createElementNS.call(wombat.proxyToObj(this),wombat.extractOriginalURL(namespaceURI),qualifiedName)};this.$wbwindow.Document.prototype.createElementNS=createElementNS,this.$wbwindow.document.createElementNS=createElementNS}},Wombat.prototype.initInsertAdjacentElementHTMLOverrides=function(){var Element=this.$wbwindow.Element;if(Element&&Element.prototype){var elementProto=Element.prototype,rewriteFn=this.rewriteInsertAdjHTMLOrElemArgs;if(elementProto.insertAdjacentHTML){var origInsertAdjacentHTML=elementProto.insertAdjacentHTML;elementProto.insertAdjacentHTML=function insertAdjacentHTML(position,text){return rewriteFn(this,origInsertAdjacentHTML,position,text,true)}}if(elementProto.insertAdjacentElement){var origIAdjElem=elementProto.insertAdjacentElement;elementProto.insertAdjacentElement=function insertAdjacentElement(position,element){return rewriteFn(this,origIAdjElem,position,element,false)}}}},Wombat.prototype.initDomOverride=function(){var Node=this.$wbwindow.Node;if(Node&&Node.prototype){var rewriteFn=this.rewriteNodeFuncArgs;if(Node.prototype.appendChild){var originalAppendChild=Node.prototype.appendChild;Node.prototype.appendChild=function appendChild(newNode,oldNode){return rewriteFn(this,originalAppendChild,newNode,oldNode)}}if(Node.prototype.insertBefore){var originalInsertBefore=Node.prototype.insertBefore;Node.prototype.insertBefore=function insertBefore(newNode,oldNode){return rewriteFn(this,originalInsertBefore,newNode,oldNode)}}if(Node.prototype.replaceChild){var originalReplaceChild=Node.prototype.replaceChild;Node.prototype.replaceChild=function replaceChild(newNode,oldNode){return rewriteFn(this,originalReplaceChild,newNode,oldNode)}}this.overridePropToProxy(Node.prototype,"ownerDocument"),this.overridePropToProxy(this.$wbwindow.HTMLHtmlElement.prototype,"parentNode"),this.overridePropToProxy(this.$wbwindow.Event.prototype,"target")}this.$wbwindow.Element&&this.$wbwindow.Element.prototype&&(this.overrideParentNodeAppendPrepend(this.$wbwindow.Element),this.overrideChildNodeInterface(this.$wbwindow.Element,false)),this.$wbwindow.DocumentFragment&&this.$wbwindow.DocumentFragment.prototype&&this.overrideParentNodeAppendPrepend(this.$wbwindow.DocumentFragment)},Wombat.prototype.initDocOverrides=function($document){if(Object.defineProperty){this.overrideReferrer($document),this.defGetterProp($document,"origin",function origin(){return this.WB_wombat_location.origin}),this.defGetterProp(this.$wbwindow,"origin",function origin(){return this.WB_wombat_location.origin});var wombat=this,domain_setter=function domain(val){var loc=this.WB_wombat_location;loc&&wombat.endsWith(loc.hostname,val)&&(this.__wb_domain=val)},domain_getter=function domain(){return this.__wb_domain||this.WB_wombat_location.hostname};this.defProp($document,"domain",domain_setter,domain_getter)}},Wombat.prototype.initDocWriteOpenCloseOverride=function(){if(this.$wbwindow.DOMParser){var DocumentProto=this.$wbwindow.Document.prototype,$wbDocument=this.$wbwindow.document,docWriteWritelnRWFn=this.rewriteDocWriteWriteln,orig_doc_write=$wbDocument.write,new_write=function write(){return docWriteWritelnRWFn(this,orig_doc_write,arguments)};$wbDocument.write=new_write,DocumentProto.write=new_write;var orig_doc_writeln=$wbDocument.writeln,new_writeln=function writeln(){return docWriteWritelnRWFn(this,orig_doc_writeln,arguments)};$wbDocument.writeln=new_writeln,DocumentProto.writeln=new_writeln;var wombat=this,orig_doc_open=$wbDocument.open,new_open=function open(){var res,thisObj=wombat.proxyToObj(this);if(arguments.length===3){var rwUrl=wombat.rewriteUrl(arguments[0],false,"mp_");res=orig_doc_open.call(thisObj,rwUrl,arguments[1],arguments[2]),wombat.initNewWindowWombat(res,arguments[0])}else res=orig_doc_open.call(thisObj),wombat.initNewWindowWombat(thisObj.defaultView);return res};$wbDocument.open=new_open,DocumentProto.open=new_open;var originalClose=$wbDocument.close,newClose=function close(){var thisObj=wombat.proxyToObj(this);return wombat.initNewWindowWombat(thisObj.defaultView),originalClose.__WB_orig_apply?originalClose.__WB_orig_apply(thisObj,arguments):originalClose.apply(thisObj,arguments)};$wbDocument.close=newClose,DocumentProto.close=newClose;var oBodyGetter=this.getOrigGetter(DocumentProto,"body"),oBodySetter=this.getOrigSetter(DocumentProto,"body");oBodyGetter&&oBodySetter&&this.defProp(DocumentProto,"body",function body(newBody){return newBody&&(newBody instanceof HTMLBodyElement||newBody instanceof HTMLFrameSetElement)&&wombat.rewriteElemComplete(newBody),oBodySetter.call(wombat.proxyToObj(this),newBody)},oBodyGetter)}},Wombat.prototype.initIframeWombat=function(iframe){var win;win=iframe._get_contentWindow?iframe._get_contentWindow.call(iframe):iframe.contentWindow;try{if(!win||win===this.$wbwindow||win._skip_wombat||win._wb_wombat)return}catch(e){return}var src=iframe.src;this.initNewWindowWombat(win,src)},Wombat.prototype.initNewWindowWombat=function(win,src){var fullWombat=false;if(win&&!win._wb_wombat){if((!src||src===""||this.startsWithOneOf(src,["about:blank","javascript:"]))&&(fullWombat=true),!fullWombat&&this.wb_info.isSW){var origURL=this.extractOriginalURL(src);(origURL==="about:blank"||origURL.startsWith("srcdoc:")||origURL.startsWith("blob:"))&&(fullWombat=true)}if(fullWombat){var newInfo={};Object.assign(newInfo,this.wb_info);var wombat=new Wombat(win,newInfo);win._wb_wombat=wombat.wombatInit()}else this.initProtoPmOrigin(win),this.initPostMessageOverride(win),this.initMessageEventOverride(win),this.initCheckThisFunc(win)}},Wombat.prototype.initTimeoutIntervalOverrides=function(){var rewriteFn=this.rewriteSetTimeoutInterval;if(this.$wbwindow.setTimeout&&!this.$wbwindow.setTimeout.__$wbpatched$__){var originalSetTimeout=this.$wbwindow.setTimeout;this.$wbwindow.setTimeout=function setTimeout(){return rewriteFn(this,originalSetTimeout,arguments)},this.$wbwindow.setTimeout.__$wbpatched$__=true}if(this.$wbwindow.setInterval&&!this.$wbwindow.setInterval.__$wbpatched$__){var originalSetInterval=this.$wbwindow.setInterval;this.$wbwindow.setInterval=function setInterval(){return rewriteFn(this,originalSetInterval,arguments)},this.$wbwindow.setInterval.__$wbpatched$__=true}},Wombat.prototype.initWorkerOverrides=function(){var wombat=this;if(this.$wbwindow.Worker&&!this.$wbwindow.Worker._wb_worker_overriden){var orig_worker=this.$wbwindow.Worker;this.$wbwindow.Worker=function(Worker_){return function Worker(url,options){return wombat.domConstructorErrorChecker(this,"Worker",arguments),new Worker_(wombat.rewriteWorker(url),options)}}(orig_worker),this.$wbwindow.Worker.prototype=orig_worker.prototype,Object.defineProperty(this.$wbwindow.Worker.prototype,"constructor",{value:this.$wbwindow.Worker}),this.$wbwindow.Worker._wb_worker_overriden=true}if(this.$wbwindow.SharedWorker&&!this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden){var oSharedWorker=this.$wbwindow.SharedWorker;this.$wbwindow.SharedWorker=function(SharedWorker_){return function SharedWorker(url,options){return wombat.domConstructorErrorChecker(this,"SharedWorker",arguments),new SharedWorker_(wombat.rewriteWorker(url),options)}}(oSharedWorker),this.$wbwindow.SharedWorker.prototype=oSharedWorker.prototype,Object.defineProperty(this.$wbwindow.SharedWorker.prototype,"constructor",{value:this.$wbwindow.SharedWorker}),this.$wbwindow.SharedWorker.__wb_sharedWorker_overriden=true}if(this.$wbwindow.ServiceWorkerContainer&&this.$wbwindow.ServiceWorkerContainer.prototype&&this.$wbwindow.ServiceWorkerContainer.prototype.register){var orig_register=this.$wbwindow.ServiceWorkerContainer.prototype.register;this.$wbwindow.ServiceWorkerContainer.prototype.register=function register(scriptURL,options){var newScriptURL=new URL(scriptURL,wombat.$wbwindow.document.baseURI).href,mod=wombat.getPageUnderModifier();return options&&options.scope?options.scope=wombat.rewriteUrl(options.scope,false,mod):options={scope:wombat.rewriteUrl("/",false,mod)},orig_register.call(this,wombat.rewriteUrl(newScriptURL,false,"sw_"),options)}}if(this.$wbwindow.Worklet&&this.$wbwindow.Worklet.prototype&&this.$wbwindow.Worklet.prototype.addModule&&!this.$wbwindow.Worklet.__wb_workerlet_overriden){var oAddModule=this.$wbwindow.Worklet.prototype.addModule;this.$wbwindow.Worklet.prototype.addModule=function addModule(moduleURL,options){var rwModuleURL=wombat.rewriteUrl(moduleURL,false,"js_");return oAddModule.call(this,rwModuleURL,options)},this.$wbwindow.Worklet.__wb_workerlet_overriden=true}},Wombat.prototype.initLocOverride=function(loc,oSetter,oGetter){if(Object.defineProperty)for(var prop,i=0;i=0&&props.splice(foundInx,1);return props}})}catch(e){console.log(e)}},Wombat.prototype.initHashChange=function(){if(this.$wbwindow.__WB_top_frame){var wombat=this,receive_hash_change=function receive_hash_change(event){if(event.data&&event.data.from_top){var message=event.data.message;message.wb_type&&(message.wb_type!=="outer_hashchange"||wombat.$wbwindow.location.hash==message.hash||(wombat.$wbwindow.location.hash=message.hash))}},send_hash_change=function send_hash_change(){var message={wb_type:"hashchange",hash:wombat.$wbwindow.location.hash};wombat.sendTopMessage(message)};this.$wbwindow.addEventListener("message",receive_hash_change),this.$wbwindow.addEventListener("hashchange",send_hash_change)}},Wombat.prototype.initPostMessageOverride=function($wbwindow){if($wbwindow.postMessage&&!$wbwindow.__orig_postMessage){var orig=$wbwindow.postMessage,wombat=this;$wbwindow.__orig_postMessage=orig;var postmessage_rewritten=function postMessage(message,targetOrigin,transfer,from_top){var from,src_id,this_obj=wombat.proxyToObj(this);if(this_obj.__WB_source&&this_obj.__WB_source.WB_wombat_location){var source=this_obj.__WB_source;if(from=source.WB_wombat_location.origin,this_obj.__WB_win_id||(this_obj.__WB_win_id={},this_obj.__WB_counter=0),!source.__WB_id){var id=this_obj.__WB_counter;source.__WB_id=id+source.WB_wombat_location.href,this_obj.__WB_counter+=1}this_obj.__WB_win_id[source.__WB_id]=source,src_id=source.__WB_id,this_obj.__WB_source=undefined}else from=window.WB_wombat_location.origin;var to_origin=targetOrigin;to_origin===this_obj.location.origin&&(to_origin=from);var new_message={from:from,to_origin:to_origin,src_id:src_id,message:message,from_top:from_top};if(targetOrigin!=="*"){if(this_obj.location.origin==="null"||this_obj.location.origin==="")return;targetOrigin=this_obj.location.origin}return orig.call(this_obj,new_message,targetOrigin,transfer)};$wbwindow.postMessage=postmessage_rewritten,$wbwindow.Window.prototype.postMessage=postmessage_rewritten;var eventTarget=null;eventTarget=$wbwindow.EventTarget&&$wbwindow.EventTarget.prototype?$wbwindow.EventTarget.prototype:$wbwindow;var _oAddEventListener=eventTarget.addEventListener;eventTarget.addEventListener=function addEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.add_or_get(listener,function(){return wrapEventListener(listener,obj,wombat)}):type==="storage"?wombat.storage_listeners.add_or_get(listener,function(){return wrapSameOriginEventListener(listener,obj)}):rwListener=listener,rwListener)return _oAddEventListener.call(obj,type,rwListener,useCapture)};var _oRemoveEventListener=eventTarget.removeEventListener;eventTarget.removeEventListener=function removeEventListener(type,listener,useCapture){var rwListener,obj=wombat.proxyToObj(this);if(type==="message"?rwListener=wombat.message_listeners.remove(listener):type==="storage"?wombat.storage_listeners.remove(listener):rwListener=listener,rwListener)return _oRemoveEventListener.call(obj,type,rwListener,useCapture)};var override_on_prop=function(onevent,wrapperFN){var orig_setter=wombat.getOrigSetter($wbwindow,onevent),setter=function(value){this["__orig_"+onevent]=value;var obj=wombat.proxyToObj(this),listener=value?wrapperFN(value,obj,wombat):value;return orig_setter.call(obj,listener)},getter=function(){return this["__orig_"+onevent]};wombat.defProp($wbwindow,onevent,setter,getter)};override_on_prop("onmessage",wrapEventListener),override_on_prop("onstorage",wrapSameOriginEventListener)}},Wombat.prototype.initMessageEventOverride=function($wbwindow){!$wbwindow.MessageEvent||$wbwindow.MessageEvent.prototype.__extended||(this.addEventOverride("target"),this.addEventOverride("srcElement"),this.addEventOverride("currentTarget"),this.addEventOverride("eventPhase"),this.addEventOverride("path"),this.overridePropToProxy($wbwindow.MessageEvent.prototype,"source"),$wbwindow.MessageEvent.prototype.__extended=true)},Wombat.prototype.initUIEventsOverrides=function(){this.overrideAnUIEvent("UIEvent"),this.overrideAnUIEvent("MouseEvent"),this.overrideAnUIEvent("TouchEvent"),this.overrideAnUIEvent("FocusEvent"),this.overrideAnUIEvent("KeyboardEvent"),this.overrideAnUIEvent("WheelEvent"),this.overrideAnUIEvent("InputEvent"),this.overrideAnUIEvent("CompositionEvent")},Wombat.prototype.initOpenOverride=function(){var orig=this.$wbwindow.open;this.$wbwindow.Window.prototype.open&&(orig=this.$wbwindow.Window.prototype.open);var wombat=this,open_rewritten=function open(strUrl,strWindowName,strWindowFeatures){var rwStrUrl=wombat.rewriteUrl(strUrl,false,""),res=orig.call(wombat.proxyToObj(this),rwStrUrl,strWindowName,strWindowFeatures);return wombat.initNewWindowWombat(res,strUrl),res};this.$wbwindow.open=open_rewritten,this.$wbwindow.Window.prototype.open&&(this.$wbwindow.Window.prototype.open=open_rewritten);for(var i=0;i