diff --git a/public/index.php b/public/index.php
index b264e42..834c319 100644
--- a/public/index.php
+++ b/public/index.php
@@ -7,12 +7,15 @@ require_once __DIR__ . '/../startup.php';
 
 $request = Http\HttpRequest::create();
 
-header('Content-Type: ' . FWIF::CONTENT_TYPE);
+header('Content-Type: ' . (PAT_DEBUG ? 'text/plain; charset=us-ascii' : FWIF::CONTENT_TYPE));
+
+$tPlat = Platform::getUser();
+
+// ONLY EXACT MATCHES ↓
 
 if($request->match('GET', '/packages')) {
     $tags = explode(';', (string)$request->getQueryParam('tags', FILTER_SANITIZE_STRING));
     $packages = empty($tags) ? Patchouli::getPackages() : Patchouli::getPackagesWithTags($tags);
-    header('Content-Type: text/plain; charset=us-ascii');
     echo FWIF::encode($packages);
     return;
 }
@@ -25,5 +28,17 @@ if($request->match('GET', '/')) {
     return;
 }
 
-http_response_code(404);
-echo '{"code":404,"message":"Path not found."}';
+// REGEX MATCHES ↓
+
+if($request->match('GET', '#^/packages/([a-z0-9-]+)/?$#', $args)) {
+    try {
+        $packageInfo = Patchouli::getPackage($args[0]);
+    } catch(PackageNotFoundException $ex) {
+        http_response_code(404);
+        $packageInfo = new ErrorResponse(404, 'Package not found.');
+    }
+    echo FWIF::encode($packageInfo);
+    return;
+}
+
+echo FWIF::encode(new ErrorResponse(404, 'Path not found.'));
diff --git a/src/Dummy/DummyPackage.php b/src/Dummy/DummyPackage.php
index 051322a..35451ca 100644
--- a/src/Dummy/DummyPackage.php
+++ b/src/Dummy/DummyPackage.php
@@ -17,6 +17,17 @@ class DummyPackage implements IPackage {
         return new Version;
     }
 
+    public function getFiles(): array {
+        return [
+            new DummyPackageFile('filename.bin'),
+            new DummyPackageFile('boobs.png'),
+        ];
+    }
+
+    public function getTargets(): array {
+        return [new DummyPackageTarget];
+    }
+
     public function getDependencies(): array {
         return [];
     }
@@ -25,57 +36,10 @@ class DummyPackage implements IPackage {
         $data = [
             'id' => $this->getId(),
             'name' => $this->getName(),
-            'version' => $this->getVersion(),
+            'ver' => $this->getVersion(),
+            'files' => $this->getFiles(),
+            'targ' => $this->getTargets(),
             'deps' => [],
-            /*'null' => null,
-            'true' => true,
-            'false' => false,
-            'zero' => 0,
-            'u8' => 0x42,
-            'u16' => 0x4344,
-            'u24' => 0x454647,
-            'u32' => 0x58596061,
-            'u40' => 0x6263646566,
-            'u48' => 0x676869707172,
-            'u56' => 0x73747576777879,
-            'u64' => 0x7481828384858687,
-            'neg32' => -12345678,
-            'neg64' => -1234567890987654,
-            'float' => 12345.6789,
-            'float2' => 8.0,
-            'floatNeg' => -12345.6789,
-            'floatPi' => M_PI,
-            'floatE' => M_E,
-            'floatLog2E' => M_LOG2E,
-            'floatLog10E' => M_LOG10E,
-            'floatLn2' => M_LN2,
-            'floatLn10' => M_LN10,
-            'floatPi2' => M_PI_2,
-            'floatPi4' => M_PI_4,
-            'floatM1Pi' => M_1_PI,
-            'floatM2Pi' => M_2_PI,
-            'floatSqrtPi' => M_SQRTPI,
-            'float2SqrtPi' => M_2_SQRTPI,
-            'floatSqrt2' => M_SQRT2,
-            'floatSqrt3' => M_SQRT3,
-            'floatSqrt12' => M_SQRT1_2,
-            'floatLnPi' => M_LNPI,
-            'floatEuler' => M_EULER,
-            'floatNaN' => NAN,
-            'floatInf' => INF,
-            'floatNegInf' => -INF,
-            'floatZero' => 0.0,
-            'floatNegZero' => -0.0,
-            'invalid' => "\xFF\x25\x25\x02\xFF蠕。蝮F鄒守清\xFF\xFF\xFF",
-            'datetime' => new \DateTime('2013-01-27 23:14:44 CET'),
-            'datetimeNegative' => new \DateTime('-2013-01-27 23:14:44 CET'),
-            'datetimeNow' => new \DateTime(),
-            'period' => (new \DateTime())->diff(new \DateTime('2013-01-27 23:14:44 CET')),
-            'periodNegative' => (new \DateTime('2013-01-27 23:14:44 CET'))->diff(new \DateTime()),
-            'periodZero' => (new \DateTime())->diff(new \DateTime()),
-            'array' => ['e', 'a', 0x55],
-            'object' => new \stdClass,
-            'misaka' => '御坂 美琴',*/
         ];
         foreach($this->getDependencies() as $dependency)
             $data['deps'][] = $dependency->getName();
diff --git a/src/Dummy/DummyPackageFile.php b/src/Dummy/DummyPackageFile.php
new file mode 100644
index 0000000..55a4d13
--- /dev/null
+++ b/src/Dummy/DummyPackageFile.php
@@ -0,0 +1,40 @@
+<?php
+namespace Patchouli\Dummy;
+
+use Patchouli\IPackageFile;
+use UnexpectedValueException;
+
+class DummyPackageFile implements IPackageFile {
+    private string $filename;
+
+    public function __construct(string $filename) {
+        $this->filename = $filename;
+    }
+
+    public function getFileName(): string {
+        return $this->filename;;
+    }
+
+    public function getFileType(): string {
+        return 'application/octet-stream';
+    }
+
+    public function isRemote(): bool {
+        return true;
+    }
+
+    public function getRemoteUrl(): string {
+        return 'http://flash.moe/assets/errors/404.jpg';
+    }
+
+    public function getLocalStream() {
+        throw new UnexpectedValueException;
+    }
+
+    public function fwifSerialize(): array {
+        return [
+            'name' => $this->getFileName(),
+            'type' => $this->getFileType(),
+        ];
+    }
+}
diff --git a/src/Dummy/DummyPackageTarget.php b/src/Dummy/DummyPackageTarget.php
new file mode 100644
index 0000000..2a4f0a5
--- /dev/null
+++ b/src/Dummy/DummyPackageTarget.php
@@ -0,0 +1,32 @@
+<?php
+namespace Patchouli\Dummy;
+
+use Patchouli\Platform;
+use Patchouli\IPackageTarget;
+
+class DummyPackageTarget implements IPackageTarget {
+    function getPlatform(): Platform {
+        return Platform::get(Platform::WIN_IA32);
+    }
+
+    public function getFiles(): array {
+        return [
+            new DummyPackageFile('poop.exe'),
+        ];
+    }
+
+    public function getDependencies(): array {
+        return [];
+    }
+
+    public function fwifSerialize(): array {
+        $data = [
+            'plat'  => $this->getPlatform(),
+            'files' => $this->getFiles(),
+            'deps'  => [],
+        ];
+        foreach($this->getDependencies() as $pack)
+            $data['deps'][] = $pack->getName();
+        return $data;
+    }
+}
diff --git a/src/ErrorResponse.php b/src/ErrorResponse.php
new file mode 100644
index 0000000..34dd8e6
--- /dev/null
+++ b/src/ErrorResponse.php
@@ -0,0 +1,21 @@
+<?php
+namespace Patchouli;
+
+use FWIF\FWIFSerializable;
+
+class ErrorResponse implements FWIFSerializable {
+    private int $code;
+    private string $message;
+
+    public function __construct(int $code, string $message = 'An unspecified error occurred.') {
+        $this->code = $code;
+        $this->message = $message;
+    }
+
+    public function fwifSerialize() {
+        return [
+            'c' => $this->code,
+            'm' => $this->message,
+        ];
+    }
+}
diff --git a/src/IPackage.php b/src/IPackage.php
index 340966d..0168b30 100644
--- a/src/IPackage.php
+++ b/src/IPackage.php
@@ -6,5 +6,7 @@ use FWIF\FWIFSerializable;
 interface IPackage extends FWIFSerializable {
     function getName(): string;
     function getVersion(): Version;
+    function getFiles(): array;
+    function getTargets(): array;
     function getDependencies(): array;
 }
diff --git a/src/IPackageFile.php b/src/IPackageFile.php
new file mode 100644
index 0000000..1e57c5c
--- /dev/null
+++ b/src/IPackageFile.php
@@ -0,0 +1,12 @@
+<?php
+namespace Patchouli;
+
+use FWIF\FWIFSerializable;
+
+interface IPackageFile extends FWIFSerializable {
+    function getFileName(): string;
+    function getFileType(): string;
+    function isRemote(): bool;
+    function getRemoteUrl(): string;
+    function getLocalStream();
+}
diff --git a/src/IPackageTarget.php b/src/IPackageTarget.php
new file mode 100644
index 0000000..466cc59
--- /dev/null
+++ b/src/IPackageTarget.php
@@ -0,0 +1,10 @@
+<?php
+namespace Patchouli;
+
+use FWIF\FWIFSerializable;
+
+interface IPackageTarget extends FWIFSerializable {
+    function getPlatform(): Platform;
+    function getFiles(): array;
+    function getDependencies(): array;
+}
diff --git a/src/PackageNotFoundException.php b/src/PackageNotFoundException.php
new file mode 100644
index 0000000..1c064e9
--- /dev/null
+++ b/src/PackageNotFoundException.php
@@ -0,0 +1,4 @@
+<?php
+namespace Patchouli;
+
+class PackageNotFoundException extends PatchouliException {}
diff --git a/src/Patchouli.php b/src/Patchouli.php
index 3627901..562bd3b 100644
--- a/src/Patchouli.php
+++ b/src/Patchouli.php
@@ -13,6 +13,7 @@ class Patchouli extends SingleInstance {
     }
 
     public function _getPackage(string $packageName): IPackage {
+        //throw new PackageNotFoundException;
         return new DummyPackage;
     }
 }
diff --git a/src/PatchouliException.php b/src/PatchouliException.php
new file mode 100644
index 0000000..ecb5975
--- /dev/null
+++ b/src/PatchouliException.php
@@ -0,0 +1,4 @@
+<?php
+namespace Patchouli;
+
+class PatchouliException extends \Exception {}
diff --git a/src/Platform.php b/src/Platform.php
new file mode 100644
index 0000000..315ce47
--- /dev/null
+++ b/src/Platform.php
@@ -0,0 +1,144 @@
+<?php
+namespace Patchouli;
+
+use InvalidArgumentException;
+use FWIF\FWIFSerializable;
+
+class Platform implements FWIFSerializable {
+    public const ANY = '*';
+
+    public const WINDOWS = 'win';
+    public const OSX     = 'mac';
+    public const LINUX   = 'lin';
+
+    public const IA32  = 'ia32';
+    public const AMD64 = 'amd64';
+    public const ARMSF = 'armsf';
+    public const ARMHF = 'armhf';
+    public const ARM64 = 'arm64';
+
+    public const ANY_ANY = self::ANY . '/' . self::ANY;
+
+    public const ANY_IA32  = self::ANY . '/' . self::IA32;
+    public const ANY_AMD64 = self::ANY . '/' . self::AMD64;
+    public const ANY_ARMSF = self::ANY . '/' . self::ARMSF;
+    public const ANY_ARMHF = self::ANY . '/' . self::ARMHF;
+    public const ANY_ARM64 = self::ANY . '/' . self::ARM64;
+
+    public const WIN_ANY   = self::WINDOWS . '/' . self::ANY;
+    public const WIN_IA32  = self::WINDOWS . '/' . self::IA32;
+    public const WIN_AMD64 = self::WINDOWS . '/' . self::AMD64;
+    public const WIN_ARM64 = self::WINDOWS . '/' . self::ARM64;
+
+    public const OSX_ANY   = self::OSX . '/' . self::ANY;
+    public const OSX_AMD64 = self::OSX . '/' . self::AMD64;
+    public const OSX_ARM64 = self::OSX . '/' . self::ARM64;
+
+    public const LIN_ANY   = self::LINUX . '/' . self::ANY;
+    public const LIN_IA32  = self::LINUX . '/' . self::IA32;
+    public const LIN_AMD64 = self::LINUX . '/' . self::AMD64;
+    public const LIN_ARMSF = self::LINUX . '/' . self::ARMSF;
+    public const LIN_ARMHF = self::LINUX . '/' . self::ARMHF;
+    public const LIN_ARM64 = self::LINUX . '/' . self::ARM64;
+
+    private static $platforms = [];
+
+    public static function get(string $platform): self {
+        if(empty(self::$platforms[$platform]))
+            self::$platforms[$platform] = new static($platform);
+        return self::$platforms[$platform];
+    }
+
+    public static function getUser(?string $os = null, ?string $arch = null): self {
+        $os ??= filter_input(INPUT_GET, 'os', FILTER_SANITIZE_STRING);
+        $arch ??= filter_input(INPUT_GET, 'arch', FILTER_SANITIZE_STRING);
+        if(empty($os) || !ctype_alnum($os))
+            $os = self::ANY;
+        if(empty($arch) || !ctype_alnum($arch))
+            $arch = self::ANY;
+        return self::get(sprintf('%s/%s', strtolower($os), strtolower($arch)));
+    }
+
+    public static function isMatch(string $platform, Platform $other): bool {
+        return self::get($platform)->match($other);
+    }
+
+    public static function isWindows(Platform $other): bool { return self::isMatch(self::WIN_ANY, $other); }
+    public static function isMacOS(Platform $other)  : bool { return self::isMatch(self::OSX_ANY, $other); }
+    public static function isLinux(Platform $other)  : bool { return self::isMatch(self::LIN_ANY, $other); }
+
+    private string $os;
+    private string $arch;
+    private ?Version $version;
+
+    public function __construct(string $os, ?string $arch = null, ?Version $version = null) {
+        if($arch == null) {
+            if(strpos($os, '/') === true)
+                throw new InvalidArgumentException('$arch cannot be null if $os doesn\'t contain a slash.');
+            $parts = explode('/', $os, 2);
+            $os    = $parts[0];
+            $arch  = $parts[1];
+        }
+        
+        $this->os = $os;
+        $this->arch = $arch;
+        $this->version = $version;
+    }
+
+    public function getOperatingSystem(): string {
+        return $this->os;
+    }
+
+    public function getArchitecture(): string {
+        return $this->arch;
+    }
+
+    public function hasVersion(): bool {
+        return $this->version !== null;
+    }
+    public function getVersion(): Version {
+        return $this->version;
+    }
+
+    public function withVersion(Version $version): self {
+        return new static($this->getOperatingSystem(), $this->getArchitecture(), $version);
+    }
+
+    public function matchOperatingSystem(Platform $other): bool {
+        if($this->getOperatingSystem() === '*'
+            || $other->getOperatingSystem() === '*')
+            return true;
+        return $this->getOperatingSystem() === $other->getOperatingSystem();
+    }
+
+    public function matchArchitecture(Platform $other): bool {
+        if($this->getArchitecture() === '*'
+            || $other->getArchitecture() === '*')
+            return true;
+        return $this->getArchitecture() === $other->getArchitecture();
+    }
+
+    public function matchVersion(Platform $other): bool {
+        if(!$this->hasVersion())
+            return !$other->hasVersion();
+        if(!$other->hasVersion())
+            return false;
+        return $this->getVersion()->match($other->getVersion());
+    }
+
+    public function match(Platform $other, bool $matchVersion = false): bool {
+        if(!$this->matchOperatingSystem($other) || !$this->matchArchitecture($other))
+            return false;
+        if($matchVersion && !$this->matchVersion($other))
+            return false;
+        return true;
+    }
+
+    public function __toString(): string {
+        return sprintf('%s/%s', $this->getOperatingSystem(), $this->getArchitecture());
+    }
+
+    public function fwifSerialize() {
+        return (string)$this;
+    }
+}
\ No newline at end of file
diff --git a/src/Version.php b/src/Version.php
index 7f7436b..c8030ec 100644
--- a/src/Version.php
+++ b/src/Version.php
@@ -4,11 +4,15 @@ namespace Patchouli;
 use FWIF\FWIFSerializable;
 
 class Version implements FWIFSerializable {
-    public function fwifSerialize(): string {
-        return (string)$this;
+    public function match(Version $other): bool {
+        return true;
     }
 
     public function __toString(): string {
         return '1.0.0';
     }
+
+    public function fwifSerialize(): string {
+        return (string)$this;
+    }
 }