//常规dijkstra算法,先考虑最短路径排列,再建数组更新费用,更新费用时注意要在最短路径的前提下更新;
//关于数组规模的问题,目的地可能为n,目的地又可能作为容器指标,所以容器数量至少为n+1;
#include <cstdint>
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<vector>
using namespace std;
const int MAXN = 1000;
const int INF = INT16_MAX;
struct Edge {
int to;
int length;
int price;
Edge(int b, int d, int p): to(b), length(d), price(p) {}
};
struct Point {
int number;
int distance;
int fee;
Point(int num, int dis, int fee): number(num), distance(dis), fee(fee) {}
bool operator<(const Point& p)const {
if (distance != p.distance) {
return distance > p.distance;
} else {
return fee > p.fee;
}
}
};
vector<Edge>G[MAXN];
int dis[MAXN];
int Fee[MAXN];
void dijkstra(int s) {
priority_queue<Point>Q;
dis[s] = 0;
Fee[s] = 0;
Q.push(Point(s, dis[s], Fee[s]));
while (!Q.empty()) {
int u = Q.top().number;
Q.pop();
for (int i = 0; i < G[u].size(); i++) {
int d = G[u][i].length;//距离
int v = G[u][i].to; //目的地
int p = G[u][i].price; //价格
if (dis[v] > dis[u] + d) {
dis[v] = dis[u] + d;
Fee[v] = Fee[u] + p;
Q.push(Point(v, dis[v], Fee[v]));
} else if (dis[v] == dis[u] + d) {
if (Fee[v] > Fee[u] + p) {
Fee[v] = Fee[u] + p;
Q.push(Point(v, dis[v], Fee[v]));
}
}
}
}
return ;
}
int main() {
int n, m;
while (scanf("%d %d", &n, &m) != EOF) {
if (n == 0 && m == 0) {
break;
}
fill(dis, dis + n + 1, INF); //n+1
fill(Fee, Fee + n + 1, INF); ///n+1
memset(G, 0, sizeof(G));
for (int i = 0; i < m; i++) {
int a;
int b;
int d;
int p;
scanf("%d %d %d %d", &a, &b, &d, &p);
G[a].push_back(Edge(b, d, p));
G[b].push_back(Edge(a, d, p));
}
int u, v;
scanf("%d %d", &u, &v);
dijkstra(u);
printf("%d %d\n", dis[v], Fee[v]);
}
return 0;
}